diff --git a/.gitignore b/.gitignore index f2915a9..6482763 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,6 @@ composer.phar # Mac DS_Store Files .DS_Store + +# local phpunit config +/phpunit.xml \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 2add223..99ff95a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,9 @@ language: php php: - - 5.3 - 5.4 - 5.5 -env: - - CUBRID_VERSION=9.1.0 - services: - redis-server - memcached @@ -22,7 +18,7 @@ before_script: - tests/unit/data/travis/cubrid-setup.sh script: - - phpunit --coverage-clover tests/unit/runtime/coveralls/clover.xml --verbose --exclude-group mssql,oci,wincache,xcache,zenddata + - phpunit --coverage-clover tests/unit/runtime/coveralls/clover.xml --verbose --exclude-group mssql,oci,wincache,xcache,zenddata,vendor after_script: - php vendor/bin/coveralls diff --git a/LICENSE.md b/LICENSE.md index 6edcc4f..e98f03d 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,7 +1,7 @@ The Yii framework is free software. It is released under the terms of the following BSD License. -Copyright © 2008-2013 by Yii Software LLC (http://www.yiisoft.com) +Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com) All rights reserved. Redistribution and use in source and binary forms, with or without @@ -29,4 +29,4 @@ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file +POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index 1cd0121..3bc2d67 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ DIRECTORY STRUCTURE REQUIREMENTS ------------ -The minimum requirement by Yii is that your Web server supports PHP 5.3.?. +The minimum requirement by Yii is that your Web server supports PHP 5.4. DOCUMENTATION @@ -42,7 +42,7 @@ DOCUMENTATION For 1.1 users, you may refer to [Upgrading from Yii 1.1](docs/guide/upgrade-from-v1.md) to have a general idea of what has changed in 2.0. -We are writing more documentation to get you started and learn more in depth. +[Definitive Guide draft](docs/guide/index.md) is available. It's not complete yet but main parts are already OK. HOW TO PARTICIPATE diff --git a/apps/advanced/LICENSE.md b/apps/advanced/LICENSE.md index 6edcc4f..e98f03d 100644 --- a/apps/advanced/LICENSE.md +++ b/apps/advanced/LICENSE.md @@ -1,7 +1,7 @@ The Yii framework is free software. It is released under the terms of the following BSD License. -Copyright © 2008-2013 by Yii Software LLC (http://www.yiisoft.com) +Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com) All rights reserved. Redistribution and use in source and binary forms, with or without @@ -29,4 +29,4 @@ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file +POSSIBILITY OF SUCH DAMAGE. diff --git a/apps/advanced/README.md b/apps/advanced/README.md index 6860c11..00beb56 100644 --- a/apps/advanced/README.md +++ b/apps/advanced/README.md @@ -52,7 +52,7 @@ environments/ contains environment-based overrides REQUIREMENTS ------------ -The minimum requirement by Yii is that your Web server supports PHP 5.3.?. +The minimum requirement by Yii is that your Web server supports PHP 5.4.0. In order for captcha to work you need either GD2 extension or ImageMagick PHP extension. @@ -76,6 +76,13 @@ php composer.phar create-project --stability=dev yiisoft/yii2-app-advanced yii-a Note that in order to install some dependencies you must have `php_openssl` extension enabled. +After the application is installed, switch to the project folder and run the following command +to initialize the application: + +~~~ +./init (init on Windows) +~~~ + ### Install from an Archive File @@ -103,9 +110,11 @@ GETTING STARTED After you install the application, you have to conduct the following steps to initialize the installed application. You only need to do these once for all. -1. Execute the `init` command and select `dev` as environment. +1. Execute the `init` command and select `dev` as environment. Alternatively you can execute it as `init --env=Development` +or `init --env=Production`. 2. Create a new database. It is assumed that MySQL InnoDB is used. If not, adjust `console/migrations/m130524_201442_init.php`. 3. In `common/config/params.php` set your database details in `components.db` values. +4. Apply migrations with `yii migrate`. Now you should be able to access: diff --git a/apps/advanced/backend/assets/.gitkeep b/apps/advanced/backend/assets/.gitkeep deleted file mode 100644 index 72e8ffc..0000000 --- a/apps/advanced/backend/assets/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -* diff --git a/apps/advanced/backend/config/AppAsset.php b/apps/advanced/backend/assets/AppAsset.php similarity index 73% rename from apps/advanced/backend/config/AppAsset.php rename to apps/advanced/backend/assets/AppAsset.php index 267e48c..bd5c3a0 100644 --- a/apps/advanced/backend/config/AppAsset.php +++ b/apps/advanced/backend/assets/AppAsset.php @@ -5,7 +5,7 @@ * @license http://www.yiiframework.com/license/ */ -namespace backend\config; +namespace backend\assets; use yii\web\AssetBundle; @@ -17,13 +17,10 @@ class AppAsset extends AssetBundle { public $basePath = '@webroot'; public $baseUrl = '@web'; - public $css = array( - 'css/site.css', - ); - public $js = array( - ); - public $depends = array( + public $css = ['css/site.css']; + public $js = []; + public $depends = [ 'yii\web\YiiAsset', 'yii\bootstrap\BootstrapAsset', - ); + ]; } diff --git a/apps/advanced/backend/config/main.php b/apps/advanced/backend/config/main.php index 30c1825..f2745e2 100644 --- a/apps/advanced/backend/config/main.php +++ b/apps/advanced/backend/config/main.php @@ -1,5 +1,5 @@ <?php -$rootDir = __DIR__ . '/../..'; +$rootDir = dirname(dirname(__DIR__)); $params = array_merge( require($rootDir . '/common/config/params.php'), @@ -8,35 +8,36 @@ $params = array_merge( require(__DIR__ . '/params-local.php') ); -return array( +return [ 'id' => 'app-backend', 'basePath' => dirname(__DIR__), - 'vendorPath' => dirname(dirname(__DIR__)) . '/vendor', - 'preload' => array('log'), + 'vendorPath' => $rootDir . '/vendor', + 'preload' => ['log'], 'controllerNamespace' => 'backend\controllers', - 'modules' => array( - ), - 'components' => array( - 'request' => array( + 'modules' => [], + 'extensions' => require($rootDir . '/vendor/yiisoft/extensions.php'), + 'components' => [ + 'request' => [ 'enableCsrfValidation' => true, - ), + ], 'db' => $params['components.db'], 'cache' => $params['components.cache'], - 'user' => array( + 'mail' => $params['components.mail'], + 'user' => [ 'identityClass' => 'common\models\User', - ), - 'log' => array( + ], + 'log' => [ 'traceLevel' => YII_DEBUG ? 3 : 0, - 'targets' => array( - array( + 'targets' => [ + [ 'class' => 'yii\log\FileTarget', - 'levels' => array('error', 'warning'), - ), - ), - ), - 'errorHandler' => array( + 'levels' => ['error', 'warning'], + ], + ], + ], + 'errorHandler' => [ 'errorAction' => 'site/error', - ), - ), + ], + ], 'params' => $params, -); +]; diff --git a/apps/advanced/backend/config/params.php b/apps/advanced/backend/config/params.php index 1643a70..0e625dc 100644 --- a/apps/advanced/backend/config/params.php +++ b/apps/advanced/backend/config/params.php @@ -1,4 +1,4 @@ <?php -return array( +return [ 'adminEmail' => 'admin@example.com', -); +]; diff --git a/apps/advanced/backend/controllers/SiteController.php b/apps/advanced/backend/controllers/SiteController.php index 28f2310..ecf684c 100644 --- a/apps/advanced/backend/controllers/SiteController.php +++ b/apps/advanced/backend/controllers/SiteController.php @@ -10,32 +10,31 @@ class SiteController extends Controller { public function behaviors() { - return array( - 'access' => array( + return [ + 'access' => [ 'class' => \yii\web\AccessControl::className(), - 'rules' => array( - array( - 'actions' => array('login'), + 'rules' => [ + [ + 'actions' => ['login', 'error'], 'allow' => true, - 'roles' => array('?'), - ), - array( - 'actions' => array('logout', 'index'), + ], + [ + 'actions' => ['logout', 'index'], 'allow' => true, - 'roles' => array('@'), - ), - ), - ), - ); + 'roles' => ['@'], + ], + ], + ], + ]; } public function actions() { - return array( - 'error' => array( + return [ + 'error' => [ 'class' => 'yii\web\ErrorAction', - ), - ); + ], + ]; } public function actionIndex() @@ -45,13 +44,17 @@ class SiteController extends Controller public function actionLogin() { + if (!\Yii::$app->user->isGuest) { + $this->goHome(); + } + $model = new LoginForm(); if ($model->load($_POST) && $model->login()) { - return $this->goHome(); + return $this->goBack(); } else { - return $this->render('login', array( + return $this->render('login', [ 'model' => $model, - )); + ]); } } diff --git a/apps/advanced/backend/views/layouts/main.php b/apps/advanced/backend/views/layouts/main.php index 928f990..fdffc26 100644 --- a/apps/advanced/backend/views/layouts/main.php +++ b/apps/advanced/backend/views/layouts/main.php @@ -1,60 +1,60 @@ <?php -use backend\config\AppAsset; +use backend\assets\AppAsset; use yii\helpers\Html; use yii\bootstrap\Nav; use yii\bootstrap\NavBar; use yii\widgets\Breadcrumbs; /** - * @var $this \yii\base\View - * @var $content string + * @var \yii\web\View $this + * @var string $content */ AppAsset::register($this); ?> <?php $this->beginPage(); ?> <!DOCTYPE html> -<html lang="en"> +<html lang="<?= Yii::$app->language ?>"> <head> - <meta charset="<?php echo Yii::$app->charset; ?>"/> - <title><?php echo Html::encode($this->title); ?></title> + <meta charset="<?= Yii::$app->charset ?>"/> + <title><?= Html::encode($this->title) ?></title> <?php $this->head(); ?> </head> <body> <?php $this->beginBody(); ?> <?php - NavBar::begin(array( + NavBar::begin([ 'brandLabel' => 'My Company', 'brandUrl' => Yii::$app->homeUrl, - 'options' => array( + 'options' => [ 'class' => 'navbar-inverse navbar-fixed-top', - ), - )); - $menuItems = array( - array('label' => 'Home', 'url' => array('/site/index')), - ); + ], + ]); + $menuItems = [ + ['label' => 'Home', 'url' => ['/site/index']], + ]; if (Yii::$app->user->isGuest) { - $menuItems[] = array('label' => 'Login', 'url' => array('/site/login')); + $menuItems[] = ['label' => 'Login', 'url' => ['/site/login']]; } else { - $menuItems[] = array('label' => 'Logout (' . Yii::$app->user->identity->username .')' , 'url' => array('/site/logout')); + $menuItems[] = ['label' => 'Logout (' . Yii::$app->user->identity->username .')' , 'url' => ['/site/logout']]; } - echo Nav::widget(array( - 'options' => array('class' => 'navbar-nav pull-right'), + echo Nav::widget([ + 'options' => ['class' => 'navbar-nav navbar-right'], 'items' => $menuItems, - )); + ]); NavBar::end(); ?> <div class="container"> - <?php echo Breadcrumbs::widget(array( - 'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : array(), - )); ?> - <?php echo $content; ?> + <?= Breadcrumbs::widget([ + 'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [], + ]) ?> + <?= $content ?> </div> <footer class="footer"> <div class="container"> - <p class="pull-left">© My Company <?php echo date('Y'); ?></p> - <p class="pull-right"><?php echo Yii::powered(); ?></p> + <p class="pull-left">© My Company <?= date('Y') ?></p> + <p class="pull-right"><?= Yii::powered() ?></p> </div> </footer> diff --git a/apps/advanced/backend/views/site/error.php b/apps/advanced/backend/views/site/error.php index 024e27d..1b7ce04 100644 --- a/apps/advanced/backend/views/site/error.php +++ b/apps/advanced/backend/views/site/error.php @@ -3,7 +3,7 @@ use yii\helpers\Html; /** - * @var yii\base\View $this + * @var yii\web\View $this * @var string $name * @var string $message * @var Exception $exception @@ -13,10 +13,10 @@ $this->title = $name; ?> <div class="site-error"> - <h1><?php echo Html::encode($this->title); ?></h1> + <h1><?= Html::encode($this->title) ?></h1> <div class="alert alert-danger"> - <?php echo nl2br(Html::encode($message)); ?> + <?= nl2br(Html::encode($message)) ?> </div> <p> diff --git a/apps/advanced/backend/views/site/index.php b/apps/advanced/backend/views/site/index.php index f2e6d5e..bcb2278 100644 --- a/apps/advanced/backend/views/site/index.php +++ b/apps/advanced/backend/views/site/index.php @@ -1,6 +1,6 @@ <?php /** - * @var yii\base\View $this + * @var yii\web\View $this */ $this->title = 'My Yii Application'; ?> diff --git a/apps/advanced/backend/views/site/login.php b/apps/advanced/backend/views/site/login.php index 0c16570..41973e5 100644 --- a/apps/advanced/backend/views/site/login.php +++ b/apps/advanced/backend/views/site/login.php @@ -3,7 +3,7 @@ use yii\helpers\Html; use yii\widgets\ActiveForm; /** - * @var yii\base\View $this + * @var yii\web\View $this * @var yii\widgets\ActiveForm $form * @var app\models\LoginForm $model */ @@ -11,18 +11,18 @@ $this->title = 'Login'; $this->params['breadcrumbs'][] = $this->title; ?> <div class="site-login"> - <h1><?php echo Html::encode($this->title); ?></h1> + <h1><?= Html::encode($this->title) ?></h1> <p>Please fill out the following fields to login:</p> <div class="row"> <div class="col-lg-5"> - <?php $form = ActiveForm::begin(array('id' => 'login-form')); ?> - <?php echo $form->field($model, 'username'); ?> - <?php echo $form->field($model, 'password')->passwordInput(); ?> - <?php echo $form->field($model, 'rememberMe')->checkbox(); ?> + <?php $form = ActiveForm::begin(['id' => 'login-form']); ?> + <?= $form->field($model, 'username') ?> + <?= $form->field($model, 'password')->passwordInput() ?> + <?= $form->field($model, 'rememberMe')->checkbox() ?> <div class="form-group"> - <?php echo Html::submitButton('Login', array('class' => 'btn btn-primary')); ?> + <?= Html::submitButton('Login', ['class' => 'btn btn-primary']) ?> </div> <?php ActiveForm::end(); ?> </div> diff --git a/apps/advanced/backend/web/css/site.css b/apps/advanced/backend/web/css/site.css index e6db73c..6a355bd 100644 --- a/apps/advanced/backend/web/css/site.css +++ b/apps/advanced/backend/web/css/site.css @@ -17,3 +17,24 @@ body { font-size: 21px; padding: 14px 24px; } + +/* add sorting icons to gridview sort links */ +a.asc:after, a.desc:after { + position: relative; + top: 1px; + display: inline-block; + font-family: 'Glyphicons Halflings'; + font-style: normal; + font-weight: normal; + line-height: 1; + padding-left: 5px; +} + +a.asc:after { content: /*"\e113"*/"\e151"; } +a.desc:after { content: /*"\e114"*/"\e152"; } + +.sort-numerical a.asc:after { content: "\e153"; } +.sort-numerical a.desc:after { content: "\e154"; } + +.sort-ordinal a.asc:after { content: "\e155"; } +.sort-ordinal a.desc:after { content: "\e156"; } diff --git a/apps/advanced/common/config/params.php b/apps/advanced/common/config/params.php index 2dff87b..4d83098 100644 --- a/apps/advanced/common/config/params.php +++ b/apps/advanced/common/config/params.php @@ -1,22 +1,26 @@ <?php -Yii::setAlias('common', __DIR__ . '/../'); -Yii::setAlias('frontend', __DIR__ . '/../../frontend'); -Yii::setAlias('backend', __DIR__ . '/../../backend'); +Yii::setAlias('common', realpath(__DIR__ . '/../')); +Yii::setAlias('frontend', realpath(__DIR__ . '/../../frontend')); +Yii::setAlias('backend', realpath(__DIR__ . '/../../backend')); -return array( +return [ 'adminEmail' => 'admin@example.com', 'supportEmail' => 'support@example.com', - 'components.cache' => array( + 'components.cache' => [ 'class' => 'yii\caching\FileCache', - ), + ], - 'components.db' => array( + 'components.mail' => [ + 'class' => 'yii\swiftmailer\Mailer', + ], + + 'components.db' => [ 'class' => 'yii\db\Connection', 'dsn' => 'mysql:host=localhost;dbname=yii2advanced', 'username' => 'root', 'password' => '', 'charset' => 'utf8', - ), -); + ], +]; diff --git a/apps/advanced/common/models/LoginForm.php b/apps/advanced/common/models/LoginForm.php index 4631dbd..38888d9 100644 --- a/apps/advanced/common/models/LoginForm.php +++ b/apps/advanced/common/models/LoginForm.php @@ -14,19 +14,21 @@ class LoginForm extends Model public $password; public $rememberMe = true; + private $_user = false; + /** * @return array the validation rules. */ public function rules() { - return array( + return [ // username and password are both required - array('username, password', 'required'), + [['username', 'password'], 'required'], // password is validated by validatePassword() - array('password', 'validatePassword'), + ['password', 'validatePassword'], // rememberMe must be a boolean value - array('rememberMe', 'boolean'), - ); + ['rememberMe', 'boolean'], + ]; } /** @@ -35,7 +37,7 @@ class LoginForm extends Model */ public function validatePassword() { - $user = User::findByUsername($this->username); + $user = $this->getUser(); if (!$user || !$user->validatePassword($this->password)) { $this->addError('password', 'Incorrect username or password.'); } @@ -48,11 +50,22 @@ class LoginForm extends Model public function login() { if ($this->validate()) { - $user = User::findByUsername($this->username); - Yii::$app->user->login($user, $this->rememberMe ? 3600*24*30 : 0); - return true; + return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600*24*30 : 0); } else { return false; } } + + /** + * Finds user by [[username]] + * + * @return User|null + */ + private function getUser() + { + if ($this->_user === false) { + $this->_user = User::findByUsername($this->username); + } + return $this->_user; + } } diff --git a/apps/advanced/common/models/User.php b/apps/advanced/common/models/User.php index 62baf48..17bd630 100644 --- a/apps/advanced/common/models/User.php +++ b/apps/advanced/common/models/User.php @@ -34,15 +34,15 @@ class User extends ActiveRecord implements IdentityInterface public function behaviors() { - return array( - 'timestamp' => array( + return [ + 'timestamp' => [ 'class' => 'yii\behaviors\AutoTimestamp', - 'attributes' => array( - ActiveRecord::EVENT_BEFORE_INSERT => array('create_time', 'update_time'), + 'attributes' => [ + ActiveRecord::EVENT_BEFORE_INSERT => ['create_time', 'update_time'], ActiveRecord::EVENT_BEFORE_UPDATE => 'update_time', - ), - ), - ); + ], + ], + ]; } /** @@ -64,7 +64,7 @@ class User extends ActiveRecord implements IdentityInterface */ public static function findByUsername($username) { - return static::find(array('username' => $username, 'status' => static::STATUS_ACTIVE)); + return static::find(['username' => $username, 'status' => static::STATUS_ACTIVE]); } /** @@ -103,29 +103,29 @@ class User extends ActiveRecord implements IdentityInterface public function rules() { - return array( - array('username', 'filter', 'filter' => 'trim'), - array('username', 'required'), - array('username', 'string', 'min' => 2, 'max' => 255), - - array('email', 'filter', 'filter' => 'trim'), - array('email', 'required'), - array('email', 'email'), - array('email', 'unique', 'message' => 'This email address has already been taken.', 'on' => 'signup'), - array('email', 'exist', 'message' => 'There is no user with such email.', 'on' => 'requestPasswordResetToken'), - - array('password', 'required'), - array('password', 'string', 'min' => 6), - ); + return [ + ['username', 'filter', 'filter' => 'trim'], + ['username', 'required'], + ['username', 'string', 'min' => 2, 'max' => 255], + + ['email', 'filter', 'filter' => 'trim'], + ['email', 'required'], + ['email', 'email'], + ['email', 'unique', 'message' => 'This email address has already been taken.', 'on' => 'signup'], + ['email', 'exist', 'message' => 'There is no user with such email.', 'on' => 'requestPasswordResetToken'], + + ['password', 'required'], + ['password', 'string', 'min' => 6], + ]; } public function scenarios() { - return array( - 'signup' => array('username', 'email', 'password'), - 'resetPassword' => array('password'), - 'requestPasswordResetToken' => array('email'), - ); + return [ + 'signup' => ['username', 'email', 'password'], + 'resetPassword' => ['password'], + 'requestPasswordResetToken' => ['email'], + ]; } public function beforeSave($insert) diff --git a/apps/advanced/composer.json b/apps/advanced/composer.json index 2d5b987..9fd15d2 100644 --- a/apps/advanced/composer.json +++ b/apps/advanced/composer.json @@ -14,17 +14,20 @@ }, "minimum-stability": "dev", "require": { - "php": ">=5.3.0", + "php": ">=5.4.0", "yiisoft/yii2": "dev-master", - "yiisoft/yii2-composer": "dev-master" + "yiisoft/yii2-swiftmailer": "dev-master", + "yiisoft/yii2-bootstrap": "dev-master", + "yiisoft/yii2-debug": "dev-master", + "yiisoft/yii2-gii": "dev-master" }, "scripts": { "post-create-project-cmd": [ - "yii\\composer\\InstallHandler::setPermissions" + "yii\\composer\\Installer::setPermission" ] }, "extra": { - "yii-install-writable": [ + "writable": [ "backend/runtime", "backend/web/assets", diff --git a/apps/advanced/console/config/main.php b/apps/advanced/console/config/main.php index 7a223c3..0142e32 100644 --- a/apps/advanced/console/config/main.php +++ b/apps/advanced/console/config/main.php @@ -8,24 +8,26 @@ $params = array_merge( require(__DIR__ . '/params-local.php') ); -return array( +return [ 'id' => 'app-console', 'basePath' => dirname(__DIR__), 'vendorPath' => dirname(dirname(__DIR__)) . '/vendor', 'controllerNamespace' => 'console\controllers', - 'modules' => array( - ), - 'components' => array( + 'modules' => [ + ], + 'extensions' => require(__DIR__ . '/../../vendor/yiisoft/extensions.php'), + 'components' => [ 'db' => $params['components.db'], 'cache' => $params['components.cache'], - 'log' => array( - 'targets' => array( - array( + 'mail' => $params['components.mail'], + 'log' => [ + 'targets' => [ + [ 'class' => 'yii\log\FileTarget', - 'levels' => array('error', 'warning'), - ), - ), - ), - ), + 'levels' => ['error', 'warning'], + ], + ], + ], + ], 'params' => $params, -); +]; diff --git a/apps/advanced/console/config/params.php b/apps/advanced/console/config/params.php index 1643a70..0e625dc 100644 --- a/apps/advanced/console/config/params.php +++ b/apps/advanced/console/config/params.php @@ -1,4 +1,4 @@ <?php -return array( +return [ 'adminEmail' => 'admin@example.com', -); +]; diff --git a/apps/advanced/console/migrations/m130524_201442_init.php b/apps/advanced/console/migrations/m130524_201442_init.php index e7b9e84..1315d8d 100644 --- a/apps/advanced/console/migrations/m130524_201442_init.php +++ b/apps/advanced/console/migrations/m130524_201442_init.php @@ -9,7 +9,7 @@ class m130524_201442_init extends \yii\db\Migration // MySQL-specific table options. Adjust if you plan working with another DBMS $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_general_ci ENGINE=InnoDB'; - $this->createTable('tbl_user', array( + $this->createTable('tbl_user', [ 'id' => Schema::TYPE_PK, 'username' => Schema::TYPE_STRING.' NOT NULL', 'auth_key' => Schema::TYPE_STRING.'(32) NOT NULL', @@ -21,7 +21,7 @@ class m130524_201442_init extends \yii\db\Migration 'status' => 'tinyint NOT NULL DEFAULT 10', 'create_time' => Schema::TYPE_INTEGER.' NOT NULL', 'update_time' => Schema::TYPE_INTEGER.' NOT NULL', - ), $tableOptions); + ], $tableOptions); } public function down() diff --git a/apps/advanced/environments/dev/backend/config/main-local.php b/apps/advanced/environments/dev/backend/config/main-local.php index 2689ed1..cc887e5 100644 --- a/apps/advanced/environments/dev/backend/config/main-local.php +++ b/apps/advanced/environments/dev/backend/config/main-local.php @@ -1,11 +1,11 @@ <?php -return array( - 'preload' => array( +return [ + 'preload' => [ //'debug', - ), - 'modules' => array( -// 'debug' => array( + ], + 'modules' => [ +// 'debug' => [ // 'class' => 'yii\debug\Module', -// ), - ), -); +// ], + ], +]; diff --git a/apps/advanced/environments/dev/backend/config/params-local.php b/apps/advanced/environments/dev/backend/config/params-local.php index 5b61b0e..d0b9c34 100644 --- a/apps/advanced/environments/dev/backend/config/params-local.php +++ b/apps/advanced/environments/dev/backend/config/params-local.php @@ -1,3 +1,3 @@ <?php -return array( -); +return [ +]; diff --git a/apps/advanced/environments/dev/common/config/params-local.php b/apps/advanced/environments/dev/common/config/params-local.php index 5b61b0e..d0b9c34 100644 --- a/apps/advanced/environments/dev/common/config/params-local.php +++ b/apps/advanced/environments/dev/common/config/params-local.php @@ -1,3 +1,3 @@ <?php -return array( -); +return [ +]; diff --git a/apps/advanced/environments/dev/console/config/main-local.php b/apps/advanced/environments/dev/console/config/main-local.php index 5b61b0e..d0b9c34 100644 --- a/apps/advanced/environments/dev/console/config/main-local.php +++ b/apps/advanced/environments/dev/console/config/main-local.php @@ -1,3 +1,3 @@ <?php -return array( -); +return [ +]; diff --git a/apps/advanced/environments/dev/console/config/params-local.php b/apps/advanced/environments/dev/console/config/params-local.php index 5b61b0e..d0b9c34 100644 --- a/apps/advanced/environments/dev/console/config/params-local.php +++ b/apps/advanced/environments/dev/console/config/params-local.php @@ -1,3 +1,3 @@ <?php -return array( -); +return [ +]; diff --git a/apps/advanced/environments/dev/frontend/config/main-local.php b/apps/advanced/environments/dev/frontend/config/main-local.php index 35d10ed..40df724 100644 --- a/apps/advanced/environments/dev/frontend/config/main-local.php +++ b/apps/advanced/environments/dev/frontend/config/main-local.php @@ -1,11 +1,11 @@ <?php -return array( - 'preload' => array( +return [ + 'preload' => [ //'debug', - ), - 'modules' => array( -// 'debug' => array( + ], + 'modules' => [ +// 'debug' => [ // 'class' => 'yii\debug\Module', -// ), - ), -); +// ], + ], +]; diff --git a/apps/advanced/environments/dev/frontend/config/params-local.php b/apps/advanced/environments/dev/frontend/config/params-local.php index 5b61b0e..d0b9c34 100644 --- a/apps/advanced/environments/dev/frontend/config/params-local.php +++ b/apps/advanced/environments/dev/frontend/config/params-local.php @@ -1,3 +1,3 @@ <?php -return array( -); +return [ +]; diff --git a/apps/advanced/environments/dev/frontend/web/index.php b/apps/advanced/environments/dev/frontend/web/index.php index 9a7cbae..2113419 100644 --- a/apps/advanced/environments/dev/frontend/web/index.php +++ b/apps/advanced/environments/dev/frontend/web/index.php @@ -4,7 +4,6 @@ defined('YII_ENV') or define('YII_ENV', 'dev'); require(__DIR__ . '/../../vendor/autoload.php'); require(__DIR__ . '/../../vendor/yiisoft/yii2/yii/Yii.php'); -Yii::importNamespaces(require(__DIR__ . '/../../vendor/composer/autoload_namespaces.php')); $config = yii\helpers\ArrayHelper::merge( require(__DIR__ . '/../config/main.php'), diff --git a/apps/advanced/environments/dev/yii b/apps/advanced/environments/dev/yii index 9a257d7..5d1cd90 100644 --- a/apps/advanced/environments/dev/yii +++ b/apps/advanced/environments/dev/yii @@ -16,7 +16,6 @@ defined('STDIN') or define('STDIN', fopen('php://stdin', 'r')); require(__DIR__ . '/vendor/autoload.php'); require(__DIR__ . '/vendor/yiisoft/yii2/yii/Yii.php'); -Yii::importNamespaces(require(__DIR__ . '/vendor/composer/autoload_namespaces.php')); $config = yii\helpers\ArrayHelper::merge( require(__DIR__ . '/console/config/main.php'), @@ -24,4 +23,5 @@ $config = yii\helpers\ArrayHelper::merge( ); $application = new yii\console\Application($config); -return $application->run(); +$exitCode = $application->run(); +exit($exitCode); diff --git a/apps/advanced/environments/index.php b/apps/advanced/environments/index.php index 78d221a..a2097bf 100644 --- a/apps/advanced/environments/index.php +++ b/apps/advanced/environments/index.php @@ -6,33 +6,33 @@ * format: * * ```php - * return array( - * 'environment name' => array( + * return [ + * 'environment name' => [ * 'path' => 'directory storing the local files', - * 'writable' => array( + * 'writable' => [ * // list of directories that should be set writable - * ), - * ), - * ); + * ], + * ], + * ]; * ``` */ -return array( - 'Development' => array( +return [ + 'Development' => [ 'path' => 'dev', - 'writable' => array( + 'writable' => [ // handled by composer.json already - ), - 'executable' => array( + ], + 'executable' => [ 'yii', - ), - ), - 'Production' => array( + ], + ], + 'Production' => [ 'path' => 'prod', - 'writable' => array( + 'writable' => [ // handled by composer.json already - ), - 'executable' => array( + ], + 'executable' => [ 'yii', - ), - ), -); + ], + ], +]; diff --git a/apps/advanced/environments/prod/backend/config/main-local.php b/apps/advanced/environments/prod/backend/config/main-local.php index 5b61b0e..d0b9c34 100644 --- a/apps/advanced/environments/prod/backend/config/main-local.php +++ b/apps/advanced/environments/prod/backend/config/main-local.php @@ -1,3 +1,3 @@ <?php -return array( -); +return [ +]; diff --git a/apps/advanced/environments/prod/backend/config/params-local.php b/apps/advanced/environments/prod/backend/config/params-local.php index 5b61b0e..d0b9c34 100644 --- a/apps/advanced/environments/prod/backend/config/params-local.php +++ b/apps/advanced/environments/prod/backend/config/params-local.php @@ -1,3 +1,3 @@ <?php -return array( -); +return [ +]; diff --git a/apps/advanced/environments/prod/common/config/params-local.php b/apps/advanced/environments/prod/common/config/params-local.php index 5b61b0e..d0b9c34 100644 --- a/apps/advanced/environments/prod/common/config/params-local.php +++ b/apps/advanced/environments/prod/common/config/params-local.php @@ -1,3 +1,3 @@ <?php -return array( -); +return [ +]; diff --git a/apps/advanced/environments/prod/console/config/main-local.php b/apps/advanced/environments/prod/console/config/main-local.php index 5b61b0e..d0b9c34 100644 --- a/apps/advanced/environments/prod/console/config/main-local.php +++ b/apps/advanced/environments/prod/console/config/main-local.php @@ -1,3 +1,3 @@ <?php -return array( -); +return [ +]; diff --git a/apps/advanced/environments/prod/console/config/params-local.php b/apps/advanced/environments/prod/console/config/params-local.php index 5b61b0e..d0b9c34 100644 --- a/apps/advanced/environments/prod/console/config/params-local.php +++ b/apps/advanced/environments/prod/console/config/params-local.php @@ -1,3 +1,3 @@ <?php -return array( -); +return [ +]; diff --git a/apps/advanced/environments/prod/frontend/config/main-local.php b/apps/advanced/environments/prod/frontend/config/main-local.php index 5b61b0e..d0b9c34 100644 --- a/apps/advanced/environments/prod/frontend/config/main-local.php +++ b/apps/advanced/environments/prod/frontend/config/main-local.php @@ -1,3 +1,3 @@ <?php -return array( -); +return [ +]; diff --git a/apps/advanced/environments/prod/frontend/config/params-local.php b/apps/advanced/environments/prod/frontend/config/params-local.php index 5b61b0e..d0b9c34 100644 --- a/apps/advanced/environments/prod/frontend/config/params-local.php +++ b/apps/advanced/environments/prod/frontend/config/params-local.php @@ -1,3 +1,3 @@ <?php -return array( -); +return [ +]; diff --git a/apps/advanced/environments/prod/frontend/web/index.php b/apps/advanced/environments/prod/frontend/web/index.php index cbd096b..fc62a78 100644 --- a/apps/advanced/environments/prod/frontend/web/index.php +++ b/apps/advanced/environments/prod/frontend/web/index.php @@ -4,7 +4,6 @@ defined('YII_ENV') or define('YII_ENV', 'prod'); require(__DIR__ . '/../../vendor/autoload.php'); require(__DIR__ . '/../../vendor/yiisoft/yii2/yii/Yii.php'); -Yii::importNamespaces(require(__DIR__ . '/../../vendor/composer/autoload_namespaces.php')); $config = yii\helpers\ArrayHelper::merge( require(__DIR__ . '/../config/main.php'), diff --git a/apps/advanced/environments/prod/yii b/apps/advanced/environments/prod/yii index 7f24a70..b17d9a7 100644 --- a/apps/advanced/environments/prod/yii +++ b/apps/advanced/environments/prod/yii @@ -16,7 +16,6 @@ defined('STDIN') or define('STDIN', fopen('php://stdin', 'r')); require(__DIR__ . '/vendor/autoload.php'); require(__DIR__ . '/vendor/yiisoft/yii2/yii/Yii.php'); -Yii::importNamespaces(require(__DIR__ . '/vendor/composer/autoload_namespaces.php')); $config = yii\helpers\ArrayHelper::merge( require(__DIR__ . '/console/config/main.php'), @@ -24,4 +23,5 @@ $config = yii\helpers\ArrayHelper::merge( ); $application = new yii\console\Application($config); -return $application->run(); +$exitCode = $application->run(); +exit($exitCode); diff --git a/apps/advanced/frontend/config/AppAsset.php b/apps/advanced/frontend/assets/AppAsset.php similarity index 73% rename from apps/advanced/frontend/config/AppAsset.php rename to apps/advanced/frontend/assets/AppAsset.php index 4df653c..03c5382 100644 --- a/apps/advanced/frontend/config/AppAsset.php +++ b/apps/advanced/frontend/assets/AppAsset.php @@ -5,7 +5,7 @@ * @license http://www.yiiframework.com/license/ */ -namespace frontend\config; +namespace frontend\assets; use yii\web\AssetBundle; @@ -17,13 +17,13 @@ class AppAsset extends AssetBundle { public $basePath = '@webroot'; public $baseUrl = '@web'; - public $css = array( + public $css = [ 'css/site.css', - ); - public $js = array( - ); - public $depends = array( + ]; + public $js = [ + ]; + public $depends = [ 'yii\web\YiiAsset', 'yii\bootstrap\BootstrapAsset', - ); + ]; } diff --git a/apps/advanced/frontend/config/main.php b/apps/advanced/frontend/config/main.php index 975a3b4..e0fd2de 100644 --- a/apps/advanced/frontend/config/main.php +++ b/apps/advanced/frontend/config/main.php @@ -8,35 +8,37 @@ $params = array_merge( require(__DIR__ . '/params-local.php') ); -return array( +return [ 'id' => 'app-frontend', 'basePath' => dirname(__DIR__), - 'vendorPath' => dirname(dirname(__DIR__)) . '/vendor', + 'vendorPath' => $rootDir . '/vendor', 'controllerNamespace' => 'frontend\controllers', - 'modules' => array( + 'modules' => [ 'gii' => 'yii\gii\Module' - ), - 'components' => array( - 'request' => array( + ], + 'extensions' => require($rootDir . '/vendor/yiisoft/extensions.php'), + 'components' => [ + 'request' => [ 'enableCsrfValidation' => true, - ), + ], 'db' => $params['components.db'], 'cache' => $params['components.cache'], - 'user' => array( + 'mail' => $params['components.mail'], + 'user' => [ 'identityClass' => 'common\models\User', - ), - 'log' => array( + ], + 'log' => [ 'traceLevel' => YII_DEBUG ? 3 : 0, - 'targets' => array( - array( + 'targets' => [ + [ 'class' => 'yii\log\FileTarget', - 'levels' => array('error', 'warning'), - ), - ), - ), - 'errorHandler' => array( + 'levels' => ['error', 'warning'], + ], + ], + ], + 'errorHandler' => [ 'errorAction' => 'site/error', - ), - ), + ], + ], 'params' => $params, -); +]; diff --git a/apps/advanced/frontend/config/params.php b/apps/advanced/frontend/config/params.php index 1643a70..0e625dc 100644 --- a/apps/advanced/frontend/config/params.php +++ b/apps/advanced/frontend/config/params.php @@ -1,4 +1,4 @@ <?php -return array( +return [ 'adminEmail' => 'admin@example.com', -); +]; diff --git a/apps/advanced/frontend/controllers/SiteController.php b/apps/advanced/frontend/controllers/SiteController.php index a9413de..184d16c 100644 --- a/apps/advanced/frontend/controllers/SiteController.php +++ b/apps/advanced/frontend/controllers/SiteController.php @@ -14,37 +14,37 @@ class SiteController extends Controller { public function behaviors() { - return array( - 'access' => array( + return [ + 'access' => [ 'class' => \yii\web\AccessControl::className(), - 'only' => array('login', 'logout', 'signup'), - 'rules' => array( - array( - 'actions' => array('login', 'signup'), + 'only' => ['logout', 'signup'], + 'rules' => [ + [ + 'actions' => ['signup'], 'allow' => true, - 'roles' => array('?'), - ), - array( - 'actions' => array('logout'), + 'roles' => ['?'], + ], + [ + 'actions' => ['logout'], 'allow' => true, - 'roles' => array('@'), - ), - ), - ), - ); + 'roles' => ['@'], + ], + ], + ], + ]; } public function actions() { - return array( - 'error' => array( + return [ + 'error' => [ 'class' => 'yii\web\ErrorAction', - ), - 'captcha' => array( + ], + 'captcha' => [ 'class' => 'yii\captcha\CaptchaAction', 'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null, - ), - ); + ], + ]; } public function actionIndex() @@ -54,13 +54,17 @@ class SiteController extends Controller public function actionLogin() { + if (!\Yii::$app->user->isGuest) { + $this->goHome(); + } + $model = new LoginForm(); if ($model->load($_POST) && $model->login()) { - return $this->goHome(); + return $this->goBack(); } else { - return $this->render('login', array( + return $this->render('login', [ 'model' => $model, - )); + ]); } } @@ -77,9 +81,9 @@ class SiteController extends Controller Yii::$app->session->setFlash('success', 'Thank you for contacting us. We will respond to you as soon as possible.'); return $this->refresh(); } else { - return $this->render('contact', array( + return $this->render('contact', [ 'model' => $model, - )); + ]); } } @@ -98,9 +102,9 @@ class SiteController extends Controller } } - return $this->render('signup', array( + return $this->render('signup', [ 'model' => $model, - )); + ]); } public function actionRequestPasswordReset() @@ -115,17 +119,17 @@ class SiteController extends Controller Yii::$app->getSession()->setFlash('error', 'There was an error sending email.'); } } - return $this->render('requestPasswordResetToken', array( + return $this->render('requestPasswordResetToken', [ 'model' => $model, - )); + ]); } public function actionResetPassword($token) { - $model = User::find(array( + $model = User::find([ 'password_reset_token' => $token, 'status' => User::STATUS_ACTIVE, - )); + ]); if (!$model) { throw new HttpException(400, 'Wrong password reset token.'); @@ -137,17 +141,17 @@ class SiteController extends Controller return $this->goHome(); } - return $this->render('resetPassword', array( + return $this->render('resetPassword', [ 'model' => $model, - )); + ]); } private function sendPasswordResetEmail($email) { - $user = User::find(array( + $user = User::find([ 'status' => User::STATUS_ACTIVE, 'email' => $email, - )); + ]); if (!$user) { return false; @@ -155,12 +159,13 @@ class SiteController extends Controller $user->password_reset_token = Security::generateRandomKey(); if ($user->save(false)) { + // todo: refactor it with mail component. pay attention to the arrangement of mail view files $fromEmail = \Yii::$app->params['supportEmail']; $name = '=?UTF-8?B?' . base64_encode(\Yii::$app->name . ' robot') . '?='; $subject = '=?UTF-8?B?' . base64_encode('Password reset for ' . \Yii::$app->name) . '?='; - $body = $this->renderPartial('/emails/passwordResetToken', array( + $body = $this->renderPartial('/emails/passwordResetToken', [ 'user' => $user, - )); + ]); $headers = "From: $name <{$fromEmail}>\r\n" . "MIME-Version: 1.0\r\n" . "Content-type: text/plain; charset=UTF-8"; diff --git a/apps/advanced/frontend/models/ContactForm.php b/apps/advanced/frontend/models/ContactForm.php index b3d8682..0a664ad 100644 --- a/apps/advanced/frontend/models/ContactForm.php +++ b/apps/advanced/frontend/models/ContactForm.php @@ -2,6 +2,7 @@ namespace frontend\models; +use Yii; use yii\base\Model; /** @@ -20,14 +21,14 @@ class ContactForm extends Model */ public function rules() { - return array( + return [ // name, email, subject and body are required - array('name, email, subject, body', 'required'), + [['name', 'email', 'subject', 'body'], 'required'], // email has to be a valid email address - array('email', 'email'), + ['email', 'email'], // verifyCode needs to be entered correctly - array('verifyCode', 'captcha'), - ); + ['verifyCode', 'captcha'], + ]; } /** @@ -35,9 +36,9 @@ class ContactForm extends Model */ public function attributeLabels() { - return array( + return [ 'verifyCode' => 'Verification Code', - ); + ]; } /** @@ -48,13 +49,12 @@ class ContactForm extends Model 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); + Yii::$app->mail->compose() + ->setTo($email) + ->setFrom([$this->email => $this->name]) + ->setSubject($this->subject) + ->setTextBody($this->body) + ->send(); return true; } else { return false; diff --git a/apps/advanced/frontend/views/emails/passwordResetToken.php b/apps/advanced/frontend/views/emails/passwordResetToken.php index 1e7a855..b617bd9 100644 --- a/apps/advanced/frontend/views/emails/passwordResetToken.php +++ b/apps/advanced/frontend/views/emails/passwordResetToken.php @@ -2,15 +2,15 @@ use yii\helpers\Html; /** - * @var yii\base\View $this + * @var yii\web\View $this * @var common\models\User $user; */ -$resetLink = Yii::$app->urlManager->createAbsoluteUrl('site/reset-password', array('token' => $user->password_reset_token)); +$resetLink = Yii::$app->urlManager->createAbsoluteUrl('site/reset-password', ['token' => $user->password_reset_token]); ?> -Hello <?php echo Html::encode($user->username)?>, +Hello <?= Html::encode($user->username) ?>, Follow the link below to reset your password: -<?php echo Html::a(Html::encode($resetLink), $resetLink)?> +<?= Html::a(Html::encode($resetLink), $resetLink) ?> diff --git a/apps/advanced/frontend/views/layouts/main.php b/apps/advanced/frontend/views/layouts/main.php index 0165ba0..febcc5a 100644 --- a/apps/advanced/frontend/views/layouts/main.php +++ b/apps/advanced/frontend/views/layouts/main.php @@ -1,65 +1,65 @@ <?php -use frontend\config\AppAsset; use yii\helpers\Html; use yii\bootstrap\Nav; use yii\bootstrap\NavBar; use yii\widgets\Breadcrumbs; +use frontend\assets\AppAsset; use frontend\widgets\Alert; /** - * @var $this \yii\base\View - * @var $content string + * @var \yii\web\View $this + * @var string $content */ AppAsset::register($this); ?> <?php $this->beginPage(); ?> <!DOCTYPE html> -<html lang="en"> +<html lang="<?= Yii::$app->language ?>"> <head> - <meta charset="<?php echo Yii::$app->charset; ?>"/> - <title><?php echo Html::encode($this->title); ?></title> + <meta charset="<?= Yii::$app->charset ?>"/> + <title><?= Html::encode($this->title) ?></title> <?php $this->head(); ?> </head> <body> <?php $this->beginBody(); ?> <?php - NavBar::begin(array( + NavBar::begin([ 'brandLabel' => 'My Company', 'brandUrl' => Yii::$app->homeUrl, - 'options' => array( + 'options' => [ 'class' => 'navbar-inverse navbar-fixed-top', - ), - )); - $menuItems = array( - array('label' => 'Home', 'url' => array('/site/index')), - array('label' => 'About', 'url' => array('/site/about')), - array('label' => 'Contact', 'url' => array('/site/contact')), - ); + ], + ]); + $menuItems = [ + ['label' => 'Home', 'url' => ['/site/index']], + ['label' => 'About', 'url' => ['/site/about']], + ['label' => 'Contact', 'url' => ['/site/contact']], + ]; if (Yii::$app->user->isGuest) { - $menuItems[] = array('label' => 'Signup', 'url' => array('/site/signup')); - $menuItems[] = array('label' => 'Login', 'url' => array('/site/login')); + $menuItems[] = ['label' => 'Signup', 'url' => ['/site/signup']]; + $menuItems[] = ['label' => 'Login', 'url' => ['/site/login']]; } else { - $menuItems[] = array('label' => 'Logout (' . Yii::$app->user->identity->username .')' , 'url' => array('/site/logout')); + $menuItems[] = ['label' => 'Logout (' . Yii::$app->user->identity->username .')' , 'url' => ['/site/logout']]; } - echo Nav::widget(array( - 'options' => array('class' => 'navbar-nav pull-right'), + echo Nav::widget([ + 'options' => ['class' => 'navbar-nav navbar-right'], 'items' => $menuItems, - )); + ]); NavBar::end(); ?> <div class="container"> - <?php echo Breadcrumbs::widget(array( - 'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : array(), - )); ?> - <?php echo Alert::widget()?> - <?php echo $content; ?> + <?= Breadcrumbs::widget([ + 'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [], + ]) ?> + <?= Alert::widget() ?> + <?= $content ?> </div> <footer class="footer"> <div class="container"> - <p class="pull-left">© My Company <?php echo date('Y'); ?></p> - <p class="pull-right"><?php echo Yii::powered(); ?></p> + <p class="pull-left">© My Company <?= date('Y') ?></p> + <p class="pull-right"><?= Yii::powered() ?></p> </div> </footer> diff --git a/apps/advanced/frontend/views/site/about.php b/apps/advanced/frontend/views/site/about.php index a372e69..ac4ab24 100644 --- a/apps/advanced/frontend/views/site/about.php +++ b/apps/advanced/frontend/views/site/about.php @@ -2,15 +2,15 @@ use yii\helpers\Html; /** - * @var yii\base\View $this + * @var yii\web\View $this */ $this->title = 'About'; $this->params['breadcrumbs'][] = $this->title; ?> <div class="site-about"> - <h1><?php echo Html::encode($this->title); ?></h1> + <h1><?= Html::encode($this->title) ?></h1> <p>This is the About page. You may modify the following file to customize its content:</p> - <code><?php echo __FILE__; ?></code> + <code><?= __FILE__ ?></code> </div> diff --git a/apps/advanced/frontend/views/site/contact.php b/apps/advanced/frontend/views/site/contact.php index 851deda..17c4f79 100644 --- a/apps/advanced/frontend/views/site/contact.php +++ b/apps/advanced/frontend/views/site/contact.php @@ -4,7 +4,7 @@ use yii\widgets\ActiveForm; use yii\captcha\Captcha; /** - * @var yii\base\View $this + * @var yii\web\View $this * @var yii\widgets\ActiveForm $form * @var app\models\ContactForm $model */ @@ -12,7 +12,7 @@ $this->title = 'Contact'; $this->params['breadcrumbs'][] = $this->title; ?> <div class="site-contact"> - <h1><?php echo Html::encode($this->title); ?></h1> + <h1><?= Html::encode($this->title) ?></h1> <p> If you have business inquiries or other questions, please fill out the following form to contact us. Thank you. @@ -20,17 +20,17 @@ $this->params['breadcrumbs'][] = $this->title; <div class="row"> <div class="col-lg-5"> - <?php $form = ActiveForm::begin(array('id' => 'contact-form')); ?> - <?php echo $form->field($model, 'name'); ?> - <?php echo $form->field($model, 'email'); ?> - <?php echo $form->field($model, 'subject'); ?> - <?php echo $form->field($model, 'body')->textArea(array('rows' => 6)); ?> - <?php echo $form->field($model, 'verifyCode')->widget(Captcha::className(), array( - 'options' => array('class' => 'form-control'), + <?php $form = ActiveForm::begin(['id' => 'contact-form']); ?> + <?= $form->field($model, 'name') ?> + <?= $form->field($model, 'email') ?> + <?= $form->field($model, 'subject') ?> + <?= $form->field($model, 'body')->textArea(['rows' => 6]) ?> + <?= $form->field($model, 'verifyCode')->widget(Captcha::className(), [ + 'options' => ['class' => 'form-control'], 'template' => '<div class="row"><div class="col-lg-3">{image}</div><div class="col-lg-6">{input}</div></div>', - )); ?> + ]) ?> <div class="form-group"> - <?php echo Html::submitButton('Submit', array('class' => 'btn btn-primary')); ?> + <?= Html::submitButton('Submit', ['class' => 'btn btn-primary']) ?> </div> <?php ActiveForm::end(); ?> </div> diff --git a/apps/advanced/frontend/views/site/error.php b/apps/advanced/frontend/views/site/error.php index 024e27d..1b7ce04 100644 --- a/apps/advanced/frontend/views/site/error.php +++ b/apps/advanced/frontend/views/site/error.php @@ -3,7 +3,7 @@ use yii\helpers\Html; /** - * @var yii\base\View $this + * @var yii\web\View $this * @var string $name * @var string $message * @var Exception $exception @@ -13,10 +13,10 @@ $this->title = $name; ?> <div class="site-error"> - <h1><?php echo Html::encode($this->title); ?></h1> + <h1><?= Html::encode($this->title) ?></h1> <div class="alert alert-danger"> - <?php echo nl2br(Html::encode($message)); ?> + <?= nl2br(Html::encode($message)) ?> </div> <p> diff --git a/apps/advanced/frontend/views/site/index.php b/apps/advanced/frontend/views/site/index.php index f2e6d5e..bcb2278 100644 --- a/apps/advanced/frontend/views/site/index.php +++ b/apps/advanced/frontend/views/site/index.php @@ -1,6 +1,6 @@ <?php /** - * @var yii\base\View $this + * @var yii\web\View $this */ $this->title = 'My Yii Application'; ?> diff --git a/apps/advanced/frontend/views/site/login.php b/apps/advanced/frontend/views/site/login.php index 5e7f6f6..75dd4ca 100644 --- a/apps/advanced/frontend/views/site/login.php +++ b/apps/advanced/frontend/views/site/login.php @@ -3,7 +3,7 @@ use yii\helpers\Html; use yii\widgets\ActiveForm; /** - * @var yii\base\View $this + * @var yii\web\View $this * @var yii\widgets\ActiveForm $form * @var app\models\LoginForm $model */ @@ -11,21 +11,21 @@ $this->title = 'Login'; $this->params['breadcrumbs'][] = $this->title; ?> <div class="site-login"> - <h1><?php echo Html::encode($this->title); ?></h1> + <h1><?= Html::encode($this->title) ?></h1> <p>Please fill out the following fields to login:</p> <div class="row"> <div class="col-lg-5"> - <?php $form = ActiveForm::begin(array('id' => 'login-form')); ?> - <?php echo $form->field($model, 'username'); ?> - <?php echo $form->field($model, 'password')->passwordInput(); ?> - <?php echo $form->field($model, 'rememberMe')->checkbox(); ?> + <?php $form = ActiveForm::begin(['id' => 'login-form']); ?> + <?= $form->field($model, 'username') ?> + <?= $form->field($model, 'password')->passwordInput() ?> + <?= $form->field($model, 'rememberMe')->checkbox() ?> <div style="color:#999;margin:1em 0"> - If you forgot your password you can <?php echo Html::a('reset it', array('site/request-password-reset'))?>. + If you forgot your password you can <?= Html::a('reset it', ['site/request-password-reset']) ?>. </div> <div class="form-group"> - <?php echo Html::submitButton('Login', array('class' => 'btn btn-primary')); ?> + <?= Html::submitButton('Login', ['class' => 'btn btn-primary']) ?> </div> <?php ActiveForm::end(); ?> </div> diff --git a/apps/advanced/frontend/views/site/requestPasswordResetToken.php b/apps/advanced/frontend/views/site/requestPasswordResetToken.php index c754948..bb13a5f 100644 --- a/apps/advanced/frontend/views/site/requestPasswordResetToken.php +++ b/apps/advanced/frontend/views/site/requestPasswordResetToken.php @@ -3,7 +3,7 @@ use yii\helpers\Html; use yii\widgets\ActiveForm; /** - * @var yii\base\View $this + * @var yii\web\View $this * @var yii\widgets\ActiveForm $form * @var common\models\User $model */ @@ -11,16 +11,16 @@ $this->title = 'Request password reset'; $this->params['breadcrumbs'][] = $this->title; ?> <div class="site-request-password-reset"> - <h1><?php echo Html::encode($this->title); ?></h1> + <h1><?= Html::encode($this->title) ?></h1> <p>Please fill out your email. A link to reset password will be sent there.</p> <div class="row"> <div class="col-lg-5"> - <?php $form = ActiveForm::begin(array('id' => 'request-password-reset-form')); ?> - <?php echo $form->field($model, 'email'); ?> + <?php $form = ActiveForm::begin(['id' => 'request-password-reset-form']); ?> + <?= $form->field($model, 'email') ?> <div class="form-group"> - <?php echo Html::submitButton('Send', array('class' => 'btn btn-primary')); ?> + <?= Html::submitButton('Send', ['class' => 'btn btn-primary']) ?> </div> <?php ActiveForm::end(); ?> </div> diff --git a/apps/advanced/frontend/views/site/resetPassword.php b/apps/advanced/frontend/views/site/resetPassword.php index 2c38028..ec9f949 100644 --- a/apps/advanced/frontend/views/site/resetPassword.php +++ b/apps/advanced/frontend/views/site/resetPassword.php @@ -3,7 +3,7 @@ use yii\helpers\Html; use yii\widgets\ActiveForm; /** - * @var yii\base\View $this + * @var yii\web\View $this * @var yii\widgets\ActiveForm $form * @var common\models\User $model */ @@ -11,16 +11,16 @@ $this->title = 'Reset password'; $this->params['breadcrumbs'][] = $this->title; ?> <div class="site-reset-password"> - <h1><?php echo Html::encode($this->title); ?></h1> + <h1><?= Html::encode($this->title) ?></h1> <p>Please choose your new password:</p> <div class="row"> <div class="col-lg-5"> - <?php $form = ActiveForm::begin(array('id' => 'reset-password-form')); ?> - <?php echo $form->field($model, 'password')->passwordInput(); ?> + <?php $form = ActiveForm::begin(['id' => 'reset-password-form']); ?> + <?= $form->field($model, 'password')->passwordInput() ?> <div class="form-group"> - <?php echo Html::submitButton('Save', array('class' => 'btn btn-primary')); ?> + <?= Html::submitButton('Save', ['class' => 'btn btn-primary']) ?> </div> <?php ActiveForm::end(); ?> </div> diff --git a/apps/advanced/frontend/views/site/signup.php b/apps/advanced/frontend/views/site/signup.php index 92525bf..3bb57fc 100644 --- a/apps/advanced/frontend/views/site/signup.php +++ b/apps/advanced/frontend/views/site/signup.php @@ -3,7 +3,7 @@ use yii\helpers\Html; use yii\widgets\ActiveForm; /** - * @var yii\base\View $this + * @var yii\web\View $this * @var yii\widgets\ActiveForm $form * @var common\models\User $model */ @@ -11,18 +11,18 @@ $this->title = 'Signup'; $this->params['breadcrumbs'][] = $this->title; ?> <div class="site-signup"> - <h1><?php echo Html::encode($this->title); ?></h1> + <h1><?= Html::encode($this->title) ?></h1> <p>Please fill out the following fields to signup:</p> <div class="row"> <div class="col-lg-5"> - <?php $form = ActiveForm::begin(array('id' => 'form-signup')); ?> - <?php echo $form->field($model, 'username'); ?> - <?php echo $form->field($model, 'email'); ?> - <?php echo $form->field($model, 'password')->passwordInput(); ?> + <?php $form = ActiveForm::begin(['id' => 'form-signup']); ?> + <?= $form->field($model, 'username') ?> + <?= $form->field($model, 'email') ?> + <?= $form->field($model, 'password')->passwordInput() ?> <div class="form-group"> - <?php echo Html::submitButton('Signup', array('class' => 'btn btn-primary')); ?> + <?= Html::submitButton('Signup', ['class' => 'btn btn-primary']) ?> </div> <?php ActiveForm::end(); ?> </div> diff --git a/apps/advanced/frontend/web/css/site.css b/apps/advanced/frontend/web/css/site.css index e6db73c..6a355bd 100644 --- a/apps/advanced/frontend/web/css/site.css +++ b/apps/advanced/frontend/web/css/site.css @@ -17,3 +17,24 @@ body { font-size: 21px; padding: 14px 24px; } + +/* add sorting icons to gridview sort links */ +a.asc:after, a.desc:after { + position: relative; + top: 1px; + display: inline-block; + font-family: 'Glyphicons Halflings'; + font-style: normal; + font-weight: normal; + line-height: 1; + padding-left: 5px; +} + +a.asc:after { content: /*"\e113"*/"\e151"; } +a.desc:after { content: /*"\e114"*/"\e152"; } + +.sort-numerical a.asc:after { content: "\e153"; } +.sort-numerical a.desc:after { content: "\e154"; } + +.sort-ordinal a.asc:after { content: "\e155"; } +.sort-ordinal a.desc:after { content: "\e156"; } diff --git a/apps/advanced/frontend/widgets/Alert.php b/apps/advanced/frontend/widgets/Alert.php index b68bfb0..e070e1b 100644 --- a/apps/advanced/frontend/widgets/Alert.php +++ b/apps/advanced/frontend/widgets/Alert.php @@ -7,42 +7,60 @@ namespace frontend\widgets; -use yii\helpers\Html; - /** - * Alert widget renders a message from session flash. You can set message as following: + * Alert widget renders a message from session flash. All flash messages are displayed + * in the sequence they were assigned using setFlash. You can set message as following: * * - \Yii::$app->getSession()->setFlash('error', 'This is the message'); * - \Yii::$app->getSession()->setFlash('success', 'This is the message'); * - \Yii::$app->getSession()->setFlash('info', 'This is the message'); * + * @author Kartik Visweswaran <kartikv2@gmail.com> * @author Alexander Makarov <sam@rmcerative.ru> */ -class Alert extends \yii\bootstrap\Alert +class Alert extends \yii\bootstrap\Widget { - private $_doNotRender = false; + /** + * @var array the alert types configuration for the flash messages. + * This array is setup as $key => $value, where: + * - $key is the name of the session flash variable + * - $value is the bootstrap alert type (i.e. danger, success, info, warning) + */ + public $alertTypes = [ + 'error' => 'danger', + 'danger' => 'danger', + 'success' => 'success', + 'info' => 'info', + 'warning' => 'warning' + ]; + + /** + * @var array the options for rendering the close button tag. + */ + public $closeButton = []; + public function init() { - if ($this->body = \Yii::$app->getSession()->getFlash('error', null, true)) { - Html::addCssClass($this->options, 'alert-danger'); - } elseif ($this->body = \Yii::$app->getSession()->getFlash('success', null, true)) { - Html::addCssClass($this->options, 'alert-success'); - } elseif ($this->body = \Yii::$app->getSession()->getFlash('info', null, true)) { - Html::addCssClass($this->options, 'alert-info'); - } elseif ($this->body = \Yii::$app->getSession()->getFlash('warning', null, true)) { - Html::addCssClass($this->options, 'alert-warning'); - } else { - $this->_doNotRender = true; - return; - } - parent::init(); - } - public function run() - { - if (!$this->_doNotRender) { - parent::run(); + $session = \Yii::$app->getSession(); + $flashes = $session->getAllFlashes(); + $appendCss = isset($this->options['class']) ? ' ' . $this->options['class'] : ''; + + foreach ($flashes as $type => $message) { + /* initialize css class for each alert box */ + $this->options['class'] = 'alert-' . $this->alertTypes[$type] . $appendCss; + + /* assign unique id to each alert box */ + $this->options['id'] = $this->getId() . '-' . $type; + + echo \yii\bootstrap\Alert::widget([ + 'body' => $message, + 'closeButton' => $this->closeButton, + 'options' => $this->options + ]); + + $session->removeFlash($type); } } } diff --git a/apps/advanced/init b/apps/advanced/init index 17ed854..4015748 100755 --- a/apps/advanced/init +++ b/apps/advanced/init @@ -1,27 +1,49 @@ #!/usr/bin/env php <?php +$params = getParams(); $root = str_replace('\\', '/', __DIR__); $envs = require("$root/environments/index.php"); $envNames = array_keys($envs); -echo "Yii Application Init Tool v1.0\n\n"; -echo "Which environment do you want the application to be initialized in?\n\n"; -foreach ($envNames as $i => $name) { - echo " [$i] $name\n"; +echo "Yii Application Initialization Tool v1.0\n\n"; + +$envName = null; +if (empty($params['env'])) { + echo "Which environment do you want the application to be initialized in?\n\n"; + foreach ($envNames as $i => $name) { + echo " [$i] $name\n"; + } + echo "\n Your choice [0-" . (count($envs) - 1) . ', or "q" to quit] '; + $answer = trim(fgets(STDIN)); + + if (!ctype_digit($answer) || !in_array($answer, range(0, count($envs) - 1))) { + echo "\n Quit initialization.\n"; + exit(0); + } + + if (isset($envNames[$answer])) { + $envName = $envNames[$answer]; + } +} +else { + $envName = $params['env']; } -echo "\n Your choice [0-" . (count($envs) - 1) . ', or "q" to quit] '; -$answer = trim(fgets(STDIN)); -if (!ctype_digit($answer) || !isset($envNames[$answer])) { - echo "\n Quit initialization.\n"; - return; + +if (!in_array($envName, $envNames)) { + $envsList = implode(', ', $envNames); + echo "\n $envName is not a valid environment. Try one of the following: $envsList. \n"; + exit(2); } -$env = $envs[$envNames[$answer]]; -echo "\n Initialize the application under '{$envNames[$answer]}' environment? [yes|no] "; -$answer = trim(fgets(STDIN)); -if (strncasecmp($answer, 'y', 1)) { - echo "\n Quit initialization.\n"; - return; +$env = $envs[$envName]; + +if (empty($params['env'])) { + echo "\n Initialize the application under '{$envNames[$answer]}' environment? [yes|no] "; + $answer = trim(fgets(STDIN)); + if (strncasecmp($answer, 'y', 1)) { + echo "\n Quit initialization.\n"; + exit(0); + } } echo "\n Start initialization ...\n\n"; @@ -51,7 +73,7 @@ echo "\n ... initialization completed.\n\n"; function getFileList($root, $basePath = '') { - $files = array(); + $files = []; $handle = opendir($root); while (($path = readdir($handle)) !== false) { if ($path === '.svn' || $path === '.' || $path === '..') { @@ -110,3 +132,23 @@ function copyFile($root, $source, $target, &$all) file_put_contents($root . '/' . $target, file_get_contents($root . '/' . $source)); return true; } + +function getParams() +{ + $rawParams = []; + if (isset($_SERVER['argv'])) { + $rawParams = $_SERVER['argv']; + array_shift($rawParams); + } + + $params = []; + foreach ($rawParams as $param) { + if (preg_match('/^--(\w+)(=(.*))?$/', $param, $matches)) { + $name = $matches[1]; + $params[$name] = isset($matches[3]) ? $matches[3] : true; + } else { + $params[] = $param; + } + } + return $params; +} diff --git a/apps/advanced/requirements.php b/apps/advanced/requirements.php index c9e6493..47bdf37 100644 --- a/apps/advanced/requirements.php +++ b/apps/advanced/requirements.php @@ -26,78 +26,78 @@ $requirementsChecker = new YiiRequirementChecker(); /** * Adjust requirements according to your application specifics. */ -$requirements = array( +$requirements = [ // Database : - array( + [ 'name' => 'PDO extension', 'mandatory' => true, 'condition' => extension_loaded('pdo'), 'by' => 'All <a href="http://www.yiiframework.com/doc/api/#system.db">DB-related classes</a>', - ), - array( + ], + [ 'name' => 'PDO SQLite extension', 'mandatory' => false, 'condition' => extension_loaded('pdo_sqlite'), 'by' => 'All <a href="http://www.yiiframework.com/doc/api/#system.db">DB-related classes</a>', 'memo' => 'Required for SQLite database.', - ), - array( + ], + [ 'name' => 'PDO MySQL extension', 'mandatory' => false, 'condition' => extension_loaded('pdo_mysql'), 'by' => 'All <a href="http://www.yiiframework.com/doc/api/#system.db">DB-related classes</a>', 'memo' => 'Required for MySQL database.', - ), + ], // Cache : - array( + [ 'name' => 'Memcache extension', 'mandatory' => false, 'condition' => extension_loaded('memcache') || extension_loaded('memcached'), 'by' => '<a href="http://www.yiiframework.com/doc/api/CMemCache">CMemCache</a>', 'memo' => extension_loaded('memcached') ? 'To use memcached set <a href="http://www.yiiframework.com/doc/api/CMemCache#useMemcached-detail">CMemCache::useMemcached</a> to <code>true</code>.' : '' - ), - array( + ], + [ 'name' => 'APC extension', 'mandatory' => false, - 'condition' => extension_loaded('apc') || extension_loaded('apc'), + 'condition' => extension_loaded('apc'), 'by' => '<a href="http://www.yiiframework.com/doc/api/CApcCache">CApcCache</a>', - ), + ], // Additional PHP extensions : - array( + [ 'name' => 'Mcrypt extension', 'mandatory' => false, 'condition' => extension_loaded('mcrypt'), 'by' => '<a href="http://www.yiiframework.com/doc/api/CSecurityManager">CSecurityManager</a>', 'memo' => 'Required by encrypt and decrypt methods.' - ), + ], // PHP ini : - 'phpSafeMode' => array( + 'phpSafeMode' => [ 'name' => 'PHP safe mode', 'mandatory' => false, 'condition' => $requirementsChecker->checkPhpIniOff("safe_mode"), 'by' => 'File uploading and console command execution', 'memo' => '"safe_mode" should be disabled at php.ini', - ), - 'phpExposePhp' => array( + ], + 'phpExposePhp' => [ 'name' => 'Expose PHP', 'mandatory' => false, 'condition' => $requirementsChecker->checkPhpIniOff("expose_php"), 'by' => 'Security reasons', 'memo' => '"expose_php" should be disabled at php.ini', - ), - 'phpAllowUrlInclude' => array( + ], + 'phpAllowUrlInclude' => [ 'name' => 'PHP allow url include', 'mandatory' => false, 'condition' => $requirementsChecker->checkPhpIniOff("allow_url_include"), 'by' => 'Security reasons', 'memo' => '"allow_url_include" should be disabled at php.ini', - ), - 'phpSmtp' => array( + ], + 'phpSmtp' => [ 'name' => 'PHP mail SMTP', 'mandatory' => false, 'condition' => strlen(ini_get('SMTP'))>0, 'by' => 'Email sending', 'memo' => 'PHP mail SMTP server required', - ), -); + ], +]; $requirementsChecker->checkYii()->check($requirements)->render(); diff --git a/apps/basic/LICENSE.md b/apps/basic/LICENSE.md index 6edcc4f..e98f03d 100644 --- a/apps/basic/LICENSE.md +++ b/apps/basic/LICENSE.md @@ -1,7 +1,7 @@ The Yii framework is free software. It is released under the terms of the following BSD License. -Copyright © 2008-2013 by Yii Software LLC (http://www.yiisoft.com) +Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com) All rights reserved. Redistribution and use in source and binary forms, with or without @@ -29,4 +29,4 @@ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file +POSSIBILITY OF SUCH DAMAGE. diff --git a/apps/basic/README.md b/apps/basic/README.md index aaa7fba..60c3270 100644 --- a/apps/basic/README.md +++ b/apps/basic/README.md @@ -31,7 +31,7 @@ DIRECTORY STRUCTURE REQUIREMENTS ------------ -The minimum requirement by Yii is that your Web server supports PHP 5.3.?. +The minimum requirement by Yii is that your Web server supports PHP 5.4.0. In order for captcha to work you need either GD2 extension or ImageMagick PHP extension. diff --git a/apps/basic/config/AppAsset.php b/apps/basic/assets/AppAsset.php similarity index 73% rename from apps/basic/config/AppAsset.php rename to apps/basic/assets/AppAsset.php index 3e22b9b..c964d36 100644 --- a/apps/basic/config/AppAsset.php +++ b/apps/basic/assets/AppAsset.php @@ -5,7 +5,7 @@ * @license http://www.yiiframework.com/license/ */ -namespace app\config; +namespace app\assets; use yii\web\AssetBundle; @@ -17,13 +17,13 @@ class AppAsset extends AssetBundle { public $basePath = '@webroot'; public $baseUrl = '@web'; - public $css = array( + public $css = [ 'css/site.css', - ); - public $js = array( - ); - public $depends = array( + ]; + public $js = [ + ]; + public $depends = [ 'yii\web\YiiAsset', 'yii\bootstrap\BootstrapAsset', - ); + ]; } diff --git a/apps/basic/codeception.yml b/apps/basic/codeception.yml index 5b1f441..b6adeb5 100644 --- a/apps/basic/codeception.yml +++ b/apps/basic/codeception.yml @@ -6,7 +6,6 @@ paths: settings: bootstrap: _bootstrap.php suite_class: \PHPUnit_Framework_TestSuite - colors: true memory_limit: 1024M log: true modules: diff --git a/apps/basic/composer.json b/apps/basic/composer.json index dd50b69..cef46b7 100644 --- a/apps/basic/composer.json +++ b/apps/basic/composer.json @@ -14,21 +14,24 @@ }, "minimum-stability": "dev", "require": { - "php": ">=5.3.0", + "php": ">=5.4.0", "yiisoft/yii2": "dev-master", - "yiisoft/yii2-composer": "dev-master" + "yiisoft/yii2-swiftmailer": "dev-master", + "yiisoft/yii2-bootstrap": "dev-master", + "yiisoft/yii2-debug": "dev-master", + "yiisoft/yii2-gii": "dev-master" }, "scripts": { "post-create-project-cmd": [ - "yii\\composer\\InstallHandler::setPermissions" + "yii\\composer\\Installer::setPermission" ] }, "extra": { - "yii-install-writable": [ + "writable": [ "runtime", "web/assets" ], - "yii-install-executable": [ + "executable": [ "yii" ] } diff --git a/apps/basic/config/console.php b/apps/basic/config/console.php index 12f13cd..6f3f9a8 100644 --- a/apps/basic/config/console.php +++ b/apps/basic/config/console.php @@ -1,25 +1,24 @@ <?php $params = require(__DIR__ . '/params.php'); -return array( - 'id' => 'bootstrap-console', +return [ + 'id' => 'basic-console', 'basePath' => dirname(__DIR__), - 'preload' => array('log'), + 'preload' => ['log'], 'controllerPath' => dirname(__DIR__) . '/commands', 'controllerNamespace' => 'app\commands', - 'modules' => array( - ), - 'components' => array( - 'cache' => array( + 'extensions' => require(__DIR__ . '/../vendor/yiisoft/extensions.php'), + 'components' => [ + 'cache' => [ 'class' => 'yii\caching\FileCache', - ), - 'log' => array( - 'targets' => array( - array( + ], + 'log' => [ + 'targets' => [ + [ 'class' => 'yii\log\FileTarget', - 'levels' => array('error', 'warning'), - ), - ), - ), - ), + 'levels' => ['error', 'warning'], + ], + ], + ], + ], 'params' => $params, -); +]; diff --git a/apps/basic/config/params.php b/apps/basic/config/params.php index 398a1ce..93cb368 100644 --- a/apps/basic/config/params.php +++ b/apps/basic/config/params.php @@ -1,5 +1,5 @@ <?php -return array( +return [ 'adminEmail' => 'admin@example.com', -); +]; diff --git a/apps/basic/config/web.php b/apps/basic/config/web.php index e7d9420..cf921b0 100644 --- a/apps/basic/config/web.php +++ b/apps/basic/config/web.php @@ -1,33 +1,37 @@ <?php $params = require(__DIR__ . '/params.php'); -$config = array( - 'id' => 'bootstrap', +$config = [ + 'id' => 'basic', 'basePath' => dirname(__DIR__), - 'components' => array( - 'request' => array( + 'extensions' => require(__DIR__ . '/../vendor/yiisoft/extensions.php'), + 'components' => [ + 'request' => [ 'enableCsrfValidation' => true, - ), - 'cache' => array( + ], + 'cache' => [ 'class' => 'yii\caching\FileCache', - ), - 'user' => array( + ], + 'user' => [ 'identityClass' => 'app\models\User', - ), - 'errorHandler' => array( + ], + 'errorHandler' => [ 'errorAction' => 'site/error', - ), - 'log' => array( + ], + 'mail' => [ + 'class' => 'yii\swiftmailer\Mailer', + ], + 'log' => [ 'traceLevel' => YII_DEBUG ? 3 : 0, - 'targets' => array( - array( + 'targets' => [ + [ 'class' => 'yii\log\FileTarget', - 'levels' => array('error', 'warning'), - ), - ), - ), - ), + 'levels' => ['error', 'warning'], + ], + ], + ], + ], 'params' => $params, -); +]; if (YII_ENV_DEV) { $config['preload'][] = 'debug'; diff --git a/apps/basic/controllers/SiteController.php b/apps/basic/controllers/SiteController.php index 1196280..e3ca29b 100644 --- a/apps/basic/controllers/SiteController.php +++ b/apps/basic/controllers/SiteController.php @@ -13,43 +13,38 @@ class SiteController extends Controller { public function behaviors() { - return array( - 'access' => array( + return [ + 'access' => [ 'class' => AccessControl::className(), - 'only' => array('login', 'logout'), - 'rules' => array( - array( - 'actions' => array('login'), + 'only' => ['logout'], + 'rules' => [ + [ + 'actions' => ['logout'], 'allow' => true, - 'roles' => array('?'), - ), - array( - 'actions' => array('logout'), - 'allow' => true, - 'roles' => array('@'), - ), - ), - ), - 'verbs' => array( + 'roles' => ['@'], + ], + ], + ], + 'verbs' => [ 'class' => VerbFilter::className(), - 'actions' => array( - 'logout' => array('post'), - ), - ), - ); + 'actions' => [ + 'logout' => ['post'], + ], + ], + ]; } public function actions() { - return array( - 'error' => array( + return [ + 'error' => [ 'class' => 'yii\web\ErrorAction', - ), - 'captcha' => array( + ], + 'captcha' => [ 'class' => 'yii\captcha\CaptchaAction', 'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null, - ), - ); + ], + ]; } public function actionIndex() @@ -59,13 +54,17 @@ class SiteController extends Controller public function actionLogin() { + if (!\Yii::$app->user->isGuest) { + $this->goHome(); + } + $model = new LoginForm(); if ($model->load($_POST) && $model->login()) { - return $this->goHome(); + return $this->goBack(); } else { - return $this->render('login', array( + return $this->render('login', [ 'model' => $model, - )); + ]); } } @@ -82,9 +81,9 @@ class SiteController extends Controller Yii::$app->session->setFlash('contactFormSubmitted'); return $this->refresh(); } else { - return $this->render('contact', array( + return $this->render('contact', [ 'model' => $model, - )); + ]); } } diff --git a/apps/basic/models/ContactForm.php b/apps/basic/models/ContactForm.php index 7b713a1..1344562 100644 --- a/apps/basic/models/ContactForm.php +++ b/apps/basic/models/ContactForm.php @@ -2,6 +2,7 @@ namespace app\models; +use Yii; use yii\base\Model; /** @@ -20,14 +21,14 @@ class ContactForm extends Model */ public function rules() { - return array( + return [ // name, email, subject and body are required - array('name, email, subject, body', 'required'), + [['name', 'email', 'subject', 'body'], 'required'], // email has to be a valid email address - array('email', 'email'), + ['email', 'email'], // verifyCode needs to be entered correctly - array('verifyCode', 'captcha'), - ); + ['verifyCode', 'captcha'], + ]; } /** @@ -35,9 +36,9 @@ class ContactForm extends Model */ public function attributeLabels() { - return array( + return [ 'verifyCode' => 'Verification Code', - ); + ]; } /** @@ -48,13 +49,12 @@ class ContactForm extends Model 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); + Yii::$app->mail->compose() + ->setTo($email) + ->setFrom([$this->email => $this->name]) + ->setSubject($this->subject) + ->setTextBody($this->body) + ->send(); return true; } else { return false; diff --git a/apps/basic/models/LoginForm.php b/apps/basic/models/LoginForm.php index 5ba1dc6..a365db7 100644 --- a/apps/basic/models/LoginForm.php +++ b/apps/basic/models/LoginForm.php @@ -14,19 +14,21 @@ class LoginForm extends Model public $password; public $rememberMe = true; + private $_user = false; + /** * @return array the validation rules. */ public function rules() { - return array( + return [ // username and password are both required - array('username, password', 'required'), + [['username', 'password'], 'required'], // password is validated by validatePassword() - array('password', 'validatePassword'), + ['password', 'validatePassword'], // rememberMe must be a boolean value - array('rememberMe', 'boolean'), - ); + ['rememberMe', 'boolean'], + ]; } /** @@ -35,7 +37,7 @@ class LoginForm extends Model */ public function validatePassword() { - $user = User::findByUsername($this->username); + $user = $this->getUser(); if (!$user || !$user->validatePassword($this->password)) { $this->addError('password', 'Incorrect username or password.'); } @@ -48,11 +50,22 @@ class LoginForm extends Model public function login() { if ($this->validate()) { - $user = User::findByUsername($this->username); - Yii::$app->user->login($user, $this->rememberMe ? 3600*24*30 : 0); - return true; + return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600*24*30 : 0); } else { return false; } } + + /** + * Finds user by [[username]] + * + * @return User|null + */ + private function getUser() + { + if ($this->_user === false) { + $this->_user = User::findByUsername($this->username); + } + return $this->_user; + } } diff --git a/apps/basic/models/User.php b/apps/basic/models/User.php index e1088a0..af4c42e 100644 --- a/apps/basic/models/User.php +++ b/apps/basic/models/User.php @@ -9,31 +9,31 @@ class User extends \yii\base\Object implements \yii\web\IdentityInterface public $password; public $authKey; - private static $users = array( - '100' => array( + private static $users = [ + '100' => [ 'id' => '100', 'username' => 'admin', 'password' => 'admin', 'authKey' => 'test100key', - ), - '101' => array( + ], + '101' => [ 'id' => '101', 'username' => 'demo', 'password' => 'demo', 'authKey' => 'test101key', - ), - ); + ], + ]; public static function findIdentity($id) { - return isset(self::$users[$id]) ? new self(self::$users[$id]) : null; + return isset(self::$users[$id]) ? new static(self::$users[$id]) : null; } public static function findByUsername($username) { foreach (self::$users as $user) { if (strcasecmp($user['username'], $username) === 0) { - return new self($user); + return new static($user); } } return null; diff --git a/apps/basic/requirements.php b/apps/basic/requirements.php index c9e6493..47bdf37 100644 --- a/apps/basic/requirements.php +++ b/apps/basic/requirements.php @@ -26,78 +26,78 @@ $requirementsChecker = new YiiRequirementChecker(); /** * Adjust requirements according to your application specifics. */ -$requirements = array( +$requirements = [ // Database : - array( + [ 'name' => 'PDO extension', 'mandatory' => true, 'condition' => extension_loaded('pdo'), 'by' => 'All <a href="http://www.yiiframework.com/doc/api/#system.db">DB-related classes</a>', - ), - array( + ], + [ 'name' => 'PDO SQLite extension', 'mandatory' => false, 'condition' => extension_loaded('pdo_sqlite'), 'by' => 'All <a href="http://www.yiiframework.com/doc/api/#system.db">DB-related classes</a>', 'memo' => 'Required for SQLite database.', - ), - array( + ], + [ 'name' => 'PDO MySQL extension', 'mandatory' => false, 'condition' => extension_loaded('pdo_mysql'), 'by' => 'All <a href="http://www.yiiframework.com/doc/api/#system.db">DB-related classes</a>', 'memo' => 'Required for MySQL database.', - ), + ], // Cache : - array( + [ 'name' => 'Memcache extension', 'mandatory' => false, 'condition' => extension_loaded('memcache') || extension_loaded('memcached'), 'by' => '<a href="http://www.yiiframework.com/doc/api/CMemCache">CMemCache</a>', 'memo' => extension_loaded('memcached') ? 'To use memcached set <a href="http://www.yiiframework.com/doc/api/CMemCache#useMemcached-detail">CMemCache::useMemcached</a> to <code>true</code>.' : '' - ), - array( + ], + [ 'name' => 'APC extension', 'mandatory' => false, - 'condition' => extension_loaded('apc') || extension_loaded('apc'), + 'condition' => extension_loaded('apc'), 'by' => '<a href="http://www.yiiframework.com/doc/api/CApcCache">CApcCache</a>', - ), + ], // Additional PHP extensions : - array( + [ 'name' => 'Mcrypt extension', 'mandatory' => false, 'condition' => extension_loaded('mcrypt'), 'by' => '<a href="http://www.yiiframework.com/doc/api/CSecurityManager">CSecurityManager</a>', 'memo' => 'Required by encrypt and decrypt methods.' - ), + ], // PHP ini : - 'phpSafeMode' => array( + 'phpSafeMode' => [ 'name' => 'PHP safe mode', 'mandatory' => false, 'condition' => $requirementsChecker->checkPhpIniOff("safe_mode"), 'by' => 'File uploading and console command execution', 'memo' => '"safe_mode" should be disabled at php.ini', - ), - 'phpExposePhp' => array( + ], + 'phpExposePhp' => [ 'name' => 'Expose PHP', 'mandatory' => false, 'condition' => $requirementsChecker->checkPhpIniOff("expose_php"), 'by' => 'Security reasons', 'memo' => '"expose_php" should be disabled at php.ini', - ), - 'phpAllowUrlInclude' => array( + ], + 'phpAllowUrlInclude' => [ 'name' => 'PHP allow url include', 'mandatory' => false, 'condition' => $requirementsChecker->checkPhpIniOff("allow_url_include"), 'by' => 'Security reasons', 'memo' => '"allow_url_include" should be disabled at php.ini', - ), - 'phpSmtp' => array( + ], + 'phpSmtp' => [ 'name' => 'PHP mail SMTP', 'mandatory' => false, 'condition' => strlen(ini_get('SMTP'))>0, 'by' => 'Email sending', 'memo' => 'PHP mail SMTP server required', - ), -); + ], +]; $requirementsChecker->checkYii()->check($requirements)->render(); diff --git a/apps/basic/tests/README.md b/apps/basic/tests/README.md new file mode 100644 index 0000000..c87f762 --- /dev/null +++ b/apps/basic/tests/README.md @@ -0,0 +1,20 @@ +This folder contains various tests for the basic application. +These tests are developed with [Codeception PHP Testing Framework](http://codeception.com/). + +To run the tests, follow these steps: + +1. [Install Codeception](http://codeception.com/quickstart) if you do not have it yet. +2. Create test configuration files based on your environment: + - Copy `acceptance.suite.dist.yml` to `acceptance.suite.yml` and customize it; + - Copy `functional.suite.dist.yml` to `functional.suite.yml` and customize it; + - Copy `unit.suite.dist.yml` to `unit.suite.yml` and customize it. +3. Switch to the parent folder and run tests: + +``` +cd .. +php codecept.phar build // rebuild test scripts, only need to be run once +php codecept.phar run // run all available tests +``` + +Please refer to [Codeception tutorial](http://codeception.com/docs/01-Introduction) for +more details about writing acceptance, functional and unit tests. diff --git a/apps/basic/tests/acceptance/ContactCept.php b/apps/basic/tests/acceptance/ContactCept.php index 73527ab..5ec5641 100644 --- a/apps/basic/tests/acceptance/ContactCept.php +++ b/apps/basic/tests/acceptance/ContactCept.php @@ -4,7 +4,7 @@ $I->wantTo('ensure that contact works'); $I->amOnPage('?r=site/contact'); $I->see('Contact', 'h1'); -$I->submitForm('#contact-form', array()); +$I->submitForm('#contact-form', []); $I->see('Contact', 'h1'); $I->see('Name cannot be blank'); $I->see('Email cannot be blank'); @@ -12,25 +12,25 @@ $I->see('Subject cannot be blank'); $I->see('Body cannot be blank'); $I->see('The verification code is incorrect'); -$I->submitForm('#contact-form', array( +$I->submitForm('#contact-form', [ 'ContactForm[name]' => 'tester', 'ContactForm[email]' => 'tester.email', 'ContactForm[subject]' => 'test subject', 'ContactForm[body]' => 'test content', 'ContactForm[verifyCode]' => 'testme', -)); +]); $I->dontSee('Name cannot be blank', '.help-inline'); $I->see('Email is not a valid email address.'); $I->dontSee('Subject cannot be blank', '.help-inline'); $I->dontSee('Body cannot be blank', '.help-inline'); $I->dontSee('The verification code is incorrect', '.help-inline'); -$I->submitForm('#contact-form', array( +$I->submitForm('#contact-form', [ 'ContactForm[name]' => 'tester', 'ContactForm[email]' => 'tester@example.com', 'ContactForm[subject]' => 'test subject', 'ContactForm[body]' => 'test content', 'ContactForm[verifyCode]' => 'testme', -)); +]); $I->dontSeeElement('#contact-form'); $I->see('Thank you for contacting us. We will respond to you as soon as possible.'); diff --git a/apps/basic/tests/acceptance/LoginCept.php b/apps/basic/tests/acceptance/LoginCept.php index 77c4a07..5621b15 100644 --- a/apps/basic/tests/acceptance/LoginCept.php +++ b/apps/basic/tests/acceptance/LoginCept.php @@ -4,20 +4,20 @@ $I->wantTo('ensure that login works'); $I->amOnPage('?r=site/login'); $I->see('Login', 'h1'); -$I->submitForm('#login-form', array()); +$I->submitForm('#login-form', []); $I->dontSee('Logout (admin)'); $I->see('Username cannot be blank'); $I->see('Password cannot be blank'); -$I->submitForm('#login-form', array( +$I->submitForm('#login-form', [ 'LoginForm[username]' => 'admin', 'LoginForm[password]' => 'wrong', -)); +]); $I->dontSee('Logout (admin)'); $I->see('Incorrect username or password'); -$I->submitForm('#login-form', array( +$I->submitForm('#login-form', [ 'LoginForm[username]' => 'admin', 'LoginForm[password]' => 'admin', -)); +]); $I->see('Logout (admin)'); diff --git a/apps/basic/tests/acceptance/WebGuy.php b/apps/basic/tests/acceptance/WebGuy.php index 397761c..f08ed9c 100644 --- a/apps/basic/tests/acceptance/WebGuy.php +++ b/apps/basic/tests/acceptance/WebGuy.php @@ -17,13 +17,21 @@ use Codeception\Module\WebHelper; * @method void expect($prediction) * @method void amGoingTo($argumentation) * @method void am($role) - * @method void lookForwardTo($role) + * @method void lookForwardTo($achieveValue) + * @method void offsetGet($offset) + * @method void offsetSet($offset, $value) + * @method void offsetExists($offset) + * @method void offsetUnset($offset) */ class WebGuy extends \Codeception\AbstractGuy { /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Submits a form located on page. * Specify the form by it's css or xpath selector. * Fill the form fields values as array. @@ -62,13 +70,11 @@ class WebGuy extends \Codeception\AbstractGuy * * @param $selector * @param $params - * @see PhpBrowser::submitForm() + * @see Codeception\Module\PhpBrowser::submitForm() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function submitForm($selector, $params) { - $this->scenario->action('submitForm', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Action('submitForm', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -78,6 +84,10 @@ class WebGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * If your page triggers an ajax request, you can perform it manually. * This action sends a POST ajax request with specified params. * Additional params can be passed as array. @@ -96,13 +106,11 @@ class WebGuy extends \Codeception\AbstractGuy * * @param $uri * @param $params - * @see PhpBrowser::sendAjaxPostRequest() + * @see Codeception\Module\PhpBrowser::sendAjaxPostRequest() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function sendAjaxPostRequest($uri, $params = null) { - $this->scenario->action('sendAjaxPostRequest', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Action('sendAjaxPostRequest', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -112,6 +120,10 @@ class WebGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * If your page triggers an ajax request, you can perform it manually. * This action sends a GET ajax request with specified params. * @@ -119,13 +131,11 @@ class WebGuy extends \Codeception\AbstractGuy * * @param $uri * @param $params - * @see PhpBrowser::sendAjaxGetRequest() + * @see Codeception\Module\PhpBrowser::sendAjaxGetRequest() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function sendAjaxGetRequest($uri, $params = null) { - $this->scenario->action('sendAjaxGetRequest', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Action('sendAjaxGetRequest', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -135,14 +145,34 @@ class WebGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Asserts that current page has 404 response status code. + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Module\PhpBrowser::seePageNotFound() + * @return \Codeception\Maybe + */ + public function canSeePageNotFound() { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('seePageNotFound', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Asserts that current page has 404 response status code. - * @see PhpBrowser::seePageNotFound() + * @see Codeception\Module\PhpBrowser::seePageNotFound() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function seePageNotFound() { - $this->scenario->assertion('seePageNotFound', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Assertion('seePageNotFound', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -152,17 +182,40 @@ class WebGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks that response code is equal to value provided. + * + * @param $code + * @return mixed + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Module\PhpBrowser::seeResponseCodeIs() + * @return \Codeception\Maybe + */ + public function canSeeResponseCodeIs($code) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('seeResponseCodeIs', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Checks that response code is equal to value provided. * * @param $code * @return mixed - * @see PhpBrowser::seeResponseCodeIs() + * @see Codeception\Module\PhpBrowser::seeResponseCodeIs() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function seeResponseCodeIs($code) { - $this->scenario->assertion('seeResponseCodeIs', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Assertion('seeResponseCodeIs', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -172,17 +225,19 @@ class WebGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Adds HTTP authentication via username/password. * * @param $username * @param $password - * @see PhpBrowser::amHttpAuthenticated() + * @see Codeception\Module\PhpBrowser::amHttpAuthenticated() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function amHttpAuthenticated($username, $password) { - $this->scenario->condition('amHttpAuthenticated', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Condition('amHttpAuthenticated', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -192,6 +247,10 @@ class WebGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Low-level API method. * If Codeception commands are not enough, use [Guzzle HTTP Client](http://guzzlephp.org/) methods directly * @@ -216,13 +275,135 @@ class WebGuy extends \Codeception\AbstractGuy * If Codeception lacks important Guzzle Client methods implement then and submit patches. * * @param callable $function - * @see PhpBrowser::executeInGuzzle() + * @see Codeception\Module\PhpBrowser::executeInGuzzle() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function executeInGuzzle($function) { - $this->scenario->action('executeInGuzzle', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Action('executeInGuzzle', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + + + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Assert if the specified checkbox is checked. + * Use css selector or xpath to match. + * + * Example: + * + * ``` php + * <?php + * $I->seeCheckboxIsChecked('#agree'); // I suppose user agreed to terms + * $I->seeCheckboxIsChecked('#signup_form input[type=checkbox]'); // I suppose user agreed to terms, If there is only one checkbox in form. + * $I->seeCheckboxIsChecked('//form/input[@type=checkbox and @name=agree]'); + * ?> + * ``` + * + * @param $checkbox + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Module\PhpBrowser::seeCheckboxIsChecked() + * @return \Codeception\Maybe + */ + public function canSeeCheckboxIsChecked($checkbox) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('seeCheckboxIsChecked', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Assert if the specified checkbox is checked. + * Use css selector or xpath to match. + * + * Example: + * + * ``` php + * <?php + * $I->seeCheckboxIsChecked('#agree'); // I suppose user agreed to terms + * $I->seeCheckboxIsChecked('#signup_form input[type=checkbox]'); // I suppose user agreed to terms, If there is only one checkbox in form. + * $I->seeCheckboxIsChecked('//form/input[@type=checkbox and @name=agree]'); + * ?> + * ``` + * + * @param $checkbox + * @see Codeception\Module\PhpBrowser::seeCheckboxIsChecked() + * @return \Codeception\Maybe + */ + public function seeCheckboxIsChecked($checkbox) { + $this->scenario->addStep(new \Codeception\Step\Assertion('seeCheckboxIsChecked', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + + + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Assert if the specified checkbox is unchecked. + * Use css selector or xpath to match. + * + * Example: + * + * ``` php + * <?php + * $I->dontSeeCheckboxIsChecked('#agree'); // I suppose user didn't agree to terms + * $I->seeCheckboxIsChecked('#signup_form input[type=checkbox]'); // I suppose user didn't check the first checkbox in form. + * ?> + * ``` + * + * @param $checkbox + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Module\PhpBrowser::dontSeeCheckboxIsChecked() + * @return \Codeception\Maybe + */ + public function cantSeeCheckboxIsChecked($checkbox) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('dontSeeCheckboxIsChecked', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Assert if the specified checkbox is unchecked. + * Use css selector or xpath to match. + * + * Example: + * + * ``` php + * <?php + * $I->dontSeeCheckboxIsChecked('#agree'); // I suppose user didn't agree to terms + * $I->seeCheckboxIsChecked('#signup_form input[type=checkbox]'); // I suppose user didn't check the first checkbox in form. + * ?> + * ``` + * + * @param $checkbox + * @see Codeception\Module\PhpBrowser::dontSeeCheckboxIsChecked() + * @return \Codeception\Maybe + */ + public function dontSeeCheckboxIsChecked($checkbox) { + $this->scenario->addStep(new \Codeception\Step\Assertion('dontSeeCheckboxIsChecked', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -232,16 +413,18 @@ class WebGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Opens the page. * * @param $page - * @see Mink::amOnPage() + * @see Codeception\Util\Mink::amOnPage() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function amOnPage($page) { - $this->scenario->condition('amOnPage', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Condition('amOnPage', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -251,6 +434,10 @@ class WebGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Sets 'url' configuration parameter to hosts subdomain. * It does not open a page on subdomain. Use `amOnPage` for that * @@ -267,13 +454,54 @@ class WebGuy extends \Codeception\AbstractGuy * ``` * @param $subdomain * @return mixed - * @see Mink::amOnSubdomain() + * @see Codeception\Util\Mink::amOnSubdomain() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function amOnSubdomain($subdomain) { - $this->scenario->condition('amOnSubdomain', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Condition('amOnSubdomain', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + + + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * @param string $text + * @param string $selector + * + * @return void + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Mink::dontSee() + * @return \Codeception\Maybe + */ + public function cantSee($text, $selector = null) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('dontSee', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * @param string $text + * @param string $selector + * + * @return void + * @see Codeception\Util\Mink::dontSee() + * @return \Codeception\Maybe + */ + public function dontSee($text, $selector = null) { + $this->scenario->addStep(new \Codeception\Step\Assertion('dontSee', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -283,36 +511,42 @@ class WebGuy extends \Codeception\AbstractGuy /** - * Check if current page doesn't contain the text specified. + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Check if current page contains the text specified. * Specify the css selector to match only specific region. * * Examples: * - * ```php + * ``` php * <?php - * $I->dontSee('Login'); // I can suppose user is already logged in - * $I->dontSee('Sign Up','h1'); // I can suppose it's not a signup page - * $I->dontSee('Sign Up','//body/h1'); // with XPath + * $I->see('Logout'); // I can suppose user is logged in + * $I->see('Sign Up','h1'); // I can suppose it's a signup page + * $I->see('Sign Up','//body/h1'); // with XPath + * ?> * ``` * * @param $text * @param null $selector - * @see Mink::dontSee() + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Mink::see() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ - public function dontSee($text, $selector = null) { - $this->scenario->action('dontSee', func_get_args()); + public function canSee($text, $selector = null) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('see', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); } return new Maybe(); } - - /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Check if current page contains the text specified. * Specify the css selector to match only specific region. * @@ -323,18 +557,16 @@ class WebGuy extends \Codeception\AbstractGuy * $I->see('Logout'); // I can suppose user is logged in * $I->see('Sign Up','h1'); // I can suppose it's a signup page * $I->see('Sign Up','//body/h1'); // with XPath - * + * ?> * ``` * * @param $text * @param null $selector - * @see Mink::see() + * @see Codeception\Util\Mink::see() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function see($text, $selector = null) { - $this->scenario->assertion('see', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Assertion('see', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -344,6 +576,10 @@ class WebGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Checks if there is a link with text specified. * Specify url to match link with exact this url. * @@ -353,18 +589,47 @@ class WebGuy extends \Codeception\AbstractGuy * <?php * $I->seeLink('Logout'); // matches <a href="#">Logout</a> * $I->seeLink('Logout','/logout'); // matches <a href="/logout">Logout</a> + * ?> + * ``` * + * @param $text + * @param null $url + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Mink::seeLink() + * @return \Codeception\Maybe + */ + public function canSeeLink($text, $url = null) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('seeLink', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks if there is a link with text specified. + * Specify url to match link with exact this url. + * + * Examples: + * + * ``` php + * <?php + * $I->seeLink('Logout'); // matches <a href="#">Logout</a> + * $I->seeLink('Logout','/logout'); // matches <a href="/logout">Logout</a> + * ?> * ``` * * @param $text * @param null $url - * @see Mink::seeLink() + * @see Codeception\Util\Mink::seeLink() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function seeLink($text, $url = null) { - $this->scenario->assertion('seeLink', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Assertion('seeLink', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -374,6 +639,10 @@ class WebGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Checks if page doesn't contain the link with text specified. * Specify url to narrow the results. * @@ -382,18 +651,46 @@ class WebGuy extends \Codeception\AbstractGuy * ``` php * <?php * $I->dontSeeLink('Logout'); // I suppose user is not logged in + * ?> + * ``` * + * @param $text + * @param null $url + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Mink::dontSeeLink() + * @return \Codeception\Maybe + */ + public function cantSeeLink($text, $url = null) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('dontSeeLink', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks if page doesn't contain the link with text specified. + * Specify url to narrow the results. + * + * Examples: + * + * ``` php + * <?php + * $I->dontSeeLink('Logout'); // I suppose user is not logged in + * ?> * ``` * * @param $text * @param null $url - * @see Mink::dontSeeLink() + * @see Codeception\Util\Mink::dontSeeLink() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function dontSeeLink($text, $url = null) { - $this->scenario->action('dontSeeLink', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Assertion('dontSeeLink', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -403,6 +700,10 @@ class WebGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Perform a click on link or button. * Link or button are found by their names or CSS selector. * Submits a form if button is a submit type. @@ -431,13 +732,11 @@ class WebGuy extends \Codeception\AbstractGuy * ``` * @param $link * @param $context - * @see Mink::click() + * @see Codeception\Util\Mink::click() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function click($link, $context = null) { - $this->scenario->action('click', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Action('click', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -447,22 +746,50 @@ class WebGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks if element exists on a page, matching it by CSS or XPath + * + * ``` php + * <?php + * $I->seeElement('.error'); + * $I->seeElement('//form/input[1]'); + * ?> + * ``` + * @param $selector + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Mink::seeElement() + * @return \Codeception\Maybe + */ + public function canSeeElement($selector) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('seeElement', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Checks if element exists on a page, matching it by CSS or XPath * * ``` php * <?php * $I->seeElement('.error'); - * $I->seeElement(//form/input[1]); + * $I->seeElement('//form/input[1]'); * ?> * ``` * @param $selector - * @see Mink::seeElement() + * @see Codeception\Util\Mink::seeElement() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function seeElement($selector) { - $this->scenario->assertion('seeElement', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Assertion('seeElement', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -472,22 +799,54 @@ class WebGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks if element does not exist (or is visible) on a page, matching it by CSS or XPath + * + * Example: + * + * ``` php + * <?php + * $I->dontSeeElement('.error'); + * $I->dontSeeElement('//form/input[1]'); + * ?> + * ``` + * @param $selector + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Mink::dontSeeElement() + * @return \Codeception\Maybe + */ + public function cantSeeElement($selector) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('dontSeeElement', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Checks if element does not exist (or is visible) on a page, matching it by CSS or XPath * + * Example: + * * ``` php * <?php * $I->dontSeeElement('.error'); - * $I->dontSeeElement(//form/input[1]); + * $I->dontSeeElement('//form/input[1]'); * ?> * ``` * @param $selector - * @see Mink::dontSeeElement() + * @see Codeception\Util\Mink::dontSeeElement() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function dontSeeElement($selector) { - $this->scenario->action('dontSeeElement', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Assertion('dontSeeElement', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -497,14 +856,16 @@ class WebGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Reloads current page - * @see Mink::reloadPage() + * @see Codeception\Util\Mink::reloadPage() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function reloadPage() { - $this->scenario->action('reloadPage', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Action('reloadPage', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -514,14 +875,16 @@ class WebGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Moves back in history - * @see Mink::moveBack() + * @see Codeception\Util\Mink::moveBack() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function moveBack() { - $this->scenario->action('moveBack', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Action('moveBack', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -531,14 +894,16 @@ class WebGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Moves forward in history - * @see Mink::moveForward() + * @see Codeception\Util\Mink::moveForward() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function moveForward() { - $this->scenario->action('moveForward', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Action('moveForward', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -548,17 +913,27 @@ class WebGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Fills a text field or textarea with value. + * + * Example: + * + * ``` php + * <?php + * $I->fillField("//input[@type='text']", "Hello World!"); + * ?> + * ``` * * @param $field * @param $value - * @see Mink::fillField() + * @see Codeception\Util\Mink::fillField() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function fillField($field, $value) { - $this->scenario->action('fillField', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Action('fillField', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -568,27 +943,265 @@ class WebGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Selects an option in select tag or in radio button group. * * Example: * * ``` php * <?php - * $I->selectOption('form select[name=account]', 'Premium'); - * $I->selectOption('form input[name=payment]', 'Monthly'); - * $I->selectOption('//form/select[@name=account]', 'Monthly'); + * $I->selectOption('form select[name=account]', 'Premium'); + * $I->selectOption('form input[name=payment]', 'Monthly'); + * $I->selectOption('//form/select[@name=account]', 'Monthly'); + * ?> + * ``` + * + * Can select multiple options if second argument is array: + * + * ``` php + * <?php + * $I->selectOption('Which OS do you use?', array('Windows','Linux')); + * ?> + * ``` + * + * @param $select + * @param $option + * @see Codeception\Util\Mink::selectOption() + * @return \Codeception\Maybe + */ + public function selectOption($select, $option) { + $this->scenario->addStep(new \Codeception\Step\Action('selectOption', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + + + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Ticks a checkbox. + * For radio buttons use `selectOption` method. + * + * Example: + * + * ``` php + * <?php + * $I->checkOption('#agree'); + * ?> + * ``` + * + * @param $option + * @see Codeception\Util\Mink::checkOption() + * @return \Codeception\Maybe + */ + public function checkOption($option) { + $this->scenario->addStep(new \Codeception\Step\Action('checkOption', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + + + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Unticks a checkbox. + * + * Example: + * + * ``` php + * <?php + * $I->uncheckOption('#notify'); + * ?> + * ``` + * + * @param $option + * @see Codeception\Util\Mink::uncheckOption() + * @return \Codeception\Maybe + */ + public function uncheckOption($option) { + $this->scenario->addStep(new \Codeception\Step\Action('uncheckOption', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + + + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks that current uri contains a value + * + * ``` php + * <?php + * // to match: /home/dashboard + * $I->seeInCurrentUrl('home'); + * // to match: /users/1 + * $I->seeInCurrentUrl('/users/'); + * ?> + * ``` + * + * @param $uri + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Mink::seeInCurrentUrl() + * @return \Codeception\Maybe + */ + public function canSeeInCurrentUrl($uri) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('seeInCurrentUrl', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks that current uri contains a value + * + * ``` php + * <?php + * // to match: /home/dashboard + * $I->seeInCurrentUrl('home'); + * // to match: /users/1 + * $I->seeInCurrentUrl('/users/'); + * ?> + * ``` + * + * @param $uri + * @see Codeception\Util\Mink::seeInCurrentUrl() + * @return \Codeception\Maybe + */ + public function seeInCurrentUrl($uri) { + $this->scenario->addStep(new \Codeception\Step\Assertion('seeInCurrentUrl', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + + + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks that current uri does not contain a value + * + * ``` php + * <?php + * $I->dontSeeInCurrentUrl('/users/'); + * ?> + * ``` + * + * @param $uri + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Mink::dontSeeInCurrentUrl() + * @return \Codeception\Maybe + */ + public function cantSeeInCurrentUrl($uri) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('dontSeeInCurrentUrl', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks that current uri does not contain a value + * + * ``` php + * <?php + * $I->dontSeeInCurrentUrl('/users/'); + * ?> + * ``` + * + * @param $uri + * @see Codeception\Util\Mink::dontSeeInCurrentUrl() + * @return \Codeception\Maybe + */ + public function dontSeeInCurrentUrl($uri) { + $this->scenario->addStep(new \Codeception\Step\Assertion('dontSeeInCurrentUrl', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + + + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks that current url is equal to value. + * Unlike `seeInCurrentUrl` performs a strict check. + * + * ``` php + * <?php + * // to match root url + * $I->seeCurrentUrlEquals('/'); + * ?> + * ``` + * + * @param $uri + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Mink::seeCurrentUrlEquals() + * @return \Codeception\Maybe + */ + public function canSeeCurrentUrlEquals($uri) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('seeCurrentUrlEquals', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks that current url is equal to value. + * Unlike `seeInCurrentUrl` performs a strict check. + * + * ``` php + * <?php + * // to match root url + * $I->seeCurrentUrlEquals('/'); * ?> * ``` * - * @param $select - * @param $option - * @see Mink::selectOption() + * @param $uri + * @see Codeception\Util\Mink::seeCurrentUrlEquals() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ - public function selectOption($select, $option) { - $this->scenario->action('selectOption', func_get_args()); + public function seeCurrentUrlEquals($uri) { + $this->scenario->addStep(new \Codeception\Step\Assertion('seeCurrentUrlEquals', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -598,52 +1211,54 @@ class WebGuy extends \Codeception\AbstractGuy /** - * Ticks a checkbox. - * For radio buttons use `selectOption` method. + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- * - * Example: + * Checks that current url is not equal to value. + * Unlike `dontSeeInCurrentUrl` performs a strict check. * * ``` php * <?php - * $I->checkOption('#agree'); + * // current url is not root + * $I->dontSeeCurrentUrlEquals('/'); * ?> * ``` * - * @param $option - * @see Mink::checkOption() + * @param $uri + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Mink::dontSeeCurrentUrlEquals() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ - public function checkOption($option) { - $this->scenario->action('checkOption', func_get_args()); + public function cantSeeCurrentUrlEquals($uri) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('dontSeeCurrentUrlEquals', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); } return new Maybe(); } - - /** - * Unticks a checkbox. + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- * - * Example: + * Checks that current url is not equal to value. + * Unlike `dontSeeInCurrentUrl` performs a strict check. * * ``` php * <?php - * $I->uncheckOption('#notify'); + * // current url is not root + * $I->dontSeeCurrentUrlEquals('/'); * ?> * ``` * - * @param $option - * @see Mink::uncheckOption() + * @param $uri + * @see Codeception\Util\Mink::dontSeeCurrentUrlEquals() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ - public function uncheckOption($option) { - $this->scenario->action('uncheckOption', func_get_args()); + public function dontSeeCurrentUrlEquals($uri) { + $this->scenario->addStep(new \Codeception\Step\Assertion('dontSeeCurrentUrlEquals', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -653,50 +1268,52 @@ class WebGuy extends \Codeception\AbstractGuy /** - * Checks that current uri contains a value + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks that current url is matches a RegEx value * * ``` php * <?php - * // to match: /home/dashboard - * $I->seeInCurrentUrl('home'); - * // to match: /users/1 - * $I->seeInCurrentUrl('/users/'); + * // to match root url + * $I->seeCurrentUrlMatches('~$/users/(\d+)~'); * ?> * ``` * * @param $uri - * @see Mink::seeInCurrentUrl() + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Mink::seeCurrentUrlMatches() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ - public function seeInCurrentUrl($uri) { - $this->scenario->assertion('seeInCurrentUrl', func_get_args()); + public function canSeeCurrentUrlMatches($uri) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('seeCurrentUrlMatches', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); } return new Maybe(); } - - /** - * Checks that current uri does not contain a value + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks that current url is matches a RegEx value * * ``` php * <?php - * $I->dontSeeInCurrentUrl('/users/'); + * // to match root url + * $I->seeCurrentUrlMatches('~$/users/(\d+)~'); * ?> * ``` * * @param $uri - * @see Mink::dontSeeInCurrentUrl() + * @see Codeception\Util\Mink::seeCurrentUrlMatches() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ - public function dontSeeInCurrentUrl($uri) { - $this->scenario->action('dontSeeInCurrentUrl', func_get_args()); + public function seeCurrentUrlMatches($uri) { + $this->scenario->addStep(new \Codeception\Step\Assertion('seeCurrentUrlMatches', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -706,47 +1323,52 @@ class WebGuy extends \Codeception\AbstractGuy /** - * Checks that current url is equal to value. - * Unlike `seeInCurrentUrl` performs a strict check. + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks that current url does not match a RegEx value * + * ``` php * <?php * // to match root url - * $I->seeCurrentUrlEquals('/'); + * $I->dontSeeCurrentUrlMatches('~$/users/(\d+)~'); * ?> + * ``` * * @param $uri - * @see Mink::seeCurrentUrlEquals() + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Mink::dontSeeCurrentUrlMatches() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ - public function seeCurrentUrlEquals($uri) { - $this->scenario->assertion('seeCurrentUrlEquals', func_get_args()); + public function cantSeeCurrentUrlMatches($uri) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('dontSeeCurrentUrlMatches', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); } return new Maybe(); } - - /** - * Checks that current url is not equal to value. - * Unlike `dontSeeInCurrentUrl` performs a strict check. + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks that current url does not match a RegEx value * + * ``` php * <?php - * // current url is not root - * $I->dontSeeCurrentUrlEquals('/'); + * // to match root url + * $I->dontSeeCurrentUrlMatches('~$/users/(\d+)~'); * ?> + * ``` * * @param $uri - * @see Mink::dontSeeCurrentUrlEquals() + * @see Codeception\Util\Mink::dontSeeCurrentUrlMatches() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ - public function dontSeeCurrentUrlEquals($uri) { - $this->scenario->action('dontSeeCurrentUrlEquals', func_get_args()); + public function dontSeeCurrentUrlMatches($uri) { + $this->scenario->addStep(new \Codeception\Step\Assertion('dontSeeCurrentUrlMatches', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -756,45 +1378,40 @@ class WebGuy extends \Codeception\AbstractGuy /** - * Checks that current url is matches a RegEx value + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- * - * <?php - * // to match root url - * $I->seeCurrentUrlMatches('~$/users/(\d+)~'); - * ?> + * Checks that cookie is set. * - * @param $uri - * @see Mink::seeCurrentUrlMatches() + * @param $cookie + * @return mixed + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Mink::seeCookie() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ - public function seeCurrentUrlMatches($uri) { - $this->scenario->assertion('seeCurrentUrlMatches', func_get_args()); + public function canSeeCookie($cookie) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('seeCookie', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); } return new Maybe(); } - - /** - * Checks that current url does not match a RegEx value + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- * - * <?php - * // to match root url - * $I->dontSeeCurrentUrlMatches('~$/users/(\d+)~'); - * ?> + * Checks that cookie is set. * - * @param $uri - * @see Mink::dontSeeCurrentUrlMatches() + * @param $cookie + * @return mixed + * @see Codeception\Util\Mink::seeCookie() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ - public function dontSeeCurrentUrlMatches($uri) { - $this->scenario->action('dontSeeCurrentUrlMatches', func_get_args()); + public function seeCookie($cookie) { + $this->scenario->addStep(new \Codeception\Step\Assertion('seeCookie', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -804,31 +1421,40 @@ class WebGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- * - * @see Mink::seeCookie() + * Checks that cookie doesn't exist + * + * @param $cookie + * @return mixed + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Mink::dontSeeCookie() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ - public function seeCookie($cookie) { - $this->scenario->assertion('seeCookie', func_get_args()); + public function cantSeeCookie($cookie) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('dontSeeCookie', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); } return new Maybe(); } - - /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks that cookie doesn't exist * - * @see Mink::dontSeeCookie() + * @param $cookie + * @return mixed + * @see Codeception\Util\Mink::dontSeeCookie() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function dontSeeCookie($cookie) { - $this->scenario->action('dontSeeCookie', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Assertion('dontSeeCookie', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -838,14 +1464,20 @@ class WebGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Sets a cookie. * - * @see Mink::setCookie() + * @param $cookie + * @param $value + * @return mixed + * @see Codeception\Util\Mink::setCookie() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function setCookie($cookie, $value) { - $this->scenario->action('setCookie', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Action('setCookie', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -855,14 +1487,19 @@ class WebGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- * - * @see Mink::resetCookie() + * Unsets cookie + * + * @param $cookie + * @return mixed + * @see Codeception\Util\Mink::resetCookie() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function resetCookie($cookie) { - $this->scenario->action('resetCookie', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Action('resetCookie', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -872,14 +1509,19 @@ class WebGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- * - * @see Mink::grabCookie() + * Grabs a cookie value. + * + * @param $cookie + * @return mixed + * @see Codeception\Util\Mink::grabCookie() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function grabCookie($cookie) { - $this->scenario->action('grabCookie', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Action('grabCookie', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -889,6 +1531,10 @@ class WebGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Takes a parameters from current URI by RegEx. * If no url provided returns full URI. * @@ -902,13 +1548,11 @@ class WebGuy extends \Codeception\AbstractGuy * @param null $uri * @internal param $url * @return mixed - * @see Mink::grabFromCurrentUrl() + * @see Codeception\Util\Mink::grabFromCurrentUrl() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function grabFromCurrentUrl($uri = null) { - $this->scenario->action('grabFromCurrentUrl', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Action('grabFromCurrentUrl', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -918,26 +1562,28 @@ class WebGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Attaches file from Codeception data directory to upload field. * * Example: * * ``` php * <?php - * // file is stored in 'tests/data/tests.xls' - * $I->attachFile('prices.xls'); + * // file is stored in 'tests/_data/prices.xls' + * $I->attachFile('input[@type="file"]', 'prices.xls'); * ?> * ``` * * @param $field * @param $filename - * @see Mink::attachFile() + * @see Codeception\Util\Mink::attachFile() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function attachFile($field, $filename) { - $this->scenario->action('attachFile', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Action('attachFile', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -947,6 +1593,38 @@ class WebGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks if option is selected in select field. + * + * ``` php + * <?php + * $I->seeOptionIsSelected('#form input[name=payment]', 'Visa'); + * ?> + * ``` + * + * @param $selector + * @param $optionText + * @return mixed + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Mink::seeOptionIsSelected() + * @return \Codeception\Maybe + */ + public function canSeeOptionIsSelected($select, $text) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('seeOptionIsSelected', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Checks if option is selected in select field. * * ``` php @@ -958,13 +1636,11 @@ class WebGuy extends \Codeception\AbstractGuy * @param $selector * @param $optionText * @return mixed - * @see Mink::seeOptionIsSelected() + * @see Codeception\Util\Mink::seeOptionIsSelected() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function seeOptionIsSelected($select, $text) { - $this->scenario->assertion('seeOptionIsSelected', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Assertion('seeOptionIsSelected', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -974,6 +1650,10 @@ class WebGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Checks if option is not selected in select field. * * ``` php @@ -985,43 +1665,39 @@ class WebGuy extends \Codeception\AbstractGuy * @param $selector * @param $optionText * @return mixed - * @see Mink::dontSeeOptionIsSelected() + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Mink::dontSeeOptionIsSelected() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ - public function dontSeeOptionIsSelected($select, $text) { - $this->scenario->action('dontSeeOptionIsSelected', func_get_args()); + public function cantSeeOptionIsSelected($select, $text) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('dontSeeOptionIsSelected', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); } return new Maybe(); } - - /** - * Assert if the specified checkbox is checked. - * Use css selector or xpath to match. + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- * - * Example: + * Checks if option is not selected in select field. * * ``` php * <?php - * $I->seeCheckboxIsChecked('#agree'); // I suppose user agreed to terms - * $I->seeCheckboxIsChecked('#signup_form input[type=checkbox]'); // I suppose user agreed to terms, If there is only one checkbox in form. - * $I->seeCheckboxIsChecked('//form/input[@type=checkbox and @name=agree]'); - * + * $I->dontSeeOptionIsSelected('#form input[name=payment]', 'Visa'); + * ?> * ``` * - * @param $checkbox - * @see Mink::seeCheckboxIsChecked() + * @param $selector + * @param $optionText + * @return mixed + * @see Codeception\Util\Mink::dontSeeOptionIsSelected() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ - public function seeCheckboxIsChecked($checkbox) { - $this->scenario->assertion('seeCheckboxIsChecked', func_get_args()); + public function dontSeeOptionIsSelected($select, $text) { + $this->scenario->addStep(new \Codeception\Step\Assertion('dontSeeOptionIsSelected', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -1031,35 +1707,44 @@ class WebGuy extends \Codeception\AbstractGuy /** - * Assert if the specified checkbox is unchecked. - * Use css selector or xpath to match. + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks that an input field or textarea contains value. + * Field is matched either by label or CSS or Xpath * * Example: * * ``` php * <?php - * $I->dontSeeCheckboxIsChecked('#agree'); // I suppose user didn't agree to terms - * $I->seeCheckboxIsChecked('#signup_form input[type=checkbox]'); // I suppose user didn't check the first checkbox in form. - * + * $I->seeInField('Body','Type your comment here'); + * $I->seeInField('form textarea[name=body]','Type your comment here'); + * $I->seeInField('form input[type=hidden]','hidden_value'); + * $I->seeInField('#searchform input','Search'); + * $I->seeInField('//form/*[@name=search]','Search'); + * ?> * ``` * - * @param $checkbox - * @see Mink::dontSeeCheckboxIsChecked() + * @param $field + * @param $value + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Mink::seeInField() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ - public function dontSeeCheckboxIsChecked($checkbox) { - $this->scenario->action('dontSeeCheckboxIsChecked', func_get_args()); + public function canSeeInField($field, $value) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('seeInField', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); } return new Maybe(); } - - /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Checks that an input field or textarea contains value. * Field is matched either by label or CSS or Xpath * @@ -1077,13 +1762,11 @@ class WebGuy extends \Codeception\AbstractGuy * * @param $field * @param $value - * @see Mink::seeInField() + * @see Codeception\Util\Mink::seeInField() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function seeInField($field, $value) { - $this->scenario->assertion('seeInField', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Assertion('seeInField', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -1093,6 +1776,43 @@ class WebGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks that an input field or textarea doesn't contain value. + * Field is matched either by label or CSS or Xpath + * Example: + * + * ``` php + * <?php + * $I->dontSeeInField('Body','Type your comment here'); + * $I->dontSeeInField('form textarea[name=body]','Type your comment here'); + * $I->dontSeeInField('form input[type=hidden]','hidden_value'); + * $I->dontSeeInField('#searchform input','Search'); + * $I->dontSeeInField('//form/*[@name=search]','Search'); + * ?> + * ``` + * + * @param $field + * @param $value + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Mink::dontSeeInField() + * @return \Codeception\Maybe + */ + public function cantSeeInField($field, $value) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('dontSeeInField', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Checks that an input field or textarea doesn't contain value. * Field is matched either by label or CSS or Xpath * Example: @@ -1109,13 +1829,11 @@ class WebGuy extends \Codeception\AbstractGuy * * @param $field * @param $value - * @see Mink::dontSeeInField() + * @see Codeception\Util\Mink::dontSeeInField() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function dontSeeInField($field, $value) { - $this->scenario->action('dontSeeInField', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Assertion('dontSeeInField', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -1125,6 +1843,10 @@ class WebGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Finds and returns text contents of element. * Element is searched by CSS selector, XPath or matcher by regex. * @@ -1140,13 +1862,11 @@ class WebGuy extends \Codeception\AbstractGuy * * @param $cssOrXPathOrRegex * @return mixed - * @see Mink::grabTextFrom() + * @see Codeception\Util\Mink::grabTextFrom() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function grabTextFrom($cssOrXPathOrRegex) { - $this->scenario->action('grabTextFrom', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Action('grabTextFrom', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -1156,6 +1876,10 @@ class WebGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Finds and returns field and returns it's value. * Searches by field name, then by CSS, then by XPath * @@ -1171,13 +1895,66 @@ class WebGuy extends \Codeception\AbstractGuy * * @param $field * @return mixed - * @see Mink::grabValueFrom() + * @see Codeception\Util\Mink::grabValueFrom() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function grabValueFrom($field) { - $this->scenario->action('grabValueFrom', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Action('grabValueFrom', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + + + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks that page title contains text. + * + * ``` php + * <?php + * $I->seeInTitle('Blog - Post #1'); + * ?> + * ``` + * + * @param $title + * @return mixed + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Mink::seeInTitle() + * @return \Codeception\Maybe + */ + public function canSeeInTitle($title) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('seeInTitle', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks that page title contains text. + * + * ``` php + * <?php + * $I->seeInTitle('Blog - Post #1'); + * ?> + * ``` + * + * @param $title + * @return mixed + * @see Codeception\Util\Mink::seeInTitle() + * @return \Codeception\Maybe + */ + public function seeInTitle($title) { + $this->scenario->addStep(new \Codeception\Step\Assertion('seeInTitle', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -1187,14 +1964,40 @@ class WebGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks that page title does not contain text. + * + * @param $title + * @return mixed + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Mink::dontSeeInTitle() + * @return \Codeception\Maybe + */ + public function cantSeeInTitle($title) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('dontSeeInTitle', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- * - * @see Mink::grabAttribute() + * Checks that page title does not contain text. + * + * @param $title + * @return mixed + * @see Codeception\Util\Mink::dontSeeInTitle() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ - public function grabAttribute() { - $this->scenario->action('grabAttribute', func_get_args()); + public function dontSeeInTitle($title) { + $this->scenario->addStep(new \Codeception\Step\Assertion('dontSeeInTitle', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); diff --git a/apps/basic/tests/functional/ContactCept.php b/apps/basic/tests/functional/ContactCept.php index 6feafd9..b58361a 100644 --- a/apps/basic/tests/functional/ContactCept.php +++ b/apps/basic/tests/functional/ContactCept.php @@ -4,7 +4,7 @@ $I->wantTo('ensure that contact works'); $I->amOnPage('?r=site/contact'); $I->see('Contact', 'h1'); -$I->submitForm('#contact-form', array()); +$I->submitForm('#contact-form', []); $I->see('Contact', 'h1'); $I->see('Name cannot be blank'); $I->see('Email cannot be blank'); @@ -12,25 +12,25 @@ $I->see('Subject cannot be blank'); $I->see('Body cannot be blank'); $I->see('The verification code is incorrect'); -$I->submitForm('#contact-form', array( +$I->submitForm('#contact-form', [ 'ContactForm[name]' => 'tester', 'ContactForm[email]' => 'tester.email', 'ContactForm[subject]' => 'test subject', 'ContactForm[body]' => 'test content', 'ContactForm[verifyCode]' => 'testme', -)); +]); $I->dontSee('Name cannot be blank', '.help-inline'); $I->see('Email is not a valid email address.'); $I->dontSee('Subject cannot be blank', '.help-inline'); $I->dontSee('Body cannot be blank', '.help-inline'); $I->dontSee('The verification code is incorrect', '.help-inline'); -$I->submitForm('#contact-form', array( +$I->submitForm('#contact-form', [ 'ContactForm[name]' => 'tester', 'ContactForm[email]' => 'tester@example.com', 'ContactForm[subject]' => 'test subject', 'ContactForm[body]' => 'test content', 'ContactForm[verifyCode]' => 'testme', -)); +]); $I->dontSeeElement('#contact-form'); $I->see('Thank you for contacting us. We will respond to you as soon as possible.'); diff --git a/apps/basic/tests/functional/LoginCept.php b/apps/basic/tests/functional/LoginCept.php index 11f8f6b..9d71378 100644 --- a/apps/basic/tests/functional/LoginCept.php +++ b/apps/basic/tests/functional/LoginCept.php @@ -4,20 +4,20 @@ $I->wantTo('ensure that login works'); $I->amOnPage('?r=site/login'); $I->see('Login', 'h1'); -$I->submitForm('#login-form', array()); +$I->submitForm('#login-form', []); $I->dontSee('Logout (admin)'); $I->see('Username cannot be blank'); $I->see('Password cannot be blank'); -$I->submitForm('#login-form', array( +$I->submitForm('#login-form', [ 'LoginForm[username]' => 'admin', 'LoginForm[password]' => 'wrong', -)); +]); $I->dontSee('Logout (admin)'); $I->see('Incorrect username or password'); -$I->submitForm('#login-form', array( +$I->submitForm('#login-form', [ 'LoginForm[username]' => 'admin', 'LoginForm[password]' => 'admin', -)); +]); $I->see('Logout (admin)'); diff --git a/apps/basic/tests/functional/TestGuy.php b/apps/basic/tests/functional/TestGuy.php index 767d564..553a44f 100644 --- a/apps/basic/tests/functional/TestGuy.php +++ b/apps/basic/tests/functional/TestGuy.php @@ -18,24 +18,30 @@ use Codeception\Module\Yii2; * @method void expect($prediction) * @method void amGoingTo($argumentation) * @method void am($role) - * @method void lookForwardTo($role) + * @method void lookForwardTo($achieveValue) + * @method void offsetGet($offset) + * @method void offsetSet($offset, $value) + * @method void offsetExists($offset) + * @method void offsetUnset($offset) */ class TestGuy extends \Codeception\AbstractGuy { /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Enters a directory In local filesystem. * Project root directory is used by default * * @param $path - * @see Filesystem::amInPath() + * @see Codeception\Module\Filesystem::amInPath() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function amInPath($path) { - $this->scenario->condition('amInPath', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Condition('amInPath', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -45,6 +51,10 @@ class TestGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Opens a file and stores it's content. * * Usage: @@ -57,13 +67,11 @@ class TestGuy extends \Codeception\AbstractGuy * ``` * * @param $filename - * @see Filesystem::openFile() + * @see Codeception\Module\Filesystem::openFile() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function openFile($filename) { - $this->scenario->action('openFile', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Action('openFile', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -73,6 +81,10 @@ class TestGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Deletes a file * * ``` php @@ -82,13 +94,11 @@ class TestGuy extends \Codeception\AbstractGuy * ``` * * @param $filename - * @see Filesystem::deleteFile() + * @see Codeception\Module\Filesystem::deleteFile() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function deleteFile($filename) { - $this->scenario->action('deleteFile', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Action('deleteFile', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -98,6 +108,10 @@ class TestGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Deletes directory with all subdirectories * * ``` php @@ -107,13 +121,11 @@ class TestGuy extends \Codeception\AbstractGuy * ``` * * @param $dirname - * @see Filesystem::deleteDir() + * @see Codeception\Module\Filesystem::deleteDir() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function deleteDir($dirname) { - $this->scenario->action('deleteDir', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Action('deleteDir', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -123,6 +135,10 @@ class TestGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Copies directory with all contents * * ``` php @@ -133,13 +149,11 @@ class TestGuy extends \Codeception\AbstractGuy * * @param $src * @param $dst - * @see Filesystem::copyDir() + * @see Codeception\Module\Filesystem::copyDir() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function copyDir($src, $dst) { - $this->scenario->action('copyDir', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Action('copyDir', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -149,6 +163,39 @@ class TestGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks If opened file has `text` in it. + * + * Usage: + * + * ``` php + * <?php + * $I->openFile('composer.json'); + * $I->seeInThisFile('codeception/codeception'); + * ?> + * ``` + * + * @param $text + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Module\Filesystem::seeInThisFile() + * @return \Codeception\Maybe + */ + public function canSeeInThisFile($text) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('seeInThisFile', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Checks If opened file has `text` in it. * * Usage: @@ -161,13 +208,11 @@ class TestGuy extends \Codeception\AbstractGuy * ``` * * @param $text - * @see Filesystem::seeInThisFile() + * @see Codeception\Module\Filesystem::seeInThisFile() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function seeInThisFile($text) { - $this->scenario->assertion('seeInThisFile', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Assertion('seeInThisFile', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -177,6 +222,40 @@ class TestGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks the strict matching of file contents. + * Unlike `seeInThisFile` will fail if file has something more then expected lines. + * Better to use with HEREDOC strings. + * Matching is done after removing "\r" chars from file content. + * + * ``` php + * <?php + * $I->openFile('process.pid'); + * $I->seeFileContentsEqual('3192'); + * ?> + * ``` + * + * @param $text + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Module\Filesystem::seeFileContentsEqual() + * @return \Codeception\Maybe + */ + public function canSeeFileContentsEqual($text) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('seeFileContentsEqual', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Checks the strict matching of file contents. * Unlike `seeInThisFile` will fail if file has something more then expected lines. * Better to use with HEREDOC strings. @@ -190,13 +269,11 @@ class TestGuy extends \Codeception\AbstractGuy * ``` * * @param $text - * @see Filesystem::seeFileContentsEqual() + * @see Codeception\Module\Filesystem::seeFileContentsEqual() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function seeFileContentsEqual($text) { - $this->scenario->assertion('seeFileContentsEqual', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Assertion('seeFileContentsEqual', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -206,23 +283,52 @@ class TestGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Checks If opened file doesn't contain `text` in it * * ``` php * <?php * $I->openFile('composer.json'); - * $I->seeInThisFile('codeception/codeception'); + * $I->dontSeeInThisFile('codeception/codeception'); + * ?> + * ``` + * + * @param $text + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Module\Filesystem::dontSeeInThisFile() + * @return \Codeception\Maybe + */ + public function cantSeeInThisFile($text) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('dontSeeInThisFile', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks If opened file doesn't contain `text` in it + * + * ``` php + * <?php + * $I->openFile('composer.json'); + * $I->dontSeeInThisFile('codeception/codeception'); * ?> * ``` * * @param $text - * @see Filesystem::dontSeeInThisFile() + * @see Codeception\Module\Filesystem::dontSeeInThisFile() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function dontSeeInThisFile($text) { - $this->scenario->action('dontSeeInThisFile', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Assertion('dontSeeInThisFile', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -232,14 +338,16 @@ class TestGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Deletes a file - * @see Filesystem::deleteThisFile() + * @see Codeception\Module\Filesystem::deleteThisFile() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function deleteThisFile() { - $this->scenario->action('deleteThisFile', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Action('deleteThisFile', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -249,6 +357,38 @@ class TestGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks if file exists in path. + * Opens a file when it's exists + * + * ``` php + * <?php + * $I->seeFileFound('UserModel.php','app/models'); + * ?> + * ``` + * + * @param $filename + * @param string $path + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Module\Filesystem::seeFileFound() + * @return \Codeception\Maybe + */ + public function canSeeFileFound($filename, $path = null) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('seeFileFound', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Checks if file exists in path. * Opens a file when it's exists * @@ -260,13 +400,11 @@ class TestGuy extends \Codeception\AbstractGuy * * @param $filename * @param string $path - * @see Filesystem::seeFileFound() + * @see Codeception\Module\Filesystem::seeFileFound() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function seeFileFound($filename, $path = null) { - $this->scenario->assertion('seeFileFound', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Assertion('seeFileFound', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -276,6 +414,10 @@ class TestGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Erases directory contents * * ``` php @@ -285,13 +427,11 @@ class TestGuy extends \Codeception\AbstractGuy * ``` * * @param $dirname - * @see Filesystem::cleanDir() + * @see Codeception\Module\Filesystem::cleanDir() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function cleanDir($dirname) { - $this->scenario->action('cleanDir', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Action('cleanDir', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -301,17 +441,19 @@ class TestGuy extends \Codeception\AbstractGuy /** - * Adds HTTP authentication via username/password. + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Authenticates user for HTTP_AUTH * * @param $username * @param $password - * @see Framework::amHttpAuthenticated() + * @see Codeception\Util\Framework::amHttpAuthenticated() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function amHttpAuthenticated($username, $password) { - $this->scenario->condition('amHttpAuthenticated', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Condition('amHttpAuthenticated', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -321,6 +463,10 @@ class TestGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Opens the page. * Requires relative uri as parameter * @@ -336,13 +482,11 @@ class TestGuy extends \Codeception\AbstractGuy * ``` * * @param $page - * @see Framework::amOnPage() + * @see Codeception\Util\Framework::amOnPage() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function amOnPage($page) { - $this->scenario->condition('amOnPage', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Condition('amOnPage', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -352,6 +496,10 @@ class TestGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Perform a click on link or button. * Link or button are found by their names or CSS selector. * Submits a form if button is a submit type. @@ -380,13 +528,11 @@ class TestGuy extends \Codeception\AbstractGuy * ``` * @param $link * @param $context - * @see Framework::click() + * @see Codeception\Util\Framework::click() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function click($link, $context = null) { - $this->scenario->action('click', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Action('click', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -396,6 +542,10 @@ class TestGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Check if current page contains the text specified. * Specify the css selector to match only specific region. * @@ -406,18 +556,48 @@ class TestGuy extends \Codeception\AbstractGuy * $I->see('Logout'); // I can suppose user is logged in * $I->see('Sign Up','h1'); // I can suppose it's a signup page * $I->see('Sign Up','//body/h1'); // with XPath + * ?> + * ``` + * + * @param $text + * @param null $selector + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Framework::see() + * @return \Codeception\Maybe + */ + public function canSee($text, $selector = null) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('see', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Check if current page contains the text specified. + * Specify the css selector to match only specific region. + * + * Examples: * + * ``` php + * <?php + * $I->see('Logout'); // I can suppose user is logged in + * $I->see('Sign Up','h1'); // I can suppose it's a signup page + * $I->see('Sign Up','//body/h1'); // with XPath + * ?> * ``` * * @param $text * @param null $selector - * @see Framework::see() + * @see Codeception\Util\Framework::see() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function see($text, $selector = null) { - $this->scenario->assertion('see', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Assertion('see', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -427,6 +607,42 @@ class TestGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Check if current page doesn't contain the text specified. + * Specify the css selector to match only specific region. + * + * Examples: + * + * ```php + * <?php + * $I->dontSee('Login'); // I can suppose user is already logged in + * $I->dontSee('Sign Up','h1'); // I can suppose it's not a signup page + * $I->dontSee('Sign Up','//body/h1'); // with XPath + * ?> + * ``` + * + * @param $text + * @param null $selector + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Framework::dontSee() + * @return \Codeception\Maybe + */ + public function cantSee($text, $selector = null) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('dontSee', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Check if current page doesn't contain the text specified. * Specify the css selector to match only specific region. * @@ -437,17 +653,16 @@ class TestGuy extends \Codeception\AbstractGuy * $I->dontSee('Login'); // I can suppose user is already logged in * $I->dontSee('Sign Up','h1'); // I can suppose it's not a signup page * $I->dontSee('Sign Up','//body/h1'); // with XPath + * ?> * ``` * * @param $text * @param null $selector - * @see Framework::dontSee() + * @see Codeception\Util\Framework::dontSee() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function dontSee($text, $selector = null) { - $this->scenario->action('dontSee', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Assertion('dontSee', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -457,6 +672,10 @@ class TestGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Checks if there is a link with text specified. * Specify url to match link with exact this url. * @@ -466,18 +685,47 @@ class TestGuy extends \Codeception\AbstractGuy * <?php * $I->seeLink('Logout'); // matches <a href="#">Logout</a> * $I->seeLink('Logout','/logout'); // matches <a href="/logout">Logout</a> + * ?> + * ``` + * + * @param $text + * @param null $url + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Framework::seeLink() + * @return \Codeception\Maybe + */ + public function canSeeLink($text, $url = null) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('seeLink', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- * + * Checks if there is a link with text specified. + * Specify url to match link with exact this url. + * + * Examples: + * + * ``` php + * <?php + * $I->seeLink('Logout'); // matches <a href="#">Logout</a> + * $I->seeLink('Logout','/logout'); // matches <a href="/logout">Logout</a> + * ?> * ``` * * @param $text * @param null $url - * @see Framework::seeLink() + * @see Codeception\Util\Framework::seeLink() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function seeLink($text, $url = null) { - $this->scenario->assertion('seeLink', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Assertion('seeLink', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -487,6 +735,10 @@ class TestGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Checks if page doesn't contain the link with text specified. * Specify url to narrow the results. * @@ -495,46 +747,46 @@ class TestGuy extends \Codeception\AbstractGuy * ``` php * <?php * $I->dontSeeLink('Logout'); // I suppose user is not logged in - * + * ?> * ``` * * @param $text * @param null $url - * @see Framework::dontSeeLink() + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Framework::dontSeeLink() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ - public function dontSeeLink($text, $url = null) { - $this->scenario->action('dontSeeLink', func_get_args()); + public function cantSeeLink($text, $url = null) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('dontSeeLink', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); } return new Maybe(); } - - /** - * Checks that current uri contains a value + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks if page doesn't contain the link with text specified. + * Specify url to narrow the results. + * + * Examples: * * ``` php * <?php - * // to match: /home/dashboard - * $I->seeInCurrentUrl('home'); - * // to match: /users/1 - * $I->seeInCurrentUrl('/users/'); + * $I->dontSeeLink('Logout'); // I suppose user is not logged in * ?> * ``` * - * @param $uri - * @see Framework::seeInCurrentUrl() + * @param $text + * @param null $url + * @see Codeception\Util\Framework::dontSeeLink() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ - public function seeInCurrentUrl($uri) { - $this->scenario->assertion('seeInCurrentUrl', func_get_args()); + public function dontSeeLink($text, $url = null) { + $this->scenario->addStep(new \Codeception\Step\Assertion('dontSeeLink', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -544,47 +796,56 @@ class TestGuy extends \Codeception\AbstractGuy /** - * Checks that current uri does not contain a value + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks that current uri contains a value * * ``` php * <?php - * $I->dontSeeInCurrentUrl('/users/'); + * // to match: /home/dashboard + * $I->seeInCurrentUrl('home'); + * // to match: /users/1 + * $I->seeInCurrentUrl('/users/'); * ?> * ``` * * @param $uri - * @see Framework::dontSeeInCurrentUrl() + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Framework::seeInCurrentUrl() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ - public function dontSeeInCurrentUrl($uri) { - $this->scenario->action('dontSeeInCurrentUrl', func_get_args()); + public function canSeeInCurrentUrl($uri) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('seeInCurrentUrl', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); } return new Maybe(); } - - /** - * Checks that current url is equal to value. - * Unlike `seeInCurrentUrl` performs a strict check. + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks that current uri contains a value * + * ``` php * <?php - * // to match root url - * $I->seeCurrentUrlEquals('/'); + * // to match: /home/dashboard + * $I->seeInCurrentUrl('home'); + * // to match: /users/1 + * $I->seeInCurrentUrl('/users/'); * ?> + * ``` * * @param $uri - * @see Framework::seeCurrentUrlEquals() + * @see Codeception\Util\Framework::seeInCurrentUrl() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ - public function seeCurrentUrlEquals($uri) { - $this->scenario->assertion('seeCurrentUrlEquals', func_get_args()); + public function seeInCurrentUrl($uri) { + $this->scenario->addStep(new \Codeception\Step\Assertion('seeInCurrentUrl', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -594,46 +855,50 @@ class TestGuy extends \Codeception\AbstractGuy /** - * Checks that current url is not equal to value. - * Unlike `dontSeeInCurrentUrl` performs a strict check. + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks that current uri does not contain a value * + * ``` php * <?php - * // current url is not root - * $I->dontSeeCurrentUrlEquals('/'); + * $I->dontSeeInCurrentUrl('/users/'); * ?> + * ``` * * @param $uri - * @see Framework::dontSeeCurrentUrlEquals() + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Framework::dontSeeInCurrentUrl() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ - public function dontSeeCurrentUrlEquals($uri) { - $this->scenario->action('dontSeeCurrentUrlEquals', func_get_args()); + public function cantSeeInCurrentUrl($uri) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('dontSeeInCurrentUrl', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); } return new Maybe(); } - - /** - * Checks that current url is matches a RegEx value + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks that current uri does not contain a value * + * ``` php * <?php - * // to match root url - * $I->seeCurrentUrlMatches('~$/users/(\d+)~'); + * $I->dontSeeInCurrentUrl('/users/'); * ?> + * ``` * * @param $uri - * @see Framework::seeCurrentUrlMatches() + * @see Codeception\Util\Framework::dontSeeInCurrentUrl() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ - public function seeCurrentUrlMatches($uri) { - $this->scenario->assertion('seeCurrentUrlMatches', func_get_args()); + public function dontSeeInCurrentUrl($uri) { + $this->scenario->addStep(new \Codeception\Step\Assertion('dontSeeInCurrentUrl', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -643,21 +908,166 @@ class TestGuy extends \Codeception\AbstractGuy /** - * Checks that current url does not match a RegEx value + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- * + * Checks that current url is equal to value. + * Unlike `seeInCurrentUrl` performs a strict check. + * + * ``` php * <?php * // to match root url - * $I->dontSeeCurrentUrlMatches('~$/users/(\d+)~'); + * $I->seeCurrentUrlEquals('/'); * ?> + * ``` * * @param $uri - * @see Framework::dontSeeCurrentUrlMatches() + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Framework::seeCurrentUrlEquals() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ - public function dontSeeCurrentUrlMatches($uri) { - $this->scenario->action('dontSeeCurrentUrlMatches', func_get_args()); + public function canSeeCurrentUrlEquals($uri) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('seeCurrentUrlEquals', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks that current url is equal to value. + * Unlike `seeInCurrentUrl` performs a strict check. + * + * ``` php + * <?php + * // to match root url + * $I->seeCurrentUrlEquals('/'); + * ?> + * ``` + * + * @param $uri + * @see Codeception\Util\Framework::seeCurrentUrlEquals() + * @return \Codeception\Maybe + */ + public function seeCurrentUrlEquals($uri) { + $this->scenario->addStep(new \Codeception\Step\Assertion('seeCurrentUrlEquals', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + + + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks that current url is not equal to value. + * Unlike `dontSeeInCurrentUrl` performs a strict check. + * + * ``` php + * <?php + * // current url is not root + * $I->dontSeeCurrentUrlEquals('/'); + * ?> + * ``` + * + * @param $uri + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Framework::dontSeeCurrentUrlEquals() + * @return \Codeception\Maybe + */ + public function cantSeeCurrentUrlEquals($uri) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('dontSeeCurrentUrlEquals', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks that current url is not equal to value. + * Unlike `dontSeeInCurrentUrl` performs a strict check. + * + * ``` php + * <?php + * // current url is not root + * $I->dontSeeCurrentUrlEquals('/'); + * ?> + * ``` + * + * @param $uri + * @see Codeception\Util\Framework::dontSeeCurrentUrlEquals() + * @return \Codeception\Maybe + */ + public function dontSeeCurrentUrlEquals($uri) { + $this->scenario->addStep(new \Codeception\Step\Assertion('dontSeeCurrentUrlEquals', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + + + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks that current url is matches a RegEx value + * + * ``` php + * <?php + * // to match root url + * $I->seeCurrentUrlMatches('~$/users/(\d+)~'); + * ?> + * ``` + * + * @param $uri + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Framework::seeCurrentUrlMatches() + * @return \Codeception\Maybe + */ + public function canSeeCurrentUrlMatches($uri) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('seeCurrentUrlMatches', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks that current url is matches a RegEx value + * + * ``` php + * <?php + * // to match root url + * $I->seeCurrentUrlMatches('~$/users/(\d+)~'); + * ?> + * ``` + * + * @param $uri + * @see Codeception\Util\Framework::seeCurrentUrlMatches() + * @return \Codeception\Maybe + */ + public function seeCurrentUrlMatches($uri) { + $this->scenario->addStep(new \Codeception\Step\Assertion('seeCurrentUrlMatches', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -667,6 +1077,65 @@ class TestGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks that current url does not match a RegEx value + * + * ``` php + * <?php + * // to match root url + * $I->dontSeeCurrentUrlMatches('~$/users/(\d+)~'); + * ?> + * ``` + * + * @param $uri + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Framework::dontSeeCurrentUrlMatches() + * @return \Codeception\Maybe + */ + public function cantSeeCurrentUrlMatches($uri) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('dontSeeCurrentUrlMatches', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks that current url does not match a RegEx value + * + * ``` php + * <?php + * // to match root url + * $I->dontSeeCurrentUrlMatches('~$/users/(\d+)~'); + * ?> + * ``` + * + * @param $uri + * @see Codeception\Util\Framework::dontSeeCurrentUrlMatches() + * @return \Codeception\Maybe + */ + public function dontSeeCurrentUrlMatches($uri) { + $this->scenario->addStep(new \Codeception\Step\Assertion('dontSeeCurrentUrlMatches', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + + + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Takes a parameters from current URI by RegEx. * If no url provided returns full URI. * @@ -680,13 +1149,11 @@ class TestGuy extends \Codeception\AbstractGuy * @param null $uri * @internal param $url * @return mixed - * @see Framework::grabFromCurrentUrl() + * @see Codeception\Util\Framework::grabFromCurrentUrl() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function grabFromCurrentUrl($uri = null) { - $this->scenario->action('grabFromCurrentUrl', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Action('grabFromCurrentUrl', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -696,6 +1163,10 @@ class TestGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Assert if the specified checkbox is checked. * Use css selector or xpath to match. * @@ -706,17 +1177,46 @@ class TestGuy extends \Codeception\AbstractGuy * $I->seeCheckboxIsChecked('#agree'); // I suppose user agreed to terms * $I->seeCheckboxIsChecked('#signup_form input[type=checkbox]'); // I suppose user agreed to terms, If there is only one checkbox in form. * $I->seeCheckboxIsChecked('//form/input[@type=checkbox and @name=agree]'); + * ?> + * ``` * + * @param $checkbox + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Framework::seeCheckboxIsChecked() + * @return \Codeception\Maybe + */ + public function canSeeCheckboxIsChecked($checkbox) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('seeCheckboxIsChecked', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Assert if the specified checkbox is checked. + * Use css selector or xpath to match. + * + * Example: + * + * ``` php + * <?php + * $I->seeCheckboxIsChecked('#agree'); // I suppose user agreed to terms + * $I->seeCheckboxIsChecked('#signup_form input[type=checkbox]'); // I suppose user agreed to terms, If there is only one checkbox in form. + * $I->seeCheckboxIsChecked('//form/input[@type=checkbox and @name=agree]'); + * ?> * ``` * * @param $checkbox - * @see Framework::seeCheckboxIsChecked() + * @see Codeception\Util\Framework::seeCheckboxIsChecked() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function seeCheckboxIsChecked($checkbox) { - $this->scenario->assertion('seeCheckboxIsChecked', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Assertion('seeCheckboxIsChecked', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -726,6 +1226,10 @@ class TestGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Assert if the specified checkbox is unchecked. * Use css selector or xpath to match. * @@ -735,17 +1239,45 @@ class TestGuy extends \Codeception\AbstractGuy * <?php * $I->dontSeeCheckboxIsChecked('#agree'); // I suppose user didn't agree to terms * $I->seeCheckboxIsChecked('#signup_form input[type=checkbox]'); // I suppose user didn't check the first checkbox in form. + * ?> + * ``` * + * @param $checkbox + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Framework::dontSeeCheckboxIsChecked() + * @return \Codeception\Maybe + */ + public function cantSeeCheckboxIsChecked($checkbox) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('dontSeeCheckboxIsChecked', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Assert if the specified checkbox is unchecked. + * Use css selector or xpath to match. + * + * Example: + * + * ``` php + * <?php + * $I->dontSeeCheckboxIsChecked('#agree'); // I suppose user didn't agree to terms + * $I->seeCheckboxIsChecked('#signup_form input[type=checkbox]'); // I suppose user didn't check the first checkbox in form. + * ?> * ``` * * @param $checkbox - * @see Framework::dontSeeCheckboxIsChecked() + * @see Codeception\Util\Framework::dontSeeCheckboxIsChecked() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function dontSeeCheckboxIsChecked($checkbox) { - $this->scenario->action('dontSeeCheckboxIsChecked', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Assertion('dontSeeCheckboxIsChecked', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -755,6 +1287,44 @@ class TestGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks that an input field or textarea contains value. + * Field is matched either by label or CSS or Xpath + * + * Example: + * + * ``` php + * <?php + * $I->seeInField('Body','Type your comment here'); + * $I->seeInField('form textarea[name=body]','Type your comment here'); + * $I->seeInField('form input[type=hidden]','hidden_value'); + * $I->seeInField('#searchform input','Search'); + * $I->seeInField('//form/*[@name=search]','Search'); + * ?> + * ``` + * + * @param $field + * @param $value + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Framework::seeInField() + * @return \Codeception\Maybe + */ + public function canSeeInField($field, $value) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('seeInField', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Checks that an input field or textarea contains value. * Field is matched either by label or CSS or Xpath * @@ -772,13 +1342,11 @@ class TestGuy extends \Codeception\AbstractGuy * * @param $field * @param $value - * @see Framework::seeInField() + * @see Codeception\Util\Framework::seeInField() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function seeInField($field, $value) { - $this->scenario->assertion('seeInField', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Assertion('seeInField', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -788,6 +1356,10 @@ class TestGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Checks that an input field or textarea doesn't contain value. * Field is matched either by label or CSS or Xpath * Example: @@ -804,13 +1376,44 @@ class TestGuy extends \Codeception\AbstractGuy * * @param $field * @param $value - * @see Framework::dontSeeInField() + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Framework::dontSeeInField() + * @return \Codeception\Maybe + */ + public function cantSeeInField($field, $value) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('dontSeeInField', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks that an input field or textarea doesn't contain value. + * Field is matched either by label or CSS or Xpath + * Example: + * + * ``` php + * <?php + * $I->dontSeeInField('Body','Type your comment here'); + * $I->dontSeeInField('form textarea[name=body]','Type your comment here'); + * $I->dontSeeInField('form input[type=hidden]','hidden_value'); + * $I->dontSeeInField('#searchform input','Search'); + * $I->dontSeeInField('//form/*[@name=search]','Search'); + * ?> + * ``` + * + * @param $field + * @param $value + * @see Codeception\Util\Framework::dontSeeInField() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function dontSeeInField($field, $value) { - $this->scenario->action('dontSeeInField', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Assertion('dontSeeInField', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -820,6 +1423,10 @@ class TestGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Submits a form located on page. * Specify the form by it's css or xpath selector. * Fill the form fields values as array. @@ -858,13 +1465,11 @@ class TestGuy extends \Codeception\AbstractGuy * * @param $selector * @param $params - * @see Framework::submitForm() + * @see Codeception\Util\Framework::submitForm() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function submitForm($selector, $params) { - $this->scenario->action('submitForm', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Action('submitForm', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -874,17 +1479,27 @@ class TestGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Fills a text field or textarea with value. + * + * Example: + * + * ``` php + * <?php + * $I->fillField("//input[@type='text']", "Hello World!"); + * ?> + * ``` * * @param $field * @param $value - * @see Framework::fillField() + * @see Codeception\Util\Framework::fillField() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function fillField($field, $value) { - $this->scenario->action('fillField', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Action('fillField', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -894,6 +1509,10 @@ class TestGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Selects an option in select tag or in radio button group. * * Example: @@ -906,15 +1525,21 @@ class TestGuy extends \Codeception\AbstractGuy * ?> * ``` * + * Can select multiple options if second argument is array: + * + * ``` php + * <?php + * $I->selectOption('Which OS do you use?', array('Windows','Linux')); + * ?> + * ``` + * * @param $select * @param $option - * @see Framework::selectOption() + * @see Codeception\Util\Framework::selectOption() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function selectOption($select, $option) { - $this->scenario->action('selectOption', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Action('selectOption', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -924,6 +1549,10 @@ class TestGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Ticks a checkbox. * For radio buttons use `selectOption` method. * @@ -936,13 +1565,11 @@ class TestGuy extends \Codeception\AbstractGuy * ``` * * @param $option - * @see Framework::checkOption() + * @see Codeception\Util\Framework::checkOption() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function checkOption($option) { - $this->scenario->action('checkOption', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Action('checkOption', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -952,6 +1579,10 @@ class TestGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Unticks a checkbox. * * Example: @@ -963,13 +1594,11 @@ class TestGuy extends \Codeception\AbstractGuy * ``` * * @param $option - * @see Framework::uncheckOption() + * @see Codeception\Util\Framework::uncheckOption() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function uncheckOption($option) { - $this->scenario->action('uncheckOption', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Action('uncheckOption', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -979,26 +1608,28 @@ class TestGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Attaches file from Codeception data directory to upload field. * * Example: * * ``` php * <?php - * // file is stored in 'tests/data/tests.xls' - * $I->attachFile('prices.xls'); + * // file is stored in 'tests/_data/prices.xls' + * $I->attachFile('input[@type="file"]', 'prices.xls'); * ?> * ``` * * @param $field * @param $filename - * @see Framework::attachFile() + * @see Codeception\Util\Framework::attachFile() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function attachFile($field, $filename) { - $this->scenario->action('attachFile', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Action('attachFile', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -1008,6 +1639,10 @@ class TestGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * If your page triggers an ajax request, you can perform it manually. * This action sends a GET ajax request with specified params. * @@ -1015,13 +1650,11 @@ class TestGuy extends \Codeception\AbstractGuy * * @param $uri * @param $params - * @see Framework::sendAjaxGetRequest() + * @see Codeception\Util\Framework::sendAjaxGetRequest() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function sendAjaxGetRequest($uri, $params = null) { - $this->scenario->action('sendAjaxGetRequest', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Action('sendAjaxGetRequest', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -1031,6 +1664,10 @@ class TestGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * If your page triggers an ajax request, you can perform it manually. * This action sends a POST ajax request with specified params. * Additional params can be passed as array. @@ -1049,13 +1686,11 @@ class TestGuy extends \Codeception\AbstractGuy * * @param $uri * @param $params - * @see Framework::sendAjaxPostRequest() + * @see Codeception\Util\Framework::sendAjaxPostRequest() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function sendAjaxPostRequest($uri, $params = null) { - $this->scenario->action('sendAjaxPostRequest', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Action('sendAjaxPostRequest', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -1065,23 +1700,10 @@ class TestGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- * - * @see Framework::formatResponse() - * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! - */ - public function formatResponse($response) { - $this->scenario->action('formatResponse', func_get_args()); - if ($this->scenario->running()) { - $result = $this->scenario->runStep(); - return new Maybe($result); - } - return new Maybe(); - } - - - /** * Finds and returns text contents of element. * Element is searched by CSS selector, XPath or matcher by regex. * @@ -1097,13 +1719,11 @@ class TestGuy extends \Codeception\AbstractGuy * * @param $cssOrXPathOrRegex * @return mixed - * @see Framework::grabTextFrom() + * @see Codeception\Util\Framework::grabTextFrom() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function grabTextFrom($cssOrXPathOrRegex) { - $this->scenario->action('grabTextFrom', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Action('grabTextFrom', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -1113,6 +1733,10 @@ class TestGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Finds and returns field and returns it's value. * Searches by field name, then by CSS, then by XPath * @@ -1128,13 +1752,11 @@ class TestGuy extends \Codeception\AbstractGuy * * @param $field * @return mixed - * @see Framework::grabValueFrom() + * @see Codeception\Util\Framework::grabValueFrom() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function grabValueFrom($field) { - $this->scenario->action('grabValueFrom', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Action('grabValueFrom', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -1144,22 +1766,50 @@ class TestGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Checks if element exists on a page, matching it by CSS or XPath * * ``` php * <?php * $I->seeElement('.error'); - * $I->seeElement(//form/input[1]); + * $I->seeElement('//form/input[1]'); * ?> * ``` * @param $selector - * @see Framework::seeElement() + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Framework::seeElement() + * @return \Codeception\Maybe + */ + public function canSeeElement($selector) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('seeElement', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks if element exists on a page, matching it by CSS or XPath + * + * ``` php + * <?php + * $I->seeElement('.error'); + * $I->seeElement('//form/input[1]'); + * ?> + * ``` + * @param $selector + * @see Codeception\Util\Framework::seeElement() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function seeElement($selector) { - $this->scenario->assertion('seeElement', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Assertion('seeElement', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -1169,22 +1819,54 @@ class TestGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks if element does not exist (or is visible) on a page, matching it by CSS or XPath + * + * Example: + * + * ``` php + * <?php + * $I->dontSeeElement('.error'); + * $I->dontSeeElement('//form/input[1]'); + * ?> + * ``` + * @param $selector + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Framework::dontSeeElement() + * @return \Codeception\Maybe + */ + public function cantSeeElement($selector) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('dontSeeElement', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Checks if element does not exist (or is visible) on a page, matching it by CSS or XPath * + * Example: + * * ``` php * <?php * $I->dontSeeElement('.error'); - * $I->dontSeeElement(//form/input[1]); + * $I->dontSeeElement('//form/input[1]'); * ?> * ``` * @param $selector - * @see Framework::dontSeeElement() + * @see Codeception\Util\Framework::dontSeeElement() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function dontSeeElement($selector) { - $this->scenario->action('dontSeeElement', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Assertion('dontSeeElement', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -1194,6 +1876,10 @@ class TestGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Checks if option is selected in select field. * * ``` php @@ -1205,13 +1891,39 @@ class TestGuy extends \Codeception\AbstractGuy * @param $selector * @param $optionText * @return mixed - * @see Framework::seeOptionIsSelected() + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Framework::seeOptionIsSelected() + * @return \Codeception\Maybe + */ + public function canSeeOptionIsSelected($select, $optionText) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('seeOptionIsSelected', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks if option is selected in select field. + * + * ``` php + * <?php + * $I->seeOptionIsSelected('#form input[name=payment]', 'Visa'); + * ?> + * ``` + * + * @param $selector + * @param $optionText + * @return mixed + * @see Codeception\Util\Framework::seeOptionIsSelected() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function seeOptionIsSelected($select, $optionText) { - $this->scenario->assertion('seeOptionIsSelected', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Assertion('seeOptionIsSelected', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -1221,6 +1933,10 @@ class TestGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Checks if option is not selected in select field. * * ``` php @@ -1232,13 +1948,39 @@ class TestGuy extends \Codeception\AbstractGuy * @param $selector * @param $optionText * @return mixed - * @see Framework::dontSeeOptionIsSelected() + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Framework::dontSeeOptionIsSelected() + * @return \Codeception\Maybe + */ + public function cantSeeOptionIsSelected($select, $optionText) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('dontSeeOptionIsSelected', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks if option is not selected in select field. + * + * ``` php + * <?php + * $I->dontSeeOptionIsSelected('#form input[name=payment]', 'Visa'); + * ?> + * ``` + * + * @param $selector + * @param $optionText + * @return mixed + * @see Codeception\Util\Framework::dontSeeOptionIsSelected() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function dontSeeOptionIsSelected($select, $optionText) { - $this->scenario->action('dontSeeOptionIsSelected', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Assertion('dontSeeOptionIsSelected', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -1248,14 +1990,34 @@ class TestGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Asserts that current page has 404 response status code. + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Framework::seePageNotFound() + * @return \Codeception\Maybe + */ + public function canSeePageNotFound() { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('seePageNotFound', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Asserts that current page has 404 response status code. - * @see Framework::seePageNotFound() + * @see Codeception\Util\Framework::seePageNotFound() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function seePageNotFound() { - $this->scenario->assertion('seePageNotFound', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Assertion('seePageNotFound', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); @@ -1265,17 +2027,138 @@ class TestGuy extends \Codeception\AbstractGuy /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * * Checks that response code is equal to value provided. * * @param $code * @return mixed - * @see Framework::seeResponseCodeIs() + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Framework::seeResponseCodeIs() + * @return \Codeception\Maybe + */ + public function canSeeResponseCodeIs($code) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('seeResponseCodeIs', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks that response code is equal to value provided. + * + * @param $code + * @return mixed + * @see Codeception\Util\Framework::seeResponseCodeIs() * @return \Codeception\Maybe - * ! This method is generated. DO NOT EDIT. ! - * ! Documentation taken from corresponding module ! */ public function seeResponseCodeIs($code) { - $this->scenario->assertion('seeResponseCodeIs', func_get_args()); + $this->scenario->addStep(new \Codeception\Step\Assertion('seeResponseCodeIs', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + + + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks that page title contains text. + * + * ``` php + * <?php + * $I->seeInTitle('Blog - Post #1'); + * ?> + * ``` + * + * @param $title + * @return mixed + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Framework::seeInTitle() + * @return \Codeception\Maybe + */ + public function canSeeInTitle($title) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('seeInTitle', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks that page title contains text. + * + * ``` php + * <?php + * $I->seeInTitle('Blog - Post #1'); + * ?> + * ``` + * + * @param $title + * @return mixed + * @see Codeception\Util\Framework::seeInTitle() + * @return \Codeception\Maybe + */ + public function seeInTitle($title) { + $this->scenario->addStep(new \Codeception\Step\Assertion('seeInTitle', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + + + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks that page title does not contain text. + * + * @param $title + * @return mixed + * Conditional Assertion: Test won't be stopped on fail + * @see Codeception\Util\Framework::dontSeeInTitle() + * @return \Codeception\Maybe + */ + public function cantSeeInTitle($title) { + $this->scenario->addStep(new \Codeception\Step\ConditionalAssertion('dontSeeInTitle', func_get_args())); + if ($this->scenario->running()) { + $result = $this->scenario->runStep(); + return new Maybe($result); + } + return new Maybe(); + } + /** + * This method is generated. + * Documentation taken from corresponding module. + * ---------------------------------------------- + * + * Checks that page title does not contain text. + * + * @param $title + * @return mixed + * @see Codeception\Util\Framework::dontSeeInTitle() + * @return \Codeception\Maybe + */ + public function dontSeeInTitle($title) { + $this->scenario->addStep(new \Codeception\Step\Assertion('dontSeeInTitle', func_get_args())); if ($this->scenario->running()) { $result = $this->scenario->runStep(); return new Maybe($result); diff --git a/apps/basic/tests/unit/CodeGuy.php b/apps/basic/tests/unit/CodeGuy.php index adcd618..613c754 100644 --- a/apps/basic/tests/unit/CodeGuy.php +++ b/apps/basic/tests/unit/CodeGuy.php @@ -1,23 +1,26 @@ <?php // This class was automatically generated by build task -// You can change it manually, but it will be overwritten on next build +// You should not change it manually as it will be overwritten on next build // @codingStandardsIgnoreFile -use Codeception\Maybe; + +use \Codeception\Maybe; use Codeception\Module\CodeHelper; /** * Inherited methods + * @method void execute($callable) * @method void wantToTest($text) * @method void wantTo($text) - * @method void amTesting($method) - * @method void amTestingMethod($method) - * @method void testMethod($signature) * @method void expectTo($prediction) * @method void expect($prediction) * @method void amGoingTo($argumentation) * @method void am($role) - * @method void lookForwardTo($role) + * @method void lookForwardTo($achieveValue) + * @method void offsetGet($offset) + * @method void offsetSet($offset, $value) + * @method void offsetExists($offset) + * @method void offsetUnset($offset) */ class CodeGuy extends \Codeception\AbstractGuy diff --git a/apps/basic/views/layouts/main.php b/apps/basic/views/layouts/main.php index 1b7083d..04acdfb 100644 --- a/apps/basic/views/layouts/main.php +++ b/apps/basic/views/layouts/main.php @@ -3,58 +3,59 @@ use yii\helpers\Html; use yii\bootstrap\Nav; use yii\bootstrap\NavBar; use yii\widgets\Breadcrumbs; +use app\assets\AppAsset; /** - * @var $this \yii\base\View - * @var $content string + * @var \yii\web\View $this + * @var string $content */ -app\config\AppAsset::register($this); +AppAsset::register($this); ?> <?php $this->beginPage(); ?> <!DOCTYPE html> -<html lang="en"> +<html lang="<?= Yii::$app->language ?>"> <head> - <meta charset="<?php echo Yii::$app->charset; ?>"/> - <title><?php echo Html::encode($this->title); ?></title> + <meta charset="<?= Yii::$app->charset ?>"/> + <title><?= Html::encode($this->title) ?></title> <?php $this->head(); ?> </head> <body> <?php $this->beginBody(); ?> <?php - NavBar::begin(array( + NavBar::begin([ 'brandLabel' => 'My Company', 'brandUrl' => Yii::$app->homeUrl, - 'options' => array( + 'options' => [ 'class' => 'navbar-inverse navbar-fixed-top', - ), - )); - echo Nav::widget(array( - 'options' => array('class' => 'navbar-nav pull-right'), - 'items' => array( - array('label' => 'Home', 'url' => array('/site/index')), - array('label' => 'About', 'url' => array('/site/about')), - array('label' => 'Contact', 'url' => array('/site/contact')), + ], + ]); + echo Nav::widget([ + 'options' => ['class' => 'navbar-nav navbar-right'], + 'items' => [ + ['label' => 'Home', 'url' => ['/site/index']], + ['label' => 'About', 'url' => ['/site/about']], + ['label' => 'Contact', 'url' => ['/site/contact']], Yii::$app->user->isGuest ? - array('label' => 'Login', 'url' => array('/site/login')) : - array('label' => 'Logout (' . Yii::$app->user->identity->username .')' , - 'url' => array('/site/logout'), - 'linkOptions' => array('data-method' => 'post')), - ), - )); + ['label' => 'Login', 'url' => ['/site/login']] : + ['label' => 'Logout (' . Yii::$app->user->identity->username . ')' , + 'url' => ['/site/logout'], + 'linkOptions' => ['data-method' => 'post']], + ], + ]); NavBar::end(); ?> <div class="container"> - <?php echo Breadcrumbs::widget(array( - 'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : array(), - )); ?> - <?php echo $content; ?> + <?= Breadcrumbs::widget([ + 'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [], + ]) ?> + <?= $content ?> </div> <footer class="footer"> <div class="container"> - <p class="pull-left">© My Company <?php echo date('Y'); ?></p> - <p class="pull-right"><?php echo Yii::powered(); ?></p> + <p class="pull-left">© My Company <?= date('Y') ?></p> + <p class="pull-right"><?= Yii::powered() ?></p> </div> </footer> diff --git a/apps/basic/views/site/about.php b/apps/basic/views/site/about.php index 967b8fe..0608dd2 100644 --- a/apps/basic/views/site/about.php +++ b/apps/basic/views/site/about.php @@ -2,17 +2,17 @@ use yii\helpers\Html; /** - * @var yii\base\View $this + * @var yii\web\View $this */ $this->title = 'About'; $this->params['breadcrumbs'][] = $this->title; ?> <div class="site-about"> - <h1><?php echo Html::encode($this->title); ?></h1> + <h1><?= Html::encode($this->title) ?></h1> <p> This is the About page. You may modify the following file to customize its content: </p> - <code><?php echo __FILE__; ?></code> + <code><?= __FILE__ ?></code> </div> diff --git a/apps/basic/views/site/contact.php b/apps/basic/views/site/contact.php index d1411c0..d2e59af 100644 --- a/apps/basic/views/site/contact.php +++ b/apps/basic/views/site/contact.php @@ -4,7 +4,7 @@ use yii\widgets\ActiveForm; use yii\captcha\Captcha; /** - * @var yii\base\View $this + * @var yii\web\View $this * @var yii\widgets\ActiveForm $form * @var app\models\ContactForm $model */ @@ -12,7 +12,7 @@ $this->title = 'Contact'; $this->params['breadcrumbs'][] = $this->title; ?> <div class="site-contact"> - <h1><?php echo Html::encode($this->title); ?></h1> + <h1><?= Html::encode($this->title) ?></h1> <?php if (Yii::$app->session->hasFlash('contactFormSubmitted')): ?> @@ -28,17 +28,17 @@ $this->params['breadcrumbs'][] = $this->title; <div class="row"> <div class="col-lg-5"> - <?php $form = ActiveForm::begin(array('id' => 'contact-form')); ?> - <?php echo $form->field($model, 'name'); ?> - <?php echo $form->field($model, 'email'); ?> - <?php echo $form->field($model, 'subject'); ?> - <?php echo $form->field($model, 'body')->textArea(array('rows' => 6)); ?> - <?php echo $form->field($model, 'verifyCode')->widget(Captcha::className(), array( - 'options' => array('class' => 'form-control'), + <?php $form = ActiveForm::begin(['id' => 'contact-form']); ?> + <?= $form->field($model, 'name') ?> + <?= $form->field($model, 'email') ?> + <?= $form->field($model, 'subject') ?> + <?= $form->field($model, 'body')->textArea(['rows' => 6]) ?> + <?= $form->field($model, 'verifyCode')->widget(Captcha::className(), [ + 'options' => ['class' => 'form-control'], 'template' => '<div class="row"><div class="col-lg-3">{image}</div><div class="col-lg-6">{input}</div></div>', - )); ?> + ]) ?> <div class="form-group"> - <?php echo Html::submitButton('Submit', array('class' => 'btn btn-primary')); ?> + <?= Html::submitButton('Submit', ['class' => 'btn btn-primary']) ?> </div> <?php ActiveForm::end(); ?> </div> diff --git a/apps/basic/views/site/error.php b/apps/basic/views/site/error.php index 024e27d..1b7ce04 100644 --- a/apps/basic/views/site/error.php +++ b/apps/basic/views/site/error.php @@ -3,7 +3,7 @@ use yii\helpers\Html; /** - * @var yii\base\View $this + * @var yii\web\View $this * @var string $name * @var string $message * @var Exception $exception @@ -13,10 +13,10 @@ $this->title = $name; ?> <div class="site-error"> - <h1><?php echo Html::encode($this->title); ?></h1> + <h1><?= Html::encode($this->title) ?></h1> <div class="alert alert-danger"> - <?php echo nl2br(Html::encode($message)); ?> + <?= nl2br(Html::encode($message)) ?> </div> <p> diff --git a/apps/basic/views/site/index.php b/apps/basic/views/site/index.php index f2e6d5e..bcb2278 100644 --- a/apps/basic/views/site/index.php +++ b/apps/basic/views/site/index.php @@ -1,6 +1,6 @@ <?php /** - * @var yii\base\View $this + * @var yii\web\View $this */ $this->title = 'My Yii Application'; ?> diff --git a/apps/basic/views/site/login.php b/apps/basic/views/site/login.php index f61d9d7..da022d6 100644 --- a/apps/basic/views/site/login.php +++ b/apps/basic/views/site/login.php @@ -3,7 +3,7 @@ use yii\helpers\Html; use yii\widgets\ActiveForm; /** - * @var yii\base\View $this + * @var yii\web\View $this * @var yii\widgets\ActiveForm $form * @var app\models\LoginForm $model */ @@ -11,30 +11,30 @@ $this->title = 'Login'; $this->params['breadcrumbs'][] = $this->title; ?> <div class="site-login"> - <h1><?php echo Html::encode($this->title); ?></h1> + <h1><?= Html::encode($this->title) ?></h1> <p>Please fill out the following fields to login:</p> - <?php $form = ActiveForm::begin(array( + <?php $form = ActiveForm::begin([ 'id' => 'login-form', - 'options' => array('class' => 'form-horizontal'), - 'fieldConfig' => array( + 'options' => ['class' => 'form-horizontal'], + 'fieldConfig' => [ 'template' => "{label}\n<div class=\"col-lg-3\">{input}</div>\n<div class=\"col-lg-8\">{error}</div>", - 'labelOptions' => array('class' => 'col-lg-1 control-label'), - ), - )); ?> + 'labelOptions' => ['class' => 'col-lg-1 control-label'], + ], + ]); ?> - <?php echo $form->field($model, 'username'); ?> + <?= $form->field($model, 'username') ?> - <?php echo $form->field($model, 'password')->passwordInput(); ?> + <?= $form->field($model, 'password')->passwordInput() ?> - <?php echo $form->field($model, 'rememberMe', array( + <?= $form->field($model, 'rememberMe', [ 'template' => "<div class=\"col-lg-offset-1 col-lg-3\">{input}</div>\n<div class=\"col-lg-8\">{error}</div>", - ))->checkbox(); ?> + ])->checkbox() ?> <div class="form-group"> <div class="col-lg-offset-1 col-lg-11"> - <?php echo Html::submitButton('Login', array('class' => 'btn btn-primary')); ?> + <?= Html::submitButton('Login', ['class' => 'btn btn-primary']) ?> </div> </div> diff --git a/apps/basic/web/css/site.css b/apps/basic/web/css/site.css index 84c46e8..707c66d 100644 --- a/apps/basic/web/css/site.css +++ b/apps/basic/web/css/site.css @@ -18,3 +18,24 @@ body { font-size: 21px; padding: 14px 24px; } + +/* add sorting icons to gridview sort links */ +a.asc:after, a.desc:after { + position: relative; + top: 1px; + display: inline-block; + font-family: 'Glyphicons Halflings'; + font-style: normal; + font-weight: normal; + line-height: 1; + padding-left: 5px; +} + +a.asc:after { content: /*"\e113"*/"\e151"; } +a.desc:after { content: /*"\e114"*/"\e152"; } + +.sort-numerical a.asc:after { content: "\e153"; } +.sort-numerical a.desc:after { content: "\e154"; } + +.sort-ordinal a.asc:after { content: "\e155"; } +.sort-ordinal a.desc:after { content: "\e156"; } diff --git a/apps/basic/web/index-test.php b/apps/basic/web/index-test.php index 79273ae..bd61e0b 100644 --- a/apps/basic/web/index-test.php +++ b/apps/basic/web/index-test.php @@ -1,6 +1,6 @@ <?php -if (!in_array(@$_SERVER['REMOTE_ADDR'], array('127.0.0.1', '::1'))) { +if (!in_array(@$_SERVER['REMOTE_ADDR'], ['127.0.0.1', '::1'])) { die('You are not allowed to access this file.'); } @@ -10,9 +10,15 @@ defined('YII_ENV') or define('YII_ENV', 'test'); require_once(__DIR__ . '/../vendor/autoload.php'); require_once(__DIR__ . '/../vendor/yiisoft/yii2/yii/Yii.php'); -Yii::importNamespaces(require(__DIR__ . '/../vendor/composer/autoload_namespaces.php')); $config = require(__DIR__ . '/../config/web-test.php'); -$application = new yii\web\Application($config); -$application->run(); +if (isset($this)) { + // run in functional tests + $config['class'] = 'yii\web\Application'; + return $config; +} else { + // run in acceptance tests + $application = new yii\web\Application($config); + $application->run(); +} diff --git a/apps/basic/web/index.php b/apps/basic/web/index.php index 51673d3..e9eeb33 100644 --- a/apps/basic/web/index.php +++ b/apps/basic/web/index.php @@ -6,7 +6,6 @@ defined('YII_ENV') or define('YII_ENV', 'dev'); require(__DIR__ . '/../vendor/autoload.php'); require(__DIR__ . '/../vendor/yiisoft/yii2/yii/Yii.php'); -Yii::importNamespaces(require(__DIR__ . '/../vendor/composer/autoload_namespaces.php')); $config = require(__DIR__ . '/../config/web.php'); diff --git a/apps/basic/yii b/apps/basic/yii index cd979ca..9188f85 100755 --- a/apps/basic/yii +++ b/apps/basic/yii @@ -15,7 +15,6 @@ defined('STDIN') or define('STDIN', fopen('php://stdin', 'r')); require(__DIR__ . '/vendor/autoload.php'); require(__DIR__ . '/vendor/yiisoft/yii2/yii/Yii.php'); -Yii::importNamespaces(require(__DIR__ . '/vendor/composer/autoload_namespaces.php')); $config = require(__DIR__ . '/config/console.php'); diff --git a/apps/benchmark/LICENSE.md b/apps/benchmark/LICENSE.md index 6edcc4f..e98f03d 100644 --- a/apps/benchmark/LICENSE.md +++ b/apps/benchmark/LICENSE.md @@ -1,7 +1,7 @@ The Yii framework is free software. It is released under the terms of the following BSD License. -Copyright © 2008-2013 by Yii Software LLC (http://www.yiisoft.com) +Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com) All rights reserved. Redistribution and use in source and binary forms, with or without @@ -29,4 +29,4 @@ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file +POSSIBILITY OF SUCH DAMAGE. diff --git a/apps/benchmark/README.md b/apps/benchmark/README.md index 2d5871a..7eddae8 100644 --- a/apps/benchmark/README.md +++ b/apps/benchmark/README.md @@ -27,7 +27,7 @@ DIRECTORY STRUCTURE REQUIREMENTS ------------ -The minimum requirement by Yii is that your Web server supports PHP 5.3.?. +The minimum requirement by Yii is that your Web server supports PHP 5.4.0. INSTALLATION diff --git a/apps/benchmark/composer.json b/apps/benchmark/composer.json index 2b077e0..c074233 100644 --- a/apps/benchmark/composer.json +++ b/apps/benchmark/composer.json @@ -17,7 +17,7 @@ }, "minimum-stability": "dev", "require": { - "php": ">=5.3.0", + "php": ">=5.4.0", "yiisoft/yii2": "dev-master" } } diff --git a/apps/benchmark/index.php b/apps/benchmark/index.php index ddf6081..984beb2 100644 --- a/apps/benchmark/index.php +++ b/apps/benchmark/index.php @@ -4,15 +4,15 @@ defined('YII_DEBUG') or define('YII_DEBUG', false); require(__DIR__ . '/protected/vendor/yiisoft/yii2/yii/Yii.php'); -$config = array( +$config = [ 'id' => 'benchmark', 'basePath' => __DIR__ . '/protected', - 'components' => array( - 'urlManager' => array( + 'components' => [ + 'urlManager' => [ 'enablePrettyUrl' => true, - ), - ) -); + ], + ], +]; $application = new yii\web\Application($config); $application->run(); diff --git a/build/build b/build/build index a6c7cc8..b1d6bb2 100755 --- a/build/build +++ b/build/build @@ -13,9 +13,9 @@ defined('STDIN') or define('STDIN', fopen('php://stdin', 'r')); require(__DIR__ . '/../framework/yii/Yii.php'); -$application = new yii\console\Application(array( +$application = new yii\console\Application([ 'id' => 'yii-build', 'basePath' => __DIR__, 'controllerNamespace' => 'yii\build\controllers', -)); +]); $application->run(); diff --git a/build/build.xml b/build/build.xml index 9c18af0..b0975dc 100644 --- a/build/build.xml +++ b/build/build.xml @@ -265,7 +265,7 @@ Please update yiisite/common/data/versions.php file with the following code: where <target name> can be one of the following: - - sync : synchronize yiilite.php and YiiBase.php + - sync : synchronize yiilite.php and BaseYii.php - message : extract i18n messages of the framework - src : build source release - doc : build documentation release (Windows only) diff --git a/build/controllers/ClassmapController.php b/build/controllers/ClassmapController.php index 2a0483c..a0ba886 100644 --- a/build/controllers/ClassmapController.php +++ b/build/controllers/ClassmapController.php @@ -32,7 +32,7 @@ class ClassmapController extends Controller if ($mapFile === null) { $mapFile = YII_PATH . '/classes.php'; } - $options = array( + $options = [ 'filter' => function ($path) { if (is_file($path)) { $file = basename($path); @@ -42,18 +42,15 @@ class ClassmapController extends Controller } return null; }, - 'only' => array('.php'), - 'except' => array( + 'only' => ['.php'], + 'except' => [ 'Yii.php', - 'YiiBase.php', - '/debug/', + 'BaseYii.php', '/console/', - '/test/', - '/gii/', - ), - ); + ], + ]; $files = FileHelper::findFiles($root, $options); - $map = array(); + $map = []; foreach ($files as $file) { if (($pos = strpos($file, $root)) !== 0) { die("Something wrong: $file\n"); @@ -76,9 +73,9 @@ class ClassmapController extends Controller * @license http://www.yiiframework.com/license/ */ -return array( +return [ $map -); +]; EOD; if (is_file($mapFile) && file_get_contents($mapFile) === $output) { diff --git a/build/controllers/LocaleController.php b/build/controllers/LocaleController.php deleted file mode 100644 index 7b2a5df..0000000 --- a/build/controllers/LocaleController.php +++ /dev/null @@ -1,114 +0,0 @@ -<?php -/** - * @link http://www.yiiframework.com/ - * @copyright Copyright (c) 2008 Yii Software LLC - * @license http://www.yiiframework.com/license/ - */ - -namespace yii\build\controllers; - -use yii\console\Exception; -use yii\console\Controller; - -/** - * http://www.unicode.org/cldr/charts/supplemental/language_plural_rules.html - * @author Qiang Xue <qiang.xue@gmail.com> - * @since 2.0 - */ -class LocaleController extends Controller -{ - public $defaultAction = 'plural'; - - /** - * Generates the plural rules data. - * - * This command will parse the plural rule XML file from CLDR and convert them - * into appropriate PHP representation to support Yii message translation feature. - * @param string $xmlFile the original plural rule XML file (from CLDR). This file may be found in - * http://www.unicode.org/Public/cldr/latest/core.zip - * Extract the zip file and locate the file "common/supplemental/plurals.xml". - * @throws Exception - */ - public function actionPlural($xmlFile) - { - if (!is_file($xmlFile)) { - throw new Exception("The source plural rule file does not exist: $xmlFile"); - } - - $xml = simplexml_load_file($xmlFile); - - $allRules = array(); - - $patterns = array( - '/n in 0..1/' => '(n==0||n==1)', - '/\s+is\s+not\s+/i' => '!=', //is not - '/\s+is\s+/i' => '==', //is - '/n\s+mod\s+(\d+)/i' => 'fmod(n,$1)', //mod (CLDR's "mod" is "fmod()", not "%") - '/^(.*?)\s+not\s+in\s+(\d+)\.\.(\d+)/i' => '!in_array($1,range($2,$3))', //not in - '/^(.*?)\s+in\s+(\d+)\.\.(\d+)/i' => 'in_array($1,range($2,$3))', //in - '/^(.*?)\s+not\s+within\s+(\d+)\.\.(\d+)/i' => '($1<$2||$1>$3)', //not within - '/^(.*?)\s+within\s+(\d+)\.\.(\d+)/i' => '($1>=$2&&$1<=$3)', //within - ); - foreach ($xml->plurals->pluralRules as $node) { - $attributes = $node->attributes(); - $locales = explode(' ', $attributes['locales']); - $rules = array(); - - if (!empty($node->pluralRule)) { - foreach ($node->pluralRule as $rule) { - $expr_or = preg_split('/\s+or\s+/i', $rule); - foreach ($expr_or as $key_or => $val_or) { - $expr_and = preg_split('/\s+and\s+/i', $val_or); - $expr_and = preg_replace(array_keys($patterns), array_values($patterns), $expr_and); - $expr_or[$key_or] = implode('&&', $expr_and); - } - $expr = preg_replace('/\\bn\\b/', '$n', implode('||', $expr_or)); - $rules[] = preg_replace_callback('/range\((\d+),(\d+)\)/', function ($matches) { - if ($matches[2] - $matches[1] <= 5) { - return 'array(' . implode(',', range($matches[1], $matches[2])) . ')'; - } else { - return $matches[0]; - } - }, $expr); - - } - foreach ($locales as $locale) { - $allRules[$locale] = $rules; - } - } - } - // hard fix for "br": the rule is too complex - $allRules['br'] = array( - 0 => 'fmod($n,10)==1&&!in_array(fmod($n,100),array(11,71,91))', - 1 => 'fmod($n,10)==2&&!in_array(fmod($n,100),array(12,72,92))', - 2 => 'in_array(fmod($n,10),array(3,4,9))&&!in_array(fmod($n,100),array_merge(range(10,19),range(70,79),range(90,99)))', - 3 => 'fmod($n,1000000)==0&&$n!=0', - ); - if (preg_match('/\d+/', $xml->version['number'], $matches)) { - $revision = $matches[0]; - } else { - $revision = -1; - } - - echo "<?php\n"; - echo <<<EOD -/** - * Plural rules. - * - * This file is automatically generated by the "yii locale/plural" command under the "build" folder. - * Do not modify it directly. - * - * The original plural rule data used for generating this file has the following copyright terms: - * - * Copyright © 1991-2007 Unicode, Inc. All rights reserved. - * Distributed under the Terms of Use in http://www.unicode.org/copyright.html. - * - * @revision $revision (of the original plural file) - * @link http://www.yiiframework.com/ - * @copyright Copyright (c) 2008 Yii Software LLC - * @license http://www.yiiframework.com/license/ - */ -EOD; - echo "\nreturn " . var_export($allRules, true) . ';'; - } -} diff --git a/build/controllers/PhpDocController.php b/build/controllers/PhpDocController.php index 5aac7e5..c7a2fa7 100644 --- a/build/controllers/PhpDocController.php +++ b/build/controllers/PhpDocController.php @@ -44,7 +44,7 @@ class PhpDocController extends Controller $root = YII_PATH; } $root = FileHelper::normalizePath($root); - $options = array( + $options = [ 'filter' => function ($path) { if (is_file($path)) { $file = basename($path); @@ -54,16 +54,16 @@ class PhpDocController extends Controller } return null; }, - 'only' => array('.php'), - 'except' => array( - 'YiiBase.php', + 'only' => ['.php'], + 'except' => [ + 'BaseYii.php', 'Yii.php', '/debug/views/', '/requirements/', '/gii/views/', '/gii/generators/', - ), - ); + ], + ]; $files = FileHelper::findFiles($root, $options); $nFilesTotal = 0; $nFilesUpdated = 0; @@ -89,7 +89,7 @@ class PhpDocController extends Controller public function globalOptions() { - return array_merge(parent::globalOptions(), array('updateFiles')); + return array_merge(parent::globalOptions(), ['updateFiles']); } protected function updateClassPropertyDocs($file, $className, $propertyDoc) @@ -115,7 +115,7 @@ class PhpDocController extends Controller if (trim($lines[1]) == '*' || substr(trim($lines[1]), 0, 3) == '* @') { $this->stderr("[WARN] Class $className has no short description.\n", Console::FG_YELLOW, Console::BOLD); } - foreach($lines as $line) { + foreach ($lines as $line) { if (substr(trim($line), 0, 9) == '* @since ') { $seenSince = true; } elseif (substr(trim($line), 0, 10) == '* @author ') { @@ -136,9 +136,9 @@ class PhpDocController extends Controller $start = $ref->getStartLine() - 2; $docStart = $start - count(explode("\n", $oldDoc)) + 1; - $newFileContent = array(); + $newFileContent = []; $n = count($fileContent); - for($i = 0; $i < $n; $i++) { + for ($i = 0; $i < $n; $i++) { if ($i > $start || $i < $docStart) { $newFileContent[] = $fileContent[$i]; } else { @@ -164,7 +164,7 @@ class PhpDocController extends Controller { $lines = explode("\n", $doc); $n = count($lines); - for($i = 0; $i < $n; $i++) { + for ($i = 0; $i < $n; $i++) { $lines[$i] = rtrim($lines[$i]); if (trim($lines[$i]) == '*' && trim($lines[$i + 1]) == '*') { unset($lines[$i]); @@ -184,7 +184,7 @@ class PhpDocController extends Controller $lines = explode("\n", $doc); $propertyPart = false; $propertyPosition = false; - foreach($lines as $i => $line) { + foreach ($lines as $i => $line) { if (substr(trim($line), 0, 12) == '* @property ') { $propertyPart = true; } elseif ($propertyPart && trim($line) == '*') { @@ -200,7 +200,7 @@ class PhpDocController extends Controller } } $finalDoc = ''; - foreach($lines as $i => $line) { + foreach ($lines as $i => $line) { $finalDoc .= $line . "\n"; if ($i == $propertyPosition) { $finalDoc .= $properties; @@ -253,16 +253,15 @@ class PhpDocController extends Controller '[\s\n]{2,}public function [g|s]et(?<name>\w+)\(((?:,? ?\$\w+ ?= ?[^,]+)*|\$\w+(?:, ?\$\w+ ?= ?[^,]+)*)\)#', $class['content']); $acrs = array_merge($properties, $gets, $sets); - //print_r($acrs); continue; - $props = array(); + $props = []; foreach ($acrs as &$acr) { $acr['name'] = lcfirst($acr['name']); $acr['comment'] = trim(preg_replace('#(^|\n)\s+\*\s?#', '$1 * ', $acr['comment'])); - $props[$acr['name']][$acr['kind']] = array( + $props[$acr['name']][$acr['kind']] = [ 'type' => $acr['type'], 'comment' => $this->fixSentence($acr['comment']), - ); + ]; } ksort($props); @@ -279,11 +278,35 @@ class PhpDocController extends Controller . ' See [[get'.ucfirst($propName).'()]] and [[set'.ucfirst($propName).'()]] for details.'; } } elseif (isset($prop['get'])) { - $note = ' This property is read-only.'; -// $docline .= '-read'; + // check if parent class has setter defined + $c = $className; + $parentSetter = false; + while($parent = get_parent_class($c)) { + if (method_exists($parent, 'set' . ucfirst($propName))) { + $parentSetter = true; + break; + } + $c = $parent; + } + if (!$parentSetter) { + $note = ' This property is read-only.'; +// $docline .= '-read'; + } } elseif (isset($prop['set'])) { - $note = ' This property is write-only.'; -// $docline .= '-write'; + // check if parent class has getter defined + $c = $className; + $parentGetter = false; + while($parent = get_parent_class($c)) { + if (method_exists($parent, 'set' . ucfirst($propName))) { + $parentGetter = true; + break; + } + $c = $parent; + } + if (!$parentGetter) { + $note = ' This property is write-only.'; +// $docline .= '-write'; + } } else { continue; } @@ -299,12 +322,12 @@ class PhpDocController extends Controller $phpdoc .= " *\n"; } } - return array($className, $phpdoc); + return [$className, $phpdoc]; } protected function match($pattern, $subject) { - $sets = array(); + $sets = []; preg_match_all($pattern . 'suU', $subject, $sets, PREG_SET_ORDER); foreach ($sets as &$set) foreach ($set as $i => $match) diff --git a/docs/api/base/Component.md b/docs/api/base/Component.md index 01ef462..f198798 100644 --- a/docs/api/base/Component.md +++ b/docs/api/base/Component.md @@ -25,8 +25,8 @@ In the above, an anonymous function is attached to the "update" event of the pos the following types of event handlers: - anonymous function: `function($event) { ... }` -- object method: `array($object, 'handleAdd')` -- static class method: `array('Page', 'handleAdd')` +- object method: `[$object, 'handleAdd']` +- static class method: `['Page', 'handleAdd']` - global function: `'handleAdd'` The signature of an event handler should be like the following: @@ -41,9 +41,9 @@ You can also attach a handler to an event when configuring a component with a co The syntax is like the following: ~~~ -array( +[ 'on add' => function($event) { ... } -) +] ~~~ where `on add` stands for attaching an event to the `add` event. @@ -69,11 +69,11 @@ One can also attach a behavior to a component when configuring it with a configu following: ~~~ -array( - 'as tree' => array( +[ + 'as tree' => [ 'class' => 'Tree', - ), -) + ], +] ~~~ where `as tree` stands for attaching a behavior named `tree`, and the array will be passed to [[\Yii::createObject()]] diff --git a/docs/api/base/Object.md b/docs/api/base/Object.md index 1b9fca0..a6ab2c1 100644 --- a/docs/api/base/Object.md +++ b/docs/api/base/Object.md @@ -50,12 +50,12 @@ In order to ensure the above life cycles, if a child class of Object needs to ov it should be done like the following: ~~~ -public function __construct($param1, $param2, ..., $config = array()) +public function __construct($param1, $param2, ..., $config = []) { ... parent::__construct($config); } ~~~ -That is, a `$config` parameter (defaults to `array()`) should be declared as the last parameter +That is, a `$config` parameter (defaults to `[]`) should be declared as the last parameter of the constructor, and the parent implementation should be called at the end of the constructor. diff --git a/docs/api/db/ActiveRecord-find.md b/docs/api/db/ActiveRecord-find.md index 42940b6..8653853 100644 --- a/docs/api/db/ActiveRecord-find.md +++ b/docs/api/db/ActiveRecord-find.md @@ -8,7 +8,7 @@ $customers = Customer::find()->all(); // find all active customers and order them by their age: $customers = Customer::find() - ->where(array('status' => 1)) + ->where(['status' => 1]) ->orderBy('age') ->all(); @@ -16,11 +16,11 @@ $customers = Customer::find() $customer = Customer::find(10); // the above is equivalent to: -$customer = Customer::find()->where(array('id' => 10))->one(); +$customer = Customer::find()->where(['id' => 10])->one(); // find a single customer whose age is 30 and whose status is 1 -$customer = Customer::find(array('age' => 30, 'status' => 1)); +$customer = Customer::find(['age' => 30, 'status' => 1]); // the above is equivalent to: -$customer = Customer::find()->where(array('age' => 30, 'status' => 1))->one(); +$customer = Customer::find()->where(['age' => 30, 'status' => 1])->one(); ~~~ \ No newline at end of file diff --git a/docs/api/db/ActiveRecord.md b/docs/api/db/ActiveRecord.md index d8bedb4..ef050d0 100644 --- a/docs/api/db/ActiveRecord.md +++ b/docs/api/db/ActiveRecord.md @@ -1,6 +1,6 @@ ActiveRecord implements the [Active Record design pattern](http://en.wikipedia.org/wiki/Active_record). The idea is that an ActiveRecord object is associated with a row in a database table -so object properties are mapped to colums of the corresponding database row. +so object properties are mapped to columns of the corresponding database row. For example, a `Customer` object is associated with a row in the `tbl_customer` table. Instead of writing raw SQL statements to access the data in the table, you can call intuitive methods available in the corresponding ActiveRecord class @@ -40,18 +40,18 @@ instance which serves as the DB connection. Usually this component is configured via application configuration like the following: ~~~ -return array( - 'components' => array( - 'db' => array( +return [ + 'components' => [ + 'db' => [ 'class' => 'yii\db\Connection', 'dsn' => 'mysql:host=localhost;dbname=testdb', 'username' => 'demo', 'password' => 'demo', // turn on schema caching to improve performance // 'schemaCacheDuration' => 3600, - ), - ), -); + ], + ], +]; ~~~ @@ -70,13 +70,13 @@ The followings are some examples, ~~~ // to retrieve all *active* customers and order them by their ID: $customers = Customer::find() - ->where(array('status' => $active)) + ->where(['status' => $active]) ->orderBy('id') ->all(); // to return a single customer whose ID is 1: $customer = Customer::find() - ->where(array('id' => 1)) + ->where(['id' => 1]) ->one(); // or use the following shortcut approach: @@ -88,7 +88,7 @@ $customers = Customer::findBySql($sql)->all(); // to return the number of *active* customers: $count = Customer::find() - ->where(array('status' => $active)) + ->where(['status' => $active]) ->count(); // to return customers in terms of arrays rather than `Customer` objects: @@ -158,7 +158,7 @@ $customer = Customer::find($id); $customer->delete(); // to increment the age of all customers by 1 -Customer::updateAllCounters(array('age' => 1)); +Customer::updateAllCounters(['age' => 1]); ~~~ @@ -175,7 +175,7 @@ class Customer extends \yii\db\ActiveRecord { public function getOrders() { - return $this->hasMany('Order', array('customer_id' => 'id')); + return $this->hasMany(Order::className(), ['customer_id' => 'id']); } } @@ -183,7 +183,7 @@ class Order extends \yii\db\ActiveRecord { public function getCustomer() { - return $this->hasOne('Customer', array('id' => 'customer_id')); + return $this->hasOne(Customer::className(), ['id' => 'customer_id']); } } ~~~ @@ -194,8 +194,7 @@ a one-many relationship. For example, a customer has many orders. And the [[hasO method declares a many-one or one-one relationship. For example, an order has one customer. Both methods take two parameters: -- `$class`: the name of the class related models should use. If specified without - a namespace, the namespace will be taken from the declaring class. +- `$class`: the name of the class that the related models should use. - `$link`: the association between columns from two tables. This should be given as an array. The keys of the array are the names of the columns from the table associated with `$class`, while the values of the array are the names of the columns from the declaring class. @@ -223,8 +222,8 @@ class Customer extends \yii\db\ActiveRecord { public function getBigOrders($threshold = 100) { - return $this->hasMany('Order', array('customer_id' => 'id')) - ->where('subtotal > :threshold', array(':threshold' => $threshold)) + return $this->hasMany(Order::className(), ['customer_id' => 'id']) + ->where('subtotal > :threshold', [':threshold' => $threshold]) ->orderBy('id'); } } @@ -244,8 +243,8 @@ class Order extends \yii\db\ActiveRecord { public function getItems() { - return $this->hasMany('Item', array('id' => 'item_id')) - ->viaTable('tbl_order_item', array('order_id' => 'id')); + return $this->hasMany(Item::className(), ['id' => 'item_id']) + ->viaTable('tbl_order_item', ['order_id' => 'id']); } } ~~~ @@ -259,12 +258,12 @@ class Order extends \yii\db\ActiveRecord { public function getOrderItems() { - return $this->hasMany('OrderItem', array('order_id' => 'id')); + return $this->hasMany(OrderItem::className(), ['order_id' => 'id']); } public function getItems() { - return $this->hasMany('Item', array('id' => 'item_id')) + return $this->hasMany(Item::className(), ['id' => 'item_id']) ->via('orderItems'); } } @@ -331,11 +330,11 @@ $orders = $customer->getOrders()->where('subtotal>100')->all(); // eager loading: SELECT * FROM tbl_customer LIMIT 10 SELECT * FROM tbl_order WHERE customer_id IN (1,2,...) AND subtotal>100 -$customers = Customer::find()->limit(100)->with(array( +$customers = Customer::find()->limit(100)->with([ 'orders' => function($query) { $query->andWhere('subtotal>100'); }, -))->all(); +])->all(); ~~~ @@ -437,7 +436,7 @@ class Customer extends \yii\db\ActiveRecord */ public static function olderThan($query, $age = 30) { - $query->andWhere('age > :age', array(':age' => $age)); + $query->andWhere('age > :age', [':age' => $age]); } } diff --git a/docs/guide/active-record.md b/docs/guide/active-record.md index fc98f98..90826b0 100644 --- a/docs/guide/active-record.md +++ b/docs/guide/active-record.md @@ -54,16 +54,16 @@ By default, ActiveRecord assumes that there is an application component named `d [[Connection]] instance. Usually this component is configured in application configuration file: ```php -return array( - 'components' => array( - 'db' => array( +return [ + 'components' => [ + 'db' => [ 'class' => 'yii\db\Connection', 'dsn' => 'mysql:host=localhost;dbname=testdb', 'username' => 'demo', 'password' => 'demo', - ), - ), -); + ], + ], +]; ``` Please read the [Database basics](database-basics.md) section to learn more on how to configure and use database connections. @@ -82,7 +82,7 @@ the same set of flexible and powerful DB query methods. The following examples d ```php // to retrieve all *active* customers and order them by their ID: $customers = Customer::find() - ->where(array('status' => $active)) + ->where(['status' => $active]) ->orderBy('id') ->all(); @@ -91,7 +91,7 @@ $customer = Customer::find(1); // the above code is equivalent to the following: $customer = Customer::find() - ->where(array('id' => 1)) + ->where(['id' => 1]) ->one(); // to retrieve customers using a raw SQL statement: @@ -100,7 +100,7 @@ $customers = Customer::findBySql($sql)->all(); // to return the number of *active* customers: $count = Customer::find() - ->where(array('status' => $active)) + ->where(['status' => $active]) ->count(); // to return customers in terms of arrays rather than `Customer` objects: @@ -172,10 +172,12 @@ $customer = Customer::find($id); $customer->delete(); // to increment the age of ALL customers by 1 -Customer::updateAllCounters(array('age' => 1)); +Customer::updateAllCounters(['age' => 1]); ``` -Notice that you can always use the `save` method, and ActiveRecord will automatically perform an INSERT for new records and an UPDATE for existing ones. +> Info: The `save()` method will either perform an `INSERT` or `UPDATE` SQL statement, depending + on whether the ActiveRecord being saved is new or not by checking `ActiveRecord::isNewRecord`. + Data Input and Validation ------------------------- @@ -200,7 +202,7 @@ class Customer extends \yii\db\ActiveRecord { public function getOrders() { - return $this->hasMany('Order', array('customer_id' => 'id')); + return $this->hasMany(Order::className(), ['customer_id' => 'id']); } } @@ -208,7 +210,7 @@ class Order extends \yii\db\ActiveRecord { public function getCustomer() { - return $this->hasOne('Customer', array('id' => 'customer_id')); + return $this->hasOne(Customer::className(), ['id' => 'customer_id']); } } ``` @@ -255,8 +257,8 @@ class Customer extends \yii\db\ActiveRecord { public function getBigOrders($threshold = 100) { - return $this->hasMany('Order', array('customer_id' => 'id')) - ->where('subtotal > :threshold', array(':threshold' => $threshold)) + return $this->hasMany(Order::className(), ['customer_id' => 'id']) + ->where('subtotal > :threshold', [':threshold' => $threshold]) ->orderBy('id'); } } @@ -289,8 +291,8 @@ class Order extends \yii\db\ActiveRecord { public function getItems() { - return $this->hasMany('Item', array('id' => 'item_id')) - ->viaTable('tbl_order_item', array('order_id' => 'id')); + return $this->hasMany(Item::className(), ['id' => 'item_id']) + ->viaTable('tbl_order_item', ['order_id' => 'id']); } } ``` @@ -304,12 +306,12 @@ class Order extends \yii\db\ActiveRecord { public function getOrderItems() { - return $this->hasMany('OrderItem', array('order_id' => 'id')); + return $this->hasMany(OrderItem::className(), ['order_id' => 'id']); } public function getItems() { - return $this->hasMany('Item', array('id' => 'item_id')) + return $this->hasMany(Item::className(), ['id' => 'item_id']) ->via('orderItems'); } } @@ -377,11 +379,11 @@ $orders = $customer->getOrders()->where('subtotal>100')->all(); // eager loading: SELECT * FROM tbl_customer LIMIT 10 SELECT * FROM tbl_order WHERE customer_id IN (1,2,...) AND subtotal>100 -$customers = Customer::find()->limit(100)->with(array( +$customers = Customer::find()->limit(100)->with([ 'orders' => function($query) { $query->andWhere('subtotal>100'); }, -))->all(); +])->all(); ``` @@ -481,7 +483,7 @@ class Customer extends \yii\db\ActiveRecord */ public static function olderThan($query, $age = 30) { - $query->andWhere('age > :age', array(':age' => $age)); + $query->andWhere('age > :age', [':age' => $age]); } } @@ -515,7 +517,7 @@ class Feature extends \yii\db\ActiveRecord public function getProduct() { - return $this->hasOne('Product', array('product_id' => 'id')); + return $this->hasOne(Product::className(), ['product_id' => 'id']); } } @@ -525,7 +527,7 @@ class Product extends \yii\db\ActiveRecord public function getFeatures() { - return $this->hasMany('Feature', array('id' => 'product_id')); + return $this->hasMany(Feature::className(), ['id' => 'product_id']); } } ``` @@ -564,17 +566,17 @@ class Feature extends \yii\db\ActiveRecord public function getProduct() { - return $this->hasOne('Product', array('product_id' => 'id')); + return $this->hasOne(Product::className(), ['product_id' => 'id']); } public function scenarios() { - return array( - 'userCreates' => array( - 'attributes' => array('name', 'value'), - 'atomic' => array(self::OP_INSERT), - ), - ); + return [ + 'userCreates' => [ + 'attributes' => ['name', 'value'], + 'atomic' => [self::OP_INSERT], + ], + ]; } } @@ -584,17 +586,17 @@ class Product extends \yii\db\ActiveRecord public function getFeatures() { - return $this->hasMany('Feature', array('id' => 'product_id')); + return $this->hasMany(Feature::className(), ['id' => 'product_id']); } public function scenarios() { - return array( - 'userCreates' => array( - 'attributes' => array('title', 'price'), - 'atomic' => array(self::OP_INSERT), - ), - ); + return [ + 'userCreates' => [ + 'attributes' => ['title', 'price'], + 'atomic' => [self::OP_INSERT], + ], + ]; } public function afterValidate() diff --git a/docs/guide/application.md b/docs/guide/application.md deleted file mode 100644 index e69de29..0000000 --- a/docs/guide/application.md +++ /dev/null diff --git a/docs/guide/apps-advanced.md b/docs/guide/apps-advanced.md index 61e2489..890b1e9 100644 --- a/docs/guide/apps-advanced.md +++ b/docs/guide/apps-advanced.md @@ -121,7 +121,7 @@ Configuring Composer After application template is installed it's a good idea to adjust default `composer.json` that can be found in the root directory: -```javascript +```json { "name": "yiisoft/yii2-app-advanced", "description": "Yii 2 Advanced Application Template", @@ -138,17 +138,20 @@ directory: }, "minimum-stability": "dev", "require": { - "php": ">=5.3.0", + "php": ">=5.4.0", "yiisoft/yii2": "dev-master", - "yiisoft/yii2-composer": "dev-master" + "yiisoft/yii2-swiftmailer": "dev-master", + "yiisoft/yii2-bootstrap": "dev-master", + "yiisoft/yii2-debug": "dev-master", + "yiisoft/yii2-gii": "dev-master" }, "scripts": { "post-create-project-cmd": [ - "yii\\composer\\InstallHandler::setPermissions" + "yii\\composer\\Installer::setPermission" ] }, "extra": { - "yii-install-writable": [ + "writable": [ "backend/runtime", "backend/web/assets", @@ -160,15 +163,13 @@ directory: ] } } - ``` First we're updating basic information. Change `name`, `description`, `keywords`, `homepage` and `support` to match your project. Now the interesting part. You can add more packages your application needs to `require` section. -For example, to use markdown helper you need to add `michelf/php-markdown`. All these packages are coming from -[packagist.org](https://packagist.org/) so feel free to browse the website for useful code. +All these packages are coming from [packagist.org](https://packagist.org/) so feel free to browse the website for useful code. After your `composer.json` is changed you can run `php composer.phar update`, wait till packages are downloaded and installed and then just use them. Autoloading of classes will be handled automatically. diff --git a/docs/guide/apps-basic.md b/docs/guide/apps-basic.md index e61ab55..a6c5ed1 100644 --- a/docs/guide/apps-basic.md +++ b/docs/guide/apps-basic.md @@ -30,6 +30,9 @@ Directory structure The basic application does not divide application directories much. Here's the basic structure: +- `assets` - application asset files. + - `AppAsset.php` - definition of application assets such as CSS, JavaScript etc. Check [Managing assets](assets.md) for + details. - `commands` - console controllers. - `config` - configuration. - `controllers` - web controllers. @@ -56,14 +59,12 @@ code repository, add it there. This directory contains configuration files: -- `AppAsset.php` - definition of application assets such as CSS, JavaScript etc. Check [Managing assets](assets.md) for - details. - `console.php` - console application configuration. - `params.php` - common application parameters. - `web.php` - web application configuration. - `web-test.php` - web application configuration used when running functional tests. -All these files except `AppAsset.php` are returning arrays used to configure corresponding application properties. Check +All these files are returning arrays used to configure corresponding application properties. Check [Configuration](configuration.md) guide section for details. ### views @@ -111,7 +112,7 @@ Configuring Composer After application template is installed it's a good idea to adjust default `composer.json` that can be found in the root directory: -```javascript +```json { "name": "yiisoft/yii2-app-basic", "description": "Yii 2 Basic Application Template", @@ -128,21 +129,24 @@ directory: }, "minimum-stability": "dev", "require": { - "php": ">=5.3.0", + "php": ">=5.4.0", "yiisoft/yii2": "dev-master", - "yiisoft/yii2-composer": "dev-master" + "yiisoft/yii2-swiftmailer": "dev-master", + "yiisoft/yii2-bootstrap": "dev-master", + "yiisoft/yii2-debug": "dev-master", + "yiisoft/yii2-gii": "dev-master" }, "scripts": { "post-create-project-cmd": [ - "yii\\composer\\InstallHandler::setPermissions" + "yii\\composer\\Installer::setPermission" ] }, "extra": { - "yii-install-writable": [ + "writable": [ "runtime", "web/assets" ], - "yii-install-executable": [ + "executable": [ "yii" ] } @@ -153,8 +157,7 @@ First we're updating basic information. Change `name`, `description`, `keywords` your project. Now the interesting part. You can add more packages your application needs to `require` section. -For example, to use markdown helper you need to add `michelf/php-markdown`. All these packages are coming from -[packagist.org](https://packagist.org/) so feel free to browse the website for useful code. +All these packages are coming from [packagist.org](https://packagist.org/) so feel free to browse the website for useful code. After your `composer.json` is changed you can run `php composer.phar update`, wait till packages are downloaded and installed and then just use them. Autoloading of classes will be handled automatically. diff --git a/docs/guide/apps-own.md b/docs/guide/apps-own.md new file mode 100644 index 0000000..ebf7597 --- /dev/null +++ b/docs/guide/apps-own.md @@ -0,0 +1,4 @@ +Creating your own Application structure +======================================= + +TDB \ No newline at end of file diff --git a/docs/guide/assets.md b/docs/guide/assets.md new file mode 100644 index 0000000..44dc29f --- /dev/null +++ b/docs/guide/assets.md @@ -0,0 +1,117 @@ +Managing assets +=============== + +An asset in Yii is a file that is included into the page. It could be CSS, JavaScript or +any other file. Framework provides many ways to work with assets from basics such as adding `<script src="` tag +for a file that is [handled by View](view.md) section to advanced usage such as pusblishing files that are not +under webserve document root, resolving JavaScript dependencies or minifying CSS. + +Declaring asset bundle +---------------------- + +In order to publish some assets you should declare an asset bundle first. The bundle defines a set of asset files or +directories to be published and their dependencies on other asset bundles. + +Both basic and advanced application templates contain `AppAsset` asset bundle class that defines assets required +applicationwide. Let's review basic application asset bundle class: + +```php +class AppAsset extends AssetBundle +{ + public $basePath = '@webroot'; + public $baseUrl = '@web'; + public $css = [ + 'css/site.css', + ]; + public $js = [ + ]; + public $depends = [ + 'yii\web\YiiAsset', + 'yii\bootstrap\BootstrapAsset', + ]; +} +``` + +In the above `$basePath` specifies web-accessible directory assets are served from. It is a base for relative +`$css` and `$js` paths i.e. `@webroot/css/site.css` for `css/site.css`. Here `@webroot` is an alias that points to +application's `web` directory. + +`$baseUrl` is used to specify base URL for the same relative `$css` and `$js` i.e. `@web/css/site.css` where `@web` +is an alias that corresponds to your website base URL such as `http://example.com/`. + +In case you have asset files under non web accessible directory, that is the case for any extension, you need +to additionally specify `$sourcePath`. Files will be copied or symlinked from source bath to base path prior to being +registered. In case source path is used `baseUrl` is generated automatically at the time of publising asset bundle. + +Dependencies on other asset bundles are specified via `$depends` property. It is an array that contains fully qualified +names of bundle classes that should be published in order for this bundle to work properly. + +Here `yii\web\YiiAsset` adds Yii's JavaScript library while `yii\bootstrap\BootstrapAsset` includes +[Bootstrap](http://getbootstrap.com) frontend framework. + +Asset bundles are regular classes so if you need to define another one, just create alike class with unique name. This +class can be placed anywhere but the convention for it is to be under `assets` directory of the applicaiton. + +Registering asset bundle +------------------------ + +Asset bundle classes are typically registered in views or, if it's main application asset, in layout. Doing it is +as simple as: + +```php +use app\assets\AppAsset; +AppAsset::register($this); +``` + +Since we're in a view context `$this` refers to `View` class. + +Overriding asset bundles +------------------------ + +Sometimes you need to override some asset bundles application wide. A good example is loading jQuery from CDN instead +of your own server. In order to do it we need to configure `assetManager` application component via config file. In case +of basic application it is `config/web.php`: + +```php +return [ + // ... + 'components' => [ + 'assetManager' => [ + 'bundles' => [ + 'yii\web\JqueryAsset' => [ + 'sourcePath' => null, + 'js' => ['//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js'] + ], + ], + ], + ], +]; +``` + +In the above we're adding asset bundle definitions to `bunldes` property of asset manager. Keys there are fully +qualified class paths to asset bundle classes we want to override while values are key-value arrays of class properties +and corresponding values to set. + +Setting `sourcePath` to `null` tells asset manager not to copy anything while `js` overrides local files with a link +to CDN. + +Enabling symlinks +----------------- + +Asset manager is able to use symlinks instead of copying files. It is turned off by default since symlinks are often +disabled on shared hosting. If your hosting environment supports symlinks you certainly should enable the feature via +application config: + +```php +return [ + // ... + 'components' => [ + 'assetManager' => [ + 'linkAssets' => true, + ], + ], +]; +``` + +There are two main benefits in enabling it. First it is faster since no copying is required and second is that assets +will always be up to date with source files. \ No newline at end of file diff --git a/docs/guide/authentication.md b/docs/guide/authentication.md index e69de29..0b81f14 100644 --- a/docs/guide/authentication.md +++ b/docs/guide/authentication.md @@ -0,0 +1,68 @@ +Authentication +============== + +Authentication is the act of verifying who a user is, and is the basis of the login process. Typically, authentication uses an identifier--a username or email address--and password, submitted through a form. The application then compares this information against that previously stored. + +In Yii all this is done semi-automatically, leaving the developer to merely implement [[\yii\web\IdentityInterface]]. Typically, implementation is accomplished using the `User` model. You can find a full featured example in the +[advanced application template](installation.md). Below only the interface methods are listed: + +```php +class User extends ActiveRecord implements IdentityInterface +{ + // ... + + /** + * Finds an identity by the given ID. + * + * @param string|integer $id the ID to be looked for + * @return IdentityInterface|null the identity object that matches the given ID. + */ + public static function findIdentity($id) + { + return static::find($id); + } + + /** + * @return int|string current user ID + */ + public function getId() + { + return $this->id; + } + + /** + * @return string current user auth key + */ + public function getAuthKey() + { + return $this->auth_key; + } + + /** + * @param string $authKey + * @return boolean if auth key is valid for current user + */ + public function validateAuthKey($authKey) + { + return $this->getAuthKey() === $authKey; + } +} +``` + +Two of the outlined methods are simple: `findIdentity` is provided with an ID and returns a model instance represented by that ID. The `getId` method returns the ID itself. +Two of the other methods--`getAuthKey` and `validateAuthKey`--are used to provide extra security to the "remember me" cookie. `getAuthKey` should return a string that is unique for each user. A good idea is to save this value when the user is created by using Yii's `Security::generateRandomKey()`: + +```php +public function beforeSave($insert) +{ + if (parent::beforeSave($insert)) { + if ($this->isNewRecord) { + $this->auth_key = Security::generateRandomKey(); + } + return true; + } + return false; +} +``` + +The `validateAuthKey` method just compares the `$authKey` variable, passed as parameter (itself retrieved from a cookie), with the value fetched from database. diff --git a/docs/guide/authorization.md b/docs/guide/authorization.md index e69de29..b49f1af 100644 --- a/docs/guide/authorization.md +++ b/docs/guide/authorization.md @@ -0,0 +1,124 @@ +Authorization +============= + +Authorization is the process of verifying that user has enough permissions to do something. Yii provides several methods +of controlling it. + +Access control basics +--------------------- + +Basic access control is very simple to implement using [[\yii\web\AccessControl]]: + +```php +class SiteController extends Controller +{ + public function behaviors() + { + return [ + 'access' => [ + 'class' => \yii\web\AccessControl::className(), + 'only' => ['login', 'logout', 'signup'], + 'rules' => [ + [ + 'actions' => ['login', 'signup'], + 'allow' => true, + 'roles' => ['?'], + ], + [ + 'actions' => ['logout'], + 'allow' => true, + 'roles' => ['@'], + ], + ], + ], + ]; + } + // ... +``` + +In the code above we're attaching access control behavior to a controller. Since there's `only` option specified, it +will be applied to 'login', 'logout' and 'signup' actions only. A set of rules that are basically options for +[[\yii\web\AccessRule]] reads as follows: + +- Allow all guest (not yet authenticated) users to access 'login' and 'signup' actions. +- Allow authenticated users to access 'logout' action. + +Rules are checked one by one from top to bottom. If rule matches, action takes place immediately. If not, next rule is +checked. If no rules matched access is denied. + +[[\yii\web\AccessRule]] is quite flexible and allows additionally to what was demonstrated checking IPs and request method +(i.e. POST, GET). If it's not enough you can specify your own check via anonymous function: + +```php +class SiteController extends Controller +{ + public function behaviors() + { + return [ + 'access' => [ + 'class' => \yii\web\AccessControl::className(), + 'only' => ['special'], + 'rules' => [ + [ + 'actions' => ['special'], + 'allow' => true, + 'matchCallback' => function ($rule, $action) { + return date('d-m') === '31-10'; + } + ], +``` + +Sometimes you want a custom action to be taken when access is denied. In this case you can specify `denyCallback`. + +Role based access control (RBAC) +-------------------------------- + +Role based access control is very flexible approach to controlling access that is a perfect match for complex systems +where permissions are customizable. + +In order to start using it some extra steps are required. First of all we need to configure `authManager` application +component: + +```php + +``` + +Then create permissions hierarchy. + +Specify roles from RBAC in controller's access control configuration or call [[User::checkAccess()]] where appropriate. + +### How it works + +TBD: write about how it works with pictures :) + +### Avoiding too much RBAC + +In order to keep auth hierarchy simple and efficient you should avoid creating and using too much nodes. Most of the time +simple checks could be used instead. For example such code that uses RBAC: + +```php +public function editArticle($id) +{ + $article = Article::find($id); + if (!$article) { + throw new HttpException(404); + } + if (!\Yii::$app->user->checkAccess('edit_article', ['article' => $article])) { + throw new HttpException(403); + } + // ... +} +``` + +can be replaced with simpler code that doesn't use RBAC: + +```php +public function editArticle($id) +{ + $article = Article::find(['id' => $id, 'author_id' => \Yii::$app->user->id]); + if (!$article) { + throw new HttpException(404); + } + // ... +} +``` diff --git a/docs/guide/basics.md b/docs/guide/basics.md new file mode 100644 index 0000000..40edd92 --- /dev/null +++ b/docs/guide/basics.md @@ -0,0 +1,86 @@ +Basic concepts of Yii +===================== + + +Component and Object +-------------------- + +Classes of the Yii framework usually extend from one of the two base classes [[Object]] and [[Component]]. +These classes provide useful features that are added automatically to all classes extending from them. + +The `Object` class provides the [configuration and property feature](../api/base/Object.md). +The `Component` class extends from `Object` and adds [event handling](events.md) and [behaviors](behaviors.md). + +`Object` is usually used for classes that represent basic data structures while `Component` is used for +application components and other classes that implement higher logic. + + +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: + +```php +class MyClass extends \yii\base\Object +{ + public function __construct($param1, $param2, $config = []) + { + // ... 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: + +```php +$object = Yii::createObject([ + 'class' => 'MyClass', + 'property1' => 'abc', + 'property2' => 'cde', +], $param1, $param2); +``` + + +Path Aliases +------------ + +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 be defined for each root namespace so that you can use Yii the 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. + + +Autoloading +----------- + +TBD + +Helper classes +-------------- + +TDB \ No newline at end of file diff --git a/docs/guide/behaviors.md b/docs/guide/behaviors.md new file mode 100644 index 0000000..8a67270 --- /dev/null +++ b/docs/guide/behaviors.md @@ -0,0 +1,4 @@ +Behaviors +========= + +TDB \ No newline at end of file diff --git a/docs/guide/bootstrap-widgets.md b/docs/guide/bootstrap-widgets.md index 432dcd8..5c48fa0 100644 --- a/docs/guide/bootstrap-widgets.md +++ b/docs/guide/bootstrap-widgets.md @@ -17,11 +17,11 @@ convenient way to include bootstrap assets in your pages with a single line adde `config` directory: ```php -public $depends = array( +public $depends = [ 'yii\web\YiiAsset', 'yii\bootstrap\BootstrapAsset', // this line // 'yii\bootstrap\BootstrapThemeAsset' // uncomment to apply bootstrap 2 style to bootstrap 3 -); +]; ``` Using bootstrap through Yii asset manager allows you to minimize its resources and combine with your own resources when @@ -45,3 +45,22 @@ framework features. All widgets belong to `\yii\bootstrap` namespace: - NavBar - Progress - Tabs + + +Using the .less files of Bootstrap directly +------------------------------------------- + +If you want to include the [Bootstrap css directly in your less files](http://getbootstrap.com/getting-started/#customizing) +you may need to disable the original bootstrap css files to be loaded. +You can do this by setting the css property of the `BootstrapAsset` to be empty. +For this you need to configure the `assetManagner` application component as follows: + +```php + 'assetManager' => [ + 'bundles' => [ + 'yii\bootstrap\BootstrapAsset' => [ + 'css' => [], + ] + ] + ] +``` diff --git a/docs/guide/caching.md b/docs/guide/caching.md index 0624d78..1c379c5 100644 --- a/docs/guide/caching.md +++ b/docs/guide/caching.md @@ -13,23 +13,23 @@ two cache servers. Note, this configuration should be done in file located at `@ in case you're using basic sample application. ```php -'components' => array( - 'cache' => array( +'components' => [ + 'cache' => [ 'class' => '\yii\caching\MemCache', - 'servers' => array( - array( + 'servers' => [ + [ 'host' => 'server1', 'port' => 11211, 'weight' => 100, - ), - array( + ], + [ 'host' => 'server2', 'port' => 11211, 'weight' => 50, - ), - ), - ), -), + ], + ], + ], +], ``` When the application is running, the cache component can be accessed through `Yii::$app->cache` call. @@ -60,7 +60,8 @@ is a summary of the available cache components: the fastest one when dealing with cache in a distributed applications (e.g. with several servers, load balancers, etc.) -* [[\yii\caching\RedisCache]]: implements a cache component based on [Redis](http://redis.io/) NoSQL database. +* [[\yii\caching\RedisCache]]: implements a cache component based on [Redis](http://redis.io/) key-value store + (redis version 2.6 or higher is required). * [[\yii\caching\WinCache]]: uses PHP [WinCache](http://iis.net/downloads/microsoft/wincache-extension) ([see also](http://php.net/manual/en/book.wincache.php)) extension. diff --git a/docs/guide/composer.md b/docs/guide/composer.md new file mode 100644 index 0000000..89eac35 --- /dev/null +++ b/docs/guide/composer.md @@ -0,0 +1,81 @@ +Composer +======== + +Yii2 uses Composer as its package manager. Composer is a PHP utility that can automatically handle the installation of needed libraries and +extensions, thereby keeping those third-party resources up to date while absolving you of the need to manually manage the project's dependencies. + +Installing Composer +------------------- + +In order to install Composer, check the official guide for your operating system: + +* [Linux](http://getcomposer.org/doc/00-intro.md#installation-nix) +* [Windows](http://getcomposer.org/doc/00-intro.md#installation-windows) + +All of the details can be found in the guide, but you'll either download Composer directly from [http://getcomposer.org/](http://getcomposer.org/), or run the following command: + +``` +curl -s http://getcomposer.org/installer | php +``` + +Adding more packages to your project +------------------------------------ + +The act of [installing a Yii application](installation.md) creates the `composer.json` file in the root directory of your project. +In this file you list the packages that your application requires. For Yii sites, the most important part of the file is the `require` section: + +``` +{ + "require": { + "Michelf/php-markdown": ">=1.3", + "ezyang/htmlpurifier": ">=4.5.0" + } +} +``` + +Within the `require` section, you specify the name and version of each required package. +The above example says that a version greater than or equal to 1.3 of Michaelf's PHP-Markdown package is required, +as is version 4.5 or greater of Ezyang's HTMLPurifier. +For details of this syntax, see the [official Composer documentation](http://getcomposer.org). + +The full list of available Composer-supported PHP packages can be found at [packagist](http://packagist.org/). + +Once you have edited the `composer.json`, you can invoke Composer to install the identified dependencies. +For the first installation of the dependencies, use this command: + +``` +php composer.phar install +``` + +This must be executed within your Yii project's directory, where the `composer.json` file can be found. +Depending upon your operating system and setup, you may need to provide paths to the PHP executable and +to the `composer.phar` script. + +For an existing installation, you can have Composer update the dependencies using: + +``` +php composer.phar update +``` + +Again, you may need to provide specific path references. + +In both cases, after some waiting, the required packages will be installed and ready to use in your Yii application. +No additional configuration of those packages will be required. + + +FAQ +--- + +### Getting "You must enable the openssl extension to download files via https" + +If you're using WAMP check [this answer at StackOverflow](http://stackoverflow.com/a/14265815/1106908). + +### Getting "Failed to clone <URL here>, git was not found, check that it is installed and in your Path env." + +Either install git or try adding `--prefer-dist` to the end of `install` or `update` command. + + +See also +-------- + +- [Official Composer documentation](http://getcomposer.org). \ No newline at end of file diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index f9b6285..5f1d8e0 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -1,10 +1,12 @@ Configuration ============= -In Yii application and majority of components have sensible defaults so it's unlikely you spend lots of time configuring -it. Still there are some mandatory options such as database connection you should set up. +Yii applications rely upon components to perform most of the common tasks, such as connecting to a database, routing browser +requests, and handling sessions. How these stock components behave can be adjusted by *configuring* your Yii application. +The majority of components have sensible defaults, so it's unlikely that you'll spend a lot of time configuring +them. Still there are some mandatory settings, such as the database connection, that you will have to establish. -How application is configured depends on application template but there are some general principles applying in any case. +How an application is configured depends on application template but there are some general principles applying in any case. Configuring options in bootstrap file ------------------------------------- @@ -14,37 +16,38 @@ console applications it's `yii`. Both are doing nearly the same job: 1. Setting common constants. 2. Including Yii itself. -3. Including Composer autoloader. +3. Including [Composer autoloader](http://getcomposer.org/doc/01-basic-usage.md#autoloading). 4. Reading config file into `$config`. 5. Creating new application instance using `$config` and running it. -Bootstrap file is not the part of framework but your application so it's OK to adjust it to fit your application. Typical +The Bootstrap file is not the part of framework but your application so it's OK to adjust it to fit your application. Typical adjustments are the value of `YII_DEBUG` that should never be `true` on production and the way config is read. Configuring application instance -------------------------------- It was mentioned above that application is configured in bootstrap file when its instance is created. Config is typically -stored in a PHP file in `/config` directory of the application and looks like the following: +stored in a PHP file in the `/config` directory of the application and looks like the following: ```php <?php -return array( +return [ 'id' => 'applicationId', 'basePath' => dirname(__DIR__), - 'components' => array( - // ... - ), + 'components' => [ + // configuration of application components goes here... + ], 'params' => require(__DIR__ . '/params.php'), -); +]; ``` In the above array keys are names of application properties. Depending on application type you can check properties of -either `\yii\web\Application` or `\yii\console\Application`. Both are extended from `\yii\base\Application`. +either [[yii\web\Application]] or [[yii\console\Application]]. Both are extended from [[yii\base\Application]]. > Note that you can configure not only public class properties but anything accessible via setter. For example, to configure runtime path you can use key named `runtimePath`. There's no such property in the application class but since there's a corresponding setter named `setRuntimePath` it will be properly configured. + This feature is added to any class that extends from [[yii\base\Object]] which is nearly any class of the Yii framework. Configuring application components ---------------------------------- @@ -53,40 +56,34 @@ Majority of Yii functionality are application components. These are attached to ```php <?php -return array( +return [ 'id' => 'applicationId', 'basePath' => dirname(__DIR__), - 'components' => array( - 'cache' => array( - 'class' => 'yii\caching\FileCache', - ), - 'user' => array( - 'identityClass' => 'app\models\User', - ), - 'errorHandler' => array( - 'errorAction' => 'site/error', - ), - 'log' => array( + 'components' => [ + 'cache' => ['class' => 'yii\caching\FileCache'], + 'user' => ['identityClass' => 'app\models\User'], + 'errorHandler' => ['errorAction' => 'site/error'], + 'log' => [ 'traceLevel' => YII_DEBUG ? 3 : 0, - 'targets' => array( - array( + 'targets' => [ + [ 'class' => 'yii\log\FileTarget', - 'levels' => array('error', 'warning'), - ), - ), - ), - ), + 'levels' => ['error', 'warning'], + ], + ], + ], + ], // ... -); +]; ``` In the above four components are configured: `cache`, `user`, `errorHandler`, `log`. Each entry key is a component ID and the value is the configuration array. ID is used to access the component like `\Yii::$app->myComponent`. -Configuration array has one special key named `class` that sets component class. The rest of the keys and values are used +Configuration array has one special key named `class` that sets the component class. The rest of the keys and values are used to configure component properties in the same way as top-level keys are used to configure application properties. -Each application has predefined set of the components. In case of configuring one of these `class` key is omitted and -application default class is used instead. You can check `registerCoreComponents` method of the application you are using +Each application has a predefined set of components. In case of configuring one of these, the `class` key is omitted and +application default class is used instead. You can check `registerCoreComponents()` method of the application you are using to get a list of component IDs and corresponding classes. Note that Yii is smart enough to configure the component when it's actually used i.e. if `cache` is never used it will diff --git a/docs/guide/console.md b/docs/guide/console.md index e69de29..055c206 100644 --- a/docs/guide/console.md +++ b/docs/guide/console.md @@ -0,0 +1,4 @@ +Building console applications +============================= + +TDB \ No newline at end of file diff --git a/docs/guide/controller.md b/docs/guide/controller.md index 2874c66..a668ba7 100644 --- a/docs/guide/controller.md +++ b/docs/guide/controller.md @@ -35,6 +35,9 @@ class SiteController extends Controller ``` As you can see, typical controller contains actions that are public class methods named as `actionSomething`. +The output of an action is what the method returns. The return value will be handled by the `response` application +component which can convert the output to differnet formats such as JSON for example. The default behavior +is to output the value unchanged though. Routes ------ @@ -86,14 +89,14 @@ class BlogController extends Controller $post = Post::find($id); $text = $post->text; - if($version) { + if ($version) { $text = $post->getHistory($version); } - return $this->render('view', array( + return $this->render('view', [ 'post' => $post, 'text' => $text, - )); + ]); } } ``` @@ -118,20 +121,18 @@ class BlogController extends Controller public function actionUpdate($id) { $post = Post::find($id); - if(!$post) { + if (!$post) { throw new HttpException(404); } - if(\Yii::$app->request->isPost)) { + if (\Yii::$app->request->isPost)) { $post->load($_POST); - if($post->save()) { - $this->redirect(array('view', 'id' => $post->id)); + if ($post->save()) { + $this->redirect(['view', 'id' => $post->id]); } } - return $this->render('update', array( - 'post' => $post, - )); + return $this->render('update', ['post' => $post]); } } ``` @@ -164,25 +165,47 @@ public SiteController extends \yii\web\Controller { public function actions() { - return array( - 'about' => array( + return [ + 'about' => [ 'class' => '@app/actions/Page', - 'view' => 'about', - ), - ), - ); + 'view' => 'about', + ], + ]; } } ``` After doing so you can access your action as `http://example.com/?r=site/about`. -Filters -------- + +Action Filters +-------------- + +Action filters are implemented via behaviors. 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: + +```php +public function behaviors() +{ + return [ + 'access' => [ + 'class' => 'yii\web\AccessControl', + 'rules' => [ + ['allow' => true, 'actions' => ['admin'], 'roles' => ['@']], + ), + ), + ); +} +``` + +more TDB Catching all incoming requests ------------------------------ +TDB See also -------- diff --git a/docs/guide/database-basics.md b/docs/guide/database-basics.md index 85bc042..511ecaf 100644 --- a/docs/guide/database-basics.md +++ b/docs/guide/database-basics.md @@ -5,6 +5,7 @@ Yii has a database access layer built on top of PHP's [PDO](http://www.php.net/m uniform API and solves some inconsistencies between different DBMS. By default Yii supports the following DBMS: - [MySQL](http://www.mysql.com/) +- [MariaDB](https://mariadb.com/) - [SQLite](http://sqlite.org/) - [PostgreSQL](http://www.postgresql.org/) - [CUBRID](http://www.cubrid.org/) (version 9.1.0 and higher). @@ -19,11 +20,11 @@ In order to start using database you need to configure database connection compo to application configuration (for "basic" web application it's `config/web.php`) like the following: ```php -return array( +return [ // ... - 'components' => array( + 'components' => [ // ... - 'db' => array( + 'db' => [ 'class' => 'yii\db\Connection', 'dsn' => 'mysql:host=localhost;dbname=mydatabase', // MySQL, MariaDB //'dsn' => 'sqlite:/path/to/database/file', // SQLite @@ -36,10 +37,10 @@ return array( 'username' => 'root', 'password' => '', 'charset' => 'utf8', - ), - ), + ], + ], // ... -); +]; ``` Please refer to the [PHP manual](http://www.php.net/manual/en/function.PDO-construct.php) for more details on the format of the DSN string. @@ -61,11 +62,11 @@ $secondaryConnection = \Yii::$app->secondDb; If you don't want to define the connection as an application component you can instantiate it directly: ```php -$connection = new \yii\db\Connection(array( +$connection = new \yii\db\Connection([ 'dsn' => $dsn, 'username' => $username, 'password' => $password, -)); +]); $connection->open(); ``` @@ -118,22 +119,20 @@ Alternatively the following syntax that takes care of proper table and column na ```php // INSERT -$connection->createCommand()->insert('tbl_user', array( +$connection->createCommand()->insert('tbl_user', [ 'name' => 'Sam', 'age' => 30, -))->execute(); +])->execute(); // INSERT multiple rows at once -$connection->createCommand()->batchInsert('tbl_user', array('name', 'age'), array( - array('Tom', 30), - array('Jane', 20), - array('Linda', 25), -))->execute(); +$connection->createCommand()->batchInsert('tbl_user', ['name', 'age'], [ + ['Tom', 30], + ['Jane', 20], + ['Linda', 25], +])->execute(); // UPDATE -$connection->createCommand()->update('tbl_user', array( - 'status' => 1, -), 'age > 30')->execute(); +$connection->createCommand()->update('tbl_user', ['status' => 1], 'age > 30')->execute(); // DELETE $connection->createCommand()->delete('tbl_user', 'status = 0')->execute(); @@ -236,11 +235,11 @@ These can be used as follows: ```php // CREATE TABLE -$connection->createCommand()->createTable('tbl_post', array( +$connection->createCommand()->createTable('tbl_post', [ 'id' => 'pk', 'title' => 'string', 'text' => 'text', -); +]; ``` For the full reference check [[\yii\db\Command]]. diff --git a/docs/guide/debugger.md b/docs/guide/debugger.md index 768f25d..9006d9d 100644 --- a/docs/guide/debugger.md +++ b/docs/guide/debugger.md @@ -10,6 +10,17 @@ Installing and configuring How to use it ------------- +Add these lines to your config file: + +``` + 'preload' => ['debug'], + 'modules' => [ + 'debug' => ['yii\debug\Module'] + ] +``` + +**Watch out: by default the debug module only works when browsing the website from the localhost. If you want to use it on a remote (staging) server, add the parameter allowedIPs to the config to whitelist your IP.** + Creating your own panels ------------------------ diff --git a/docs/guide/error.md b/docs/guide/error.md index 20bb23d..f5aa615 100644 --- a/docs/guide/error.md +++ b/docs/guide/error.md @@ -5,3 +5,52 @@ Error handling in Yii is different from plain PHP. First of all, all non-fatal e you can use `try`-`catch` to work with these. Second, even fatal errors are rendered in a nice way. In debug mode that means you have a trace and a piece of code where it happened so it takes less time to analyze and fix it. +Using controller action to render errors +---------------------------------------- + +Default Yii error page is great for development mode and is OK for production if `YII_DEBUG` is turned off but you may +have an idea how to make it more suitable for your project. An easiest way to customize it is to use controller action +for error rendering. In order to do so you need to configure `errorHandler` component via application config: + +```php +return [ + // ... + 'components' => [ + // ... + 'errorHandler' => [ + 'errorAction' => 'site/error', + ], +``` + +After it is done in case of error Yii will launch `SiteController::actionError()`. Since errors are converted to +exceptions we can get exception from error handler: + +```php +public function actionError() +{ + $exception = \Yii::$app->getErrorHandler()->exception; + $this->render('myerror', ['message' => $exception->getMessage()]); +} +``` + +Since most of the time you need to adjust look and feel only, Yii provides `ErrorAction` class that can be used in +controller instead of implementing action yourself: + +```php +public function actions() +{ + return [ + 'error' => [ + 'class' => 'yii\web\ErrorAction', + ], + ]; +} +``` + +After defining `actions` in `SiteController` as shown above you can create `views/site/error.php`. In the view there +are three varialbes available: + +- `$name`: the error name +- `$message`: the error message +- `$exception`: the exception being handled + diff --git a/docs/guide/events.md b/docs/guide/events.md new file mode 100644 index 0000000..23931f1 --- /dev/null +++ b/docs/guide/events.md @@ -0,0 +1,43 @@ +Events +====== + +TBD, see also [Component.md](../api/base/Component.md). + +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: + +```php +$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: + +```php +$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: + +```php +Yii::$app->on($eventName, $handler); +.... +// this will trigger the event and cause $handler to be invoked. +Yii::$app->trigger($eventName); +``` + +If you need to handle all instances of a class instead of the object you can attach a handler like the following: + +```php +Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function ($event) { + Yii::trace(get_class($event->sender) . ' is inserted.'); +}); +``` + +The code above defines a handler that will be triggered for every Active Record object's `EVENT_AFTER_INSERT` event. diff --git a/docs/guide/extension.md b/docs/guide/extension.md deleted file mode 100644 index e69de29..0000000 --- a/docs/guide/extension.md +++ /dev/null diff --git a/docs/guide/extensions.md b/docs/guide/extensions.md new file mode 100644 index 0000000..2fcea72 --- /dev/null +++ b/docs/guide/extensions.md @@ -0,0 +1,4 @@ +Extending Yii +============= + +TDB \ No newline at end of file diff --git a/docs/guide/form.md b/docs/guide/form.md index c1f1ba3..d31d653 100644 --- a/docs/guide/form.md +++ b/docs/guide/form.md @@ -1,3 +1,90 @@ Working with forms ================== +The primary way of using forms in Yii is through [[yii\widgets\ActiveForm]]. This approach should be preferred when the form is based upon a model. Additionally, there are some useful methods in [[\yii\helpers\Html]] that are typically used for adding buttons and help text to any form. + +When creating model-based forms, the first step is to define the model itself. The model can be either based upon the Active Record class, or the more generic Model class. For this login example, a generic model will be used: + +```php +use yii\base\Model; + +class LoginForm extends Model +{ + public $username; + public $password; + + /** + * @return array the validation rules. + */ + public function rules() + { + return [ + // username and password are both required + ['username, password', 'required'], + // password is validated by validatePassword() + ['password', 'validatePassword'], + ]; + } + + /** + * Validates the password. + * This method serves as the inline validation for password. + */ + public function validatePassword() + { + $user = User::findByUsername($this->username); + if (!$user || !$user->validatePassword($this->password)) { + $this->addError('password', 'Incorrect username or password.'); + } + } + + /** + * 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); + return true; + } else { + return false; + } + } +} +``` + +The controller will pass an instance of that model to the view, wherein the Active Form widget is used: + +```php +use yii\helpers\Html; +use yii\widgets\ActiveForm; + +<?php $form = ActiveForm::begin([ + 'id' => 'login-form', + 'options' => ['class' => 'form-horizontal'], +]) ?> + <?= $form->field($model, 'username') ?> + <?= $form->field($model, 'password')->passwordInput() ?> + + <div class="form-group"> + <div class="col-lg-offset-1 col-lg-11"> + <?= Html::submitButton('Login', ['class' => 'btn btn-primary']) ?> + </div> + </div> +<?php ActiveForm::end() ?> +``` + +In the above code, `ActiveForm::begin()` not only creates a form instance, but also marks the beginning of the form. All of the content +placed between `ActiveForm::begin()` and `ActiveForm::end()` will be wrapped within the `<form>` tag. As with any widget, you can specify some options as to how the widget should be configured by passing an array to the `begin` method. In this case, an extra CSS class and identifying ID are passed to be used in the opening `<form>` tag. + +In order to create a form element in the form, along with the element's label, and any application JavaScript validation, the `field` method of the Active Form widget is called. When the invocation of this method is echoed directly, the result is a regular (text) input. To +customize the output, you can chain additional methods to this call: + +```php +<?= $form->field($model, 'password')->passwordInput() ?> + +// or + +<?= $form->field($model, 'username')->textInput()->hint('Please enter your name')->label('Name') ?> +``` diff --git a/docs/guide/gii.md b/docs/guide/gii.md index e69de29..54f6a36 100644 --- a/docs/guide/gii.md +++ b/docs/guide/gii.md @@ -0,0 +1,25 @@ +The Gii code generation tool +============================ + +Yii2 includes a handy tool that allows rapid prototyping by generating commonly used code snippets +as well as complete CRUD controllers. + +Installing and configuring +-------------------------- + +How to use it +------------- + +Add these lines to your config file: + +```php + 'modules' => [ + 'gii' => ['yii\gii\Module'] + ] +``` + +Creating your own templates +--------------------------- + +TDB + diff --git a/docs/guide/i18n.md b/docs/guide/i18n.md index e69de29..477de0a 100644 --- a/docs/guide/i18n.md +++ b/docs/guide/i18n.md @@ -0,0 +1,266 @@ +Internationalization +==================== + +Internationalization (I18N) refers to the process of designing a software application so that it can be adapted to +various languages and regions without engineering changes. For Web applications, this is of particular importance +because the potential users may be worldwide. + +Locale and Language +------------------- + +There are two languages defined in Yii application: [[\yii\base\Application::$sourceLanguage|source language]] and +[[\yii\base\Application::$language|target language]]. + +Source language is the language original application messages are written in such as: + +```php +echo \Yii::t('app', 'I am a message!'); +``` + +> **Tip**: Default is English and it's not recommended to change it. The reason is that it's easier to find people translating from +> English to any language than from non-English to non-English. + +Target language is what's currently used. It's defined in application configuration like the following: + +```php +// ... +return [ + 'id' => 'applicationID', + 'basePath' => dirname(__DIR__), + 'language' => 'ru_RU' // ← here! +``` + +Later you can easily change it in runtime: + +```php +\Yii::$app->language = 'zh_CN'; +``` + +Basic message translation +------------------------- + +Yii basic message translation in its basic variant works without additional PHP extension. What it does is finding a +translation of the message from source language into target language. Message itself is specified as the second +`\Yii::t` method parameter: + +```php +echo \Yii::t('app', 'This is a string to translate!'); +``` + +Yii tries to load approprite translation from one of the message sources defined via `i18n` component configuration: + +```php +'components' => [ + // ... + 'i18n' => [ + 'translations' => [ + 'app*' => [ + 'class' => 'yii\i18n\PhpMessageSource', + //'basePath' => '@app/messages', + //'sourceLanguage' => 'en-US', + 'fileMap' => [ + 'app' => 'app.php', + 'app/error' => 'error.php', + ], + ], + ], + ], +], +``` + +In the above `app*` is a pattern that specifies which categories are handled by the message source. In this case we're +handling everything that begins with `app`. + +`class` defines which message source is used. The following message sources are available: + +- PhpMessageSource that uses PHP files. +- GettextMessageSource that uses GNU Gettext MO or PO files. +- DbMessageSource that uses database. + +`basePath` defines where to store messages for the currently used message source. In this case it's `messages` directory + in your application directory. In case of using database this option should be skipped. + +`sourceLanguage` defines which language is used in `\Yii::t` second argument. If not specified, application's source +language is used. + +`fileMap` specifies how message categories specified in the first argument of `\Yii::t()` are mapped to files when +`PhpMessageSource` is used. In the example we're defining two categories `app` and `app/error`. + +Instead of configuring `fileMap` you can rely on convention which is `messages/BasePath/LanguageID/CategoryName.php`. + +### Named placeholders + +You can add parameters to a translation message that will be substituted with the corresponding value after translation. +The format for this is to use curly brackets around the parameter name as you can see in the following example: + +```php +$username = 'Alexander'; +echo \Yii::t('app', 'Hello, {username}!', [ + 'username' => $username, +]); +``` + +Note that the parameter assignment is without the brackets. + +### Positional placeholders + +```php +$sum = 42; +echo \Yii::t('app', 'Balance: {0}', $sum); +``` + +> **Tip**: Try keep message strings meaningful and avoid using too many positional parameters. Remember that +> translator has source string only so it should be obvious about what will replace each placeholder. + +Advanced placeholder formatting +------------------------------- + +In order to use advanced features you need to install and enable [intl](http://www.php.net/manual/en/intro.intl.php) PHP +extension. After installing and enabling it you will be able to use extended syntax for placeholders. Either short form +`{placeholderName, argumentType}` that means default setting or full form `{placeholderName, argumentType, argumentStyle}` +that allows you to specify formatting style. + +Full reference is [available at ICU website](http://icu-project.org/apiref/icu4c/classMessageFormat.html) but since it's +a bit cryptic we have our own reference below. + +### Numbers + +```php +$sum = 42; +echo \Yii::t('app', 'Balance: {0, number}', $sum); +``` + +You can specify one of the built-in styles (`integer`, `currency`, `percent`): + +```php +$sum = 42; +echo \Yii::t('app', 'Balance: {0, number, currency}', $sum); +``` + +Or specify custom pattern: + +```php +$sum = 42; +echo \Yii::t('app', 'Balance: {0, number, ,000,000000}', $sum); +``` + +[Formatting reference](http://icu-project.org/apiref/icu4c/classicu_1_1DecimalFormat.html). + +### Dates + +```php +echo \Yii::t('app', 'Today is {0, date}', time()); +``` + +Built in formats (`short`, `medium`, `long`, `full`): + +```php +echo \Yii::t('app', 'Today is {0, date, short}', time()); +``` + +Custom pattern: + +```php +echo \Yii::t('app', 'Today is {0, date, YYYY-MM-dd}', time()); +``` + +[Formatting reference](http://icu-project.org/apiref/icu4c/classicu_1_1SimpleDateFormat.html). + +### Time + +```php +echo \Yii::t('app', 'It is {0, time}', time()); +``` + +Built in formats (`short`, `medium`, `long`, `full`): + +```php +echo \Yii::t('app', 'It is {0, time, short}', time()); +``` + +Custom pattern: + +```php +echo \Yii::t('app', 'It is {0, date, HH:mm}', time()); +``` + +[Formatting reference](http://icu-project.org/apiref/icu4c/classicu_1_1SimpleDateFormat.html). + + +### Spellout + +```php +echo \Yii::t('app', '{n,number} is spelled as {n, spellout}', ['n' => 42]); +``` + +### Ordinal + +```php +echo \Yii::t('app', 'You are {n, ordinal} visitor here!', ['n' => 42]); +``` + +Will produce "You are 42nd visitor here!". + +### Duration + + +```php +echo \Yii::t('app', 'You are here for {n, duration} already!', ['n' => 47]); +``` + +Will produce "You are here for 47 sec. already!". + +### Plurals + +Different languages have different ways to inflect plurals. Some rules are very complex so it's very handy that this +functionality is provided without the need to specify inflection rule. Instead it only requires your input of inflected +word in certain situations. + +```php +echo \Yii::t('app', 'There {n, plural, =0{are no cats} =1{is one cat} other{are # cats}}!', ['n' => 0]); +``` + +Will give us "There are no cats!". + +In the plural rule arguments above `=0` means exactly zero, `=1` stands for exactly one `other` is for any other number. +`#` is replaced with the `n` argument value. It's not that simple for languages other than English. Here's an example +for Russian: + +``` +Здесь {n, plural, =0{котов нет} =1{есть один кот} one{# кот} few{# кота} many{# котов} other{# кота}}! +``` + +In the above it worth mentioning that `=1` matches exactly `n = 1` while `one` matches `21` or `101`. + +Note that if you are using placeholder twice and one time it's used as plural another one should be used as number else +you'll get "Inconsistent types declared for an argument: U_ARGUMENT_TYPE_MISMATCH" error: + +``` +Total {count, number} {count, plural, one{item} other{items}}. +``` + +To learn which inflection forms you should specify for your language you can referer to +[rules reference at unicode.org](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html). + +### Selections + +You can select phrases based on keywords. The pattern in this case specifies how to map keywords to phrases and +provides a default phrase. + +```php +echo \Yii::t('app', '{name} is {gender} and {gender, select, female{she} male{he} other{it}} loves Yii!', [ + 'name' => 'Snoopy', + 'gender' => 'dog', +]); +``` + +Will produce "Snoopy is dog and it loves Yii!". + +In the expression `female` and `male` are possible values. `other` handler values that do not match. Strings inside +brackets are sub-expressions so could be just a string or a string with more placeholders. + +Formatters +---------- + +In order to use formatters you need to install and enable [intl](http://www.php.net/manual/en/intro.intl.php) PHP +extension. diff --git a/docs/guide/images/flow.png b/docs/guide/images/flow.png new file mode 100644 index 0000000..d868bae Binary files /dev/null and b/docs/guide/images/flow.png differ diff --git a/docs/guide/images/flow.vsd b/docs/guide/images/flow.vsd new file mode 100644 index 0000000..1d922fc Binary files /dev/null and b/docs/guide/images/flow.vsd differ diff --git a/docs/guide/images/structure.png b/docs/guide/images/structure.png new file mode 100644 index 0000000..c61958f Binary files /dev/null and b/docs/guide/images/structure.png differ diff --git a/docs/guide/images/structure.vsd b/docs/guide/images/structure.vsd new file mode 100644 index 0000000..e683556 Binary files /dev/null and b/docs/guide/images/structure.vsd differ diff --git a/docs/guide/index.md b/docs/guide/index.md index 601323a..91c4210 100644 --- a/docs/guide/index.md +++ b/docs/guide/index.md @@ -1,50 +1,61 @@ Introduction ============ -- [Overview](overview.md) +- [Overview](overview.md) - What is Yii and what is it good for? Getting started =============== -- [Installation](installation.md) -- [Configuration](configuration.md) +- [Upgrading from 1.1 to 2.0](upgrade-from-v1.md) +- [Installation](installation.md) - How to download Yii and configure the Webserver? +- [Configuration](configuration.md) - Configuration of a Yii application -Application templates -===================== +- [Basic Application Template](apps-basic.md) - A template to start a basic frontend application. +- [Advanced Application Template](apps-advanced.md) - The basis for more advanced applications. -- [Basic](apps-basic.md) -- [Advanced](apps-advanced.md) -- [Creating your own application template](apps-own.md) +- [Creating your own Application structure](apps-own.md) - Learn how to start from scratch. Base concepts ============= -- [MVC Overview](mvc.md) -- [Model](model.md) -- [View](view.md) -- [Controller](controller.md) -- [Application](application.md) +- [Basic concepts of Yii](basics.md) - The Object and Component class, Path aliases and autoloading +- [MVC](mvc.md) - Implementation of MVC in Yii and a typical MVC application flow + - [Model](model.md) - The Yii Model provides Attributes, Scenarios and data Validation + - [View](view.md) - Rendering Views applying layouts, using Widgets and asset management + - [Controller](controller.md) - controller actions, routing and action filters +- [Event Handling](events.md) - The Yii event handling mechanism +- [Behaviors](behaviors.md) Database ======== -- [Basics](database-basics.md) -- [Query Builder](query-builder.md) -- [ActiveRecord](active-record.md) -- [Database Migration](migration.md) +- [Basics](database-basics.md) - Connecting to a database, basic queries, transactions and schema manipulation +- [Query Builder](query-builder.md) - Querying the database using a simple abstraction layer +- [ActiveRecord](active-record.md) - The active record ORM, retrieving and manipulatings records and defining relations +- [Database Migration](migration.md) - Versioning your database with database migrations -Extensions -========== +Developers Toolbox +================== -- [Extending Yii](extension.md) -- [Using template engines](template.md) +- [Automatic Code Generation](gii.md) +- [Debug toolbar and debugger](debugger.md) +- [Error Handling](error.md) +- [Logging](logging.md) + +Extensions and 3rd party libraries +================================== + +- [Composer](composer.md) - How to manage applications dependencies via composer +- [Extending Yii](extensions.md) +- [Template engines](template.md) - Using template engines such as Smary or Twig Security and access control =========================== -- [Authentication](authentication.md) -- [Authorization](authorization.md) -- [Security](security.md) +- [Authentication](authentication.md) - Identifying Users +- [Authorization](authorization.md) - Access control and RBAC +- [Security](security.md) - Hashing and verifying passwords, encryption +- [Views security](view.md#security) - how to prevent XSS - Role based access control Data providers, lists and grids @@ -55,26 +66,22 @@ Data providers, lists and grids - Grids - Lists -Toolbox -======= - -- [Automatic Code Generation](gii.md) -- [Debug toolbar and debugger](debugger.md) -- [Error Handling](error.md) -- [Logging](logging.md) - -More -==== +Advanced Topics +=============== -- [Bootstrap widgets](bootstrap-widgets.md) +- [Asset Management](assets.md) - [Working with forms](form.md) -- [Model validation reference](validation.md) -- [Caching](caching.md) -- [Internationalization](i18n.md) -- [URL Management](url.md) +- [Bootstrap widgets](bootstrap-widgets.md) - Using [twitter bootstrap](http://getbootstrap.com/) - [Theming](theming.md) +- [Caching](caching.md) - Caching data, page fragments and http requests +- [Internationalization](i18n.md) - Message translation and formatting +- [URL Management](url.md) - routing, customized urls and SEO - [Console Application](console.md) - [Performance Tuning](performance.md) -- [Managing assets](assets.md) - [Testing](testing.md) -- [Upgrading from 1.1 to 2.0](upgrade-from-v1.md) + +References +========== + +- [Model validation reference](validation.md) +- [Official Composer documentation](http://getcomposer.org) \ No newline at end of file diff --git a/docs/guide/installation.md b/docs/guide/installation.md index 16a496c..1575163 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -1,75 +1,110 @@ Installation ============ +There are two ways you can install the Yii framework: + +* Installation via [Composer](http://getcomposer.org/) (recommended) +* Download an application template packed with all requirements including the Yii Framework + + Installing via Composer ----------------------- -The recommended way of installing Yii is by using [Composer](http://getcomposer.org/) package manager. If you do not -have it, you may download it from [http://getcomposer.org/](http://getcomposer.org/) or run the following command: +The recommended way to install Yii is to use the [Composer](http://getcomposer.org/) package manager. If you do not already +have Composer installed, you may download it from [http://getcomposer.org/](http://getcomposer.org/) or run the following command: ``` curl -s http://getcomposer.org/installer | php ``` -Yii provides a few ready-to-use application templates. Based on your needs, you may choose one of them to bootstrap -your project. +For problems or more information, see the official Composer guide: + +* [Linux](http://getcomposer.org/doc/00-intro.md#installation-nix) +* [Windows](http://getcomposer.org/doc/00-intro.md#installation-windows) -There are two application templates available: +With Composer installed, you can create a new Yii site using one of Yii's ready-to-use application templates. +Based on your needs, choosing the right template can help bootstrap your project. -- [basic](https://github.com/yiisoft/yii2-app-basic) that is just a basic frontend application template. -- [advanced](https://github.com/yiisoft/yii2-app-advanced) that is a set of frontend, backend, console, common - (shared code) and environments support. +Currently, there are two application templates available: + +- The [Basic Application Template](https://github.com/yiisoft/yii2-app-basic) - just a basic frontend application template. +- The [Advanced Application Template](https://github.com/yiisoft/yii2-app-advanced) - consisting of a frontend, a backend, + console resources, common (shared code), and support for environments. + +For installation instructions for these templates, see the above linked pages. +To read more about the ideas behind these application templates and proposed usage, +refer to the [basic application template](apps-basic.md) and [advanced application template](apps-advanced.md) documents. + +If you do not want to use a template and want to start from scratch you'll find information in the document about +[creating your own application structure](apps-own.md). This is only recommended for advanced users. -Please refer to installation instructions on these pages. To read more about ideas behing these application templates and -proposed usage refer to [basic application template](apps-basic.md) and [advanced application template](apps-advanced.md). Installing from zip ------------------- -Installation from zip mainly involves the following two steps: +Installation from a zip file involves two steps: + + 1. Downloading an application template from [yiiframework.com](http://www.yiiframework.com/download/). + 2. Unpacking the downloaded file. - 1. Download Yii Framework from [yiiframework.com](http://www.yiiframework.com/). - 2. Unpack the Yii release file to a Web-accessible directory. +If you only want the Yii Framework files you can download a ZIP file directly from [github](https://github.com/yiisoft/yii2-framework/releases). +To create your application you might want to follow the steps described in [creating your own application structure](apps-own.md). +This is only recommended for advanced users. + +> Tip: The Yii framework itself 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 absolutely must be +exposed to web users (i.e., placed within the web directory). Other PHP scripts, including those +part of the Yii Framework, should be protected from web access to prevent possible exploitation by hackers. -> 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: +Yii's requirements. You can do so by running the requirement checker +script in a web browser or from the command line. -~~~ -http://hostname/path/to/yii/requirements/index.php -~~~ +If you have installed a Yii application template via zip or composer you'll find a `requirements.php` file in the +base directory of your application. + +In order to run this script on the command line use the following command: + +``` +php requirements.php +``` + +In order to run this script in your browser, you should ensure it is accessable by the webserver and +access `http://hostname/path/to/yii-app/requirements.php` in your browser. +If you are using Linux you can create a hard link to make it accessable, using the following command: -Yii requires PHP 5.3.7, so the server must have PHP 5.3.7 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. +``` +ln requirements.php ../requirements.php +``` + +Yii 2 requires PHP 5.4.0 or higher. Yii has been tested with the [Apache HTTP server](http://httpd.apache.org/) and +[Nginx HTTP server](http://nginx.org/) on Windows and Linux. +Yii may also be usable on other web servers and platforms, provided that PHP 5.4 or higher 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: +Yii is ready to work with a default Apache web server configuration. As a security measure, Yii comes with `.htaccess` +files in the Yii framework folder to deny access to those restricted resources. + +By default, requests for pages in a Yii-based site go through the bootstrap file, usually named `index.php`, and placed +in the application's `web` directory. The result will be URLs in the format `http://hostname/index.php/controller/action/param/value`. + +To hide the bootstrap file in your URLs, add `mod_rewrite` instructions to the `.htaccess` file in your web document root +(or add the instructions to the virtual host configuration in Apache's `httpd.conf` file). The applicable instructions are: ~~~ RewriteEngine on -# if a directory or a file exists, use it directly +# If a directory or a file exists, use it directly RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d -# otherwise forward it to index.php +# Otherwise forward it to index.php RewriteRule . index.php ~~~ @@ -77,63 +112,48 @@ 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. +Yii can also be used with the popular [Nginx](http://wiki.nginx.org/) web server, so long it has PHP installed as +an [FPM SAPI](http://php.net/install.fpm). Below is a sample host configuration for a Yii-based site on Nginx. +The configuration tells the server to send all requests for non-existent resources through the bootstrap file, +resulting in "prettier" URLs without the need for `index.php` references. ~~~ 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; + client_max_body_size 128M; - location / { - index index.html $yii_bootstrap; - try_files $uri $uri/ /$yii_bootstrap?$args; - } + listen 80; ## listen for ipv4 + #listen [::]:80 default_server ipv6only=on; ## listen for ipv6 - location ~ ^/(protected|framework|themes/\w+/views) { - deny all; - } + server_name mysite.local; + root /path/to/project/web; + index $yii_bootstrap; - #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; - } + access_log /path/to/project/log/access.log main; + error_log /path/to/project/log/error.log; - # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 - # - location ~ \.php { - fastcgi_split_path_info ^(.+\.php)(.*)$; + location / { + # Redirect everything that isn't real file to yii bootstrap file including arguments. + try_files $uri $uri/ /$yii_bootstrap?$args; + } - #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; - } + # uncomment to 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; + #} + #error_page 404 /404.html; - #for php-cgi + location ~ \.php$ { + include fastcgi.conf; fastcgi_pass 127.0.0.1:9000; - #for php-fpm #fastcgi_pass unix:/var/run/php5-fpm.sock; - 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; + location ~ /\.(ht|svn|git) { + deny all; } } ~~~ -Using this configuration you can set `cgi.fix_pathinfo=0` in php.ini to avoid -many unnecessary system `stat()` calls. +When using this configuration, you should set `cgi.fix_pathinfo=0` in the `php.ini` file in order to avoid many unnecessary system `stat()` calls. diff --git a/docs/guide/logging.md b/docs/guide/logging.md index e69de29..ce14742 100644 --- a/docs/guide/logging.md +++ b/docs/guide/logging.md @@ -0,0 +1,5 @@ +Logging +======= + + +TDB \ No newline at end of file diff --git a/docs/guide/migration.md b/docs/guide/migration.md index d6c7165..8474bbe 100644 --- a/docs/guide/migration.md +++ b/docs/guide/migration.md @@ -82,11 +82,11 @@ class m101129_185401_create_news_table extends \yii\db\Migration { public function up() { - $this->db->createCommand()->createTable('tbl_news', array( + $this->db->createCommand()->createTable('tbl_news', [ 'id' => 'pk', 'title' => 'string(128) NOT NULL', 'content' => 'text', - ))->execute(); + ])->execute(); } public function down() @@ -128,11 +128,11 @@ class m101129_185401_create_news_table extends \yii\db\Migration $transaction=$this->getDbConnection()->beginTransaction(); try { - $this->db->createCommand()->createTable('tbl_news', array( + $this->db->createCommand()->createTable('tbl_news', [ 'id' => 'pk', 'title' => 'string NOT NULL', 'content' => 'text', - ))->execute(); + ])->execute(); $transaction->commit(); } catch(Exception $e) diff --git a/docs/guide/model.md b/docs/guide/model.md index b93fb7a..465cb16 100644 --- a/docs/guide/model.md +++ b/docs/guide/model.md @@ -8,16 +8,15 @@ In keeping with the MVC approach, a model in Yii is intended for storing or temp - Massive attribute assignment: the ability to populate multiple model attributes in one step. - Scenario-based data validation. -Models in Yii extend from the [[\yii\base\Model]] class. Models are typically used to both hold data and define the validation rules for that data. The validation rules greatly simply the generation of models from complex web forms. -The Model class is also the base for more advanced models with additional functionality such as [Active Record](active-record.md). +Models in Yii extend from the [[\yii\base\Model]] class. Models are typically used to both hold data and define the validation rules for that data (aka, the business logic). The business logic greatly simplifies the generation of models from complex web forms by providing validation and error reporting. +The Model class is also the base class for more advanced models with additional functionality, such as [Active Record](active-record.md). Attributes ---------- -Attributes store the actual data represented by a model and can -be accessed like object member variables. For example, a `Post` model -may contain a `title` attribute and a `content` attribute which may be -accessed as follows: +The actual data represented by a model is stored in the model's *attributes*. Model attributes can +be accessed like the member variables of any object. For example, a `Post` model +may contain a `title` attribute and a `content` attribute, accessible as follows: ```php $post = new Post; @@ -28,7 +27,7 @@ echo $post->content; ``` Since [[\yii\base\Model|Model]] implements the [ArrayAccess](http://php.net/manual/en/class.arrayaccess.php) interface, -you can also access the attributes like accessing array elements: +you can also access the attributes as if they were array elements: ```php $post = new Post; @@ -51,8 +50,8 @@ class LoginForm extends \yii\base\Model } ``` -Derived model classes may use different ways to declare attributes by overriding the [[\yii\base\Model::attributes()|attributes()]] -method. For example, [[\yii\db\ActiveRecord]] defines attributes as the column names of the database table +Derived model classes may declare attributes in different ways, by overriding the [[\yii\base\Model::attributes()|attributes()]] +method. For example, [[\yii\db\ActiveRecord]] defines attributes using the column names of the database table that is associated with the class. @@ -60,13 +59,11 @@ Attribute Labels ---------------- Attribute labels are mainly used for display purpose. For example, given an attribute `firstName`, we can declare -a label `First Name` which is more user-friendly and can be displayed to end users in places such as form labels, +a label `First Name` that is more user-friendly when displayed to end users in places such as form labels and error messages. Given an attribute name, you can obtain its label by calling [[\yii\base\Model::getAttributeLabel()]]. -To declare attribute labels, you should override the [[\yii\base\Model::attributeLabels()]] method and return -a mapping from attribute names to attribute labels, like shown in the example below. If an attribute is not found -in this mapping, its label will be generated using the [[\yii\base\Model::generateAttributeLabel()]] method, which -in many cases, will generate reasonable labels (e.g. `username` to `Username`, `orderNumber` to `Order Number`). +To declare attribute labels, override the [[\yii\base\Model::attributeLabels()]] method. The overridden method returns a mapping of attribute names to attribute labels, as shown in the example below. If an attribute is not found +in this mapping, its label will be generated using the [[\yii\base\Model::generateAttributeLabel()]] method. In many cases, [[\yii\base\Model::generateAttributeLabel()]] will generate reasonable labels (e.g. `username` to `Username`, `orderNumber` to `Order Number`). ```php // LoginForm has two attributes: username and password @@ -77,10 +74,10 @@ class LoginForm extends \yii\base\Model public function attributeLabels() { - return array( + return [ 'username' => 'Your name', 'password' => 'Your password', - ); + ]; } } ``` @@ -88,39 +85,41 @@ class LoginForm extends \yii\base\Model Scenarios --------- -A model may be used in different scenarios. For example, a `User` model may be used to collect user login inputs, -and it may also be used for user registration purpose. For this reason, each model has a property named `scenario` -which stores the name of the scenario that the model is currently being used in. As we will explain in the next -few sections, the concept of scenario is mainly used for data validation and massive attribute assignment. +A model may be used in different *scenarios*. For example, a `User` model may be used to collect user login inputs, +but it may also be used for user registration purposes. In the one scenario, every piece of data is required; in the other, only the username and password would be. + +To easily implement the business logic for different scenarios, each model has a property named `scenario` +that stores the name of the scenario that the model is currently being used in. As will be explained in the next +few sections, the concept of scenarios is mainly used for data validation and massive attribute assignment. Associated with each scenario is a list of attributes that are *active* in that particular scenario. For example, in the `login` scenario, only the `username` and `password` attributes are active; while in the `register` scenario, additional attributes such as `email` are *active*. -Possible scenarios should be listed in the `scenarios()` method which returns an array whose keys are the scenario -names and whose values are the corresponding active attribute lists. Below is an example: +Possible scenarios should be listed in the `scenarios()` method. This method returns an array whose keys are the scenario +names and whose values are lists of attributes that should be active in that scenario: ```php class User extends \yii\db\ActiveRecord { public function scenarios() { - return array( - 'login' => array('username', 'password'), - 'register' => array('username', 'email', 'password'), - ); + return [ + 'login' => ['username', 'password'], + 'register' => ['username', 'email', 'password'], + ]; } } ``` -Sometimes, we want to mark an attribute as not safe for massive assignment (but we still want it to be validated). -We may do so by prefixing an exclamation character to the attribute name when declaring it in `scenarios()`. For example, +Sometimes, we want to mark an attribute as not safe for massive assignment (but we still want the attribute to be validated). +We may do so by prefixing an exclamation character to the attribute name when declaring it in `scenarios()`. For example: ```php -array('username', 'password', '!secret') +['username', 'password', '!secret'] ``` -Active model scenario could be set using one of the following ways: +Identifying the active model scenario can be done using one of the following approaches: ```php class EmployeeController extends \yii\web\Controller @@ -128,14 +127,14 @@ class EmployeeController extends \yii\web\Controller public function actionCreate($id = null) { // first way - $employee = new Employee(array('scenario' => 'managementPanel')); + $employee = new Employee(['scenario' => 'managementPanel']); // second way $employee = new Employee; $employee->scenario = 'managementPanel'; // third way - $employee = Employee::find()->where('id = :id', array(':id' => $id))->one(); + $employee = Employee::find()->where('id = :id', [':id' => $id])->one(); if ($employee !== null) { $employee->setScenario('managementPanel'); } @@ -143,8 +142,7 @@ class EmployeeController extends \yii\web\Controller } ``` -In the example above we are using [Active Record](active-record.md). For basic form models it's rarely needed to -use scenarios since form model is typically used for a single form. +The example above presumes that the model is based upon [Active Record](active-record.md). For basic form models, scenarios are rarely needed, as the basic form model is normally tied directly to a single form. Validation ---------- @@ -171,7 +169,7 @@ or several attributes and is effective in one or several scenarios. A rule can b instance of a [[\yii\validators\Validator]] child class, or an array with the following format: ```php -array( +[ 'attribute1, attribute2, ...', 'validator class or alias', // specifies in which scenario(s) this rule is active. @@ -182,7 +180,7 @@ array( 'property1' => 'value1', 'property2' => 'value2', // ... -) +] ``` When `validate()` is called, the actual validation rules executed are determined using both of the following criteria: @@ -216,10 +214,10 @@ var_dump($attributes); Using the same `attributes` property you can massively assign data from associative array to model attributes: ```php -$attributes = array( +$attributes = [ 'title' => 'Model attributes', 'create_time' => time(), -); +]; $post->attributes = $attributes; ``` @@ -238,24 +236,24 @@ assignment is described in `scenarios` method: ```php function rules() { - return array( + return [ // rule applied when corresponding field is "safe" - array('username', 'string', 'length' => array(4, 32)), - array('first_name', 'string', 'max' => 128), - array('password', 'required'), + ['username', 'string', 'length' => [4, 32]], + ['first_name', 'string', 'max' => 128], + ['password', 'required'], // rule applied when scenario is "signup" no matter if field is "safe" or not - array('hashcode', 'check', 'on' => 'signup'), - ); + ['hashcode', 'check', 'on' => 'signup'], + ]; } function scenarios() { - return array( + return [ // on signup allow mass assignment of username - 'signup' => array('username', 'password'), - 'update' => array('username', 'first_name'), - ); + 'signup' => ['username', 'password'], + 'update' => ['username', 'first_name'], + ]; } ``` diff --git a/docs/guide/mvc.md b/docs/guide/mvc.md index f67ba4d..22f86a1 100644 --- a/docs/guide/mvc.md +++ b/docs/guide/mvc.md @@ -2,51 +2,45 @@ 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. +widely adopted in web and other application programming. MVC aims to separate business logic from +user interface considerations, allowing developers to more easily change individual components of an application without affecting, or even touching, another. + +In MVC, the *model* represents the +information (the data) and the business rules to which the data must adhere. The *view* contains elements +of the user interface, such as text, images, and form elements. The *controller* manages +the communication between the model and the view, acting as an agent. + +Besides implementing the MVC design pattern, Yii also introduces a *front-controller*, called +*application*. The front-controller encapsulates the *execution context* for the processing of a request. This means that the front-controller collects information about a user request, and +then dispatches it to an appropriate controller for actual handling of that request. In other words, the front-controller is the primary application manager, handling all requests and delegating action accordingly. The following diagram shows the static structure of a Yii application: - + A Typical Workflow ------------------ -The following diagram shows a typical workflow of a Yii application when -it is handling a user request: - - - - 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. +The following diagram shows a typical workflow of a Yii application handling a user request: + + + +1. A user makes a request of the URL `http://www.example.com/index.php?r=post/show&id=1`. + The Web server handles the request by executing the bootstrap script `index.php`. +2. The bootstrap script creates an [[Application|yii\web\Application]] instance and runs it. +3. The Application instance obtains the detailed user request information from an application component named `request`. +4. The application determines which [controller](controller.md) and which action of that controller was requested. + This is accomplished 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 users 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 then executed, if execution is allowed by the filters (e.g., if the user has permission to execute that action). +6. The action creates a `Post` [model](model.md) instance, using the underlying database table, where the ID value of the corresponding record is `1`. +7. The action renders a [view](view.md) named `show`, providing to the view the `Post` model instance. +8. The view reads the attributes of the `Post` model instance and displays the values of those attributes. +9. The view executes some [widgets](view.md#widgets). +10. The view rendering result -the output from the previous steps- is embedded in a [layout](view.md#layout) to create a complete page. +11. The action completes the view rendering and displays the result to the user. \ No newline at end of file diff --git a/docs/guide/overview.md b/docs/guide/overview.md index 835c511..a9d1a38 100644 --- a/docs/guide/overview.md +++ b/docs/guide/overview.md @@ -11,17 +11,19 @@ Requirements ------------ To run a Yii-powered Web application, you need a Web server that supports -PHP 5.3.? or greater. +PHP 5.4.0 or greater. For developers who want to use Yii, understanding object-oriented programming (OOP) is very helpful, because Yii is a pure OOP framework. +Yii 2.0 also makes use of the latest features of PHP such as [namespaces](http://www.php.net/manual/en/language.namespaces.php) +so you should be familiar with how they work. 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 +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 projects, etc. @@ -35,4 +37,7 @@ How does Yii Compare with Other Frameworks? - Yii strikes a good balance between simplicity and features. - Syntax and overall development usability are taken seriously by the Yii development team. - Performance is one of the key goals for the Yii framework. -- The Yii development team is constantly watching what other Web frameworks are doing to see what best practices and features should be incorporated into Yii. The initial Yii release was heavily influenced by Ruby on Rails. Still, no framework or feature is being blindly copied into Yii; all decisions are based upon what's best for Web developers and in keeping with Yii's philosophy. +- The Yii development team is constantly watching what other Web frameworks are doing to see what best practices and + features should be incorporated into Yii. The initial Yii release was heavily influenced by Ruby on Rails. + Still, no framework or feature is being blindly copied into Yii; all decisions are based upon what's best + for Web developers and in keeping with Yii's philosophy. diff --git a/docs/guide/performance.md b/docs/guide/performance.md index 3d89af9..42aa042 100644 --- a/docs/guide/performance.md +++ b/docs/guide/performance.md @@ -1,7 +1,7 @@ Performance Tuning ================== -Application performance consists of two parts. First is the framework performance +The performance of your web application is based upon 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 @@ -41,11 +41,11 @@ to save the time of parsing database schema. This can be done by setting the `protected/config/main.php`: ```php -return array( +return [ // ... - 'components' => array( + 'components' => [ // ... - 'db' => array( + 'db' => [ 'class' => 'yii\db\Connection', 'dsn' => 'mysql:host=localhost;dbname=mydatabase', 'username' => 'root', @@ -57,12 +57,12 @@ return array( // Name of the cache component used. Default is 'cache'. //'schemaCache' => 'cache', - ), - 'cache' => array( + ], + 'cache' => [ 'class' => 'yii\caching\FileCache', - ), - ), -); + ], + ], +]; ``` Note that `cache` application component should be configured. @@ -79,10 +79,10 @@ switch to another storage such as database. You can do so by configuring your application via `protected/config/main.php`: ```php -return array( +return [ // ... - 'components' => array( - 'session' => array( + 'components' => [ + 'session' => [ 'class' => 'yii\web\DbSession', // Set the following if want to use DB component other than @@ -91,9 +91,9 @@ return array( // To override default session table set the following // 'sessionTable' => 'my_session', - ), - ), -); + ], + ], +]; ``` You can use `CacheSession` to store sessions using cache. Note that some @@ -154,9 +154,7 @@ class PostController extends Controller public function actionIndex() { $posts = Post::find()->orderBy('id DESC')->limit(100)->asArray()->all(); - return $this->render('index', array( - 'posts' => $posts, - )); + return $this->render('index', ['posts' => $posts]); } } ``` diff --git a/docs/guide/query-builder.md b/docs/guide/query-builder.md index 2ec2581..ac79f1d 100644 --- a/docs/guide/query-builder.md +++ b/docs/guide/query-builder.md @@ -40,12 +40,12 @@ $query->select('user_id')->distinct()->from('tbl_post'); Select options can be specified as array. It's especially useful when these are formed dynamically. ```php -$query->select(array('tbl_user.name AS author', 'tbl_post.title as title')) // <-- specified as array +$query->select(['tbl_user.name AS author', 'tbl_post.title as title']) // <-- specified as array ->from('tbl_user') ->leftJoin('tbl_post', 'tbl_post.user_id = tbl_user.id'); // <-- join with another table ``` -In the code above we've used `leftJoin` method to select from two related tables at the same time. Firsrt parameter +In the code above we've used `leftJoin` method to select from two related tables at the same time. First parameter specifies table name and the second is the join condition. Query builder has the following methods to join tables: - `innerJoin` @@ -67,9 +67,7 @@ is `where`. There are multiple ways to use it. The simplest is to specify condition in a string: ```php -$query->where('status=:status', array( - ':status' => $status, -)); +$query->where('status=:status', [':status' => $status]); ``` When using this format make sure you're binding parameters and not creating a query by string concatenation. @@ -79,19 +77,17 @@ Instead of binding status value immediately you can do it using `params` or `add ```php $query->where('status=:status'); -$query->addParams(array( - ':status' => $status, -)); +$query->addParams([':status' => $status]); ``` There is another convenient way to use the method called hash format: ```php -$query->where(array( +$query->where([ 'status' => 10, 'type' => 2, - 'id' => array(4, 8, 15, 16, 23, 42), -)); + 'id' => [4, 8, 15, 16, 23, 42], +]); ``` It will generate the following SQL: @@ -103,9 +99,7 @@ WHERE (`status` = 10) AND (`type` = 2) AND (`id` IN (4, 8, 15, 16, 23, 42)) If you'll specify value as `null` such as the following: ```php -$query->where(array( - 'status' => null, -)); +$query->where(['status' => null]); ``` SQL generated will be: @@ -114,31 +108,31 @@ SQL generated will be: WHERE (`status` IS NULL) ``` -Another way to use the method is the operand format which is `array(operator, operand1, operand2, ...)`. +Another way to use the method is the operand format which is `[operator, operand1, operand2, ...]`. Operator can be one of the following: - `and`: the operands should be concatenated together using `AND`. For example, - `array('and', 'id=1', 'id=2')` will generate `id=1 AND id=2`. If an operand is an array, + `['and', 'id=1', 'id=2']` will generate `id=1 AND id=2`. If an operand is an array, it will be converted into a string using the rules described here. For example, - `array('and', 'type=1', array('or', 'id=1', 'id=2'))` will generate `type=1 AND (id=1 OR id=2)`. + `['and', 'type=1', ['or', 'id=1', 'id=2']]` will generate `type=1 AND (id=1 OR id=2)`. The method will NOT do any quoting or escaping. - `or`: similar to the `and` operator except that the operands are concatenated using `OR`. - `between`: operand 1 should be the column name, and operand 2 and 3 should be the starting and ending values of the range that the column is in. - For example, `array('between', 'id', 1, 10)` will generate `id BETWEEN 1 AND 10`. + For example, `['between', 'id', 1, 10]` will generate `id BETWEEN 1 AND 10`. - `not between`: similar to `between` except the `BETWEEN` is replaced with `NOT BETWEEN` in the generated condition. - `in`: operand 1 should be a column or DB expression, and operand 2 be an array representing the range of the values that the column or DB expression should be in. For example, - `array('in', 'id', array(1, 2, 3))` will generate `id IN (1, 2, 3)`. + `['in', 'id', [1, 2, 3]]` will generate `id IN (1, 2, 3)`. The method will properly quote the column name and escape values in the range. - `not in`: similar to the `in` operator except that `IN` is replaced with `NOT IN` in the generated condition. - `like`: operand 1 should be a column or DB expression, and operand 2 be a string or an array representing the values that the column or DB expression should be like. - For example, `array('like', 'name', '%tester%')` will generate `name LIKE '%tester%'`. + For example, `['like', 'name', '%tester%']` will generate `name LIKE '%tester%'`. When the value range is given as an array, multiple `LIKE` predicates will be generated and concatenated - using `AND`. For example, `array('like', 'name', array('%test%', '%sample%'))` will generate + using `AND`. For example, `['like', 'name', ['%test%', '%sample%']]` will generate `name LIKE '%test%' AND name LIKE '%sample%'`. The method will properly quote the column name and escape values in the range. - `or like`: similar to the `like` operator except that `OR` is used to concatenate the `LIKE` @@ -154,7 +148,7 @@ If you are building parts of condition dynamically it's very convenient to use ` $status = 10; $search = 'yii'; -$query->where(array('status' => $status)); +$query->where(['status' => $status]); if (!empty($search)) { $query->addWhere('like', 'title', $search); } @@ -172,10 +166,10 @@ Order For ordering results `orderBy` and `addOrderBy` could be used: ```php -$query->orderBy(array( - 'id' => Query::SORT_ASC, - 'name' => Query::SORT_DESC, -)); +$query->orderBy([ + 'id' => SORT_ASC, + 'name' => SORT_DESC, +]); ``` Here we are ordering by `id` ascending and then by `name` descending. @@ -192,14 +186,14 @@ $query->groupBy('id, status'); If you want to add another field after using `groupBy`: ```php -$query->addGroupBy(array('created_at', 'updated_at')); +$query->addGroupBy(['created_at', 'updated_at']); ``` To add a `HAVING` condition the corresponding `having` method and its `andHaving` and `orHaving` can be used. Parameters for these are similar to the ones for `where` methods group: ```php -$query->having(array('status' => $status)); +$query->having(['status' => $status]); ``` Limit and offset diff --git a/docs/guide/security.md b/docs/guide/security.md index af30e5b..f54a133 100644 --- a/docs/guide/security.md +++ b/docs/guide/security.md @@ -1,35 +1,36 @@ Security ======== +Good security is vital to the health and success of many websites. Unfortunately, many developers may cut corners when it comes to security due to a lack of understanding or too large of an implementation hurdle. To make your Yii-based site as secure as possible, the Yii framework has baked in several excellent, and easy to use, security features. + Hashing and verifying passwords ------------------------------- +------------------------------- Most developers know that you cannot store passwords in plain text, but many believe it's safe to hash passwords using `md5` or `sha1`. There was a time when those hashing algorithms were sufficient, but modern hardware makes it possible to break those hashes very quickly using a brute force attack. -In order to truly secure user passwords, even in the worst case scenario (your database is broken into), you need to use a hashing algorithm that is resistant to brute force attacks. The best current choice is bcrypt. In PHP, you can create a bcrypt hash by using [crypt function](http://php.net/manual/en/function.crypt.php). However, this function is not easy to use properly, so Yii provides two helper functions for generating hash from -password and verifying existing hash. +In order to truly secure user passwords, even in the worst case scenario (your database is broken into), you need to use a hashing algorithm that is resistant to brute force attacks. The best current choice is `bcrypt`. In PHP, you can create a `bcrypt` hash by using the [crypt function](http://php.net/manual/en/function.crypt.php). However, this function is not easy to use properly, so Yii provides two helper functions to make securely generating and verifying hashes easier. -When user sets his password we're taking password string from POST and then getting a hash: +When a user provides a password for the first time (e.g., upon registration), the password needs to be hashed: ```php $hash = \yii\helpers\Security::generatePasswordHash($password); ``` -The hash we've got is persisted to database to be used later. +The hash would then be associated with the model, so that it will be stored in the database for later use. -Then when user is trying to log in we're verifying the password he entered against a hash that we've previously persisted: +When user attempts to log in, the submitted log in password must be verified against the previously hashed and stored password: ```php -if(Security::validatePassword($password, $hash)) { +use \yii\helpers; +if (Security::validatePassword($password, $hash)) { // all good, logging user in -} -else { +} else { // wrong password } ``` -Random data +Creating random data ----------- Random data is useful in many cases. For example, when resetting a password via email you need to generate a token, @@ -64,9 +65,11 @@ Then when user want to read it: $data = \yii\helpers\Security::decrypt($encryptedData, $secretWord); ``` -Making sure data wasn't modified +Confirming data integrity -------------------------------- +Making sure data wasn't modified + hashData() validateData() diff --git a/docs/guide/template.md b/docs/guide/template.md index f9405ff..26c9707 100644 --- a/docs/guide/template.md +++ b/docs/guide/template.md @@ -7,23 +7,23 @@ The `view` component is responsible for rendering views. You can add a custom template engines by reconfiguring this component's behavior: ```php -array( - 'components' => array( - 'view' => array( - 'class' => 'yii\base\View', - 'renderers' => array( - 'tpl' => array( +[ + 'components' => [ + 'view' => [ + 'class' => 'yii\web\View', + 'renderers' => [ + 'tpl' => [ 'class' => 'yii\renderers\SmartyViewRenderer', - ), - 'twig' => array( + ], + 'twig' => [ 'class' => 'yii\renderers\TwigViewRenderer', 'twigPath' => '@app/vendors/Twig', - ), + ], // ... - ), - ), - ), -) + ], + ], + ], +] ``` Note that the Smarty and Twig packages themselves are not bundled with Yii. You must download them yourself. Then unpack the packages and place the resulting files in a logical location, such as the application's `protected/vendor` folder. Finally, specify the correct `smartyPath` or `twigPath`, as in the code above (for Twig). @@ -36,7 +36,7 @@ Unlike standard view files, when using Twig, you must include the extension whe or `$this->renderPartial()` from your controller: ```php -echo $this->render('renderer.twig', array('username' => 'Alex')); +echo $this->render('renderer.twig', ['username' => 'Alex']); ``` ### Additional functions @@ -63,7 +63,7 @@ To use Smarty, you need to create templates in files with the `.tpl` extension ( or `$this->renderPartial()` from your controller: ```php -echo $this->render('renderer.tpl', array('username' => 'Alex')); +echo $this->render('renderer.tpl', ['username' => 'Alex']); ``` ### Additional functions diff --git a/docs/guide/testing.md b/docs/guide/testing.md index e69de29..4b88a9a 100644 --- a/docs/guide/testing.md +++ b/docs/guide/testing.md @@ -0,0 +1,4 @@ +Testing +======= + +TDB \ No newline at end of file diff --git a/docs/guide/theming.md b/docs/guide/theming.md index e69de29..308316a 100644 --- a/docs/guide/theming.md +++ b/docs/guide/theming.md @@ -0,0 +1,4 @@ +Theming +======= + +TDB \ No newline at end of file diff --git a/docs/guide/upgrade-from-v1.md b/docs/guide/upgrade-from-v1.md index f174864..37a7511 100644 --- a/docs/guide/upgrade-from-v1.md +++ b/docs/guide/upgrade-from-v1.md @@ -29,6 +29,8 @@ If your class does not need the event or behavior feature, you should consider u `Object` as the base class. This is usually the case for classes that represent basic data structures. +More details about Object and component can be found in the [Basic concepts section](basics.md). + Object Configuration -------------------- @@ -38,9 +40,9 @@ of `Object` should declare its constructor (if needed) in the following way so t it can be properly configured: ```php -class MyClass extends \yii\Object +class MyClass extends \yii\base\Object { - public function __construct($param1, $param2, $config = array()) + public function __construct($param1, $param2, $config = []) { // ... initialization before configuration is applied @@ -65,13 +67,14 @@ By following this convention, you will be able to create and configure a new obj using a configuration array like the following: ```php -$object = Yii::createObject(array( +$object = Yii::createObject([ 'class' => 'MyClass', 'property1' => 'abc', 'property2' => 'cde', -), $param1, $param2); +], $param1, $param2); ``` +More on configuration can be found in the [Basic concepts section](basics.md). Events @@ -106,6 +109,18 @@ Yii::$app->on($eventName, $handler); Yii::$app->trigger($eventName); ``` +If you need to handle all instances of a class instead of the object you can attach a handler like the following: + +```php +Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function ($event) { + Yii::trace(get_class($event->sender) . ' is inserted.'); +}); +``` + +The code above defines a handler that will be triggered for every Active Record object's `EVENT_AFTER_INSERT` event. + +See [Event handling section](events.md) for more details. + Path Alias ---------- @@ -123,6 +138,8 @@ a class like `yii\web\Request` can be autoloaded by Yii. If you use a third part 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. +More on path aliases can be found in the [Basic concepts section](basics.md). + View ---- @@ -156,6 +173,8 @@ extension for your Smarty views, or `twig` for Twig views. You may also configur `View::renderers` property to use other template engines. See [Using template engines](template.md) section of the guide for more details. +See [View section](view.md) for more details. + Models ------ @@ -177,10 +196,10 @@ if (isset($_POST['Post'])) { $model->save(); -$postTags = array(); +$postTags = []; $tagsCount = count($_POST['PostTag']); -while($tagsCount-- > 0){ - $postTags[] = new PostTag(array('post_id' => $model->id)); +while ($tagsCount-- > 0) { + $postTags[] = new PostTag(['post_id' => $model->id]); } Model::loadMultiple($postTags, $_POST); ``` @@ -193,10 +212,10 @@ a list of scenarios and the corresponding attributes that need to be validated w ```php public function scenarios() { - return array( - 'backend' => array('email', 'role'), - 'frontend' => array('email', '!name'), - ); + return [ + 'backend' => ['email', 'role'], + 'frontend' => ['email', '!name'], + ]; } ``` @@ -221,6 +240,7 @@ sending them out. You have to `echo` them explicitly, e.g., `echo $this->render( To learn more about Yii 2.0 controllers refer to [Controller](controller.md) section of the guide. + Widgets ------- @@ -229,13 +249,13 @@ methods of the `Widget` class. For example, ```php // Note that you have to "echo" the result to display it -echo \yii\widgets\Menu::widget(array('items' => $items)); +echo \yii\widgets\Menu::widget(['items' => $items]); // Passing an array to initialize the object properties -$form = \yii\widgets\ActiveForm::begin(array( - 'options' => array('class' => 'form-horizontal'), - 'fieldConfig' => array('inputOptions' => array('class' => 'input-xlarge')), -)); +$form = \yii\widgets\ActiveForm::begin([ + 'options' => ['class' => 'form-horizontal'], + 'fieldConfig' => ['inputOptions' => ['class' => 'input-xlarge']], +]); ... form inputs here ... \yii\widgets\ActiveForm::end(); ``` @@ -243,13 +263,15 @@ $form = \yii\widgets\ActiveForm::begin(array( Previously in 1.1, you would have to enter the widget class names as strings via the `beginWidget()`, `endWidget()` and `widget()` methods of `CBaseController`. The approach above gets better IDE support. +For more on widgets see the [View section](view.md#widgets). + Themes ------ Themes work completely different in 2.0. They are 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('/web/views' => '/web/themes/basic')`, then the themed version for a view file +`['/web/views' => '/web/themes/basic']`, then the themed version for a view file `/web/views/site/index.php` will be `/web/themes/basic/site/index.php`. For this reason, theme can now be applied to any view file, even if a view rendered outside @@ -258,6 +280,8 @@ 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. +For more on themes see the [Theming section](theming.md). + Console Applications -------------------- @@ -273,6 +297,8 @@ are treated as global options declared in `globalOptions()`. Yii 2.0 supports automatic generation of command help information from comment blocks. +For more on console applications see the [Console section](console.md). + I18N ---- @@ -281,7 +307,7 @@ Yii 2.0 removes date formatter and number formatter in favor of the PECL intl PH 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`. +sources based on message categories. For more information, see the class documentation for [I18N](i18n.md). Action Filters @@ -295,17 +321,18 @@ code in a controller: ```php public function behaviors() { - return array( - 'access' => array( + return [ + 'access' => [ 'class' => 'yii\web\AccessControl', - 'rules' => array( - array('allow' => true, 'actions' => array('admin'), 'roles' => array('@')), + 'rules' => [ + ['allow' => true, 'actions' => ['admin'], 'roles' => ['@']], ), ), ); } ``` +For more on action filters see the [Controller section](controller.md#action-filters). Assets @@ -320,7 +347,7 @@ By registering an asset bundle via `AssetBundle::register()`, you will be able t the assets in that bundle accessible via Web, and the current page will automatically contain the references to the JavaScript and CSS files specified in that bundle. - +To learn more about assets see the [asset manager documentation](assets.md). Static Helpers -------------- @@ -340,10 +367,10 @@ It is represented as an `ActiveField` object. Using fields, you can build a form ```php <?php $form = yii\widgets\ActiveForm::begin(); ?> - <?php echo $form->field($model, 'username'); ?> - <?php echo $form->field($model, 'password')->passwordInput(); ?> + <?= $form->field($model, 'username') ?> + <?= $form->field($model, 'password')->passwordInput() ?> <div class="form-group"> - <?php echo Html::submitButton('Login'); ?> + <?= Html::submitButton('Login') ?> </div> <?php yii\widgets\ActiveForm::end(); ?> ``` @@ -386,7 +413,7 @@ class Customer extends \yii\db\ActiveRecord { public function getOrders() { - return $this->hasMany('Order', array('customer_id' => 'id')); + return $this->hasMany('Order', ['customer_id' => 'id']); } } ``` @@ -409,7 +436,7 @@ use the `find()` method: ```php // to retrieve all *active* customers and order them by their ID: $customers = Customer::find() - ->where(array('status' => $active)) + ->where(['status' => $active]) ->orderBy('id') ->all(); // return the customer whose PK is 1 @@ -432,6 +459,8 @@ By default, ActiveRecord now only saves dirty attributes. In 1.1, all attributes are saved to database when you call `save()`, regardless of having changed or not, unless you explicitly list the attributes to save. +See [active record docs](active-record.md) for more details. + Auto-quoting Table and Column Names ------------------------------------ @@ -467,21 +496,25 @@ both `post/popular` and `post/1/popular`. In 1.1, you would have to use two rule the same goal. ```php -array( +[ 'pattern' => 'post/<page:\d+>/<tag>', 'route' => 'post/index', - 'defaults' => array('page' => 1), -) + 'defaults' => ['page' => 1], +] ``` - +More details in the [Url manager docs](url.md). Response -------- +TBD + Extensions ---------- +TBD + Integration with Composer ------------------------- diff --git a/docs/guide/upgrade.md b/docs/guide/upgrade.md deleted file mode 100644 index e69de29..0000000 --- a/docs/guide/upgrade.md +++ /dev/null diff --git a/docs/guide/url.md b/docs/guide/url.md index 46bb177..454a827 100644 --- a/docs/guide/url.md +++ b/docs/guide/url.md @@ -1,3 +1,120 @@ URL Management ============== +The concept of URL management in Yii fairly simple. URL management is based on the premise that the application uses internal routes and parameters +everywhere. The framework itself will then translates routes into URLs, and translate URLs into routs, according to the URL manager's configuration. +This approach allows you to change site-wide URLs merely by edited a single config file, without ever touching the application code. + +Internal route +-------------- + +When implementing an application using Yii, you'll deal with internal routes and parameters. Each controller and controller action has a corresponding internal route, such as `site/index` or `user/create`. In the former, `site` is referred to as the *controller ID* while `index` is referred to as the *action ID*. In the second example, `user` is the controller ID and `create` is the action ID. If controller belongs to a *module*, the internal route is prefixed with the module ID, such as `blog/post/index` for a blog module (with `post` being the controller ID and `index` being the action ID). + +Creating URLs +------------- + +The most important rule for creating URLs in your site is to always do so using the URL manager. The URL manager is an +application component with the `urlManager` ID. This component is accessible both from web and console applications via +`\Yii::$app->urlManager`. The component makes availabe the two following URL creation methods: + +- `createUrl($route, $params = [])` +- `createAbsoluteUrl($route, $params = [])` + +The `createUrl` method creates a URL relative to the application root, such as `/index.php/site/index/`. The `createAbsoluteUrl` method creates URL prefixed with the proper protocol and +hostname: `http://www.example.com/index.php/site/index`. The former is suitable for internal application URLs, while the latter is used when you need to create rules for outside the website, such as when sending emails or generating an RSS feed. + +Some examples: + +```php +echo \Yii::$app->urlManager->createUrl('site/page', ['id' => 'about']); +// /index.php/site/page/id/about/ +echo \Yii::$app->urlManager->createAbsoluteUrl('blog/post/index'); +// http://www.example.com/index.php/blog/post/index/ +``` + +The exact format of the outputted URL will depend upon how the URL manager is configured (which is the point). The above examples may also output: + +* `/site/page/id/about/` +* `/index.php?r=site/page&id=about` +* `http://www.example.com/blog/post/index/` +* `http://www.example.com/index.php?r=blog/post/index` + +Inside a web application controller, you can use the controller's own `createUrl` shortcut method. Unlike the global `createUrl` method, the controller version is context sensitive: + +```php +echo $this->createUrl(''); // currently active route +echo $this->createUrl('view', ['id' => 'contact']); // same controller, different action +echo $this->createUrl('post/index'); // same module, different controller and action +echo $this->createUrl('/site/index'); // absolute route no matter what controller is making this call +``` + +> **Tip**: In order to generate URL with a hashtag, for example `/index.php?r=site/page&id=100#title`, you need to + specify the parameter named `#` using `$this->createUrl('post/read', ['id' => 100, '#' => 'title'])`. + +Customizing URLs +---------------- + +By default, Yii uses a query string format for URLs, such as `/index.php?r=news/view&id=100`. In order to make URLs +human-friendly (i.e., more readable), you need to configure the `urlManager` component in the application's configuration file. Enabling "pretty" URLs will convert the query string format to a directory-based format: `/index.php/news/view/id/100`. Disabling the `showScriptName` parameter means that `index.php` will not be part of the URLs. Here's the relevant part of the application's configuration file. + +```php +<?php +return [ + // ... + 'components' => [ + 'urlManager' => [ + 'enablePrettyUrl' => true, + 'showScriptName' => false, + ], + ], +]; +``` + +Note that this configuration will only work if the web server has been properly configured for Yii, see (installation)[installation.md#recommended-apache-configuration]. + +### Named parameters + +### Handling subdomains + +### Faking URL Suffix + +```php +<?php +return [ + // ... + 'components' => [ + 'urlManager' => [ + 'suffix' => '.html', + ], + ], +]; +``` + +### Handling REST + + +URL parsing +----------- + +Complimentary to creating URLs Yii is handling transforming custom URLs back into internal route and parameters. + +### Strict URL parsing + +By default if there's no custom rule for URL and URL matches default format such as `/site/page` Yii tries to run a +corresponding controller's action. This behavior could be disabled so if there's no custom rule match, a 404 not found +error will be produced immediately. + +```php +<?php +return [ + // ... + 'components' => [ + 'urlManager' => [ + 'enableStrictParsing' => true, + ], + ], +]; +``` + +Creating your own rule classes +------------------------------ diff --git a/docs/guide/validation.md b/docs/guide/validation.md index 0322573..59242f5 100644 --- a/docs/guide/validation.md +++ b/docs/guide/validation.md @@ -8,7 +8,7 @@ Standard Yii validators ----------------------- Standard Yii validators could be specified using aliases instead of referring to class names. Here's the list of all -validators budled with Yii with their most useful properties: +validators bundled with Yii with their most useful properties: ### `boolean`: [[BooleanValidator]] @@ -38,7 +38,7 @@ Compares the specified attribute value with another value and validates if they Verifies if the attribute represents a date, time or datetime in a proper format. -- `format` the date format that the value being validated should follow accodring to [[http://www.php.net/manual/en/datetime.createfromformat.php]]. _('Y-m-d')_ +- `format` the date format that the value being validated should follow according to [[http://www.php.net/manual/en/datetime.createfromformat.php]]. _('Y-m-d')_ - `timestampAttribute` the name of the attribute to receive the parsing result. ### `default`: [[DefaultValueValidator]] @@ -90,16 +90,16 @@ Converts the attribute value according to a filter. Typically a callback is either the name of PHP function: ```php -array('password', 'filter', 'filter' => 'trim'), +['password', 'filter', 'filter' => 'trim'], ``` Or an anonymous function: ```php -array('text', 'filter', 'filter' => function ($value) { +['text', 'filter', 'filter' => function ($value) { // here we are removing all swear words from text return $newValue; -}), +}], ``` ### `in`: [[RangeValidator]] @@ -139,7 +139,7 @@ Serves as a dummy validator whose main purpose is to mark the attributes to be s Validates that the attribute value is of certain length. -- `length` specifies the length limit of the value to be validated. Can be `exactly X`, `array(min X)`, `array(min X, max Y)`. +- `length` specifies the length limit of the value to be validated. Can be `exactly X`, `[min X]`, `[min X, max Y]`. - `max` maximum length. If not set, it means no maximum length limit. - `min` minimum length. If not set, it means no minimum length limit. - `encoding` the encoding of the string value to be validated. _([[\yii\base\Application::charset]])_ @@ -157,7 +157,7 @@ Validates that the attribute value is unique in the corresponding database table Validates that the attribute value is a valid http or https URL. -- `validSchemes` list of URI schemes which should be considered valid. _array('http', 'https')_ +- `validSchemes` list of URI schemes which should be considered valid. _['http', 'https']_ - `defaultScheme` the default URI scheme. If the input doesn't contain the scheme part, the default scheme will be prepended to it. _(null)_ - `enableIDN` whether validation process should take into account IDN (internationalized domain names). _(false)_ @@ -179,4 +179,4 @@ if ($validator->validateValue($email)) { } ``` -TBD: refer to http://www.yiiframework.com/wiki/56/ for the format \ No newline at end of file +TBD: refer to http://www.yiiframework.com/wiki/56/ for the format diff --git a/docs/guide/view.md b/docs/guide/view.md index 7618eb2..ae86809 100644 --- a/docs/guide/view.md +++ b/docs/guide/view.md @@ -1,7 +1,8 @@ View ==== -View is an important part of MVC and is responsible for how data is presented to the end user. +View is an important part of MVC and is responsible for presenting data to end users. + Basics ------ @@ -15,9 +16,7 @@ View is typically called from controller action like the following: ```php public function actionIndex() { - return $this->render('index', array( - 'username' => 'samdark', - )); + return $this->render('index', ['username' => 'samdark']); } ``` @@ -29,7 +28,7 @@ as the corresponding key. So the view for the action above should be in `views/site/index.php` and can be something like: ```php -<p>Hello, <?php echo $username?>!</p> +<p>Hello, <?= $username ?>!</p> ``` Instead of just scalar values you can pass anything else such as arrays or objects. @@ -39,20 +38,20 @@ Widgets Widgets are a self-contained building blocks for your views. A widget may contain advanced logic, typically takes some configuration and data and returns HTML. There is a good number of widgets bundled with Yii such as [active form](form.md), -breadcrumbs, menu or [wrappers around bootstrap component framework](boostrap-widgets.md). Additionally there are +breadcrumbs, menu or [wrappers around bootstrap component framework](bootstrap-widgets.md). Additionally there are extensions providing additional widgets such as official one for jQueryUI components. In order to use widget you need to do the following: ```php // Note that you have to "echo" the result to display it -echo \yii\widgets\Menu::widget(array('items' => $items)); +echo \yii\widgets\Menu::widget(['items' => $items]); // Passing an array to initialize the object properties -$form = \yii\widgets\ActiveForm::begin(array( - 'options' => array('class' => 'form-horizontal'), - 'fieldConfig' => array('inputOptions' => array('class' => 'input-xlarge')), -)); +$form = \yii\widgets\ActiveForm::begin([ + 'options' => ['class' => 'form-horizontal'], + 'fieldConfig' => ['inputOptions' => ['class' => 'input-xlarge']], +]); ... form inputs here ... \yii\widgets\ActiveForm::end(); ``` @@ -77,7 +76,7 @@ use yii\helpers\Html; ?> <div class="username"> - <?php echo Html::encode($user->name); ?> + <?= Html::encode($user->name) ?> </div> ``` @@ -98,7 +97,7 @@ use yii\helpers\HtmlPurifier; ?> <div class="post"> - <?php echo HtmlPurifier::process($post->text); ?> + <?= HtmlPurifier::process($post->text) ?> </div> ``` @@ -114,7 +113,7 @@ to learn more refer to [Using template engines](template.md) section of the guid Using View object in templates ------------------------------ -An instance of `yii\base\View` component is available in view templates as `$this` variable. Using it in templates you +An instance of `yii\web\View` component is available in view templates as `$this` variable. Using it in templates you can do many useful things including setting page title and meta, registering scripts and accessing the context. ### Setting page title @@ -131,7 +130,7 @@ $this->title = 'My page title'; Adding meta tags such as encoding, description, keywords is easy with view object as well: ```php -$this->registerMetaTag(array('encoding' => 'utf-8')); +$this->registerMetaTag(['encoding' => 'utf-8']); ``` The first argument is an map of `<meta>` tag option names and values. The code above will produce: @@ -143,8 +142,8 @@ The first argument is an map of `<meta>` tag option names and values. The code a Sometimes there's a need to have only a single tag of a type. In this case you need to specify the second argument: ```html -$this->registerMetaTag(array('description' => 'This is my cool website made with Yii!'), 'meta-description'); -$this->registerMetaTag(array('description' => 'This website is about funny raccoons.'), 'meta-description'); +$this->registerMetaTag(['description' => 'This is my cool website made with Yii!'], 'meta-description'); +$this->registerMetaTag(['description' => 'This website is about funny raccoons.'], 'meta-description'); ``` If there are multiple calls with the same value of the second argument (`meta-description` in this case), the latter will @@ -160,12 +159,12 @@ override the former and only a single tag will be rendered: server. Yii view object has a method to work with these: ```php -$this->registerLinkTag(array( +$this->registerLinkTag([ 'title' => 'Lives News for Yii Framework', 'rel' => 'alternate', 'type' => 'application/rss+xml', 'href' => 'http://www.yiiframework.com/rss.xml/', -)); +]); ``` The code above will result in @@ -199,7 +198,7 @@ If you want to specify additional properties of the style tag, pass array of nam need to make sure there's only a single style tag use third argument as was mentioned in meta tags description. ```php -$this->registerCssFile("http://example.com/css/themes/black-and-white.css", array('media' => 'print'), 'css-print-theme'); +$this->registerCssFile("http://example.com/css/themes/black-and-white.css", ['media' => 'print'], 'css-print-theme'); ``` The code above will add a link to CSS file to the head section of the page. The CSS will be used only when printing the @@ -241,7 +240,7 @@ details on how to define asset bundles in [asset manager](assets.md) section of asset bundle, it's very straightforward: ```php -frontend\config\AppAsset::register($this); +frontend\assets\AppAsset::register($this); ``` ### Layout @@ -257,16 +256,16 @@ use yii\helpers\Html; ?> <?php $this->beginPage(); ?> <!DOCTYPE html> -<html lang="<?php echo Yii::$app->charset; ?>"> +<html lang="<?= Yii::$app->language ?>"> <head> - <meta charset="<?php echo Yii::$app->charset; ?>"/> - <title><?php echo Html::encode($this->title); ?></title> + <meta charset="<?= Yii::$app->charset ?>"/> + <title><?= Html::encode($this->title) ?></title> <?php $this->head(); ?> </head> <body> <?php $this->beginBody(); ?> <div class="container"> - <?php echo $content; ?> + <?= $content ?> </div> <footer class="footer">© 2013 me :)</footer> <?php $this->endBody(); ?> @@ -296,8 +295,8 @@ use yii\helpers\Html; ?> <div class="profile"> - <h2><?php echo Html::encode($username); ?></h2> - <p><?php echo Html::encode($tagline); ?></p> + <h2><?= Html::encode($username) ?></h2> + <p><?= Html::encode($tagline) ?></p> </div> ``` @@ -306,11 +305,11 @@ Then we're using it in `index.php` view where we display a list of users: ```php <div class="user-index"> <?php - foreach($users as $user) { - echo $this->render('_profile', array( + foreach ($users as $user) { + echo $this->render('_profile', [ 'username' => $user->name, 'tagline' => $user->tagline, - )); + ]); } ?> </div> @@ -319,10 +318,10 @@ Then we're using it in `index.php` view where we display a list of users: Same way we can reuse it in another view displaying a single user profile: ```php -echo $this->render('_profile', array( +echo $this->render('_profile', [ 'username' => $user->name, 'tagline' => $user->tagline, -)); +]); ``` ### Accessing context @@ -343,16 +342,16 @@ Customizing View component -------------------------- Since view is also an application component named `view` you can replace it with your own component that extends -from `yii\base\View`. It can be done via application configuration file such as `config/web.php`: +from `yii\base\View` or `yii\web\View`. It can be done via application configuration file such as `config/web.php`: ```php -return array( +return [ // ... - 'components' => array( - 'view' => array( + 'components' => [ + 'view' => [ 'class' => 'app\components\View', - ), + ], // ... - ), -); + ], +]; ``` diff --git a/docs/internals/ar.md b/docs/internals/ar.md index d59ba6a..d186939 100644 --- a/docs/internals/ar.md +++ b/docs/internals/ar.md @@ -9,16 +9,16 @@ Possible scenario formats supported by ActiveRecord: ```php public function scenarios() { - return array( + return [ // attributes array, all operations won't be wrapped with transaction - 'scenario1' => array('attribute1', 'attribute2'), + 'scenario1' => ['attribute1', 'attribute2'], // insert and update operations will be wrapped with transaction, delete won't be wrapped - 'scenario2' => array( - 'attributes' => array('attribute1', 'attribute2'), - 'atomic' => array(self::OP_INSERT, self::OP_UPDATE), - ), - ); + 'scenario2' => [ + 'attributes' => ['attribute1', 'attribute2'], + 'atomic' => [self::OP_INSERT, self::OP_UPDATE], + ], + ]; } ``` diff --git a/docs/internals/autoloader.md b/docs/internals/autoloader.md index b7696d7..76a545b 100644 --- a/docs/internals/autoloader.md +++ b/docs/internals/autoloader.md @@ -16,4 +16,4 @@ PEAR-style libraries References ---------- -- YiiBase::autoload \ No newline at end of file +- BaseYii::autoload \ No newline at end of file diff --git a/framework/yii/bootstrap/Alert.php b/extensions/bootstrap/Alert.php similarity index 89% rename from framework/yii/bootstrap/Alert.php rename to extensions/bootstrap/Alert.php index d57bcbe..29844bd 100644 --- a/framework/yii/bootstrap/Alert.php +++ b/extensions/bootstrap/Alert.php @@ -17,24 +17,22 @@ use yii\helpers\Html; * For example, * * ```php - * echo Alert::widget(array( + * echo Alert::widget([ * 'body' => 'Say hello...', - * 'closeButton' => array( + * 'closeButton' => [ * 'label' => '×', * 'tag' => 'a', - * ), - * )); + * ], + * ]); * ``` * * The following example will show the content enclosed between the [[begin()]] * and [[end()]] calls within the alert box: * * ```php - * Alert::begin(array( - * 'closeButton' => array( - * 'label' => '×', - * ), - * )); + * Alert::begin([ + * 'closeButton' => ['label' => '×'], + * ]); * * echo 'Say hello...'; * @@ -67,7 +65,7 @@ class Alert extends Widget * Please refer to the [Alert plugin help](http://twitter.github.com/bootstrap/javascript.html#alerts) * for the supported HTML attributes. */ - public $closeButton = array(); + public $closeButton = []; /** @@ -136,18 +134,16 @@ class Alert extends Widget */ protected function initOptions() { - $this->options = array_merge(array( - 'class' => 'fade in', - ), $this->options); + $this->options = array_merge(['class' => 'fade in'], $this->options); Html::addCssClass($this->options, 'alert'); if ($this->closeButton !== null) { - $this->closeButton = array_merge(array( + $this->closeButton = array_merge([ 'data-dismiss' => 'alert', 'aria-hidden' => 'true', 'class' => 'close', - ), $this->closeButton); + ], $this->closeButton); } } } diff --git a/framework/yii/bootstrap/BootstrapAsset.php b/extensions/bootstrap/BootstrapAsset.php similarity index 84% rename from framework/yii/bootstrap/BootstrapAsset.php rename to extensions/bootstrap/BootstrapAsset.php index 26dad31..27dabc0 100644 --- a/framework/yii/bootstrap/BootstrapAsset.php +++ b/extensions/bootstrap/BootstrapAsset.php @@ -15,8 +15,8 @@ use yii\web\AssetBundle; */ class BootstrapAsset extends AssetBundle { - public $sourcePath = '@yii/bootstrap/assets'; - public $css = array( + public $sourcePath = '@vendor/twbs/bootstrap/dist'; + public $css = [ 'css/bootstrap.css', - ); + ]; } diff --git a/framework/yii/bootstrap/BootstrapPluginAsset.php b/extensions/bootstrap/BootstrapPluginAsset.php similarity index 79% rename from framework/yii/bootstrap/BootstrapPluginAsset.php rename to extensions/bootstrap/BootstrapPluginAsset.php index 613e120..35bf18a 100644 --- a/framework/yii/bootstrap/BootstrapPluginAsset.php +++ b/extensions/bootstrap/BootstrapPluginAsset.php @@ -16,12 +16,12 @@ use yii\web\AssetBundle; */ class BootstrapPluginAsset extends AssetBundle { - public $sourcePath = '@yii/bootstrap/assets'; - public $js = array( + public $sourcePath = '@vendor/twbs/bootstrap/dist'; + public $js = [ 'js/bootstrap.js', - ); - public $depends = array( + ]; + public $depends = [ 'yii\web\JqueryAsset', 'yii\bootstrap\BootstrapAsset', - ); + ]; } diff --git a/framework/yii/bootstrap/BootstrapThemeAsset.php b/extensions/bootstrap/BootstrapThemeAsset.php similarity index 79% rename from framework/yii/bootstrap/BootstrapThemeAsset.php rename to extensions/bootstrap/BootstrapThemeAsset.php index d21f308..f15a0fb 100644 --- a/framework/yii/bootstrap/BootstrapThemeAsset.php +++ b/extensions/bootstrap/BootstrapThemeAsset.php @@ -17,11 +17,11 @@ use yii\web\AssetBundle; */ class BootstrapThemeAsset extends AssetBundle { - public $sourcePath = '@yii/bootstrap/assets'; - public $css = array( + public $sourcePath = '@vendor/twbs/bootstrap/dist'; + public $css = [ 'css/bootstrap-theme.css', - ); - public $depends = array( + ]; + public $depends = [ 'yii\bootstrap\BootstrapAsset', - ); + ]; } diff --git a/framework/yii/bootstrap/Button.php b/extensions/bootstrap/Button.php similarity index 94% rename from framework/yii/bootstrap/Button.php rename to extensions/bootstrap/Button.php index 5c45747..88acab7 100644 --- a/framework/yii/bootstrap/Button.php +++ b/extensions/bootstrap/Button.php @@ -15,10 +15,10 @@ use yii\helpers\Html; * For example, * * ```php - * echo Button::widget(array( + * echo Button::widget([ * 'label' => 'Action', - * 'options' => array('class' => 'btn-lg'), - * )); + * 'options' => ['class' => 'btn-lg'], + * ]); * ``` * @see http://twitter.github.io/bootstrap/javascript.html#buttons * @author Antonio Ramirez <amigo.cobos@gmail.com> diff --git a/framework/yii/bootstrap/ButtonDropdown.php b/extensions/bootstrap/ButtonDropdown.php similarity index 83% rename from framework/yii/bootstrap/ButtonDropdown.php rename to extensions/bootstrap/ButtonDropdown.php index 2e04cb6..1ffde7d 100644 --- a/framework/yii/bootstrap/ButtonDropdown.php +++ b/extensions/bootstrap/ButtonDropdown.php @@ -16,21 +16,15 @@ use yii\helpers\Html; * * ```php * // a button group using Dropdown widget - * echo ButtonDropdown::widget(array( + * echo ButtonDropdown::widget([ * 'label' => 'Action', - * 'dropdown' => array( - * 'items' => array( - * array( - * 'label' => 'DropdownA', - * 'url' => '/', - * ), - * array( - * 'label' => 'DropdownB', - * 'url' => '#', - * ), - * ), - * ), - * )); + * 'dropdown' => [ + * 'items' => [ + * ['label' => 'DropdownA', 'url' => '/'], + * ['label' => 'DropdownB', 'url' => '#'], + * ], + * ], + * ]); * ``` * @see http://twitter.github.io/bootstrap/javascript.html#buttons * @see http://twitter.github.io/bootstrap/components.html#buttonDropdowns @@ -46,11 +40,11 @@ class ButtonDropdown extends Widget /** * @var array the HTML attributes of the button. */ - public $options = array(); + public $options = []; /** * @var array the configuration array for [[Dropdown]]. */ - public $dropdown = array(); + public $dropdown = []; /** * @var boolean whether to display a group of split-styled button group. */ @@ -78,11 +72,11 @@ class ButtonDropdown extends Widget $options = $this->options; $this->options['data-toggle'] = 'dropdown'; Html::addCssClass($this->options, 'dropdown-toggle'); - $splitButton = Button::widget(array( + $splitButton = Button::widget([ 'label' => '<span class="caret"></span>', 'encodeLabel' => false, 'options' => $this->options, - )); + ]); } else { $tag = 'a'; $this->label .= ' <span class="caret"></span>'; @@ -94,12 +88,12 @@ class ButtonDropdown extends Widget $options['data-toggle'] = 'dropdown'; $splitButton = ''; } - return Button::widget(array( + return Button::widget([ 'tagName' => $tag, 'label' => $this->label, 'options' => $options, 'encodeLabel' => false, - )) . "\n" . $splitButton; + ]) . "\n" . $splitButton; } /** diff --git a/framework/yii/bootstrap/ButtonGroup.php b/extensions/bootstrap/ButtonGroup.php similarity index 82% rename from framework/yii/bootstrap/ButtonGroup.php rename to extensions/bootstrap/ButtonGroup.php index f2656c0..4fc2eb3 100644 --- a/framework/yii/bootstrap/ButtonGroup.php +++ b/extensions/bootstrap/ButtonGroup.php @@ -17,20 +17,20 @@ use yii\helpers\Html; * * ```php * // a button group with items configuration - * echo ButtonGroup::::widget(array( - * 'buttons' => array( - * array('label' => 'A'), - * array('label' => 'B'), - * ) - * )); + * echo ButtonGroup::widget([ + * 'buttons' => [ + * ['label' => 'A'], + * ['label' => 'B'], + * ] + * ]); * * // button group with an item as a string - * echo ButtonGroup::::widget(array( - * 'buttons' => array( - * Button::widget(array('label' => 'A')), - * array('label' => 'B'), - * ) - * )); + * echo ButtonGroup::widget([ + * 'buttons' => [ + * Button::widget(['label' => 'A']), + * ['label' => 'B'], + * ] + * ]); * ``` * @see http://twitter.github.io/bootstrap/javascript.html#buttons * @see http://twitter.github.io/bootstrap/components.html#buttonGroups @@ -46,7 +46,7 @@ class ButtonGroup extends Widget * - label: string, required, the button label. * - options: array, optional, the HTML attributes of the button. */ - public $buttons = array(); + public $buttons = []; /** * @var boolean whether to HTML-encode the button labels. */ @@ -78,16 +78,16 @@ class ButtonGroup extends Widget */ protected function renderButtons() { - $buttons = array(); + $buttons = []; foreach ($this->buttons as $button) { if (is_array($button)) { $label = ArrayHelper::getValue($button, 'label'); $options = ArrayHelper::getValue($button, 'options'); - $buttons[] = Button::widget(array( + $buttons[] = Button::widget([ 'label' => $label, 'options' => $options, 'encodeLabel' => $this->encodeLabels - )); + ]); } else { $buttons[] = $button; } diff --git a/framework/yii/bootstrap/Carousel.php b/extensions/bootstrap/Carousel.php similarity index 79% rename from framework/yii/bootstrap/Carousel.php rename to extensions/bootstrap/Carousel.php index ec9a0c9..8344929 100644 --- a/framework/yii/bootstrap/Carousel.php +++ b/extensions/bootstrap/Carousel.php @@ -17,22 +17,20 @@ use yii\helpers\Html; * For example: * * ```php - * echo Carousel::widget(array( - * 'items' => array( + * echo Carousel::widget([ + * 'items' => [ * // the item contains only the image * '<img src="http://twitter.github.io/bootstrap/assets/img/bootstrap-mdo-sfmoma-01.jpg"/>', * // equivalent to the above - * array( - * 'content' => '<img src="http://twitter.github.io/bootstrap/assets/img/bootstrap-mdo-sfmoma-02.jpg"/>', - * ), + * ['content' => '<img src="http://twitter.github.io/bootstrap/assets/img/bootstrap-mdo-sfmoma-02.jpg"/>'], * // the item contains both the image and the caption - * array( + * [ * 'content' => '<img src="http://twitter.github.io/bootstrap/assets/img/bootstrap-mdo-sfmoma-03.jpg"/>', * 'caption' => '<h4>This is title</h4><p>This is the caption text</p>', - * 'options' => array(...), - * ), - * ) - * )); + * 'options' => [...], + * ], + * ] + * ]); * ``` * * @see http://twitter.github.io/bootstrap/javascript.html#carousel @@ -45,23 +43,23 @@ class Carousel extends Widget * @var array|boolean the labels for the previous and the next control buttons. * If false, it means the previous and the next control buttons should not be displayed. */ - public $controls = array('‹', '›'); + public $controls = ['‹', '›']; /** * @var array list of slides in the carousel. Each array element represents a single * slide with the following structure: * * ```php - * array( + * [ * // required, slide content (HTML), such as an image tag * 'content' => '<img src="http://twitter.github.io/bootstrap/assets/img/bootstrap-mdo-sfmoma-01.jpg"/>', * // optional, the caption (HTML) of the slide * 'caption' => '<h4>This is title</h4><p>This is the caption text</p>', * // optional the HTML attributes of the slide container - * 'options' => array(), - * ) + * 'options' => [], + * ] * ``` */ - public $items = array(); + public $items = []; /** @@ -92,15 +90,15 @@ class Carousel extends Widget */ public function renderIndicators() { - $indicators = array(); + $indicators = []; for ($i = 0, $count = count($this->items); $i < $count; $i++) { - $options = array('data-target' => '#' . $this->options['id'], 'data-slide-to' => $i); + $options = ['data-target' => '#' . $this->options['id'], 'data-slide-to' => $i]; if ($i === 0) { Html::addCssClass($options, 'active'); } $indicators[] = Html::tag('li', '', $options); } - return Html::tag('ol', implode("\n", $indicators), array('class' => 'carousel-indicators')); + return Html::tag('ol', implode("\n", $indicators), ['class' => 'carousel-indicators']); } /** @@ -109,11 +107,11 @@ class Carousel extends Widget */ public function renderItems() { - $items = array(); + $items = []; for ($i = 0, $count = count($this->items); $i < $count; $i++) { $items[] = $this->renderItem($this->items[$i], $i); } - return Html::tag('div', implode("\n", $items), array('class' => 'carousel-inner')); + return Html::tag('div', implode("\n", $items), ['class' => 'carousel-inner']); } /** @@ -128,14 +126,14 @@ class Carousel extends Widget if (is_string($item)) { $content = $item; $caption = null; - $options = array(); + $options = []; } elseif (isset($item['content'])) { $content = $item['content']; $caption = ArrayHelper::getValue($item, 'caption'); if ($caption !== null) { - $caption = Html::tag('div', $caption, array('class' => 'carousel-caption')); + $caption = Html::tag('div', $caption, ['class' => 'carousel-caption']); } - $options = ArrayHelper::getValue($item, 'options', array()); + $options = ArrayHelper::getValue($item, 'options', []); } else { throw new InvalidConfigException('The "content" option is required.'); } @@ -155,14 +153,14 @@ class Carousel extends Widget public function renderControls() { if (isset($this->controls[0], $this->controls[1])) { - return Html::a($this->controls[0], '#' . $this->options['id'], array( + return Html::a($this->controls[0], '#' . $this->options['id'], [ 'class' => 'left carousel-control', 'data-slide' => 'prev', - )) . "\n" - . Html::a($this->controls[1], '#' . $this->options['id'], array( + ]) . "\n" + . Html::a($this->controls[1], '#' . $this->options['id'], [ 'class' => 'right carousel-control', 'data-slide' => 'next', - )); + ]); } elseif ($this->controls === false) { return ''; } else { diff --git a/framework/yii/bootstrap/Collapse.php b/extensions/bootstrap/Collapse.php similarity index 79% rename from framework/yii/bootstrap/Collapse.php rename to extensions/bootstrap/Collapse.php index 77fdde7..08bde22 100644 --- a/framework/yii/bootstrap/Collapse.php +++ b/extensions/bootstrap/Collapse.php @@ -17,22 +17,22 @@ use yii\helpers\Html; * For example: * * ```php - * echo Collapse::widget(array( - * 'items' => array( + * echo Collapse::widget([ + * 'items' => [ * // equivalent to the above - * 'Collapsible Group Item #1' => array( + * 'Collapsible Group Item #1' => [ * 'content' => 'Anim pariatur cliche...', * // open its content by default - * 'contentOptions' => array('class' => 'in') - * ), + * 'contentOptions' => ['class' => 'in'] + * ], * // another group item - * 'Collapsible Group Item #2' => array( + * 'Collapsible Group Item #2' => [ * 'content' => 'Anim pariatur cliche...', - * 'contentOptions' => array(...), - * 'options' => array(...), - * ), - * ) - * )); + * 'contentOptions' => [...], + * 'options' => [...], + * ], + * ] + * ]); * ``` * * @see http://twitter.github.io/bootstrap/javascript.html#collapse @@ -47,17 +47,17 @@ class Collapse extends Widget * * ```php * // item key is the actual group header - * 'Collapsible Group Item #1' => array( + * 'Collapsible Group Item #1' => [ * // required, the content (HTML) of the group * 'content' => 'Anim pariatur cliche...', * // optional the HTML attributes of the content group - * 'contentOptions' => array(), + * 'contentOptions' => [], * // optional the HTML attributes of the group - * 'options' => array(), - * ) + * 'options' => [], + * ] * ``` */ - public $items = array(); + public $items = []; /** @@ -86,10 +86,10 @@ class Collapse extends Widget */ public function renderItems() { - $items = array(); + $items = []; $index = 0; foreach ($this->items as $header => $item) { - $options = ArrayHelper::getValue($item, 'options', array()); + $options = ArrayHelper::getValue($item, 'options', []); Html::addCssClass($options, 'accordion-group'); $items[] = Html::tag('div', $this->renderItem($header, $item, ++$index), $options); } @@ -109,23 +109,23 @@ class Collapse extends Widget { if (isset($item['content'])) { $id = $this->options['id'] . '-collapse' . $index; - $options = ArrayHelper::getValue($item, 'contentOptions', array()); + $options = ArrayHelper::getValue($item, 'contentOptions', []); $options['id'] = $id; Html::addCssClass($options, 'accordion-body collapse'); - $header = Html::a($header, '#' . $id, array( + $header = Html::a($header, '#' . $id, [ 'class' => 'accordion-toggle', 'data-toggle' => 'collapse', 'data-parent' => '#' . $this->options['id'] - )) . "\n"; + ]) . "\n"; - $content = Html::tag('div', $item['content'], array('class' => 'accordion-inner')) . "\n"; + $content = Html::tag('div', $item['content'], ['class' => 'accordion-inner']) . "\n"; } else { throw new InvalidConfigException('The "content" option is required.'); } - $group = array(); + $group = []; - $group[] = Html::tag('div', $header, array('class' => 'accordion-heading')); + $group[] = Html::tag('div', $header, ['class' => 'accordion-heading']); $group[] = Html::tag('div', $content, $options); return implode("\n", $group); diff --git a/framework/yii/bootstrap/Dropdown.php b/extensions/bootstrap/Dropdown.php similarity index 94% rename from framework/yii/bootstrap/Dropdown.php rename to extensions/bootstrap/Dropdown.php index 1cc389e..fecfb0b 100644 --- a/framework/yii/bootstrap/Dropdown.php +++ b/extensions/bootstrap/Dropdown.php @@ -29,8 +29,10 @@ class Dropdown extends Widget * - visible: boolean, optional, whether this menu item is visible. Defaults to true. * - linkOptions: array, optional, the HTML attributes of the item link. * - options: array, optional, the HTML attributes of the item. + * + * To insert divider use `<li role="presentation" class="divider"></li>`. */ - public $items = array(); + public $items = []; /** * @var boolean whether the labels for header items should be HTML-encoded. */ @@ -64,7 +66,7 @@ class Dropdown extends Widget */ protected function renderItems($items) { - $lines = array(); + $lines = []; foreach ($items as $i => $item) { if (isset($item['visible']) && !$item['visible']) { unset($items[$i]); @@ -78,8 +80,8 @@ class Dropdown extends Widget throw new InvalidConfigException("The 'label' option is required."); } $label = $this->encodeLabels ? Html::encode($item['label']) : $item['label']; - $options = ArrayHelper::getValue($item, 'options', array()); - $linkOptions = ArrayHelper::getValue($item, 'linkOptions', array()); + $options = ArrayHelper::getValue($item, 'options', []); + $linkOptions = ArrayHelper::getValue($item, 'linkOptions', []); $linkOptions['tabindex'] = '-1'; $content = Html::a($label, ArrayHelper::getValue($item, 'url', '#'), $linkOptions); $lines[] = Html::tag('li', $content, $options); diff --git a/framework/yii/bootstrap/Modal.php b/extensions/bootstrap/Modal.php similarity index 88% rename from framework/yii/bootstrap/Modal.php rename to extensions/bootstrap/Modal.php index 7dfac70..94a3997 100644 --- a/framework/yii/bootstrap/Modal.php +++ b/extensions/bootstrap/Modal.php @@ -18,12 +18,10 @@ use yii\helpers\Html; * and [[end()]] calls within the modal window: * * ~~~php - * Modal::begin(array( + * Modal::begin([ * 'header' => '<h2>Hello world</h2>', - * 'toggleButton' => array( - * 'label' => 'click me', - * ), - * )); + * 'toggleButton' => ['label' => 'click me'], + * ]); * * echo 'Say hello...'; * @@ -59,7 +57,7 @@ class Modal extends Widget * Please refer to the [Modal plugin help](http://twitter.github.com/bootstrap/javascript.html#modals) * for the supported HTML attributes. */ - public $closeButton = array(); + public $closeButton = []; /** * @var array the options for rendering the toggle button tag. * The toggle button is used to toggle the visibility of the modal window. @@ -88,8 +86,8 @@ class Modal extends Widget echo $this->renderToggleButton() . "\n"; echo Html::beginTag('div', $this->options) . "\n"; - echo Html::beginTag('div', array('class' => 'modal-dialog')) . "\n"; - echo Html::beginTag('div', array('class' => 'modal-content')) . "\n"; + echo Html::beginTag('div', ['class' => 'modal-dialog']) . "\n"; + echo Html::beginTag('div', ['class' => 'modal-content']) . "\n"; echo $this->renderHeader() . "\n"; echo $this->renderBodyBegin() . "\n"; } @@ -119,7 +117,7 @@ class Modal extends Widget $this->header = $button . "\n" . $this->header; } if ($this->header !== null) { - return Html::tag('div', "\n" . $this->header . "\n", array('class' => 'modal-header')); + return Html::tag('div', "\n" . $this->header . "\n", ['class' => 'modal-header']); } else { return null; } @@ -131,7 +129,7 @@ class Modal extends Widget */ protected function renderBodyBegin() { - return Html::beginTag('div', array('class' => 'modal-body')); + return Html::beginTag('div', ['class' => 'modal-body']); } /** @@ -150,7 +148,7 @@ class Modal extends Widget protected function renderFooter() { if ($this->footer !== null) { - return Html::tag('div', "\n" . $this->footer . "\n", array('class' => 'modal-footer')); + return Html::tag('div', "\n" . $this->footer . "\n", ['class' => 'modal-footer']); } else { return null; } @@ -198,29 +196,29 @@ class Modal extends Widget */ protected function initOptions() { - $this->options = array_merge(array( + $this->options = array_merge([ 'class' => 'fade', 'role' => 'dialog', 'tabindex' => -1, - ), $this->options); + ], $this->options); Html::addCssClass($this->options, 'modal'); - - $this->clientOptions = array_merge(array( - 'show' => false, - ), $this->clientOptions); + + if ($this->clientOptions !== false) { + $this->clientOptions = array_merge(['show' => false], $this->clientOptions); + } if ($this->closeButton !== null) { - $this->closeButton = array_merge(array( + $this->closeButton = array_merge([ 'data-dismiss' => 'modal', 'aria-hidden' => 'true', 'class' => 'close', - ), $this->closeButton); + ], $this->closeButton); } if ($this->toggleButton !== null) { - $this->toggleButton = array_merge(array( + $this->toggleButton = array_merge([ 'data-toggle' => 'modal', - ), $this->toggleButton); + ], $this->toggleButton); if (!isset($this->toggleButton['data-target']) && !isset($this->toggleButton['href'])) { $this->toggleButton['data-target'] = '#' . $this->options['id']; } diff --git a/framework/yii/bootstrap/Nav.php b/extensions/bootstrap/Nav.php similarity index 77% rename from framework/yii/bootstrap/Nav.php rename to extensions/bootstrap/Nav.php index f24f729..8f0a98d 100644 --- a/framework/yii/bootstrap/Nav.php +++ b/extensions/bootstrap/Nav.php @@ -14,41 +14,35 @@ use yii\helpers\Html; /** * Nav renders a nav HTML component. - * + * * For example: * * ```php - * echo Nav::widget(array( - * 'items' => array( - * array( + * echo Nav::widget([ + * 'items' => [ + * [ * 'label' => 'Home', - * 'url' => array('site/index'), - * 'linkOptions' => array(...), - * ), - * array( + * 'url' => ['site/index'], + * 'linkOptions' => [...], + * ], + * [ * 'label' => 'Dropdown', - * 'items' => array( - * array( - * 'label' => 'Level 1 -DropdownA', - * 'url' => '#', - * 'items' => array( - * array( - * 'label' => 'Level 2 -DropdownA', - * 'url' => '#', - * ), - * ), - * ), - * array( - * 'label' => 'Level 1 -DropdownB', - * 'url' => '#', - * ), - * ), - * ), - * ), - * )); + * 'items' => [ + * ['label' => 'Level 1 - Dropdown A', 'url' => '#'], + * '<li class="divider"></li>', + * '<li class="dropdown-header">Dropdown Header</li>', + * ['label' => 'Level 1 - Dropdown B', 'url' => '#'], + * ], + * ], + * ], + * ]); * ``` - * - * @see http://twitter.github.io/bootstrap/components.html#nav + * + * Note: Multilevel dropdowns beyond Level 1 are not supported in Bootstrap 3. + * + * @see http://getbootstrap.com/components.html#dropdowns + * @see http://getbootstrap.com/components/#nav + * * @author Antonio Ramirez <amigo.cobos@gmail.com> * @since 2.0 */ @@ -69,7 +63,7 @@ class Nav extends Widget * * If a menu item is a string, it will be rendered directly without HTML encoding. */ - public $items = array(); + public $items = []; /** * @var boolean whether the nav items labels should be HTML-encoded. */ @@ -125,7 +119,7 @@ class Nav extends Widget */ public function renderItems() { - $items = array(); + $items = []; foreach ($this->items as $i => $item) { if (isset($item['visible']) && !$item['visible']) { unset($items[$i]); @@ -152,10 +146,10 @@ class Nav extends Widget throw new InvalidConfigException("The 'label' option is required."); } $label = $this->encodeLabels ? Html::encode($item['label']) : $item['label']; - $options = ArrayHelper::getValue($item, 'options', array()); + $options = ArrayHelper::getValue($item, 'options', []); $items = ArrayHelper::getValue($item, 'items'); $url = Html::url(ArrayHelper::getValue($item, 'url', '#')); - $linkOptions = ArrayHelper::getValue($item, 'linkOptions', array()); + $linkOptions = ArrayHelper::getValue($item, 'linkOptions', []); if (isset($item['active'])) { $active = ArrayHelper::remove($item, 'active', false); @@ -171,13 +165,13 @@ class Nav extends Widget $linkOptions['data-toggle'] = 'dropdown'; Html::addCssClass($options, 'dropdown'); Html::addCssClass($urlOptions, 'dropdown-toggle'); - $label .= ' ' . Html::tag('b', '', array('class' => 'caret')); + $label .= ' ' . Html::tag('b', '', ['class' => 'caret']); if (is_array($items)) { - $items = Dropdown::widget(array( + $items = Dropdown::widget([ 'items' => $items, 'encodeLabels' => $this->encodeLabels, 'clientOptions' => false, - )); + ]); } } diff --git a/framework/yii/bootstrap/NavBar.php b/extensions/bootstrap/NavBar.php similarity index 85% rename from framework/yii/bootstrap/NavBar.php rename to extensions/bootstrap/NavBar.php index 7aafbbb..620f51e 100644 --- a/framework/yii/bootstrap/NavBar.php +++ b/extensions/bootstrap/NavBar.php @@ -20,13 +20,13 @@ use yii\helpers\Html; * use yii\bootstrap\NavBar; * use yii\widgets\Menu; * - * NavBar::begin(array('brandLabel' => 'NavBar Test')); - * echo Nav::widget(array( - * 'items' => array( - * array('label' => 'Home', 'url' => array('/site/index')), - * array('label' => 'About', 'url' => array('/site/about')), - * ), - * )); + * NavBar::begin(['brandLabel' => 'NavBar Test']); + * echo Nav::widget([ + * 'items' => [ + * ['label' => 'Home', 'url' => ['/site/index']], + * ['label' => 'About', 'url' => ['/site/about']], + * ], + * ]); * NavBar::end(); * ``` * @@ -49,7 +49,7 @@ class NavBar extends Widget /** * @var array the HTML attributes of the brand link. */ - public $brandOptions = array(); + public $brandOptions = []; public $screenReaderToggleText = 'Toggle navigation'; @@ -67,16 +67,16 @@ class NavBar extends Widget } echo Html::beginTag('nav', $this->options); - echo Html::beginTag('div', array('class' => 'container')); + echo Html::beginTag('div', ['class' => 'container']); - echo Html::beginTag('div', array('class' => 'navbar-header')); + echo Html::beginTag('div', ['class' => 'navbar-header']); echo $this->renderToggleButton(); if ($this->brandLabel !== null) { echo Html::a($this->brandLabel, $this->brandUrl, $this->brandOptions); } echo Html::endTag('div'); - echo Html::beginTag('div', array('class' => 'collapse navbar-collapse navbar-ex1-collapse')); + echo Html::beginTag('div', ['class' => 'collapse navbar-collapse navbar-ex1-collapse']); } /** @@ -97,12 +97,12 @@ class NavBar extends Widget */ protected function renderToggleButton() { - $bar = Html::tag('span', '', array('class' => 'icon-bar')); + $bar = Html::tag('span', '', ['class' => 'icon-bar']); $screenReader = '<span class="sr-only">'.$this->screenReaderToggleText.'</span>'; - return Html::button("{$screenReader}\n{$bar}\n{$bar}\n{$bar}", array( + return Html::button("{$screenReader}\n{$bar}\n{$bar}\n{$bar}", [ 'class' => 'navbar-toggle', 'data-toggle' => 'collapse', 'data-target' => '.navbar-ex1-collapse', - )); + ]); } } diff --git a/framework/yii/bootstrap/Progress.php b/extensions/bootstrap/Progress.php similarity index 80% rename from framework/yii/bootstrap/Progress.php rename to extensions/bootstrap/Progress.php index d8e5a69..184451c 100644 --- a/framework/yii/bootstrap/Progress.php +++ b/extensions/bootstrap/Progress.php @@ -18,39 +18,39 @@ use yii\helpers\Html; * * ```php * // default with label - * echo Progress::widget(array( + * echo Progress::widget([ * 'percent' => 60, * 'label' => 'test', - * )); + * ]); * * // styled - * echo Progress::widget(array( + * echo Progress::widget([ * 'percent' => 65, - * 'barOptions' => array('class' => 'bar-danger') - * )); + * 'barOptions' => ['class' => 'bar-danger'] + * ]); * * // striped - * echo Progress::widget(array( + * echo Progress::widget([ * 'percent' => 70, - * 'barOptions' => array('class' => 'bar-warning'), - * 'options' => array('class' => 'progress-striped') - * )); + * 'barOptions' => ['class' => 'bar-warning'], + * 'options' => ['class' => 'progress-striped'] + * ]); * * // striped animated - * echo Progress::widget(array( + * echo Progress::widget([ * 'percent' => 70, - * 'barOptions' => array('class' => 'bar-success'), - * 'options' => array('class' => 'active progress-striped') - * )); + * 'barOptions' => ['class' => 'bar-success'], + * 'options' => ['class' => 'active progress-striped'] + * ]); * * // stacked bars - * echo Progress::widget(array( - * 'bars' => array( - * array('percent' => 30, 'options' => array('class' => 'bar-danger')), - * array('percent' => 30, 'label' => 'test', 'options' => array('class' => 'bar-success')), - * array('percent' => 35, 'options' => array('class' => 'bar-warning')) - * ) - * )); + * echo Progress::widget([ + * 'bars' => [ + * ['percent' => 30, 'options' => ['class' => 'bar-danger']], + * ['percent' => 30, 'label' => 'test', 'options' => ['class' => 'bar-success']], + * ['percent' => 35, 'options' => ['class' => 'bar-warning']], + * ] + * ]); * ``` * @see http://twitter.github.io/bootstrap/components.html#progress * @author Antonio Ramirez <amigo.cobos@gmail.com> @@ -69,20 +69,20 @@ class Progress extends Widget /** * @var array the HTML attributes of the */ - public $barOptions = array(); + public $barOptions = []; /** * @var array a set of bars that are stacked together to form a single progress bar. * Each bar is an array of the following structure: * * ```php - * array( + * [ * // required, the amount of progress as a percentage. * 'percent' => 30, * // optional, the label to be displayed on the bar * 'label' => '30%', * // optional, array, additional HTML attributes for the bar tag - * 'options' => array(), - * ) + * 'options' => [], + * ] */ public $bars; @@ -118,13 +118,13 @@ class Progress extends Widget if (empty($this->bars)) { return $this->renderBar($this->percent, $this->label, $this->barOptions); } - $bars = array(); + $bars = []; foreach ($this->bars as $bar) { $label = ArrayHelper::getValue($bar, 'label', ''); if (!isset($bar['percent'])) { throw new InvalidConfigException("The 'percent' option is required."); } - $options = ArrayHelper::getValue($bar, 'options', array()); + $options = ArrayHelper::getValue($bar, 'options', []); $bars[] = $this->renderBar($bar['percent'], $label, $options); } return implode("\n", $bars); @@ -137,7 +137,7 @@ class Progress extends Widget * @param array $options the HTML attributes of the bar * @return string the rendering result. */ - protected function renderBar($percent, $label = '', $options = array()) + protected function renderBar($percent, $label = '', $options = []) { Html::addCssClass($options, 'bar'); $options['style'] = "width:{$percent}%"; diff --git a/extensions/bootstrap/README.md b/extensions/bootstrap/README.md new file mode 100644 index 0000000..e199f64 --- /dev/null +++ b/extensions/bootstrap/README.md @@ -0,0 +1,32 @@ +Twitter Bootstrap Extension for Yii 2 +===================================== + +This is the Twitter Bootstrap extension for Yii 2. It encapsulates Bootstrap components +and plugins in terms of Yii widgets, and thus makes using Bootstrap components/plugins +in Yii applications extremely easy. For example, the following +single line of code in a view file would render a Bootstrap Progress plugin: + +```php +<?= yii\bootstrap\Progress::widget(['percent' => 60, 'label' => 'test']) ?> +``` + + +Installation +------------ + +The preferred way to install this extension is through [composer](http://getcomposer.org/download/). + +Either run + +``` +php composer.phar require yiisoft/yii2-bootstrap "*" +``` + +or add + +``` +"yiisoft/yii2-bootstrap": "*" +``` + +to the require section of your `composer.json` file. + diff --git a/framework/yii/bootstrap/Tabs.php b/extensions/bootstrap/Tabs.php similarity index 74% rename from framework/yii/bootstrap/Tabs.php rename to extensions/bootstrap/Tabs.php index d6ddd7b..2e76398 100644 --- a/framework/yii/bootstrap/Tabs.php +++ b/extensions/bootstrap/Tabs.php @@ -17,34 +17,34 @@ use yii\helpers\Html; * For example: * * ```php - * echo Tabs::widget(array( - * 'items' => array( - * array( + * echo Tabs::widget([ + * 'items' => [ + * [ * 'label' => 'One', * 'content' => 'Anim pariatur cliche...', * 'active' => true - * ), - * array( + * ], + * [ * 'label' => 'Two', * 'content' => 'Anim pariatur cliche...', - * 'headerOptions' => array(...), - * 'options' => array('id' => 'myveryownID'), - * ), - * array( + * 'headerOptions' => [...], + * 'options' => ['id' => 'myveryownID'], + * ], + * [ * 'label' => 'Dropdown', - * 'items' => array( - * array( + * 'items' => [ + * [ * 'label' => 'DropdownA', * 'content' => 'DropdownA, Anim pariatur cliche...', - * ), - * array( + * ], + * [ * 'label' => 'DropdownB', * 'content' => 'DropdownB, Anim pariatur cliche...', - * ), - * ), - * ), - * ), - * )); + * ], + * ], + * ], + * ], + * ]); * ``` * * @see http://twitter.github.io/bootstrap/javascript.html#tabs @@ -68,23 +68,27 @@ class Tabs extends Widget * * content: string, required if `items` is not set. The content (HTML) of the tab pane. * * contentOptions: optional, array, the HTML attributes of the tab content container. */ - public $items = array(); + public $items = []; /** * @var array list of HTML attributes for the item container tags. This will be overwritten * by the "options" set in individual [[items]]. The following special options are recognized: * * - tag: string, defaults to "div", the tag name of the item container tags. */ - public $itemOptions = array(); + public $itemOptions = []; /** * @var array list of HTML attributes for the header container tags. This will be overwritten * by the "headerOptions" set in individual [[items]]. */ - public $headerOptions = array(); + public $headerOptions = []; /** * @var boolean whether the labels for header items should be HTML-encoded. */ public $encodeLabels = true; + /** + * @var string, specifies the Bootstrap tab styling. + */ + public $navType = 'nav-tabs'; /** @@ -93,7 +97,7 @@ class Tabs extends Widget public function init() { parent::init(); - Html::addCssClass($this->options, 'nav nav-tabs'); + Html::addCssClass($this->options, 'nav ' . $this->navType); } /** @@ -112,14 +116,14 @@ class Tabs extends Widget */ protected function renderItems() { - $headers = array(); - $panes = array(); + $headers = []; + $panes = []; foreach ($this->items as $n => $item) { if (!isset($item['label'])) { throw new InvalidConfigException("The 'label' option is required."); } $label = $this->encodeLabels ? Html::encode($item['label']) : $item['label']; - $headerOptions = array_merge($this->headerOptions, ArrayHelper::getValue($item, 'headerOptions', array())); + $headerOptions = array_merge($this->headerOptions, ArrayHelper::getValue($item, 'headerOptions', [])); if (isset($item['items'])) { $label .= ' <b class="caret"></b>'; @@ -129,10 +133,10 @@ class Tabs extends Widget Html::addCssClass($headerOptions, 'active'); } - $header = Html::a($label, "#", array('class' => 'dropdown-toggle', 'data-toggle' => 'dropdown')) . "\n" - . Dropdown::widget(array('items' => $item['items'], 'clientOptions' => false)); + $header = Html::a($label, "#", ['class' => 'dropdown-toggle', 'data-toggle' => 'dropdown']) . "\n" + . Dropdown::widget(['items' => $item['items'], 'clientOptions' => false]); } elseif (isset($item['content'])) { - $options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', array())); + $options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', [])); $options['id'] = ArrayHelper::getValue($options, 'id', $this->options['id'] . '-tab' . $n); Html::addCssClass($options, 'tab-pane'); @@ -140,7 +144,7 @@ class Tabs extends Widget Html::addCssClass($options, 'active'); Html::addCssClass($headerOptions, 'active'); } - $header = Html::a($label, '#' . $options['id'], array('data-toggle' => 'tab')); + $header = Html::a($label, '#' . $options['id'], ['data-toggle' => 'tab']); $panes[] = Html::tag('div', $item['content'], $options); } else { throw new InvalidConfigException("Either the 'content' or 'items' option must be set."); @@ -150,7 +154,7 @@ class Tabs extends Widget } return Html::tag('ul', implode("\n", $headers), $this->options) . "\n" - . Html::tag('div', implode("\n", $panes), array('class' => 'tab-content')); + . Html::tag('div', implode("\n", $panes), ['class' => 'tab-content']); } /** @@ -174,7 +178,7 @@ class Tabs extends Widget } $content = ArrayHelper::remove($item, 'content'); - $options = ArrayHelper::remove($item, 'contentOptions', array()); + $options = ArrayHelper::remove($item, 'contentOptions', []); Html::addCssClass($options, 'tab-pane'); if (ArrayHelper::remove($item, 'active')) { Html::addCssClass($options, 'active'); diff --git a/framework/yii/bootstrap/Widget.php b/extensions/bootstrap/Widget.php similarity index 94% rename from framework/yii/bootstrap/Widget.php rename to extensions/bootstrap/Widget.php index 2959f09..ff4084d 100644 --- a/framework/yii/bootstrap/Widget.php +++ b/extensions/bootstrap/Widget.php @@ -8,7 +8,6 @@ namespace yii\bootstrap; use Yii; -use yii\base\View; use yii\helpers\Json; /** @@ -23,21 +22,21 @@ class Widget extends \yii\base\Widget /** * @var array the HTML attributes for the widget container tag. */ - public $options = array(); + public $options = []; /** * @var array the options for the underlying Bootstrap JS plugin. * Please refer to the corresponding Bootstrap plugin Web page for possible options. * For example, [this page](http://twitter.github.io/bootstrap/javascript.html#modals) shows * how to use the "Modal" plugin and the supported options (e.g. "remote"). */ - public $clientOptions = array(); + public $clientOptions = []; /** * @var array the event handlers for the underlying Bootstrap JS plugin. * Please refer to the corresponding Bootstrap plugin Web page for possible events. * For example, [this page](http://twitter.github.io/bootstrap/javascript.html#modals) shows * how to use the "Modal" plugin and the supported events (e.g. "shown"). */ - public $clientEvents = array(); + public $clientEvents = []; /** @@ -72,7 +71,7 @@ class Widget extends \yii\base\Widget } if (!empty($this->clientEvents)) { - $js = array(); + $js = []; foreach ($this->clientEvents as $event => $handler) { $js[] = "jQuery('#$id').on('$event', $handler);"; } diff --git a/extensions/mutex/composer.json b/extensions/bootstrap/composer.json similarity index 61% rename from extensions/mutex/composer.json rename to extensions/bootstrap/composer.json index db8cde3..e80de80 100644 --- a/extensions/mutex/composer.json +++ b/extensions/bootstrap/composer.json @@ -1,8 +1,8 @@ { - "name": "yiisoft/yii2-mutex", - "description": "Mutual exclusion extension for the Yii framework", - "keywords": ["yii", "mutex"], - "type": "library", + "name": "yiisoft/yii2-bootstrap", + "description": "The Twitter Bootstrap extension for the Yii framework", + "keywords": ["yii", "bootstrap"], + "type": "yii2-extension", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/yiisoft/yii2/issues?state=open", @@ -13,15 +13,16 @@ }, "authors": [ { - "name": "resurtm", - "email": "resurtm@gmail.com" + "name": "Qiang Xue", + "email": "qiang.xue@gmail.com" } ], - "minimum-stability": "dev", "require": { - "yiisoft/yii2": "*" + "yiisoft/yii2": "*", + "twbs/bootstrap": "3.0.*" }, "autoload": { - "psr-0": { "yii\\mutex": "" } - } + "psr-0": { "yii\\bootstrap\\": "" } + }, + "target-dir": "yii/bootstrap" } diff --git a/extensions/composer/Installer.php b/extensions/composer/Installer.php new file mode 100644 index 0000000..fbbe329 --- /dev/null +++ b/extensions/composer/Installer.php @@ -0,0 +1,182 @@ +<?php +/** + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\composer; + +use Composer\Package\PackageInterface; +use Composer\Installer\LibraryInstaller; +use Composer\Repository\InstalledRepositoryInterface; +use Composer\Script\CommandEvent; +use Composer\Util\Filesystem; + +/** + * @author Qiang Xue <qiang.xue@gmail.com> + * @since 2.0 + */ +class Installer extends LibraryInstaller +{ + const EXTRA_BOOTSTRAP = 'bootstrap'; + const EXTRA_WRITABLE = 'writable'; + const EXTRA_EXECUTABLE = 'executable'; + + const EXTENSION_FILE = 'yiisoft/extensions.php'; + + /** + * @inheritdoc + */ + public function supports($packageType) + { + return $packageType === 'yii2-extension'; + } + + /** + * @inheritdoc + */ + public function install(InstalledRepositoryInterface $repo, PackageInterface $package) + { + parent::install($repo, $package); + $this->addPackage($package); + } + + /** + * @inheritdoc + */ + public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) + { + parent::update($repo, $initial, $target); + $this->removePackage($initial); + $this->addPackage($target); + } + + /** + * @inheritdoc + */ + public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) + { + parent::uninstall($repo, $package); + $this->removePackage($package); + } + + protected function addPackage(PackageInterface $package) + { + $extension = [ + 'name' => $package->getName(), + 'version' => $package->getVersion(), + ]; + + $alias = $this->generateDefaultAlias($package); + if (!empty($alias)) { + $extension['alias'] = $alias; + } + $extra = $package->getExtra(); + if (isset($extra[self::EXTRA_BOOTSTRAP]) && is_string($extra[self::EXTRA_BOOTSTRAP])) { + $extension['bootstrap'] = $extra[self::EXTRA_BOOTSTRAP]; + } + + $extensions = $this->loadExtensions(); + $extensions[$package->getName()] = $extension; + $this->saveExtensions($extensions); + } + + protected function generateDefaultAlias(PackageInterface $package) + { + $autoload = $package->getAutoload(); + if (empty($autoload['psr-0'])) { + return false; + } + $fs = new Filesystem; + $vendorDir = $fs->normalizePath($this->vendorDir); + $aliases = []; + foreach ($autoload['psr-0'] as $name => $path) { + $name = str_replace('\\', '/', trim($name, '\\')); + if (!$fs->isAbsolutePath($path)) { + $path = $this->vendorDir . '/' . $package->getName() . '/' . $path; + } + $path = $fs->normalizePath($path); + if (strpos($path . '/', $vendorDir . '/') === 0) { + $aliases["@$name"] = '<vendor-dir>' . substr($path, strlen($vendorDir)) . '/' . $name; + } else { + $aliases["@$name"] = $path . '/' . $name; + } + } + return $aliases; + } + + protected function removePackage(PackageInterface $package) + { + $packages = $this->loadExtensions(); + unset($packages[$package->getName()]); + $this->saveExtensions($packages); + } + + protected function loadExtensions() + { + $file = $this->vendorDir . '/' . self::EXTENSION_FILE; + if (!is_file($file)) { + return []; + } + $extensions = require($file); + + $vendorDir = str_replace('\\', '/', $this->vendorDir); + $n = strlen($vendorDir); + + foreach ($extensions as &$extension) { + if (isset($extension['alias'])) { + foreach ($extension['alias'] as $alias => $path) { + $path = str_replace('\\', '/', $path); + if (strpos($path . '/', $vendorDir . '/') === 0) { + $extension['alias'][$alias] = '<vendor-dir>' . substr($path, $n); + } + } + } + } + + return $extensions; + } + + protected function saveExtensions(array $extensions) + { + $file = $this->vendorDir . '/' . self::EXTENSION_FILE; + $array = str_replace("'<vendor-dir>", '$vendorDir . \'', var_export($extensions, true)); + file_put_contents($file, "<?php\n\n\$vendorDir = dirname(__DIR__);\n\nreturn $array;\n"); + } + + + /** + * Sets the correct permission for the files and directories listed in the extra section. + * @param CommandEvent $event + */ + public static function setPermission($event) + { + $options = array_merge([ + self::EXTRA_WRITABLE => [], + self::EXTRA_EXECUTABLE => [], + ], $event->getComposer()->getPackage()->getExtra()); + + foreach ((array)$options[self::EXTRA_WRITABLE] as $path) { + echo "Setting writable: $path ..."; + if (is_dir($path)) { + chmod($path, 0777); + echo "done\n"; + } else { + echo "The directory was not found: " . getcwd() . DIRECTORY_SEPARATOR . $path; + return; + } + } + + foreach ((array)$options[self::EXTRA_EXECUTABLE] as $path) { + echo "Setting executable: $path ..."; + if (is_file($path)) { + chmod($path, 0755); + echo "done\n"; + } else { + echo "\n\tThe file was not found: " . getcwd() . DIRECTORY_SEPARATOR . $path . "\n"; + return; + } + } + } +} diff --git a/extensions/composer/LICENSE.md b/extensions/composer/LICENSE.md index 0bb1a8d..e98f03d 100644 --- a/extensions/composer/LICENSE.md +++ b/extensions/composer/LICENSE.md @@ -1,7 +1,7 @@ The Yii framework is free software. It is released under the terms of the following BSD License. -Copyright © 2008-2013 by Yii Software LLC (http://www.yiisoft.com) +Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com) All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/extensions/composer/Plugin.php b/extensions/composer/Plugin.php new file mode 100644 index 0000000..1111738 --- /dev/null +++ b/extensions/composer/Plugin.php @@ -0,0 +1,35 @@ +<?php +/** + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\composer; + +use Composer\Composer; +use Composer\IO\IOInterface; +use Composer\Plugin\PluginInterface; + +/** + * Plugin is the composer plugin that registers the Yii composer installer. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @since 2.0 + */ +class Plugin implements PluginInterface +{ + /** + * @inheritdoc + */ + public function activate(Composer $composer, IOInterface $io) + { + $installer = new Installer($io, $composer); + $composer->getInstallationManager()->addInstaller($installer); + $file = rtrim($composer->getConfig()->get('vendor-dir'), '/') . '/yiisoft/extensions.php'; + if (!is_file($file)) { + @mkdir(dirname($file)); + file_put_contents($file, "<?php\n\nreturn [];\n"); + } + } +} diff --git a/extensions/composer/README.md b/extensions/composer/README.md index 986fc6c..d4ce101 100644 --- a/extensions/composer/README.md +++ b/extensions/composer/README.md @@ -1,44 +1,63 @@ -Yii 2.0 Public Preview - Composer Installer -=========================================== - -Thank you for choosing Yii - a high-performance component-based PHP framework. - -If you are looking for a production-ready PHP framework, please use -[Yii v1.1](https://github.com/yiisoft/yii). - -Yii 2.0 is still under heavy development. We may make significant changes -without prior notices. **Yii 2.0 is not ready for production use yet.** - -[](http://travis-ci.org/yiisoft/yii2) - -This is the yii2 composer installer. - - -Installation ------------- - -This extension offers you enhanced composer handling for your yii2-project. It will therefor require you to use composer. - -` -php composer.phar require yiisoft/yii2-composer "*" -` - -*Note: You might have to run `php composer.phar selfupdate` before using this extension.* - - -Usage & Documentation ---------------------- - -This extensions allows you to hook to certain composer events and prepare your yii2-app for usage. - -After the package is installed, the composer.json file has to be modified to enable this extension. - -To see it in action take a look at the example apps in the repository: - -- [Basic](https://github.com/suralc/yii2/blob/master/apps/basic/composer.json#L27) -- [Advanced](https://github.com/suralc/yii2/blob/extensions-readme/apps/advanced/composer.json) - -However it might be useful to read through the official composer [documentation](http://getcomposer.org/doc/articles/scripts.md) -to understand what this extension can do for you and what it can't. - -You can also use this as a template to create your own composer additions to ease development and deployment of your app. +Yii 2 Composer Installer +======================== + +This is the composer installer for Yii 2 extensions. It implements a new composer package type named `yii2-extension`, +which should be used by all Yii 2 extensions if they are distributed as composer packages. + + +Usage +----- + +To use Yii 2 composer installer, simply set `type` to be `yii2-extension` in your `composer.json`, +like the following: + +```json +{ + "type": "yii2-extension", + "require": { + "yiisoft/yii2": "*" + }, + ... +} +``` + +You may specify a bootstrap class in the `extra` section. The `init()` method of the class will be executed each time +the Yii 2 application is responding to a request. For example, + +```json +{ + "type": "yii2-extension", + ..., + "extra": { + "bootstrap": "yii\\jui\\Extension" + } +} +``` + +The `Installer` class also implements a static method `setPermission()` that can be called after +a Yii 2 projected is installed, through the `post-create-project-cmd` composer script. +The method will set specified directories or files to be writable or executable, depending on +the corresponding parameters set in the `extra` section of the `composer.json` file. +For example, + +```json +{ + "name": "yiisoft/yii2-app-basic", + "type": "project", + ... + "scripts": { + "post-create-project-cmd": [ + "yii\\composer\\Installer::setPermission" + ] + }, + "extra": { + "writable": [ + "runtime", + "web/assets" + ], + "executable": [ + "yii" + ] + } +} +``` diff --git a/extensions/composer/composer.json b/extensions/composer/composer.json index d8cf49d..652ee14 100644 --- a/extensions/composer/composer.json +++ b/extensions/composer/composer.json @@ -1,8 +1,8 @@ { "name": "yiisoft/yii2-composer", - "description": "The composer integration for the Yii framework", - "keywords": ["yii", "composer", "install", "update"], - "type": "library", + "description": "The composer plugin for Yii extension installer", + "keywords": ["yii", "composer", "extension installer"], + "type": "composer-plugin", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/yiisoft/yii2/issues?state=open", @@ -17,11 +17,14 @@ "email": "qiang.xue@gmail.com" } ], - "minimum-stability": "dev", - "require": { - "yiisoft/yii2": "*" - }, "autoload": { - "psr-0": { "yii\\composer": "" } + "psr-0": { "yii\\composer\\": "" } + }, + "target-dir": "yii/composer", + "extra": { + "class": "yii\\composer\\Plugin" + }, + "require": { + "composer-plugin-api": "1.0.0" } } diff --git a/extensions/composer/yii/composer/InstallHandler.php b/extensions/composer/yii/composer/InstallHandler.php deleted file mode 100644 index be4037b..0000000 --- a/extensions/composer/yii/composer/InstallHandler.php +++ /dev/null @@ -1,97 +0,0 @@ -<?php -/** - * @link http://www.yiiframework.com/ - * @copyright Copyright (c) 2008 Yii Software LLC - * @license http://www.yiiframework.com/license/ - */ - -namespace yii\composer; - -use Composer\Script\CommandEvent; -use yii\console\Application; -use yii\console\Exception; - -defined('YII_DEBUG') or define('YII_DEBUG', true); - -// fcgi doesn't have STDIN defined by default -defined('STDIN') or define('STDIN', fopen('php://stdin', 'r')); - -/** - * InstallHandler is called by Composer after it installs/updates the current package. - * - * @author Qiang Xue <qiang.xue@gmail.com> - * @author Tobias Munk <schmunk@usrbin.de> - * @since 2.0 - */ -class InstallHandler -{ - const PARAM_WRITABLE = 'yii-install-writable'; - const PARAM_EXECUTABLE = 'yii-install-executable'; - const PARAM_CONFIG = 'yii-install-config'; - const PARAM_COMMANDS = 'yii-install-commands'; - - /** - * Sets the correct permissions of files and directories. - * @param CommandEvent $event - */ - public static function setPermissions($event) - { - $options = array_merge(array( - self::PARAM_WRITABLE => array(), - self::PARAM_EXECUTABLE => array(), - ), $event->getComposer()->getPackage()->getExtra()); - - foreach ((array)$options[self::PARAM_WRITABLE] as $path) { - echo "Setting writable: $path ..."; - if (is_dir($path)) { - chmod($path, 0777); - echo "done\n"; - } else { - echo "The directory was not found: " . getcwd() . DIRECTORY_SEPARATOR . $path; - return; - } - } - - foreach ((array)$options[self::PARAM_EXECUTABLE] as $path) { - echo "Setting executable: $path ..."; - if (is_file($path)) { - chmod($path, 0755); - echo "done\n"; - } else { - echo "\n\tThe file was not found: " . getcwd() . DIRECTORY_SEPARATOR . $path . "\n"; - return; - } - } - } - - /** - * Executes a yii command. - * @param CommandEvent $event - */ - public static function run($event) - { - $options = array_merge(array( - self::PARAM_COMMANDS => array(), - ), $event->getComposer()->getPackage()->getExtra()); - - if (!isset($options[self::PARAM_CONFIG])) { - throw new Exception('Please specify the "' . self::PARAM_CONFIG . '" parameter in composer.json.'); - } - $configFile = getcwd() . '/' . $options[self::PARAM_CONFIG]; - if (!is_file($configFile)) { - throw new Exception("Config file does not exist: $configFile"); - } - - require_once(__DIR__ . '/../../../yii2/yii/Yii.php'); - $application = new Application(require($configFile)); - $request = $application->getRequest(); - - foreach ((array)$options[self::PARAM_COMMANDS] as $command) { - $params = str_getcsv($command, ' '); // see http://stackoverflow.com/a/6609509/291573 - $request->setParams($params); - list($route, $params) = $request->resolve(); - echo "Running command: yii {$command}\n"; - $application->runAction($route, $params); - } - } -} diff --git a/framework/yii/debug/DebugAsset.php b/extensions/debug/DebugAsset.php similarity index 82% rename from framework/yii/debug/DebugAsset.php rename to extensions/debug/DebugAsset.php index 4f3ee89..5b459a3 100644 --- a/framework/yii/debug/DebugAsset.php +++ b/extensions/debug/DebugAsset.php @@ -15,11 +15,11 @@ use yii\web\AssetBundle; class DebugAsset extends AssetBundle { public $sourcePath = '@yii/debug/assets'; - public $css = array( + public $css = [ 'main.css', - ); - public $depends = array( + ]; + public $depends = [ 'yii\web\YiiAsset', 'yii\bootstrap\BootstrapAsset', - ); + ]; } diff --git a/framework/yii/debug/LogTarget.php b/extensions/debug/LogTarget.php similarity index 89% rename from framework/yii/debug/LogTarget.php rename to extensions/debug/LogTarget.php index b415b50..4b75cfa 100644 --- a/framework/yii/debug/LogTarget.php +++ b/extensions/debug/LogTarget.php @@ -28,7 +28,7 @@ class LogTarget extends Target * @param \yii\debug\Module $module * @param array $config */ - public function __construct($module, $config = array()) + public function __construct($module, $config = []) { parent::__construct($config); $this->module = $module; @@ -45,31 +45,31 @@ class LogTarget extends Target if (!is_dir($path)) { mkdir($path); } - $indexFile = "$path/index.json"; + $indexFile = "$path/index.data"; if (!is_file($indexFile)) { - $manifest = array(); + $manifest = []; } else { - $manifest = json_decode(file_get_contents($indexFile), true); + $manifest = unserialize(file_get_contents($indexFile)); } $request = Yii::$app->getRequest(); - $manifest[$this->tag] = $summary = array( + $manifest[$this->tag] = $summary = [ 'tag' => $this->tag, 'url' => $request->getAbsoluteUrl(), 'ajax' => $request->getIsAjax(), 'method' => $request->getMethod(), 'ip' => $request->getUserIP(), 'time' => time(), - ); + ]; $this->gc($manifest); - $dataFile = "$path/{$this->tag}.json"; - $data = array(); + $dataFile = "$path/{$this->tag}.data"; + $data = []; foreach ($this->module->panels as $id => $panel) { $data[$id] = $panel->save(); } $data['summary'] = $summary; - file_put_contents($dataFile, json_encode($data)); - file_put_contents($indexFile, json_encode($manifest)); + file_put_contents($dataFile, serialize($data)); + file_put_contents($indexFile, serialize($manifest)); } /** @@ -93,7 +93,7 @@ class LogTarget extends Target if (count($manifest) > $this->module->historySize + 10) { $n = count($manifest) - $this->module->historySize; foreach (array_keys($manifest) as $tag) { - $file = $this->module->dataPath . "/$tag.json"; + $file = $this->module->dataPath . "/$tag.data"; @unlink($file); unset($manifest[$tag]); if (--$n <= 0) { diff --git a/framework/yii/debug/Module.php b/extensions/debug/Module.php similarity index 78% rename from framework/yii/debug/Module.php rename to extensions/debug/Module.php index dd027a7..51f69a6 100644 --- a/framework/yii/debug/Module.php +++ b/extensions/debug/Module.php @@ -8,7 +8,8 @@ namespace yii\debug; use Yii; -use yii\base\View; +use yii\base\Application; +use yii\web\View; use yii\web\HttpException; /** @@ -23,10 +24,10 @@ class Module extends \yii\base\Module * @var array the list of IPs that are allowed to access this module. * Each array element represents a single IP filter which can be either an IP address * or an address with wildcard (e.g. 192.168.0.*) to represent a network segment. - * The default value is `array('127.0.0.1', '::1')`, which means the module can only be accessed + * The default value is `['127.0.0.1', '::1']`, which means the module can only be accessed * by localhost. */ - public $allowedIPs = array('127.0.0.1', '::1'); + public $allowedIPs = ['127.0.0.1', '::1']; /** * @var string the namespace that controller classes are in. */ @@ -38,7 +39,7 @@ class Module extends \yii\base\Module /** * @var array|Panel[] */ - public $panels = array(); + public $panels = []; /** * @var string the directory storing the debugger data files. This can be specified using a path alias. */ @@ -55,7 +56,10 @@ class Module extends \yii\base\Module parent::init(); $this->dataPath = Yii::getAlias($this->dataPath); $this->logTarget = Yii::$app->getLog()->targets['debug'] = new LogTarget($this); - Yii::$app->getView()->on(View::EVENT_END_BODY, array($this, 'renderToolbar')); + // do not initialize view component before application is ready (needed when debug in preload) + Yii::$app->on(Application::EVENT_BEFORE_ACTION, function() { + Yii::$app->getView()->on(View::EVENT_END_BODY, [$this, 'renderToolbar']); + }); foreach (array_merge($this->corePanels(), $this->panels) as $id => $config) { $config['module'] = $this; @@ -66,7 +70,7 @@ class Module extends \yii\base\Module public function beforeAction($action) { - Yii::$app->getView()->off(View::EVENT_END_BODY, array($this, 'renderToolbar')); + Yii::$app->getView()->off(View::EVENT_END_BODY, [$this, 'renderToolbar']); unset(Yii::$app->getLog()->targets['debug']); $this->logTarget = null; @@ -81,12 +85,12 @@ class Module extends \yii\base\Module public function renderToolbar($event) { - if (!$this->checkAccess()) { + if (!$this->checkAccess() || Yii::$app->getRequest()->getIsAjax()) { return; } - $url = Yii::$app->getUrlManager()->createUrl($this->id . '/default/toolbar', array( + $url = Yii::$app->getUrlManager()->createUrl($this->id . '/default/toolbar', [ 'tag' => $this->logTarget->tag, - )); + ]); echo '<div id="yii-debug-toolbar" data-url="' . $url . '" style="display:none"></div>'; /** @var View $view */ $view = $event->sender; @@ -102,27 +106,18 @@ class Module extends \yii\base\Module return true; } } + Yii::warning('Access to debugger is denied due to IP address restriction. The requested IP is ' . $ip, __METHOD__); return false; } protected function corePanels() { - return array( - 'config' => array( - 'class' => 'yii\debug\panels\ConfigPanel', - ), - 'request' => array( - 'class' => 'yii\debug\panels\RequestPanel', - ), - 'log' => array( - 'class' => 'yii\debug\panels\LogPanel', - ), - 'profiling' => array( - 'class' => 'yii\debug\panels\ProfilingPanel', - ), - 'db' => array( - 'class' => 'yii\debug\panels\DbPanel', - ), - ); + return [ + 'config' => ['class' => 'yii\debug\panels\ConfigPanel'], + 'request' => ['class' => 'yii\debug\panels\RequestPanel'], + 'log' => ['class' => 'yii\debug\panels\LogPanel'], + 'profiling' => ['class' => 'yii\debug\panels\ProfilingPanel'], + 'db' => ['class' => 'yii\debug\panels\DbPanel'], + ]; } } diff --git a/framework/yii/debug/Panel.php b/extensions/debug/Panel.php similarity index 97% rename from framework/yii/debug/Panel.php rename to extensions/debug/Panel.php index 6782264..055dbba 100644 --- a/framework/yii/debug/Panel.php +++ b/extensions/debug/Panel.php @@ -77,9 +77,9 @@ class Panel extends Component */ public function getUrl() { - return Yii::$app->getUrlManager()->createUrl($this->module->id . '/default/view', array( + return Yii::$app->getUrlManager()->createUrl($this->module->id . '/default/view', [ 'panel' => $this->id, 'tag' => $this->tag, - )); + ]); } } diff --git a/extensions/debug/README.md b/extensions/debug/README.md new file mode 100644 index 0000000..e02c76b --- /dev/null +++ b/extensions/debug/README.md @@ -0,0 +1,46 @@ +Debug Extension for Yii 2 +========================= + +This extension provides a debugger for Yii 2 applications. When this extension is used, +a debugger toolbar will appear at the bottom of every page. The extension also provides +a set of standalone pages to display more detailed debug information. + + +Installation +------------ + +The preferred way to install this extension is through [composer](http://getcomposer.org/download/). + +Either run + +``` +php composer.phar require yiisoft/yii2-debug "*" +``` + +or add + +``` +"yiisoft/yii2-debug": "*" +``` + +to the require section of your `composer.json` file. + + +Usage +----- + +Once the extension is installed, simply modify your application configuration as follows: + +```php +return [ + 'preload' => ['debug'], + 'modules' => [ + 'debug' => 'yii\debug\Module', + ... + ], + ... +]; +``` + +You will see a debugger toolbar showing at the bottom of every page of your application. +You can click on the toolbar to see more detailed debug information. diff --git a/framework/yii/debug/assets/main.css b/extensions/debug/assets/main.css similarity index 100% rename from framework/yii/debug/assets/main.css rename to extensions/debug/assets/main.css diff --git a/extensions/debug/composer.json b/extensions/debug/composer.json new file mode 100644 index 0000000..f60d1df --- /dev/null +++ b/extensions/debug/composer.json @@ -0,0 +1,28 @@ +{ + "name": "yiisoft/yii2-debug", + "description": "The debugger extension for the Yii framework", + "keywords": ["yii", "debug", "debugger"], + "type": "yii2-extension", + "license": "BSD-3-Clause", + "support": { + "issues": "https://github.com/yiisoft/yii2/issues?state=open", + "forum": "http://www.yiiframework.com/forum/", + "wiki": "http://www.yiiframework.com/wiki/", + "irc": "irc://irc.freenode.net/yii", + "source": "https://github.com/yiisoft/yii2" + }, + "authors": [ + { + "name": "Qiang Xue", + "email": "qiang.xue@gmail.com" + } + ], + "require": { + "yiisoft/yii2": "*", + "yiisoft/yii2-bootstrap": "*" + }, + "autoload": { + "psr-0": { "yii\\debug\\": "" } + }, + "target-dir": "yii/debug" +} diff --git a/framework/yii/debug/controllers/DefaultController.php b/extensions/debug/controllers/DefaultController.php similarity index 87% rename from framework/yii/debug/controllers/DefaultController.php rename to extensions/debug/controllers/DefaultController.php index dd01412..a4ac633 100644 --- a/framework/yii/debug/controllers/DefaultController.php +++ b/extensions/debug/controllers/DefaultController.php @@ -29,9 +29,7 @@ class DefaultController extends Controller public function actionIndex() { - return $this->render('index', array( - 'manifest' => $this->getManifest(), - )); + return $this->render('index', ['manifest' => $this->getManifest()]); } public function actionView($tag = null, $panel = null) @@ -46,22 +44,22 @@ class DefaultController extends Controller } else { $activePanel = $this->module->panels['request']; } - return $this->render('view', array( + return $this->render('view', [ 'tag' => $tag, 'summary' => $this->summary, 'manifest' => $this->getManifest(), 'panels' => $this->module->panels, 'activePanel' => $activePanel, - )); + ]); } public function actionToolbar($tag) { $this->loadData($tag); - return $this->renderPartial('toolbar', array( + return $this->renderPartial('toolbar', [ 'tag' => $tag, 'panels' => $this->module->panels, - )); + ]); } public function actionPhpinfo() @@ -74,11 +72,11 @@ class DefaultController extends Controller protected function getManifest() { if ($this->_manifest === null) { - $indexFile = $this->module->dataPath . '/index.json'; + $indexFile = $this->module->dataPath . '/index.data'; if (is_file($indexFile)) { - $this->_manifest = array_reverse(json_decode(file_get_contents($indexFile), true), true); + $this->_manifest = array_reverse(unserialize(file_get_contents($indexFile)), true); } else { - $this->_manifest = array(); + $this->_manifest = []; } } return $this->_manifest; @@ -88,8 +86,8 @@ class DefaultController extends Controller { $manifest = $this->getManifest(); if (isset($manifest[$tag])) { - $dataFile = $this->module->dataPath . "/$tag.json"; - $data = json_decode(file_get_contents($dataFile), true); + $dataFile = $this->module->dataPath . "/$tag.data"; + $data = unserialize(file_get_contents($dataFile)); foreach ($this->module->panels as $id => $panel) { if (isset($data[$id])) { $panel->tag = $tag; diff --git a/framework/yii/debug/panels/ConfigPanel.php b/extensions/debug/panels/ConfigPanel.php similarity index 89% rename from framework/yii/debug/panels/ConfigPanel.php rename to extensions/debug/panels/ConfigPanel.php index eb0d325..59cabf3 100644 --- a/framework/yii/debug/panels/ConfigPanel.php +++ b/extensions/debug/panels/ConfigPanel.php @@ -49,27 +49,28 @@ EOD; public function getDetail() { - $app = array( + $app = [ 'Yii Version' => $this->data['application']['yii'], 'Application Name' => $this->data['application']['name'], 'Environment' => $this->data['application']['env'], 'Debug Mode' => $this->data['application']['debug'] ? 'Yes' : 'No', - ); - $php = array( + ]; + $php = [ 'PHP Version' => $this->data['php']['version'], 'Xdebug' => $this->data['php']['xdebug'] ? 'Enabled' : 'Disabled', 'APC' => $this->data['php']['apc'] ? 'Enabled' : 'Disabled', 'Memcache' => $this->data['php']['memcache'] ? 'Enabled' : 'Disabled', - ); + ]; return "<h1>Configuration</h1>\n" . $this->renderData('Application Configuration', $app) . "\n" + . $this->renderExtensions() . $this->renderData('PHP Configuration', $php) . "\n" . $this->getPhpInfo(); } protected function getPhpInfo() { - return '<div>' . Html::a('Show phpinfo »', array('phpinfo'), array('class' => 'btn btn-primary')) . "</div>\n"; + return '<div>' . Html::a('Show phpinfo »', ['phpinfo'], ['class' => 'btn btn-primary']) . "</div>\n"; } protected function renderData($caption, $values) @@ -77,7 +78,7 @@ EOD; if (empty($values)) { return "<h3>$caption</h3>\n<p>Empty.</p>"; } - $rows = array(); + $rows = []; foreach ($values as $name => $value) { $rows[] = '<tr><th style="width:200px;">' . Html::encode($name) . '</th><td style="overflow:auto">' . Html::encode($value) . '</td></tr>'; } @@ -93,23 +94,36 @@ $rows EOD; } + protected function renderExtensions() + { + if (empty($this->data['extensions'])) { + return ''; + } + $data = []; + foreach ($this->data['extensions'] as $extension) { + $data[$extension['name']] = $extension['version']; + } + return $this->renderData('Installed Extensions', $data) . "\n"; + } + public function save() { - return array( + return [ 'phpVersion' => PHP_VERSION, 'yiiVersion' => Yii::getVersion(), - 'application' => array( + 'application' => [ 'yii' => Yii::getVersion(), 'name' => Yii::$app->name, 'env' => YII_ENV, 'debug' => YII_DEBUG, - ), - 'php' => array( + ], + 'php' => [ 'version' => PHP_VERSION, 'xdebug' => extension_loaded('xdebug'), 'apc' => extension_loaded('apc'), 'memcache' => extension_loaded('memcache'), - ), - ); + ], + 'extensions' => Yii::$app->extensions, + ]; } } diff --git a/framework/yii/debug/panels/DbPanel.php b/extensions/debug/panels/DbPanel.php similarity index 88% rename from framework/yii/debug/panels/DbPanel.php rename to extensions/debug/panels/DbPanel.php index 8dd2c9e..a487dd4 100644 --- a/framework/yii/debug/panels/DbPanel.php +++ b/extensions/debug/panels/DbPanel.php @@ -48,19 +48,19 @@ EOD; public function getDetail() { $timings = $this->calculateTimings(); - ArrayHelper::multisort($timings, 3, true); - $rows = array(); + ArrayHelper::multisort($timings, 3, SORT_DESC); + $rows = []; foreach ($timings as $timing) { $duration = sprintf('%.1f ms', $timing[3] * 1000); $procedure = Html::encode($timing[1]); $traces = $timing[4]; if (!empty($traces)) { - $procedure .= Html::ul($traces, array( + $procedure .= Html::ul($traces, [ 'class' => 'trace', 'item' => function ($trace) { return "<li>{$trace['file']}({$trace['line']})</li>"; }, - )); + ]); } $rows[] = "<tr><td style=\"width: 80px;\">$duration</td><td>$procedure</td>"; } @@ -91,8 +91,8 @@ EOD; return $this->_timings; } $messages = $this->data['messages']; - $timings = array(); - $stack = array(); + $timings = []; + $stack = []; foreach ($messages as $i => $log) { list($token, $level, $category, $timestamp) = $log; $log[5] = $i; @@ -100,7 +100,7 @@ EOD; $stack[] = $log; } elseif ($level == Logger::LEVEL_PROFILE_END) { if (($last = array_pop($stack)) !== null && $last[0] === $token) { - $timings[$last[5]] = array(count($stack), $token, $last[3], $timestamp - $last[3], $last[4]); + $timings[$last[5]] = [count($stack), $token, $last[3], $timestamp - $last[3], $last[4]]; } } } @@ -108,7 +108,7 @@ EOD; $now = microtime(true); while (($last = array_pop($stack)) !== null) { $delta = $now - $last[3]; - $timings[$last[5]] = array(count($stack), $last[0], $last[2], $delta, $last[4]); + $timings[$last[5]] = [count($stack), $last[0], $last[2], $delta, $last[4]]; } ksort($timings); return $this->_timings = $timings; @@ -117,9 +117,7 @@ EOD; public function save() { $target = $this->module->logTarget; - $messages = $target->filterMessages($target->messages, Logger::LEVEL_PROFILE, array('yii\db\Command::queryInternal')); - return array( - 'messages' => $messages, - ); + $messages = $target->filterMessages($target->messages, Logger::LEVEL_PROFILE, ['yii\db\Command::queryInternal']); + return ['messages' => $messages]; } } diff --git a/framework/yii/debug/panels/LogPanel.php b/extensions/debug/panels/LogPanel.php similarity index 92% rename from framework/yii/debug/panels/LogPanel.php rename to extensions/debug/panels/LogPanel.php index c562006..ccf4402 100644 --- a/framework/yii/debug/panels/LogPanel.php +++ b/extensions/debug/panels/LogPanel.php @@ -28,7 +28,7 @@ class LogPanel extends Panel public function getSummary() { - $output = array('<span class="label">' . count($this->data['messages']) . '</span>'); + $output = ['<span class="label">' . count($this->data['messages']) . '</span>']; $title = 'Logged ' . count($this->data['messages']) . ' messages'; $errorCount = count(Target::filterMessages($this->data['messages'], Logger::LEVEL_ERROR)); if ($errorCount) { @@ -51,18 +51,18 @@ EOD; public function getDetail() { - $rows = array(); + $rows = []; foreach ($this->data['messages'] as $log) { list ($message, $level, $category, $time, $traces) = $log; $time = date('H:i:s.', $time) . sprintf('%03d', (int)(($time - (int)$time) * 1000)); $message = nl2br(Html::encode($message)); if (!empty($traces)) { - $message .= Html::ul($traces, array( + $message .= Html::ul($traces, [ 'class' => 'trace', 'item' => function ($trace) { return "<li>{$trace['file']}({$trace['line']})</li>"; }, - )); + ]); } if ($level == Logger::LEVEL_ERROR) { $class = ' class="danger"'; @@ -100,8 +100,6 @@ EOD; { $target = $this->module->logTarget; $messages = $target->filterMessages($target->messages, Logger::LEVEL_ERROR | Logger::LEVEL_INFO | Logger::LEVEL_WARNING | Logger::LEVEL_TRACE); - return array( - 'messages' => $messages, - ); + return ['messages' => $messages]; } } diff --git a/framework/yii/debug/panels/ProfilingPanel.php b/extensions/debug/panels/ProfilingPanel.php similarity index 91% rename from framework/yii/debug/panels/ProfilingPanel.php rename to extensions/debug/panels/ProfilingPanel.php index c462e40..49cf081 100644 --- a/framework/yii/debug/panels/ProfilingPanel.php +++ b/extensions/debug/panels/ProfilingPanel.php @@ -44,25 +44,25 @@ EOD; public function getDetail() { $messages = $this->data['messages']; - $timings = array(); - $stack = array(); + $timings = []; + $stack = []; foreach ($messages as $i => $log) { list($token, $level, $category, $timestamp, $traces) = $log; if ($level == Logger::LEVEL_PROFILE_BEGIN) { $stack[] = $log; } elseif ($level == Logger::LEVEL_PROFILE_END) { if (($last = array_pop($stack)) !== null && $last[0] === $token) { - $timings[] = array(count($stack), $token, $category, $timestamp - $last[3], $traces); + $timings[] = [count($stack), $token, $category, $timestamp - $last[3], $traces]; } } } $now = microtime(true); while (($last = array_pop($stack)) !== null) { - $timings[] = array(count($stack), $last[0], $last[2], $now - $last[3], $last[4]); + $timings[] = [count($stack), $last[0], $last[2], $now - $last[3], $last[4]]; } - $rows = array(); + $rows = []; foreach ($timings as $timing) { $time = sprintf('%.1f ms', $timing[3] * 1000); $procedure = str_repeat('<span class="indent">→</span>', $timing[0]) . Html::encode($timing[1]); @@ -98,10 +98,10 @@ EOD; { $target = $this->module->logTarget; $messages = $target->filterMessages($target->messages, Logger::LEVEL_PROFILE); - return array( + return [ 'memory' => memory_get_peak_usage(), 'time' => microtime(true) - YII_BEGIN_TIME, 'messages' => $messages, - ); + ]; } } diff --git a/framework/yii/debug/panels/RequestPanel.php b/extensions/debug/panels/RequestPanel.php similarity index 79% rename from framework/yii/debug/panels/RequestPanel.php rename to extensions/debug/panels/RequestPanel.php index 6aa4bf8..9d9cb02 100644 --- a/framework/yii/debug/panels/RequestPanel.php +++ b/extensions/debug/panels/RequestPanel.php @@ -55,14 +55,14 @@ EOD; public function getDetail() { - $data = array( + $data = [ 'Route' => $this->data['route'], 'Action' => $this->data['action'], 'Parameters' => $this->data['actionParams'], - ); - return Tabs::widget(array( - 'items' => array( - array( + ]; + return Tabs::widget([ + 'items' => [ + [ 'label' => 'Parameters', 'content' => $this->renderData('Routing', $data) . $this->renderData('$_GET', $this->data['GET']) @@ -70,23 +70,23 @@ EOD; . $this->renderData('$_FILES', $this->data['FILES']) . $this->renderData('$_COOKIE', $this->data['COOKIE']), 'active' => true, - ), - array( + ], + [ 'label' => 'Headers', 'content' => $this->renderData('Request Headers', $this->data['requestHeaders']) . $this->renderData('Response Headers', $this->data['responseHeaders']), - ), - array( + ], + [ 'label' => 'Session', 'content' => $this->renderData('$_SESSION', $this->data['SESSION']) . $this->renderData('Flashes', $this->data['flashes']), - ), - array( + ], + [ 'label' => '$_SERVER', 'content' => $this->renderData('$_SERVER', $this->data['SERVER']), - ), - ), - )); + ], + ], + ]); } public function save() @@ -96,16 +96,16 @@ EOD; } elseif (function_exists('http_get_request_headers')) { $requestHeaders = http_get_request_headers(); } else { - $requestHeaders = array(); + $requestHeaders = []; } - $responseHeaders = array(); + $responseHeaders = []; foreach (headers_list() as $header) { if (($pos = strpos($header, ':')) !== false) { $name = substr($header, 0, $pos); $value = trim(substr($header, $pos + 1)); if (isset($responseHeaders[$name])) { if (!is_array($responseHeaders[$name])) { - $responseHeaders[$name] = array($responseHeaders[$name], $value); + $responseHeaders[$name] = [$responseHeaders[$name], $value]; } else { $responseHeaders[$name][] = $value; } @@ -127,21 +127,21 @@ EOD; } /** @var \yii\web\Session $session */ $session = Yii::$app->getComponent('session', false); - return array( - 'flashes' => $session ? $session->getAllFlashes() : array(), + return [ + 'flashes' => $session ? $session->getAllFlashes() : [], 'statusCode' => Yii::$app->getResponse()->getStatusCode(), 'requestHeaders' => $requestHeaders, 'responseHeaders' => $responseHeaders, 'route' => Yii::$app->requestedAction ? Yii::$app->requestedAction->getUniqueId() : Yii::$app->requestedRoute, 'action' => $action, 'actionParams' => Yii::$app->requestedParams, - 'SERVER' => empty($_SERVER) ? array() : $_SERVER, - 'GET' => empty($_GET) ? array() : $_GET, - 'POST' => empty($_POST) ? array() : $_POST, - 'COOKIE' => empty($_COOKIE) ? array() : $_COOKIE, - 'FILES' => empty($_FILES) ? array() : $_FILES, - 'SESSION' => empty($_SESSION) ? array() : $_SESSION, - ); + 'SERVER' => empty($_SERVER) ? [] : $_SERVER, + 'GET' => empty($_GET) ? [] : $_GET, + 'POST' => empty($_POST) ? [] : $_POST, + 'COOKIE' => empty($_COOKIE) ? [] : $_COOKIE, + 'FILES' => empty($_FILES) ? [] : $_FILES, + 'SESSION' => empty($_SESSION) ? [] : $_SESSION, + ]; } protected function renderData($caption, $values) @@ -149,9 +149,9 @@ EOD; if (empty($values)) { return "<h3>$caption</h3>\n<p>Empty.</p>"; } - $rows = array(); + $rows = []; foreach ($values as $name => $value) { - $rows[] = '<tr><th style="width: 200px;">' . Html::encode($name) . '</th><td>' . Html::encode(var_export($value, true)) . '</td></tr>'; + $rows[] = '<tr><th style="width: 200px;">' . Html::encode($name) . '</th><td>' . htmlspecialchars(var_export($value, true), ENT_QUOTES|ENT_SUBSTITUTE, \Yii::$app->charset, TRUE) . '</td></tr>'; } $rows = implode("\n", $rows); return <<<EOD diff --git a/framework/yii/debug/views/default/index.php b/extensions/debug/views/default/index.php similarity index 86% rename from framework/yii/debug/views/default/index.php rename to extensions/debug/views/default/index.php index 59d60b4..93737e2 100644 --- a/framework/yii/debug/views/default/index.php +++ b/extensions/debug/views/default/index.php @@ -3,7 +3,7 @@ use yii\helpers\Html; /** - * @var \yii\base\View $this + * @var \yii\web\View $this * @var array $manifest */ @@ -32,11 +32,11 @@ $this->title = 'Yii Debugger'; <tbody> <?php foreach ($manifest as $tag => $data): ?> <tr> - <td><?php echo Html::a($tag, array('view', 'tag' => $tag)); ?></td> - <td><?php echo date('Y-m-d h:i:sa', $data['time']); ?></td> - <td><?php echo $data['ip']; ?></td> - <td><?php echo $data['method']; ?></td> - <td><?php echo $data['url']; ?></td> + <td><?= Html::a($tag, ['view', 'tag' => $tag]) ?></td> + <td><?= date('Y-m-d h:i:sa', $data['time']) ?></td> + <td><?= $data['ip'] ?></td> + <td><?= $data['method'] ?></td> + <td><?= $data['url'] ?></td> </tr> <?php endforeach; ?> </tbody> diff --git a/framework/yii/debug/views/default/toolbar.css b/extensions/debug/views/default/toolbar.css similarity index 100% rename from framework/yii/debug/views/default/toolbar.css rename to extensions/debug/views/default/toolbar.css diff --git a/framework/yii/debug/views/default/toolbar.js b/extensions/debug/views/default/toolbar.js similarity index 100% rename from framework/yii/debug/views/default/toolbar.js rename to extensions/debug/views/default/toolbar.js diff --git a/framework/yii/debug/views/default/toolbar.php b/extensions/debug/views/default/toolbar.php similarity index 78% rename from framework/yii/debug/views/default/toolbar.php rename to extensions/debug/views/default/toolbar.php index ac238fa..7d88822 100644 --- a/framework/yii/debug/views/default/toolbar.php +++ b/extensions/debug/views/default/toolbar.php @@ -1,6 +1,6 @@ <?php /** - * @var \yii\base\View $this + * @var \yii\web\View $this * @var \yii\debug\Panel[] $panels * @var string $tag */ @@ -26,13 +26,13 @@ $url = $panels['request']->getUrl(); ?> <div id="yii-debug-toolbar"> <?php foreach ($panels as $panel): ?> - <?php echo $panel->getSummary(); ?> + <?= $panel->getSummary() ?> <?php endforeach; ?> - <span class="yii-debug-toolbar-toggler" onclick="<?php echo $minJs; ?>">›</span> + <span class="yii-debug-toolbar-toggler" onclick="<?= $minJs ?>">›</span> </div> <div id="yii-debug-toolbar-min"> - <a href="<?php echo $url; ?>" title="Open Yii Debugger" id="yii-debug-toolbar-logo"> - <img width="29" height="30" alt="" src="<?php echo ConfigPanel::getYiiLogo(); ?>"> + <a href="<?= $url ?>" title="Open Yii Debugger" id="yii-debug-toolbar-logo"> + <img width="29" height="30" alt="" src="<?= ConfigPanel::getYiiLogo() ?>"> </a> - <span class="yii-debug-toolbar-toggler" onclick="<?php echo $maxJs; ?>">‹</span> + <span class="yii-debug-toolbar-toggler" onclick="<?= $maxJs ?>">‹</span> </div> diff --git a/framework/yii/debug/views/default/view.php b/extensions/debug/views/default/view.php similarity index 76% rename from framework/yii/debug/views/default/view.php rename to extensions/debug/views/default/view.php index 190bbe2..846653c 100644 --- a/framework/yii/debug/views/default/view.php +++ b/extensions/debug/views/default/view.php @@ -5,7 +5,7 @@ use yii\bootstrap\ButtonGroup; use yii\helpers\Html; /** - * @var \yii\base\View $this + * @var \yii\web\View $this * @var array $summary * @var string $tag * @var array $manifest @@ -21,7 +21,7 @@ $this->title = 'Yii Debugger'; Yii Debugger </div> <?php foreach ($panels as $panel): ?> - <?php echo $panel->getSummary(); ?> + <?= $panel->getSummary() ?> <?php endforeach; ?> </div> @@ -32,9 +32,9 @@ $this->title = 'Yii Debugger'; <?php foreach ($panels as $id => $panel) { $label = '<i class="glyphicon glyphicon-chevron-right"></i>' . Html::encode($panel->getName()); - echo Html::a($label, array('view', 'tag' => $tag, 'panel' => $id), array( + echo Html::a($label, ['view', 'tag' => $tag, 'panel' => $id], [ 'class' => $panel === $activePanel ? 'list-group-item active' : 'list-group-item', - )); + ]); } ?> </div> @@ -43,35 +43,35 @@ $this->title = 'Yii Debugger'; <div class="callout callout-danger"> <?php $count = 0; - $items = array(); + $items = []; foreach ($manifest as $meta) { $label = $meta['tag'] . ': ' . $meta['method'] . ' ' . $meta['url'] . ($meta['ajax'] ? ' (AJAX)' : '') . ', ' . date('Y-m-d h:i:s a', $meta['time']) . ', ' . $meta['ip']; - $url = array('view', 'tag' => $meta['tag'], 'panel' => $activePanel->id); - $items[] = array( + $url = ['view', 'tag' => $meta['tag'], 'panel' => $activePanel->id]; + $items[] = [ 'label' => $label, 'url' => $url, - ); + ]; if (++$count >= 10) { break; } } - echo ButtonGroup::widget(array( - 'buttons' => array( - Html::a('All', array('index'), array('class' => 'btn btn-default')), - ButtonDropdown::widget(array( + echo ButtonGroup::widget([ + 'buttons' => [ + Html::a('All', ['index'], ['class' => 'btn btn-default']), + ButtonDropdown::widget([ 'label' => 'Last 10', - 'options' => array('class' => 'btn-default'), - 'dropdown' => array('items' => $items), - )), - ), - )); + 'options' => ['class' => 'btn-default'], + 'dropdown' => ['items' => $items], + ]), + ], + ]); echo "\n" . $summary['tag'] . ': ' . $summary['method'] . ' ' . Html::a(Html::encode($summary['url']), $summary['url']); echo ' at ' . date('Y-m-d h:i:s a', $summary['time']) . ' by ' . $summary['ip']; ?> </div> - <?php echo $activePanel->getDetail(); ?> + <?= $activePanel->getDetail() ?> </div> </div> </div> diff --git a/framework/yii/debug/views/layouts/main.php b/extensions/debug/views/layouts/main.php similarity index 86% rename from framework/yii/debug/views/layouts/main.php rename to extensions/debug/views/layouts/main.php index 8875878..5760c3a 100644 --- a/framework/yii/debug/views/layouts/main.php +++ b/extensions/debug/views/layouts/main.php @@ -1,6 +1,6 @@ <?php /** - * @var \yii\base\View $this + * @var \yii\web\View $this * @var string $content */ use yii\helpers\Html; @@ -11,12 +11,12 @@ yii\debug\DebugAsset::register($this); <html> <?php $this->beginPage(); ?> <head> - <title><?php echo Html::encode($this->title); ?></title> + <title><?= Html::encode($this->title) ?></title> <?php $this->head(); ?> </head> <body> <?php $this->beginBody(); ?> -<?php echo $content; ?> +<?= $content ?> <?php $this->endBody(); ?> </body> <?php $this->endPage(); ?> diff --git a/framework/yii/gii/CodeFile.php b/extensions/gii/CodeFile.php similarity index 95% rename from framework/yii/gii/CodeFile.php rename to extensions/gii/CodeFile.php index 2f21b4c..a5196d9 100644 --- a/framework/yii/gii/CodeFile.php +++ b/extensions/gii/CodeFile.php @@ -28,7 +28,7 @@ class CodeFile extends Object /** * The code file is new. */ - const OP_NEW = 'new'; + const OP_CREATE = 'create'; /** * The code file already exists, and the new one may need to overwrite it. */ @@ -62,13 +62,13 @@ class CodeFile extends Object */ public function __construct($path, $content) { - $this->path = strtr($path, array('/' => DIRECTORY_SEPARATOR, '\\' => DIRECTORY_SEPARATOR)); + $this->path = strtr($path, ['/' => DIRECTORY_SEPARATOR, '\\' => DIRECTORY_SEPARATOR]); $this->content = $content; $this->id = md5($this->path); if (is_file($path)) { $this->operation = file_get_contents($path) === $content ? self::OP_SKIP : self::OP_OVERWRITE; } else { - $this->operation = self::OP_NEW; + $this->operation = self::OP_CREATE; } } @@ -79,7 +79,7 @@ class CodeFile extends Object public function save() { $module = Yii::$app->controller->module; - if ($this->operation === self::OP_NEW) { + if ($this->operation === self::OP_CREATE) { $dir = dirname($this->path); if (!is_dir($dir)) { $mask = @umask(0); @@ -134,7 +134,7 @@ class CodeFile extends Object if ($type === 'php') { return highlight_string($this->content, true); - } elseif (!in_array($type, array('jpg', 'gif', 'png', 'exe'))) { + } elseif (!in_array($type, ['jpg', 'gif', 'png', 'exe'])) { return nl2br(Html::encode($this->content)); } else { return false; @@ -144,7 +144,7 @@ class CodeFile extends Object public function diff() { $type = strtolower($this->getType()); - if (in_array($type, array('jpg', 'gif', 'png', 'exe'))) { + if (in_array($type, ['jpg', 'gif', 'png', 'exe'])) { return false; } elseif ($this->operation === self::OP_OVERWRITE) { return StringHelper::diff(file($this->path), $this->content); diff --git a/framework/yii/gii/Generator.php b/extensions/gii/Generator.php similarity index 87% rename from framework/yii/gii/Generator.php rename to extensions/gii/Generator.php index eb5b8b4..05c45a7 100644 --- a/framework/yii/gii/Generator.php +++ b/extensions/gii/Generator.php @@ -11,7 +11,7 @@ use Yii; use ReflectionClass; use yii\base\InvalidConfigException; use yii\base\Model; -use yii\base\View; +use yii\web\View; /** @@ -42,7 +42,7 @@ abstract class Generator extends Model * @var array a list of available code templates. The array keys are the template names, * and the array values are the corresponding template paths or path aliases. */ - public $templates = array(); + public $templates = []; /** * @var string the name of the code template that the user has selected. * The value of this property is internally managed by this class. @@ -85,7 +85,7 @@ abstract class Generator extends Model */ public function requiredTemplates() { - return array(); + return []; } /** @@ -96,7 +96,7 @@ abstract class Generator extends Model */ public function stickyAttributes() { - return array('template'); + return ['template']; } /** @@ -107,7 +107,18 @@ abstract class Generator extends Model */ public function hints() { - return array(); + return []; + } + + /** + * Returns the list of auto complete values. + * The array keys are the attribute names, and the array values are the corresponding auto complete values. + * Auto complete values can also be callable typed in order one want to make postponed data generation. + * @return array the list of auto complete values + */ + public function autoCompleteData() + { + return []; } /** @@ -159,17 +170,17 @@ abstract class Generator extends Model * rules are included: * * ~~~ - * return array_merge(parent::rules(), array( + * return array_merge(parent::rules(), [ * ...rules for the child class... - * )); + * ]); * ~~~ */ public function rules() { - return array( - array('template', 'required', 'message' => 'A code template must be selected.'), - array('template', 'validateTemplate'), - ); + return [ + [['template'], 'required', 'message' => 'A code template must be selected.'], + [['template'], 'validateTemplate'], + ]; } /** @@ -182,7 +193,7 @@ abstract class Generator extends Model $attributes[] = 'template'; $path = $this->getStickyDataFile(); if (is_file($path)) { - $result = @include($path); + $result = json_decode(file_get_contents($path), true); if (is_array($result)) { foreach ($stickyAttributes as $name) { if (isset($result[$name])) { @@ -201,13 +212,13 @@ abstract class Generator extends Model { $stickyAttributes = $this->stickyAttributes(); $stickyAttributes[] = 'template'; - $values = array(); + $values = []; foreach ($stickyAttributes as $name) { $values[$name] = $this->$name; } $path = $this->getStickyDataFile(); @mkdir(dirname($path), 0755, true); - file_put_contents($path, "<?php\nreturn " . var_export($values, true) . ";\n"); + file_put_contents($path, json_encode($values)); } /** @@ -216,7 +227,7 @@ abstract class Generator extends Model */ public function getStickyDataFile() { - return Yii::$app->getRuntimePath() . '/gii-' . Yii::getVersion() . '/' . str_replace('\\', '-', get_class($this)) . '.php'; + return Yii::$app->getRuntimePath() . '/gii-' . Yii::getVersion() . '/' . str_replace('\\', '-', get_class($this)) . '.json'; } /** @@ -229,7 +240,7 @@ abstract class Generator extends Model */ public function save($files, $answers, &$results) { - $lines = array('Generating code using template "' . $this->getTemplatePath() . '"...'); + $lines = ['Generating code using template "' . $this->getTemplatePath() . '"...']; $hasError = false; foreach ($files as $file) { $relativePath = $file->getRelativePath(); @@ -239,7 +250,7 @@ abstract class Generator extends Model $hasError = true; $lines[] = "generating $relativePath\n<span class=\"error\">$error</span>"; } else { - $lines[] = $file->operation === CodeFile::OP_NEW ? " generated $relativePath" : " overwrote $relativePath"; + $lines[] = $file->operation === CodeFile::OP_CREATE ? " generated $relativePath" : " overwrote $relativePath"; } } else { $lines[] = " skipped $relativePath"; @@ -272,7 +283,7 @@ abstract class Generator extends Model * @param array $params list of parameters to be passed to the template file. * @return string the generated code */ - public function render($template, $params = array()) + public function render($template, $params = []) { $view = new View; $params['generator'] = $this; @@ -352,7 +363,7 @@ abstract class Generator extends Model */ public function isReservedKeyword($value) { - static $keywords = array( + static $keywords = [ '__class__', '__dir__', '__file__', @@ -432,7 +443,7 @@ abstract class Generator extends Model 'var', 'while', 'xor', - ); + ]; return in_array(strtolower($value), $keywords, true); } } diff --git a/framework/yii/gii/GiiAsset.php b/extensions/gii/GiiAsset.php similarity index 83% rename from framework/yii/gii/GiiAsset.php rename to extensions/gii/GiiAsset.php index a5fc0d0..b100750 100644 --- a/framework/yii/gii/GiiAsset.php +++ b/extensions/gii/GiiAsset.php @@ -24,21 +24,23 @@ class GiiAsset extends AssetBundle /** * @inheritdoc */ - public $css = array( + public $css = [ 'main.css', - ); + 'typeahead.js-bootstrap.css', + ]; /** * @inheritdoc */ - public $js = array( + public $js = [ 'gii.js', - ); + 'typeahead.js', + ]; /** * @inheritdoc */ - public $depends = array( + public $depends = [ 'yii\web\YiiAsset', 'yii\bootstrap\BootstrapAsset', 'yii\bootstrap\BootstrapPluginAsset', - ); + ]; } diff --git a/framework/yii/gii/Module.php b/extensions/gii/Module.php similarity index 84% rename from framework/yii/gii/Module.php rename to extensions/gii/Module.php index f82e3dd..7dc9590 100644 --- a/framework/yii/gii/Module.php +++ b/extensions/gii/Module.php @@ -16,14 +16,12 @@ use yii\web\HttpException; * To use Gii, include it as a module in the application configuration like the following: * * ~~~ - * return array( + * return [ * ...... - * 'modules' => array( - * 'gii' => array( - * 'class' => 'yii\gii\Module', - * ), - * ), - * ) + * 'modules' => [ + * 'gii' => ['class' => 'yii\gii\Module'], + * ], + * ] * ~~~ * * Because Gii generates new code files on the server, you should only use it on your own @@ -40,12 +38,12 @@ use yii\web\HttpException; * in order to access Gii: * * ~~~ - * 'rules'=>array( + * 'rules' => [ * 'gii' => 'gii', * 'gii/<controller>' => 'gii/<controller>', * 'gii/<controller>/<action>' => 'gii/<controller>/<action>', * ... - * ), + * ], * ~~~ * * You can then access Gii via URL: `http://localhost/path/to/index.php/gii` @@ -63,10 +61,10 @@ class Module extends \yii\base\Module * @var array the list of IPs that are allowed to access this module. * Each array element represents a single IP filter which can be either an IP address * or an address with wildcard (e.g. 192.168.0.*) to represent a network segment. - * The default value is `array('127.0.0.1', '::1')`, which means the module can only be accessed + * The default value is `['127.0.0.1', '::1']`, which means the module can only be accessed * by localhost. */ - public $allowedIPs = array('127.0.0.1', '::1'); + public $allowedIPs = ['127.0.0.1', '::1']; /** * @var array|Generator[] a list of generator configurations or instances. The array keys * are the generator IDs (e.g. "crud"), and the array elements are the corresponding generator @@ -78,7 +76,7 @@ class Module extends \yii\base\Module * Newly assigned generators will be merged with the [[coreGenerators()|core ones]], and the former * takes precedence in case when they have the same generator ID. */ - public $generators = array(); + public $generators = []; /** * @var integer the permission to be set for newly generated code files. * This value will be used by PHP chmod function. @@ -127,6 +125,7 @@ class Module extends \yii\base\Module return true; } } + Yii::warning('Access to Gii is denied due to IP address restriction. The requested IP is ' . $ip, __METHOD__); return false; } @@ -136,22 +135,12 @@ class Module extends \yii\base\Module */ protected function coreGenerators() { - return array( - 'model' => array( - 'class' => 'yii\gii\generators\model\Generator', - ), - 'crud' => array( - 'class' => 'yii\gii\generators\crud\Generator', - ), - 'controller' => array( - 'class' => 'yii\gii\generators\controller\Generator', - ), - 'form' => array( - 'class' => 'yii\gii\generators\form\Generator', - ), - 'module' => array( - 'class' => 'yii\gii\generators\module\Generator', - ), - ); + return [ + 'model' => ['class' => 'yii\gii\generators\model\Generator'], + 'crud' => ['class' => 'yii\gii\generators\crud\Generator'], + 'controller' => ['class' => 'yii\gii\generators\controller\Generator'], + 'form' => ['class' => 'yii\gii\generators\form\Generator'], + 'module' => ['class' => 'yii\gii\generators\module\Generator'], + ]; } } diff --git a/extensions/gii/README.md b/extensions/gii/README.md new file mode 100644 index 0000000..60f374b --- /dev/null +++ b/extensions/gii/README.md @@ -0,0 +1,47 @@ +Gii Extension for Yii 2 +======================== + +This extension provides a Web-based code generator, called Gii, for Yii 2 applications. +You can use Gii to quickly generate models, forms, modules, CRUD, etc. + + +Installation +------------ + +The preferred way to install this extension is through [composer](http://getcomposer.org/download/). + +Either run + +``` +php composer.phar require yiisoft/yii2-gii "*" +``` + +or add + +``` +"yiisoft/yii2-gii": "*" +``` + +to the require section of your `composer.json` file. + + +Usage +----- + +Once the extension is installed, simply modify your application configuration as follows: + +```php +return [ + 'modules' => [ + 'gii' => 'yii\gii\Module', + ... + ], + ... +]; +``` + +You can then access Gii through the following URL: + +``` +http://localhost/path/to/index.php?r=gii +``` diff --git a/framework/yii/gii/assets/gii.js b/extensions/gii/assets/gii.js similarity index 98% rename from framework/yii/gii/assets/gii.js rename to extensions/gii/assets/gii.js index b581d3b..a95221e 100644 --- a/framework/yii/gii/assets/gii.js +++ b/extensions/gii/assets/gii.js @@ -14,7 +14,7 @@ yii.gii = (function ($) { }; var initStickyInputs = function () { - $('.sticky:not(.error) input[type="text"],select,textarea').each(function () { + $('.sticky:not(.error)').find('input[type="text"],select,textarea').each(function () { var value; if (this.tagName === 'SELECT') { value = this.options[this.selectedIndex].text; diff --git a/framework/yii/gii/assets/logo.png b/extensions/gii/assets/logo.png similarity index 100% rename from framework/yii/gii/assets/logo.png rename to extensions/gii/assets/logo.png Binary files a/framework/yii/gii/assets/logo.png and b/extensions/gii/assets/logo.png differ diff --git a/framework/yii/gii/assets/main.css b/extensions/gii/assets/main.css similarity index 98% rename from framework/yii/gii/assets/main.css rename to extensions/gii/assets/main.css index 8efc56c..1a4f794 100644 --- a/framework/yii/gii/assets/main.css +++ b/extensions/gii/assets/main.css @@ -201,3 +201,11 @@ body { .DifferencesInline .ChangeReplace del { background: #e99; } + +/* additional styles for typeahead.js-bootstrap.css */ +.twitter-typeahead { + display: block !important; +} +.twitter-typeahead .tt-hint { + padding: 6px 12px !important; +} diff --git a/extensions/gii/assets/typeahead.js b/extensions/gii/assets/typeahead.js new file mode 100644 index 0000000..9365bd6 --- /dev/null +++ b/extensions/gii/assets/typeahead.js @@ -0,0 +1,1139 @@ +/*! + * typeahead.js 0.9.3 + * https://github.com/twitter/typeahead + * Copyright 2013 Twitter, Inc. and other contributors; Licensed MIT + */ + +(function($) { + var VERSION = "0.9.3"; + var utils = { + isMsie: function() { + var match = /(msie) ([\w.]+)/i.exec(navigator.userAgent); + return match ? parseInt(match[2], 10) : false; + }, + isBlankString: function(str) { + return !str || /^\s*$/.test(str); + }, + escapeRegExChars: function(str) { + return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); + }, + isString: function(obj) { + return typeof obj === "string"; + }, + isNumber: function(obj) { + return typeof obj === "number"; + }, + isArray: $.isArray, + isFunction: $.isFunction, + isObject: $.isPlainObject, + isUndefined: function(obj) { + return typeof obj === "undefined"; + }, + bind: $.proxy, + bindAll: function(obj) { + var val; + for (var key in obj) { + $.isFunction(val = obj[key]) && (obj[key] = $.proxy(val, obj)); + } + }, + indexOf: function(haystack, needle) { + for (var i = 0; i < haystack.length; i++) { + if (haystack[i] === needle) { + return i; + } + } + return -1; + }, + each: $.each, + map: $.map, + filter: $.grep, + every: function(obj, test) { + var result = true; + if (!obj) { + return result; + } + $.each(obj, function(key, val) { + if (!(result = test.call(null, val, key, obj))) { + return false; + } + }); + return !!result; + }, + some: function(obj, test) { + var result = false; + if (!obj) { + return result; + } + $.each(obj, function(key, val) { + if (result = test.call(null, val, key, obj)) { + return false; + } + }); + return !!result; + }, + mixin: $.extend, + getUniqueId: function() { + var counter = 0; + return function() { + return counter++; + }; + }(), + defer: function(fn) { + setTimeout(fn, 0); + }, + debounce: function(func, wait, immediate) { + var timeout, result; + return function() { + var context = this, args = arguments, later, callNow; + later = function() { + timeout = null; + if (!immediate) { + result = func.apply(context, args); + } + }; + callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) { + result = func.apply(context, args); + } + return result; + }; + }, + throttle: function(func, wait) { + var context, args, timeout, result, previous, later; + previous = 0; + later = function() { + previous = new Date(); + timeout = null; + result = func.apply(context, args); + }; + return function() { + var now = new Date(), remaining = wait - (now - previous); + context = this; + args = arguments; + if (remaining <= 0) { + clearTimeout(timeout); + timeout = null; + previous = now; + result = func.apply(context, args); + } else if (!timeout) { + timeout = setTimeout(later, remaining); + } + return result; + }; + }, + tokenizeQuery: function(str) { + return $.trim(str).toLowerCase().split(/[\s]+/); + }, + tokenizeText: function(str) { + return $.trim(str).toLowerCase().split(/[\s\-_]+/); + }, + getProtocol: function() { + return location.protocol; + }, + noop: function() {} + }; + var EventTarget = function() { + var eventSplitter = /\s+/; + return { + on: function(events, callback) { + var event; + if (!callback) { + return this; + } + this._callbacks = this._callbacks || {}; + events = events.split(eventSplitter); + while (event = events.shift()) { + this._callbacks[event] = this._callbacks[event] || []; + this._callbacks[event].push(callback); + } + return this; + }, + trigger: function(events, data) { + var event, callbacks; + if (!this._callbacks) { + return this; + } + events = events.split(eventSplitter); + while (event = events.shift()) { + if (callbacks = this._callbacks[event]) { + for (var i = 0; i < callbacks.length; i += 1) { + callbacks[i].call(this, { + type: event, + data: data + }); + } + } + } + return this; + } + }; + }(); + var EventBus = function() { + var namespace = "typeahead:"; + function EventBus(o) { + if (!o || !o.el) { + $.error("EventBus initialized without el"); + } + this.$el = $(o.el); + } + utils.mixin(EventBus.prototype, { + trigger: function(type) { + var args = [].slice.call(arguments, 1); + this.$el.trigger(namespace + type, args); + } + }); + return EventBus; + }(); + var PersistentStorage = function() { + var ls, methods; + try { + ls = window.localStorage; + ls.setItem("~~~", "!"); + ls.removeItem("~~~"); + } catch (err) { + ls = null; + } + function PersistentStorage(namespace) { + this.prefix = [ "__", namespace, "__" ].join(""); + this.ttlKey = "__ttl__"; + this.keyMatcher = new RegExp("^" + this.prefix); + } + if (ls && window.JSON) { + methods = { + _prefix: function(key) { + return this.prefix + key; + }, + _ttlKey: function(key) { + return this._prefix(key) + this.ttlKey; + }, + get: function(key) { + if (this.isExpired(key)) { + this.remove(key); + } + return decode(ls.getItem(this._prefix(key))); + }, + set: function(key, val, ttl) { + if (utils.isNumber(ttl)) { + ls.setItem(this._ttlKey(key), encode(now() + ttl)); + } else { + ls.removeItem(this._ttlKey(key)); + } + return ls.setItem(this._prefix(key), encode(val)); + }, + remove: function(key) { + ls.removeItem(this._ttlKey(key)); + ls.removeItem(this._prefix(key)); + return this; + }, + clear: function() { + var i, key, keys = [], len = ls.length; + for (i = 0; i < len; i++) { + if ((key = ls.key(i)).match(this.keyMatcher)) { + keys.push(key.replace(this.keyMatcher, "")); + } + } + for (i = keys.length; i--; ) { + this.remove(keys[i]); + } + return this; + }, + isExpired: function(key) { + var ttl = decode(ls.getItem(this._ttlKey(key))); + return utils.isNumber(ttl) && now() > ttl ? true : false; + } + }; + } else { + methods = { + get: utils.noop, + set: utils.noop, + remove: utils.noop, + clear: utils.noop, + isExpired: utils.noop + }; + } + utils.mixin(PersistentStorage.prototype, methods); + return PersistentStorage; + function now() { + return new Date().getTime(); + } + function encode(val) { + return JSON.stringify(utils.isUndefined(val) ? null : val); + } + function decode(val) { + return JSON.parse(val); + } + }(); + var RequestCache = function() { + function RequestCache(o) { + utils.bindAll(this); + o = o || {}; + this.sizeLimit = o.sizeLimit || 10; + this.cache = {}; + this.cachedKeysByAge = []; + } + utils.mixin(RequestCache.prototype, { + get: function(url) { + return this.cache[url]; + }, + set: function(url, resp) { + var requestToEvict; + if (this.cachedKeysByAge.length === this.sizeLimit) { + requestToEvict = this.cachedKeysByAge.shift(); + delete this.cache[requestToEvict]; + } + this.cache[url] = resp; + this.cachedKeysByAge.push(url); + } + }); + return RequestCache; + }(); + var Transport = function() { + var pendingRequestsCount = 0, pendingRequests = {}, maxPendingRequests, requestCache; + function Transport(o) { + utils.bindAll(this); + o = utils.isString(o) ? { + url: o + } : o; + requestCache = requestCache || new RequestCache(); + maxPendingRequests = utils.isNumber(o.maxParallelRequests) ? o.maxParallelRequests : maxPendingRequests || 6; + this.url = o.url; + this.wildcard = o.wildcard || "%QUERY"; + this.filter = o.filter; + this.replace = o.replace; + this.ajaxSettings = { + type: "get", + cache: o.cache, + timeout: o.timeout, + dataType: o.dataType || "json", + beforeSend: o.beforeSend + }; + this._get = (/^throttle$/i.test(o.rateLimitFn) ? utils.throttle : utils.debounce)(this._get, o.rateLimitWait || 300); + } + utils.mixin(Transport.prototype, { + _get: function(url, cb) { + var that = this; + if (belowPendingRequestsThreshold()) { + this._sendRequest(url).done(done); + } else { + this.onDeckRequestArgs = [].slice.call(arguments, 0); + } + function done(resp) { + var data = that.filter ? that.filter(resp) : resp; + cb && cb(data); + requestCache.set(url, resp); + } + }, + _sendRequest: function(url) { + var that = this, jqXhr = pendingRequests[url]; + if (!jqXhr) { + incrementPendingRequests(); + jqXhr = pendingRequests[url] = $.ajax(url, this.ajaxSettings).always(always); + } + return jqXhr; + function always() { + decrementPendingRequests(); + pendingRequests[url] = null; + if (that.onDeckRequestArgs) { + that._get.apply(that, that.onDeckRequestArgs); + that.onDeckRequestArgs = null; + } + } + }, + get: function(query, cb) { + var that = this, encodedQuery = encodeURIComponent(query || ""), url, resp; + cb = cb || utils.noop; + url = this.replace ? this.replace(this.url, encodedQuery) : this.url.replace(this.wildcard, encodedQuery); + if (resp = requestCache.get(url)) { + utils.defer(function() { + cb(that.filter ? that.filter(resp) : resp); + }); + } else { + this._get(url, cb); + } + return !!resp; + } + }); + return Transport; + function incrementPendingRequests() { + pendingRequestsCount++; + } + function decrementPendingRequests() { + pendingRequestsCount--; + } + function belowPendingRequestsThreshold() { + return pendingRequestsCount < maxPendingRequests; + } + }(); + var Dataset = function() { + var keys = { + thumbprint: "thumbprint", + protocol: "protocol", + itemHash: "itemHash", + adjacencyList: "adjacencyList" + }; + function Dataset(o) { + utils.bindAll(this); + if (utils.isString(o.template) && !o.engine) { + $.error("no template engine specified"); + } + if (!o.local && !o.prefetch && !o.remote) { + $.error("one of local, prefetch, or remote is required"); + } + this.name = o.name || utils.getUniqueId(); + this.limit = o.limit || 5; + this.minLength = o.minLength || 1; + this.header = o.header; + this.footer = o.footer; + this.valueKey = o.valueKey || "value"; + this.template = compileTemplate(o.template, o.engine, this.valueKey); + this.local = o.local; + this.prefetch = o.prefetch; + this.remote = o.remote; + this.itemHash = {}; + this.adjacencyList = {}; + this.storage = o.name ? new PersistentStorage(o.name) : null; + } + utils.mixin(Dataset.prototype, { + _processLocalData: function(data) { + this._mergeProcessedData(this._processData(data)); + }, + _loadPrefetchData: function(o) { + var that = this, thumbprint = VERSION + (o.thumbprint || ""), storedThumbprint, storedProtocol, storedItemHash, storedAdjacencyList, isExpired, deferred; + if (this.storage) { + storedThumbprint = this.storage.get(keys.thumbprint); + storedProtocol = this.storage.get(keys.protocol); + storedItemHash = this.storage.get(keys.itemHash); + storedAdjacencyList = this.storage.get(keys.adjacencyList); + } + isExpired = storedThumbprint !== thumbprint || storedProtocol !== utils.getProtocol(); + o = utils.isString(o) ? { + url: o + } : o; + o.ttl = utils.isNumber(o.ttl) ? o.ttl : 24 * 60 * 60 * 1e3; + if (storedItemHash && storedAdjacencyList && !isExpired) { + this._mergeProcessedData({ + itemHash: storedItemHash, + adjacencyList: storedAdjacencyList + }); + deferred = $.Deferred().resolve(); + } else { + deferred = $.getJSON(o.url).done(processPrefetchData); + } + return deferred; + function processPrefetchData(data) { + var filteredData = o.filter ? o.filter(data) : data, processedData = that._processData(filteredData), itemHash = processedData.itemHash, adjacencyList = processedData.adjacencyList; + if (that.storage) { + that.storage.set(keys.itemHash, itemHash, o.ttl); + that.storage.set(keys.adjacencyList, adjacencyList, o.ttl); + that.storage.set(keys.thumbprint, thumbprint, o.ttl); + that.storage.set(keys.protocol, utils.getProtocol(), o.ttl); + } + that._mergeProcessedData(processedData); + } + }, + _transformDatum: function(datum) { + var value = utils.isString(datum) ? datum : datum[this.valueKey], tokens = datum.tokens || utils.tokenizeText(value), item = { + value: value, + tokens: tokens + }; + if (utils.isString(datum)) { + item.datum = {}; + item.datum[this.valueKey] = datum; + } else { + item.datum = datum; + } + item.tokens = utils.filter(item.tokens, function(token) { + return !utils.isBlankString(token); + }); + item.tokens = utils.map(item.tokens, function(token) { + return token.toLowerCase(); + }); + return item; + }, + _processData: function(data) { + var that = this, itemHash = {}, adjacencyList = {}; + utils.each(data, function(i, datum) { + var item = that._transformDatum(datum), id = utils.getUniqueId(item.value); + itemHash[id] = item; + utils.each(item.tokens, function(i, token) { + var character = token.charAt(0), adjacency = adjacencyList[character] || (adjacencyList[character] = [ id ]); + !~utils.indexOf(adjacency, id) && adjacency.push(id); + }); + }); + return { + itemHash: itemHash, + adjacencyList: adjacencyList + }; + }, + _mergeProcessedData: function(processedData) { + var that = this; + utils.mixin(this.itemHash, processedData.itemHash); + utils.each(processedData.adjacencyList, function(character, adjacency) { + var masterAdjacency = that.adjacencyList[character]; + that.adjacencyList[character] = masterAdjacency ? masterAdjacency.concat(adjacency) : adjacency; + }); + }, + _getLocalSuggestions: function(terms) { + var that = this, firstChars = [], lists = [], shortestList, suggestions = []; + utils.each(terms, function(i, term) { + var firstChar = term.charAt(0); + !~utils.indexOf(firstChars, firstChar) && firstChars.push(firstChar); + }); + utils.each(firstChars, function(i, firstChar) { + var list = that.adjacencyList[firstChar]; + if (!list) { + return false; + } + lists.push(list); + if (!shortestList || list.length < shortestList.length) { + shortestList = list; + } + }); + if (lists.length < firstChars.length) { + return []; + } + utils.each(shortestList, function(i, id) { + var item = that.itemHash[id], isCandidate, isMatch; + isCandidate = utils.every(lists, function(list) { + return ~utils.indexOf(list, id); + }); + isMatch = isCandidate && utils.every(terms, function(term) { + return utils.some(item.tokens, function(token) { + return token.indexOf(term) === 0; + }); + }); + isMatch && suggestions.push(item); + }); + return suggestions; + }, + initialize: function() { + var deferred; + this.local && this._processLocalData(this.local); + this.transport = this.remote ? new Transport(this.remote) : null; + deferred = this.prefetch ? this._loadPrefetchData(this.prefetch) : $.Deferred().resolve(); + this.local = this.prefetch = this.remote = null; + this.initialize = function() { + return deferred; + }; + return deferred; + }, + getSuggestions: function(query, cb) { + var that = this, terms, suggestions, cacheHit = false; + if (query.length < this.minLength) { + return; + } + terms = utils.tokenizeQuery(query); + suggestions = this._getLocalSuggestions(terms).slice(0, this.limit); + if (suggestions.length < this.limit && this.transport) { + cacheHit = this.transport.get(query, processRemoteData); + } + !cacheHit && cb && cb(suggestions); + function processRemoteData(data) { + suggestions = suggestions.slice(0); + utils.each(data, function(i, datum) { + var item = that._transformDatum(datum), isDuplicate; + isDuplicate = utils.some(suggestions, function(suggestion) { + return item.value === suggestion.value; + }); + !isDuplicate && suggestions.push(item); + return suggestions.length < that.limit; + }); + cb && cb(suggestions); + } + } + }); + return Dataset; + function compileTemplate(template, engine, valueKey) { + var renderFn, compiledTemplate; + if (utils.isFunction(template)) { + renderFn = template; + } else if (utils.isString(template)) { + compiledTemplate = engine.compile(template); + renderFn = utils.bind(compiledTemplate.render, compiledTemplate); + } else { + renderFn = function(context) { + return "<p>" + context[valueKey] + "</p>"; + }; + } + return renderFn; + } + }(); + var InputView = function() { + function InputView(o) { + var that = this; + utils.bindAll(this); + this.specialKeyCodeMap = { + 9: "tab", + 27: "esc", + 37: "left", + 39: "right", + 13: "enter", + 38: "up", + 40: "down" + }; + this.$hint = $(o.hint); + this.$input = $(o.input).on("blur.tt", this._handleBlur).on("focus.tt", this._handleFocus).on("keydown.tt", this._handleSpecialKeyEvent); + if (!utils.isMsie()) { + this.$input.on("input.tt", this._compareQueryToInputValue); + } else { + this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) { + if (that.specialKeyCodeMap[$e.which || $e.keyCode]) { + return; + } + utils.defer(that._compareQueryToInputValue); + }); + } + this.query = this.$input.val(); + this.$overflowHelper = buildOverflowHelper(this.$input); + } + utils.mixin(InputView.prototype, EventTarget, { + _handleFocus: function() { + this.trigger("focused"); + }, + _handleBlur: function() { + this.trigger("blured"); + }, + _handleSpecialKeyEvent: function($e) { + var keyName = this.specialKeyCodeMap[$e.which || $e.keyCode]; + keyName && this.trigger(keyName + "Keyed", $e); + }, + _compareQueryToInputValue: function() { + var inputValue = this.getInputValue(), isSameQuery = compareQueries(this.query, inputValue), isSameQueryExceptWhitespace = isSameQuery ? this.query.length !== inputValue.length : false; + if (isSameQueryExceptWhitespace) { + this.trigger("whitespaceChanged", { + value: this.query + }); + } else if (!isSameQuery) { + this.trigger("queryChanged", { + value: this.query = inputValue + }); + } + }, + destroy: function() { + this.$hint.off(".tt"); + this.$input.off(".tt"); + this.$hint = this.$input = this.$overflowHelper = null; + }, + focus: function() { + this.$input.focus(); + }, + blur: function() { + this.$input.blur(); + }, + getQuery: function() { + return this.query; + }, + setQuery: function(query) { + this.query = query; + }, + getInputValue: function() { + return this.$input.val(); + }, + setInputValue: function(value, silent) { + this.$input.val(value); + !silent && this._compareQueryToInputValue(); + }, + getHintValue: function() { + return this.$hint.val(); + }, + setHintValue: function(value) { + this.$hint.val(value); + }, + getLanguageDirection: function() { + return (this.$input.css("direction") || "ltr").toLowerCase(); + }, + isOverflow: function() { + this.$overflowHelper.text(this.getInputValue()); + return this.$overflowHelper.width() > this.$input.width(); + }, + isCursorAtEnd: function() { + var valueLength = this.$input.val().length, selectionStart = this.$input[0].selectionStart, range; + if (utils.isNumber(selectionStart)) { + return selectionStart === valueLength; + } else if (document.selection) { + range = document.selection.createRange(); + range.moveStart("character", -valueLength); + return valueLength === range.text.length; + } + return true; + } + }); + return InputView; + function buildOverflowHelper($input) { + return $("<span></span>").css({ + position: "absolute", + left: "-9999px", + visibility: "hidden", + whiteSpace: "nowrap", + fontFamily: $input.css("font-family"), + fontSize: $input.css("font-size"), + fontStyle: $input.css("font-style"), + fontVariant: $input.css("font-variant"), + fontWeight: $input.css("font-weight"), + wordSpacing: $input.css("word-spacing"), + letterSpacing: $input.css("letter-spacing"), + textIndent: $input.css("text-indent"), + textRendering: $input.css("text-rendering"), + textTransform: $input.css("text-transform") + }).insertAfter($input); + } + function compareQueries(a, b) { + a = (a || "").replace(/^\s*/g, "").replace(/\s{2,}/g, " "); + b = (b || "").replace(/^\s*/g, "").replace(/\s{2,}/g, " "); + return a === b; + } + }(); + var DropdownView = function() { + var html = { + suggestionsList: '<span class="tt-suggestions"></span>' + }, css = { + suggestionsList: { + display: "block" + }, + suggestion: { + whiteSpace: "nowrap", + cursor: "pointer" + }, + suggestionChild: { + whiteSpace: "normal" + } + }; + function DropdownView(o) { + utils.bindAll(this); + this.isOpen = false; + this.isEmpty = true; + this.isMouseOverDropdown = false; + this.$menu = $(o.menu).on("mouseenter.tt", this._handleMouseenter).on("mouseleave.tt", this._handleMouseleave).on("click.tt", ".tt-suggestion", this._handleSelection).on("mouseover.tt", ".tt-suggestion", this._handleMouseover); + } + utils.mixin(DropdownView.prototype, EventTarget, { + _handleMouseenter: function() { + this.isMouseOverDropdown = true; + }, + _handleMouseleave: function() { + this.isMouseOverDropdown = false; + }, + _handleMouseover: function($e) { + var $suggestion = $($e.currentTarget); + this._getSuggestions().removeClass("tt-is-under-cursor"); + $suggestion.addClass("tt-is-under-cursor"); + }, + _handleSelection: function($e) { + var $suggestion = $($e.currentTarget); + this.trigger("suggestionSelected", extractSuggestion($suggestion)); + }, + _show: function() { + this.$menu.css("display", "block"); + }, + _hide: function() { + this.$menu.hide(); + }, + _moveCursor: function(increment) { + var $suggestions, $cur, nextIndex, $underCursor; + if (!this.isVisible()) { + return; + } + $suggestions = this._getSuggestions(); + $cur = $suggestions.filter(".tt-is-under-cursor"); + $cur.removeClass("tt-is-under-cursor"); + nextIndex = $suggestions.index($cur) + increment; + nextIndex = (nextIndex + 1) % ($suggestions.length + 1) - 1; + if (nextIndex === -1) { + this.trigger("cursorRemoved"); + return; + } else if (nextIndex < -1) { + nextIndex = $suggestions.length - 1; + } + $underCursor = $suggestions.eq(nextIndex).addClass("tt-is-under-cursor"); + this._ensureVisibility($underCursor); + this.trigger("cursorMoved", extractSuggestion($underCursor)); + }, + _getSuggestions: function() { + return this.$menu.find(".tt-suggestions > .tt-suggestion"); + }, + _ensureVisibility: function($el) { + var menuHeight = this.$menu.height() + parseInt(this.$menu.css("paddingTop"), 10) + parseInt(this.$menu.css("paddingBottom"), 10), menuScrollTop = this.$menu.scrollTop(), elTop = $el.position().top, elBottom = elTop + $el.outerHeight(true); + if (elTop < 0) { + this.$menu.scrollTop(menuScrollTop + elTop); + } else if (menuHeight < elBottom) { + this.$menu.scrollTop(menuScrollTop + (elBottom - menuHeight)); + } + }, + destroy: function() { + this.$menu.off(".tt"); + this.$menu = null; + }, + isVisible: function() { + return this.isOpen && !this.isEmpty; + }, + closeUnlessMouseIsOverDropdown: function() { + if (!this.isMouseOverDropdown) { + this.close(); + } + }, + close: function() { + if (this.isOpen) { + this.isOpen = false; + this.isMouseOverDropdown = false; + this._hide(); + this.$menu.find(".tt-suggestions > .tt-suggestion").removeClass("tt-is-under-cursor"); + this.trigger("closed"); + } + }, + open: function() { + if (!this.isOpen) { + this.isOpen = true; + !this.isEmpty && this._show(); + this.trigger("opened"); + } + }, + setLanguageDirection: function(dir) { + var ltrCss = { + left: "0", + right: "auto" + }, rtlCss = { + left: "auto", + right: " 0" + }; + dir === "ltr" ? this.$menu.css(ltrCss) : this.$menu.css(rtlCss); + }, + moveCursorUp: function() { + this._moveCursor(-1); + }, + moveCursorDown: function() { + this._moveCursor(+1); + }, + getSuggestionUnderCursor: function() { + var $suggestion = this._getSuggestions().filter(".tt-is-under-cursor").first(); + return $suggestion.length > 0 ? extractSuggestion($suggestion) : null; + }, + getFirstSuggestion: function() { + var $suggestion = this._getSuggestions().first(); + return $suggestion.length > 0 ? extractSuggestion($suggestion) : null; + }, + renderSuggestions: function(dataset, suggestions) { + var datasetClassName = "tt-dataset-" + dataset.name, wrapper = '<div class="tt-suggestion">%body</div>', compiledHtml, $suggestionsList, $dataset = this.$menu.find("." + datasetClassName), elBuilder, fragment, $el; + if ($dataset.length === 0) { + $suggestionsList = $(html.suggestionsList).css(css.suggestionsList); + $dataset = $("<div></div>").addClass(datasetClassName).append(dataset.header).append($suggestionsList).append(dataset.footer).appendTo(this.$menu); + } + if (suggestions.length > 0) { + this.isEmpty = false; + this.isOpen && this._show(); + elBuilder = document.createElement("div"); + fragment = document.createDocumentFragment(); + utils.each(suggestions, function(i, suggestion) { + suggestion.dataset = dataset.name; + compiledHtml = dataset.template(suggestion.datum); + elBuilder.innerHTML = wrapper.replace("%body", compiledHtml); + $el = $(elBuilder.firstChild).css(css.suggestion).data("suggestion", suggestion); + $el.children().each(function() { + $(this).css(css.suggestionChild); + }); + fragment.appendChild($el[0]); + }); + $dataset.show().find(".tt-suggestions").html(fragment); + } else { + this.clearSuggestions(dataset.name); + } + this.trigger("suggestionsRendered"); + }, + clearSuggestions: function(datasetName) { + var $datasets = datasetName ? this.$menu.find(".tt-dataset-" + datasetName) : this.$menu.find('[class^="tt-dataset-"]'), $suggestions = $datasets.find(".tt-suggestions"); + $datasets.hide(); + $suggestions.empty(); + if (this._getSuggestions().length === 0) { + this.isEmpty = true; + this._hide(); + } + } + }); + return DropdownView; + function extractSuggestion($el) { + return $el.data("suggestion"); + } + }(); + var TypeaheadView = function() { + var html = { + wrapper: '<span class="twitter-typeahead"></span>', + hint: '<input class="tt-hint" type="text" autocomplete="off" spellcheck="off" disabled>', + dropdown: '<span class="tt-dropdown-menu"></span>' + }, css = { + wrapper: { + position: "relative", + display: "inline-block" + }, + hint: { + position: "absolute", + top: "0", + left: "0", + borderColor: "transparent", + boxShadow: "none" + }, + query: { + position: "relative", + verticalAlign: "top", + backgroundColor: "transparent" + }, + dropdown: { + position: "absolute", + top: "100%", + left: "0", + zIndex: "100", + display: "none" + } + }; + if (utils.isMsie()) { + utils.mixin(css.query, { + backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)" + }); + } + if (utils.isMsie() && utils.isMsie() <= 7) { + utils.mixin(css.wrapper, { + display: "inline", + zoom: "1" + }); + utils.mixin(css.query, { + marginTop: "-1px" + }); + } + function TypeaheadView(o) { + var $menu, $input, $hint; + utils.bindAll(this); + this.$node = buildDomStructure(o.input); + this.datasets = o.datasets; + this.dir = null; + this.eventBus = o.eventBus; + $menu = this.$node.find(".tt-dropdown-menu"); + $input = this.$node.find(".tt-query"); + $hint = this.$node.find(".tt-hint"); + this.dropdownView = new DropdownView({ + menu: $menu + }).on("suggestionSelected", this._handleSelection).on("cursorMoved", this._clearHint).on("cursorMoved", this._setInputValueToSuggestionUnderCursor).on("cursorRemoved", this._setInputValueToQuery).on("cursorRemoved", this._updateHint).on("suggestionsRendered", this._updateHint).on("opened", this._updateHint).on("closed", this._clearHint).on("opened closed", this._propagateEvent); + this.inputView = new InputView({ + input: $input, + hint: $hint + }).on("focused", this._openDropdown).on("blured", this._closeDropdown).on("blured", this._setInputValueToQuery).on("enterKeyed tabKeyed", this._handleSelection).on("queryChanged", this._clearHint).on("queryChanged", this._clearSuggestions).on("queryChanged", this._getSuggestions).on("whitespaceChanged", this._updateHint).on("queryChanged whitespaceChanged", this._openDropdown).on("queryChanged whitespaceChanged", this._setLanguageDirection).on("escKeyed", this._closeDropdown).on("escKeyed", this._setInputValueToQuery).on("tabKeyed upKeyed downKeyed", this._managePreventDefault).on("upKeyed downKeyed", this._moveDropdownCursor).on("upKeyed downKeyed", this._openDropdown).on("tabKeyed leftKeyed rightKeyed", this._autocomplete); + } + utils.mixin(TypeaheadView.prototype, EventTarget, { + _managePreventDefault: function(e) { + var $e = e.data, hint, inputValue, preventDefault = false; + switch (e.type) { + case "tabKeyed": + hint = this.inputView.getHintValue(); + inputValue = this.inputView.getInputValue(); + preventDefault = hint && hint !== inputValue; + break; + + case "upKeyed": + case "downKeyed": + preventDefault = !$e.shiftKey && !$e.ctrlKey && !$e.metaKey; + break; + } + preventDefault && $e.preventDefault(); + }, + _setLanguageDirection: function() { + var dir = this.inputView.getLanguageDirection(); + if (dir !== this.dir) { + this.dir = dir; + this.$node.css("direction", dir); + this.dropdownView.setLanguageDirection(dir); + } + }, + _updateHint: function() { + var suggestion = this.dropdownView.getFirstSuggestion(), hint = suggestion ? suggestion.value : null, dropdownIsVisible = this.dropdownView.isVisible(), inputHasOverflow = this.inputView.isOverflow(), inputValue, query, escapedQuery, beginsWithQuery, match; + if (hint && dropdownIsVisible && !inputHasOverflow) { + inputValue = this.inputView.getInputValue(); + query = inputValue.replace(/\s{2,}/g, " ").replace(/^\s+/g, ""); + escapedQuery = utils.escapeRegExChars(query); + beginsWithQuery = new RegExp("^(?:" + escapedQuery + ")(.*$)", "i"); + match = beginsWithQuery.exec(hint); + this.inputView.setHintValue(inputValue + (match ? match[1] : "")); + } + }, + _clearHint: function() { + this.inputView.setHintValue(""); + }, + _clearSuggestions: function() { + this.dropdownView.clearSuggestions(); + }, + _setInputValueToQuery: function() { + this.inputView.setInputValue(this.inputView.getQuery()); + }, + _setInputValueToSuggestionUnderCursor: function(e) { + var suggestion = e.data; + this.inputView.setInputValue(suggestion.value, true); + }, + _openDropdown: function() { + this.dropdownView.open(); + }, + _closeDropdown: function(e) { + this.dropdownView[e.type === "blured" ? "closeUnlessMouseIsOverDropdown" : "close"](); + }, + _moveDropdownCursor: function(e) { + var $e = e.data; + if (!$e.shiftKey && !$e.ctrlKey && !$e.metaKey) { + this.dropdownView[e.type === "upKeyed" ? "moveCursorUp" : "moveCursorDown"](); + } + }, + _handleSelection: function(e) { + var byClick = e.type === "suggestionSelected", suggestion = byClick ? e.data : this.dropdownView.getSuggestionUnderCursor(); + if (suggestion) { + this.inputView.setInputValue(suggestion.value); + byClick ? this.inputView.focus() : e.data.preventDefault(); + byClick && utils.isMsie() ? utils.defer(this.dropdownView.close) : this.dropdownView.close(); + this.eventBus.trigger("selected", suggestion.datum, suggestion.dataset); + } + }, + _getSuggestions: function() { + var that = this, query = this.inputView.getQuery(); + if (utils.isBlankString(query)) { + return; + } + utils.each(this.datasets, function(i, dataset) { + dataset.getSuggestions(query, function(suggestions) { + if (query === that.inputView.getQuery()) { + that.dropdownView.renderSuggestions(dataset, suggestions); + } + }); + }); + }, + _autocomplete: function(e) { + var isCursorAtEnd, ignoreEvent, query, hint, suggestion; + if (e.type === "rightKeyed" || e.type === "leftKeyed") { + isCursorAtEnd = this.inputView.isCursorAtEnd(); + ignoreEvent = this.inputView.getLanguageDirection() === "ltr" ? e.type === "leftKeyed" : e.type === "rightKeyed"; + if (!isCursorAtEnd || ignoreEvent) { + return; + } + } + query = this.inputView.getQuery(); + hint = this.inputView.getHintValue(); + if (hint !== "" && query !== hint) { + suggestion = this.dropdownView.getFirstSuggestion(); + this.inputView.setInputValue(suggestion.value); + this.eventBus.trigger("autocompleted", suggestion.datum, suggestion.dataset); + } + }, + _propagateEvent: function(e) { + this.eventBus.trigger(e.type); + }, + destroy: function() { + this.inputView.destroy(); + this.dropdownView.destroy(); + destroyDomStructure(this.$node); + this.$node = null; + }, + setQuery: function(query) { + this.inputView.setQuery(query); + this.inputView.setInputValue(query); + this._clearHint(); + this._clearSuggestions(); + this._getSuggestions(); + } + }); + return TypeaheadView; + function buildDomStructure(input) { + var $wrapper = $(html.wrapper), $dropdown = $(html.dropdown), $input = $(input), $hint = $(html.hint); + $wrapper = $wrapper.css(css.wrapper); + $dropdown = $dropdown.css(css.dropdown); + $hint.css(css.hint).css({ + backgroundAttachment: $input.css("background-attachment"), + backgroundClip: $input.css("background-clip"), + backgroundColor: $input.css("background-color"), + backgroundImage: $input.css("background-image"), + backgroundOrigin: $input.css("background-origin"), + backgroundPosition: $input.css("background-position"), + backgroundRepeat: $input.css("background-repeat"), + backgroundSize: $input.css("background-size") + }); + $input.data("ttAttrs", { + dir: $input.attr("dir"), + autocomplete: $input.attr("autocomplete"), + spellcheck: $input.attr("spellcheck"), + style: $input.attr("style") + }); + $input.addClass("tt-query").attr({ + autocomplete: "off", + spellcheck: false + }).css(css.query); + try { + !$input.attr("dir") && $input.attr("dir", "auto"); + } catch (e) {} + return $input.wrap($wrapper).parent().prepend($hint).append($dropdown); + } + function destroyDomStructure($node) { + var $input = $node.find(".tt-query"); + utils.each($input.data("ttAttrs"), function(key, val) { + utils.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val); + }); + $input.detach().removeData("ttAttrs").removeClass("tt-query").insertAfter($node); + $node.remove(); + } + }(); + (function() { + var cache = {}, viewKey = "ttView", methods; + methods = { + initialize: function(datasetDefs) { + var datasets; + datasetDefs = utils.isArray(datasetDefs) ? datasetDefs : [ datasetDefs ]; + if (datasetDefs.length === 0) { + $.error("no datasets provided"); + } + datasets = utils.map(datasetDefs, function(o) { + var dataset = cache[o.name] ? cache[o.name] : new Dataset(o); + if (o.name) { + cache[o.name] = dataset; + } + return dataset; + }); + return this.each(initialize); + function initialize() { + var $input = $(this), deferreds, eventBus = new EventBus({ + el: $input + }); + deferreds = utils.map(datasets, function(dataset) { + return dataset.initialize(); + }); + $input.data(viewKey, new TypeaheadView({ + input: $input, + eventBus: eventBus = new EventBus({ + el: $input + }), + datasets: datasets + })); + $.when.apply($, deferreds).always(function() { + utils.defer(function() { + eventBus.trigger("initialized"); + }); + }); + } + }, + destroy: function() { + return this.each(destroy); + function destroy() { + var $this = $(this), view = $this.data(viewKey); + if (view) { + view.destroy(); + $this.removeData(viewKey); + } + } + }, + setQuery: function(query) { + return this.each(setQuery); + function setQuery() { + var view = $(this).data(viewKey); + view && view.setQuery(query); + } + } + }; + jQuery.fn.typeahead = function(method) { + if (methods[method]) { + return methods[method].apply(this, [].slice.call(arguments, 1)); + } else { + return methods.initialize.apply(this, arguments); + } + }; + })(); +})(window.jQuery); \ No newline at end of file diff --git a/extensions/gii/assets/typeahead.js-bootstrap.css b/extensions/gii/assets/typeahead.js-bootstrap.css new file mode 100644 index 0000000..987aaf5 --- /dev/null +++ b/extensions/gii/assets/typeahead.js-bootstrap.css @@ -0,0 +1,51 @@ +/* always keep this link here when updating this file: https://github.com/jharding/typeahead.js-bootstrap.css */ + +.twitter-typeahead .tt-query, +.twitter-typeahead .tt-hint { + margin-bottom: 0; +} + +.tt-dropdown-menu { + min-width: 160px; + margin-top: 2px; + padding: 5px 0; + background-color: #fff; + border: 1px solid #ccc; + border: 1px solid rgba(0,0,0,.2); + *border-right-width: 2px; + *border-bottom-width: 2px; + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; + -webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2); + -moz-box-shadow: 0 5px 10px rgba(0,0,0,.2); + box-shadow: 0 5px 10px rgba(0,0,0,.2); + -webkit-background-clip: padding-box; + -moz-background-clip: padding; + background-clip: padding-box; +} + +.tt-suggestion { + display: block; + padding: 3px 20px; +} + +.tt-suggestion.tt-is-under-cursor { + color: #fff; + background-color: #0081c2; + background-image: -moz-linear-gradient(top, #0088cc, #0077b3); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3)); + background-image: -webkit-linear-gradient(top, #0088cc, #0077b3); + background-image: -o-linear-gradient(top, #0088cc, #0077b3); + background-image: linear-gradient(to bottom, #0088cc, #0077b3); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0) +} + +.tt-suggestion.tt-is-under-cursor a { + color: #fff; +} + +.tt-suggestion p { + margin: 0; +} diff --git a/framework/yii/gii/components/ActiveField.php b/extensions/gii/components/ActiveField.php similarity index 72% rename from framework/yii/gii/components/ActiveField.php rename to extensions/gii/components/ActiveField.php index 6a67217..ae6f144 100644 --- a/framework/yii/gii/components/ActiveField.php +++ b/extensions/gii/components/ActiveField.php @@ -8,6 +8,7 @@ namespace yii\gii\components; use yii\gii\Generator; +use yii\helpers\Json; /** * @author Qiang Xue <qiang.xue@gmail.com> @@ -30,11 +31,36 @@ class ActiveField extends \yii\widgets\ActiveField if (isset($hints[$this->attribute])) { $this->hint($hints[$this->attribute]); } + $autoCompleteData = $this->model->autoCompleteData(); + if (isset($autoCompleteData[$this->attribute])) { + if (is_callable($autoCompleteData[$this->attribute])) { + $this->autoComplete(call_user_func($autoCompleteData[$this->attribute])); + } else { + $this->autoComplete($autoCompleteData[$this->attribute]); + } + } } + /** + * Makes field remember its value between page reloads + * @return static the field object itself + */ public function sticky() { $this->options['class'] .= ' sticky'; return $this; } + + /** + * Makes field auto completable + * @param array $data auto complete data (array of callables or scalars) + * @return static the field object itself + */ + public function autoComplete($data) + { + static $counter = 0; + $this->inputOptions['class'] .= ' typeahead-' . (++$counter); + $this->form->getView()->registerJs("jQuery('.typeahead-{$counter}').typeahead({local: " . Json::encode($data) . "});"); + return $this; + } } diff --git a/extensions/gii/composer.json b/extensions/gii/composer.json new file mode 100644 index 0000000..8654621 --- /dev/null +++ b/extensions/gii/composer.json @@ -0,0 +1,28 @@ +{ + "name": "yiisoft/yii2-gii", + "description": "The Gii extension for the Yii framework", + "keywords": ["yii", "gii", "code generator"], + "type": "yii2-extension", + "license": "BSD-3-Clause", + "support": { + "issues": "https://github.com/yiisoft/yii2/issues?state=open", + "forum": "http://www.yiiframework.com/forum/", + "wiki": "http://www.yiiframework.com/wiki/", + "irc": "irc://irc.freenode.net/yii", + "source": "https://github.com/yiisoft/yii2" + }, + "authors": [ + { + "name": "Qiang Xue", + "email": "qiang.xue@gmail.com" + } + ], + "require": { + "yiisoft/yii2": "*", + "yiisoft/yii2-bootstrap": "*" + }, + "autoload": { + "psr-0": { "yii\\gii\\": "" } + }, + "target-dir": "yii/gii" +} diff --git a/framework/yii/gii/controllers/DefaultController.php b/extensions/gii/controllers/DefaultController.php similarity index 96% rename from framework/yii/gii/controllers/DefaultController.php rename to extensions/gii/controllers/DefaultController.php index 305ef35..ef104c3 100644 --- a/framework/yii/gii/controllers/DefaultController.php +++ b/extensions/gii/controllers/DefaultController.php @@ -36,7 +36,7 @@ class DefaultController extends Controller public function actionView($id) { $generator = $this->loadGenerator($id); - $params = array('generator' => $generator, 'id' => $id); + $params = ['generator' => $generator, 'id' => $id]; if (isset($_POST['preview']) || isset($_POST['generate'])) { if ($generator->validate()) { $generator->saveStickyAttributes(); @@ -78,9 +78,9 @@ class DefaultController extends Controller if ($generator->validate()) { foreach ($generator->generate() as $f) { if ($f->id === $file) { - return $this->renderPartial('diff', array( + return $this->renderPartial('diff', [ 'diff' => $f->diff(), - )); + ]); } } } @@ -107,7 +107,7 @@ class DefaultController extends Controller } } - public function createUrl($route, $params = array()) + public function createUrl($route, $params = []) { if (!isset($params['id']) && $this->generator !== null) { foreach ($this->module->generators as $id => $generator) { @@ -120,7 +120,7 @@ class DefaultController extends Controller return parent::createUrl($route, $params); } - public function createActionUrl($name, $params = array()) + public function createActionUrl($name, $params = []) { foreach ($this->module->generators as $id => $generator) { if ($generator === $this->generator) { diff --git a/framework/yii/gii/generators/controller/Generator.php b/extensions/gii/generators/controller/Generator.php similarity index 81% rename from framework/yii/gii/generators/controller/Generator.php rename to extensions/gii/generators/controller/Generator.php index 9de9c17..08b29d5 100644 --- a/framework/yii/gii/generators/controller/Generator.php +++ b/extensions/gii/generators/controller/Generator.php @@ -31,7 +31,7 @@ class Generator extends \yii\gii\Generator /** * @var string the namespace of the controller class */ - public $ns = 'app\controllers'; + public $ns; /** * @var string list of action IDs separated by commas or spaces */ @@ -40,6 +40,15 @@ class Generator extends \yii\gii\Generator /** * @inheritdoc */ + public function init() + { + parent::init(); + $this->ns = \Yii::$app->controllerNamespace; + } + + /** + * @inheritdoc + */ public function getName() { return 'Controller Generator'; @@ -59,14 +68,14 @@ class Generator extends \yii\gii\Generator */ public function rules() { - return array_merge(parent::rules(), array( - array('controller, actions, baseClass, ns', 'filter', 'filter' => 'trim'), - array('controller, baseClass', 'required'), - array('controller', 'match', 'pattern' => '/^[a-z\\-\\/]*$/', 'message' => 'Only a-z, dashes (-) and slashes (/) are allowed.'), - array('actions', 'match', 'pattern' => '/^[a-z\\-,\\s]*$/', 'message' => 'Only a-z, dashes (-), spaces and commas are allowed.'), - array('baseClass', 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'), - array('ns', 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'), - )); + return array_merge(parent::rules(), [ + [['controller', 'actions', 'baseClass', 'ns'], 'filter', 'filter' => 'trim'], + [['controller', 'baseClass'], 'required'], + [['controller'], 'match', 'pattern' => '/^[a-z\\-\\/]*$/', 'message' => 'Only a-z, dashes (-) and slashes (/) are allowed.'], + [['actions'], 'match', 'pattern' => '/^[a-z\\-,\\s]*$/', 'message' => 'Only a-z, dashes (-), spaces and commas are allowed.'], + [['baseClass'], 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'], + [['ns'], 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'], + ]); } /** @@ -74,12 +83,12 @@ class Generator extends \yii\gii\Generator */ public function attributeLabels() { - return array( + return [ 'baseClass' => 'Base Class', 'controller' => 'Controller ID', 'actions' => 'Action IDs', 'ns' => 'Controller Namespace', - ); + ]; } /** @@ -87,10 +96,10 @@ class Generator extends \yii\gii\Generator */ public function requiredTemplates() { - return array( + return [ 'controller.php', 'view.php', - ); + ]; } /** @@ -98,7 +107,7 @@ class Generator extends \yii\gii\Generator */ public function stickyAttributes() { - return array('ns', 'baseClass'); + return ['ns', 'baseClass']; } /** @@ -106,7 +115,7 @@ class Generator extends \yii\gii\Generator */ public function hints() { - return array( + return [ 'controller' => 'Controller ID should be in lower case and may contain module ID(s) separated by slashes. For example: <ul> <li><code>order</code> generates <code>OrderController.php</code></li> @@ -121,7 +130,7 @@ class Generator extends \yii\gii\Generator </ul>', 'ns' => 'This is the namespace that the new controller class will use.', 'baseClass' => 'This is the class that the new controller class will extend from. Please make sure the class exists and can be autoloaded.', - ); + ]; } /** @@ -135,7 +144,7 @@ class Generator extends \yii\gii\Generator } else { $route = $this->controller . '/' . reset($actions); } - $link = Html::a('try it now', Yii::$app->getUrlManager()->createUrl($route), array('target' => '_blank')); + $link = Html::a('try it now', Yii::$app->getUrlManager()->createUrl($route), ['target' => '_blank']); return "The controller has been generated successfully. You may $link."; } @@ -144,7 +153,7 @@ class Generator extends \yii\gii\Generator */ public function generate() { - $files = array(); + $files = []; $files[] = new CodeFile( $this->getControllerFile(), @@ -154,7 +163,7 @@ class Generator extends \yii\gii\Generator foreach ($this->getActionIDs() as $action) { $files[] = new CodeFile( $this->getViewFile($action), - $this->render('view.php', array('action' => $action)) + $this->render('view.php', ['action' => $action]) ); } @@ -197,7 +206,7 @@ class Generator extends \yii\gii\Generator */ public function getModule() { - if (($pos = strpos($this->controller, '/')) !== false) { + if (($pos = strrpos($this->controller, '/')) !== false) { $id = substr($this->controller, 0, $pos); if (($module = Yii::$app->getModule($id)) !== null) { return $module; diff --git a/framework/yii/gii/generators/controller/form.php b/extensions/gii/generators/controller/form.php similarity index 90% rename from framework/yii/gii/generators/controller/form.php rename to extensions/gii/generators/controller/form.php index e4d2947..4fba36f 100644 --- a/framework/yii/gii/generators/controller/form.php +++ b/extensions/gii/generators/controller/form.php @@ -1,6 +1,6 @@ <?php /** - * @var yii\base\View $this + * @var yii\web\View $this * @var yii\widgets\ActiveForm $form * @var yii\gii\generators\controller\Generator $generator */ diff --git a/framework/yii/gii/generators/controller/templates/controller.php b/extensions/gii/generators/controller/templates/controller.php similarity index 73% rename from framework/yii/gii/generators/controller/templates/controller.php rename to extensions/gii/generators/controller/templates/controller.php index 3829d54..b62edc3 100644 --- a/framework/yii/gii/generators/controller/templates/controller.php +++ b/extensions/gii/generators/controller/templates/controller.php @@ -5,7 +5,7 @@ use yii\helpers\Inflector; /** * This is the template for generating a controller class file. * - * @var yii\base\View $this + * @var yii\web\View $this * @var yii\gii\generators\controller\Generator $generator */ @@ -13,15 +13,15 @@ echo "<?php\n"; ?> <?php if (!empty($generator->ns)): ?> -namespace <?php echo $generator->ns; ?>; +namespace <?= $generator->ns ?>; <?php endif; ?> -class <?php echo $generator->getControllerClass(); ?> extends <?php echo '\\' . trim($generator->baseClass, '\\') . "\n"; ?> +class <?= $generator->getControllerClass() ?> extends <?= '\\' . trim($generator->baseClass, '\\') . "\n" ?> { <?php foreach($generator->getActionIDs() as $action): ?> - public function action<?php echo Inflector::id2camel($action); ?>() + public function action<?= Inflector::id2camel($action) ?>() { - return $this->render('<?php echo $action; ?>'); + return $this->render('<?= $action ?>'); } <?php endforeach; ?> diff --git a/framework/yii/gii/generators/controller/templates/view.php b/extensions/gii/generators/controller/templates/view.php similarity index 73% rename from framework/yii/gii/generators/controller/templates/view.php rename to extensions/gii/generators/controller/templates/view.php index 4b75d7a..6ef2a87 100644 --- a/framework/yii/gii/generators/controller/templates/view.php +++ b/extensions/gii/generators/controller/templates/view.php @@ -2,7 +2,7 @@ /** * This is the template for generating an action view file. * - * @var yii\base\View $this + * @var yii\web\View $this * @var yii\gii\generators\controller\Generator $generator * @var string $action the action ID */ @@ -10,13 +10,13 @@ echo "<?php\n"; ?> /** - * @var yii\base\View $this + * @var yii\web\View $this */ -<?php echo "?>"; ?> +<?= "?>" ?> -<h1><?php echo $generator->getControllerID() . '/' . $action; ?></h1> +<h1><?= $generator->getControllerID() . '/' . $action ?></h1> <p> You may change the content of this page by modifying - the file <code><?php echo '<?php'; ?> echo __FILE__; ?></code>. + the file <code><?= '<?php' ?> echo __FILE__; ?></code>. </p> diff --git a/framework/yii/gii/generators/crud/Generator.php b/extensions/gii/generators/crud/Generator.php similarity index 71% rename from framework/yii/gii/generators/crud/Generator.php rename to extensions/gii/generators/crud/Generator.php index 9bce7ff..1925de2 100644 --- a/framework/yii/gii/generators/crud/Generator.php +++ b/extensions/gii/generators/crud/Generator.php @@ -8,7 +8,6 @@ namespace yii\gii\generators\crud; use Yii; -use yii\base\Model; use yii\db\ActiveRecord; use yii\db\Schema; use yii\gii\CodeFile; @@ -42,30 +41,31 @@ class Generator extends \yii\gii\Generator public function rules() { - return array_merge(parent::rules(), array( - array('moduleID, controllerClass, modelClass, searchModelClass, baseControllerClass', 'filter', 'filter' => 'trim'), - array('modelClass, searchModelClass, controllerClass, baseControllerClass, indexWidgetType', 'required'), - array('modelClass, controllerClass, baseControllerClass, searchModelClass', 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'), - array('modelClass', 'validateClass', 'params' => array('extends' => ActiveRecord::className())), - array('baseControllerClass', 'validateClass', 'params' => array('extends' => Controller::className())), - array('controllerClass', 'match', 'pattern' => '/Controller$/', 'message' => 'Controller class name must be suffixed with "Controller".'), - array('controllerClass, searchModelClass', 'validateNewClass'), - array('indexWidgetType', 'in', 'range' => array('grid', 'list')), - array('modelClass', 'validateModelClass'), - array('moduleID', 'validateModuleID'), - )); + return array_merge(parent::rules(), [ + [['moduleID', 'controllerClass', 'modelClass', 'searchModelClass', 'baseControllerClass'], 'filter', 'filter' => 'trim'], + [['modelClass', 'searchModelClass', 'controllerClass', 'baseControllerClass', 'indexWidgetType'], 'required'], + [['searchModelClass'], 'compare', 'compareAttribute' => 'modelClass', 'operator' => '!==', 'message' => 'Search Model Class must not be equal to Model Class.'], + [['modelClass', 'controllerClass', 'baseControllerClass', 'searchModelClass'], 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'], + [['modelClass'], 'validateClass', 'params' => ['extends' => ActiveRecord::className()]], + [['baseControllerClass'], 'validateClass', 'params' => ['extends' => Controller::className()]], + [['controllerClass'], 'match', 'pattern' => '/Controller$/', 'message' => 'Controller class name must be suffixed with "Controller".'], + [['controllerClass', 'searchModelClass'], 'validateNewClass'], + [['indexWidgetType'], 'in', 'range' => ['grid', 'list']], + [['modelClass'], 'validateModelClass'], + [['moduleID'], 'validateModuleID'], + ]); } public function attributeLabels() { - return array_merge(parent::attributeLabels(), array( + return array_merge(parent::attributeLabels(), [ 'modelClass' => 'Model Class', 'moduleID' => 'Module ID', 'controllerClass' => 'Controller Class', 'baseControllerClass' => 'Base Controller Class', 'indexWidgetType' => 'Widget Used in Index Page', 'searchModelClass' => 'Search Model Class', - )); + ]); } /** @@ -73,7 +73,7 @@ class Generator extends \yii\gii\Generator */ public function hints() { - return array( + return [ 'modelClass' => 'This is the ActiveRecord class associated with the table that CRUD will be built upon. You should provide a fully qualified class name, e.g., <code>app\models\Post</code>.', 'controllerClass' => 'This is the name of the controller class to be generated. You should @@ -84,16 +84,14 @@ class Generator extends \yii\gii\Generator If not set, it means the controller will belong to the application.', 'indexWidgetType' => 'This is the widget type to be used in the index page to display list of the models. You may choose either <code>GridView</code> or <code>ListView</code>', - 'searchModelClass' => 'This is the class representing the data being collecting in the search form. + 'searchModelClass' => 'This is the class representing the data being collected in the search form. A fully qualified namespaced class name is required, e.g., <code>app\models\search\PostSearch</code>.', - ); + ]; } public function requiredTemplates() { - return array( - 'controller.php', - ); + return ['controller.php']; } /** @@ -101,7 +99,7 @@ class Generator extends \yii\gii\Generator */ public function stickyAttributes() { - return array('baseControllerClass', 'moduleID', 'indexWidgetType'); + return ['baseControllerClass', 'moduleID', 'indexWidgetType']; } public function validateModelClass() @@ -131,10 +129,10 @@ class Generator extends \yii\gii\Generator { $controllerFile = Yii::getAlias('@' . str_replace('\\', '/', ltrim($this->controllerClass, '\\')) . '.php'); $searchModel = Yii::getAlias('@' . str_replace('\\', '/', ltrim($this->searchModelClass, '\\') . '.php')); - $files = array( + $files = [ new CodeFile($controllerFile, $this->render('controller.php')), new CodeFile($searchModel, $this->render('search.php')), - ); + ]; $viewPath = $this->getViewPath(); $templatePath = $this->getTemplatePath() . '/views'; @@ -192,9 +190,9 @@ class Generator extends \yii\gii\Generator } $column = $tableSchema->columns[$attribute]; if ($column->phpType === 'boolean') { - return "\$form->field(\$model, '$attribute')->checkbox();"; + return "\$form->field(\$model, '$attribute')->checkbox()"; } elseif ($column->type === 'text') { - return "\$form->field(\$model, '$attribute')->textarea(array('rows' => 6));"; + return "\$form->field(\$model, '$attribute')->textarea(['rows' => 6])"; } else { if (preg_match('/^(password|pass|passwd|passcode)$/i', $column->name)) { $input = 'passwordInput'; @@ -202,9 +200,9 @@ class Generator extends \yii\gii\Generator $input = 'textInput'; } if ($column->phpType !== 'string' || $column->size === null) { - return "\$form->field(\$model, '$attribute')->$input();"; + return "\$form->field(\$model, '$attribute')->$input()"; } else { - return "\$form->field(\$model, '$attribute')->$input(array('maxlength' => $column->size));"; + return "\$form->field(\$model, '$attribute')->$input(['maxlength' => $column->size])"; } } } @@ -218,9 +216,9 @@ class Generator extends \yii\gii\Generator $tableSchema = $this->getTableSchema(); $column = $tableSchema->columns[$attribute]; if ($column->phpType === 'boolean') { - return "\$form->field(\$model, '$attribute')->checkbox();"; + return "\$form->field(\$model, '$attribute')->checkbox()"; } else { - return "\$form->field(\$model, '$attribute');"; + return "\$form->field(\$model, '$attribute')"; } } @@ -252,7 +250,7 @@ class Generator extends \yii\gii\Generator public function generateSearchRules() { $table = $this->getTableSchema(); - $types = array(); + $types = []; foreach ($table->columns as $column) { switch ($column->type) { case Schema::TYPE_SMALLINT: @@ -278,9 +276,9 @@ class Generator extends \yii\gii\Generator } } - $rules = array(); + $rules = []; foreach ($types as $type => $columns) { - $rules[] = "array('" . implode(', ', $columns) . "', '$type')"; + $rules[] = "[['" . implode("', '", $columns) . "'], '$type']"; } return $rules; @@ -298,7 +296,7 @@ class Generator extends \yii\gii\Generator public function generateSearchLabels() { $table = $this->getTableSchema(); - $labels = array(); + $labels = []; foreach ($table->columns as $column) { if (!strcasecmp($column->name, 'id')) { $labels[$column->name] = 'ID'; @@ -316,7 +314,7 @@ class Generator extends \yii\gii\Generator public function generateSearchConditions() { $table = $this->getTableSchema(); - $conditions = array(); + $conditions = []; foreach ($table->columns as $column) { switch ($column->type) { case Schema::TYPE_SMALLINT: @@ -343,11 +341,13 @@ class Generator extends \yii\gii\Generator public function generateUrlParams() { - $pks = $this->getTableSchema()->primaryKey; + /** @var ActiveRecord $class */ + $class = $this->modelClass; + $pks = $class::primaryKey(); if (count($pks) === 1) { return "'id' => \$model->{$pks[0]}"; } else { - $params = array(); + $params = []; foreach ($pks as $pk) { $params[] = "'$pk' => \$model->$pk"; } @@ -357,7 +357,9 @@ class Generator extends \yii\gii\Generator public function generateActionParams() { - $pks = $this->getTableSchema()->primaryKey; + /** @var ActiveRecord $class */ + $class = $this->modelClass; + $pks = $class::primaryKey(); if (count($pks) === 1) { return '$id'; } else { @@ -368,11 +370,13 @@ class Generator extends \yii\gii\Generator public function generateActionParamComments() { $table = $this->getTableSchema(); - $pks = $table->primaryKey; + /** @var ActiveRecord $class */ + $class = $this->modelClass; + $pks = $class::primaryKey(); if (count($pks) === 1) { - return array('@param ' . $table->columns[$pks[0]]->phpType . ' $id'); + return ['@param ' . $table->columns[$pks[0]]->phpType . ' $id']; } else { - $params = array(); + $params = []; foreach ($pks as $pk) { $params[] = '@param ' . $table->columns[$pk]->phpType . ' $' . $pk; } diff --git a/framework/yii/gii/generators/crud/form.php b/extensions/gii/generators/crud/form.php similarity index 80% rename from framework/yii/gii/generators/crud/form.php rename to extensions/gii/generators/crud/form.php index 829b8a3..1b101c2 100644 --- a/framework/yii/gii/generators/crud/form.php +++ b/extensions/gii/generators/crud/form.php @@ -1,6 +1,6 @@ <?php /** - * @var yii\base\View $this + * @var yii\web\View $this * @var yii\widgets\ActiveForm $form * @var yii\gii\generators\crud\Generator $generator */ @@ -10,7 +10,7 @@ echo $form->field($generator, 'searchModelClass'); echo $form->field($generator, 'controllerClass'); echo $form->field($generator, 'baseControllerClass'); echo $form->field($generator, 'moduleID'); -echo $form->field($generator, 'indexWidgetType')->dropDownList(array( +echo $form->field($generator, 'indexWidgetType')->dropDownList([ 'grid' => 'GridView', 'list' => 'ListView', -)); +]); diff --git a/framework/yii/gii/generators/crud/templates/controller.php b/extensions/gii/generators/crud/templates/controller.php similarity index 58% rename from framework/yii/gii/generators/crud/templates/controller.php rename to extensions/gii/generators/crud/templates/controller.php index f53f819..f975fd1 100644 --- a/framework/yii/gii/generators/crud/templates/controller.php +++ b/extensions/gii/generators/crud/templates/controller.php @@ -5,13 +5,16 @@ use yii\helpers\StringHelper; /** * This is the template for generating a CRUD controller class file. * - * @var yii\base\View $this + * @var yii\web\View $this * @var yii\gii\generators\crud\Generator $generator */ $controllerClass = StringHelper::basename($generator->controllerClass); $modelClass = StringHelper::basename($generator->modelClass); $searchModelClass = StringHelper::basename($generator->searchModelClass); +if ($modelClass === $searchModelClass) { + $searchModelAlias = $searchModelClass.'Search'; +} $pks = $generator->getTableSchema()->primaryKey; $urlParams = $generator->generateUrlParams(); @@ -21,129 +24,128 @@ $actionParamComments = $generator->generateActionParamComments(); echo "<?php\n"; ?> -namespace <?php echo StringHelper::dirname(ltrim($generator->controllerClass, '\\')); ?>; +namespace <?= StringHelper::dirname(ltrim($generator->controllerClass, '\\')) ?>; -use <?php echo ltrim($generator->modelClass, '\\'); ?>; -use <?php echo ltrim($generator->searchModelClass, '\\'); ?>; -use yii\data\ActiveDataProvider; -use <?php echo ltrim($generator->baseControllerClass, '\\'); ?>; +use <?= ltrim($generator->modelClass, '\\') ?>; +use <?= ltrim($generator->searchModelClass, '\\') ?><?php if (isset($searchModelAlias)):?> as <?= $searchModelAlias ?><?php endif ?>; +use <?= ltrim($generator->baseControllerClass, '\\') ?>; use yii\web\HttpException; use yii\web\VerbFilter; /** - * <?php echo $controllerClass; ?> implements the CRUD actions for <?php echo $modelClass; ?> model. + * <?= $controllerClass ?> implements the CRUD actions for <?= $modelClass ?> model. */ -class <?php echo $controllerClass; ?> extends <?php echo StringHelper::basename($generator->baseControllerClass) . "\n"; ?> +class <?= $controllerClass ?> extends <?= StringHelper::basename($generator->baseControllerClass) . "\n" ?> { public function behaviors() { - return array( - 'verbs' => array( + return [ + 'verbs' => [ 'class' => VerbFilter::className(), - 'actions' => array( - 'delete' => array('post'), - ), - ), - ); + 'actions' => [ + 'delete' => ['post'], + ], + ], + ]; } /** - * Lists all <?php echo $modelClass; ?> models. + * Lists all <?= $modelClass ?> models. * @return mixed */ public function actionIndex() { - $searchModel = new <?php echo $searchModelClass; ?>; + $searchModel = new <?= isset($searchModelAlias) ? $searchModelAlias : $searchModelClass ?>; $dataProvider = $searchModel->search($_GET); - return $this->render('index', array( + return $this->render('index', [ 'dataProvider' => $dataProvider, 'searchModel' => $searchModel, - )); + ]); } /** - * Displays a single <?php echo $modelClass; ?> model. - * <?php echo implode("\n\t * ", $actionParamComments) . "\n"; ?> + * Displays a single <?= $modelClass ?> model. + * <?= implode("\n\t * ", $actionParamComments) . "\n" ?> * @return mixed */ - public function actionView(<?php echo $actionParams; ?>) + public function actionView(<?= $actionParams ?>) { - return $this->render('view', array( - 'model' => $this->findModel(<?php echo $actionParams; ?>), - )); + return $this->render('view', [ + 'model' => $this->findModel(<?= $actionParams ?>), + ]); } /** - * Creates a new <?php echo $modelClass; ?> model. + * Creates a new <?= $modelClass ?> model. * If creation is successful, the browser will be redirected to the 'view' page. * @return mixed */ public function actionCreate() { - $model = new <?php echo $modelClass; ?>; + $model = new <?= $modelClass ?>; if ($model->load($_POST) && $model->save()) { - return $this->redirect(array('view', <?php echo $urlParams; ?>)); + return $this->redirect(['view', <?= $urlParams ?>]); } else { - return $this->render('create', array( + return $this->render('create', [ 'model' => $model, - )); + ]); } } /** - * Updates an existing <?php echo $modelClass; ?> model. + * Updates an existing <?= $modelClass ?> model. * If update is successful, the browser will be redirected to the 'view' page. - * <?php echo implode("\n\t * ", $actionParamComments) . "\n"; ?> + * <?= implode("\n\t * ", $actionParamComments) . "\n" ?> * @return mixed */ - public function actionUpdate(<?php echo $actionParams; ?>) + public function actionUpdate(<?= $actionParams ?>) { - $model = $this->findModel(<?php echo $actionParams; ?>); + $model = $this->findModel(<?= $actionParams ?>); if ($model->load($_POST) && $model->save()) { - return $this->redirect(array('view', <?php echo $urlParams; ?>)); + return $this->redirect(['view', <?= $urlParams ?>]); } else { - return $this->render('update', array( + return $this->render('update', [ 'model' => $model, - )); + ]); } } /** - * Deletes an existing <?php echo $modelClass; ?> model. + * Deletes an existing <?= $modelClass ?> model. * If deletion is successful, the browser will be redirected to the 'index' page. - * <?php echo implode("\n\t * ", $actionParamComments) . "\n"; ?> + * <?= implode("\n\t * ", $actionParamComments) . "\n" ?> * @return mixed */ - public function actionDelete(<?php echo $actionParams; ?>) + public function actionDelete(<?= $actionParams ?>) { - $this->findModel(<?php echo $actionParams; ?>)->delete(); - return $this->redirect(array('index')); + $this->findModel(<?= $actionParams ?>)->delete(); + return $this->redirect(['index']); } /** - * Finds the <?php echo $modelClass; ?> model based on its primary key value. + * Finds the <?= $modelClass ?> model based on its primary key value. * If the model is not found, a 404 HTTP exception will be thrown. - * <?php echo implode("\n\t * ", $actionParamComments) . "\n"; ?> - * @return <?php echo $modelClass; ?> the loaded model + * <?= implode("\n\t * ", $actionParamComments) . "\n" ?> + * @return <?= $modelClass ?> the loaded model * @throws HttpException if the model cannot be found */ - protected function findModel(<?php echo $actionParams; ?>) + protected function findModel(<?= $actionParams ?>) { <?php if (count($pks) === 1) { $condition = '$id'; } else { - $condition = array(); + $condition = []; foreach ($pks as $pk) { $condition[] = "'$pk' => \$$pk"; } - $condition = 'array(' . implode(', ', $condition) . ')'; + $condition = '[' . implode(', ', $condition) . ']'; } ?> - if (($model = <?php echo $modelClass; ?>::find(<?php echo $condition; ?>)) !== null) { + if (($model = <?= $modelClass ?>::find(<?= $condition ?>)) !== null) { return $model; } else { throw new HttpException(404, 'The requested page does not exist.'); diff --git a/framework/yii/gii/generators/crud/templates/search.php b/extensions/gii/generators/crud/templates/search.php similarity index 70% rename from framework/yii/gii/generators/crud/templates/search.php rename to extensions/gii/generators/crud/templates/search.php index 467cee5..1411896 100644 --- a/framework/yii/gii/generators/crud/templates/search.php +++ b/extensions/gii/generators/crud/templates/search.php @@ -5,7 +5,7 @@ use yii\helpers\StringHelper; /** * This is the template for generating a CRUD controller class file. * - * @var yii\base\View $this + * @var yii\web\View $this * @var yii\gii\generators\crud\Generator $generator */ @@ -19,24 +19,24 @@ $searchConditions = $generator->generateSearchConditions(); echo "<?php\n"; ?> -namespace <?php echo StringHelper::dirname(ltrim($generator->searchModelClass, '\\')); ?>; +namespace <?= StringHelper::dirname(ltrim($generator->searchModelClass, '\\')) ?>; use yii\base\Model; use yii\data\ActiveDataProvider; -use <?php echo ltrim($generator->modelClass, '\\'); ?>; +use <?= ltrim($generator->modelClass, '\\') ?>; /** - * <?php echo $searchModelClass; ?> represents the model behind the search form about <?php echo $modelClass; ?>. + * <?= $searchModelClass ?> represents the model behind the search form about <?= $modelClass ?>. */ -class <?php echo $searchModelClass; ?> extends Model +class <?= $searchModelClass ?> extends Model { - public $<?php echo implode(";\n\tpublic $", $searchAttributes); ?>; + public $<?= implode(";\n\tpublic $", $searchAttributes) ?>; public function rules() { - return array( - <?php echo implode(",\n\t\t\t", $rules); ?>, - ); + return [ + <?= implode(",\n\t\t\t", $rules) ?>, + ]; } /** @@ -44,25 +44,25 @@ class <?php echo $searchModelClass; ?> extends Model */ public function attributeLabels() { - return array( + return [ <?php foreach ($labels as $name => $label): ?> - <?php echo "'$name' => '" . addslashes($label) . "',\n"; ?> + <?= "'$name' => '" . addslashes($label) . "',\n" ?> <?php endforeach; ?> - ); + ]; } public function search($params) { - $query = <?php echo $modelClass; ?>::find(); - $dataProvider = new ActiveDataProvider(array( + $query = <?= $modelClass ?>::find(); + $dataProvider = new ActiveDataProvider([ 'query' => $query, - )); + ]); if (!($this->load($params) && $this->validate())) { return $dataProvider; } - <?php echo implode("\n\t\t", $searchConditions); ?> + <?= implode("\n\t\t", $searchConditions) ?> return $dataProvider; } @@ -74,10 +74,10 @@ class <?php echo $searchModelClass; ?> extends Model return; } if ($partialMatch) { - $value = '%' . strtr($value, array('%'=>'\%', '_'=>'\_', '\\'=>'\\\\')) . '%'; - $query->andWhere(array('like', $attribute, $value)); + $value = '%' . strtr($value, ['%'=>'\%', '_'=>'\_', '\\'=>'\\\\']) . '%'; + $query->andWhere(['like', $attribute, $value]); } else { - $query->andWhere(array($attribute => $value)); + $query->andWhere([$attribute => $value]); } } } diff --git a/framework/yii/gii/generators/crud/templates/views/_form.php b/extensions/gii/generators/crud/templates/views/_form.php similarity index 70% rename from framework/yii/gii/generators/crud/templates/views/_form.php rename to extensions/gii/generators/crud/templates/views/_form.php index 2d9d5dc..c93ef84 100644 --- a/framework/yii/gii/generators/crud/templates/views/_form.php +++ b/extensions/gii/generators/crud/templates/views/_form.php @@ -4,7 +4,7 @@ use yii\helpers\Inflector; use yii\helpers\StringHelper; /** - * @var yii\base\View $this + * @var yii\web\View $this * @var yii\gii\generators\crud\Generator $generator */ @@ -22,23 +22,23 @@ use yii\helpers\Html; use yii\widgets\ActiveForm; /** - * @var yii\base\View $this - * @var <?php echo ltrim($generator->modelClass, '\\'); ?> $model + * @var yii\web\View $this + * @var <?= ltrim($generator->modelClass, '\\') ?> $model * @var yii\widgets\ActiveForm $form */ ?> -<div class="<?php echo Inflector::camel2id(StringHelper::basename($generator->modelClass)); ?>-form"> +<div class="<?= Inflector::camel2id(StringHelper::basename($generator->modelClass)) ?>-form"> - <?php echo '<?php'; ?> $form = ActiveForm::begin(); ?> + <?= "<?php " ?>$form = ActiveForm::begin(); ?> <?php foreach ($safeAttributes as $attribute) { - echo "\t\t<?php echo " . $generator->generateActiveField($attribute) . " ?>\n\n"; + echo "\t\t<?= " . $generator->generateActiveField($attribute) . " ?>\n\n"; } ?> <div class="form-group"> - <?php echo '<?php'; ?> echo Html::submitButton($model->isNewRecord ? 'Create' : 'Update', array('class' => 'btn btn-primary')); ?> + <?= "<?= " ?>Html::submitButton($model->isNewRecord ? 'Create' : 'Update', ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?> </div> - <?php echo '<?php'; ?> ActiveForm::end(); ?> + <?= "<?php " ?>ActiveForm::end(); ?> </div> diff --git a/framework/yii/gii/generators/crud/templates/views/_search.php b/extensions/gii/generators/crud/templates/views/_search.php similarity index 62% rename from framework/yii/gii/generators/crud/templates/views/_search.php rename to extensions/gii/generators/crud/templates/views/_search.php index a649589..0e23a5c 100644 --- a/framework/yii/gii/generators/crud/templates/views/_search.php +++ b/extensions/gii/generators/crud/templates/views/_search.php @@ -4,7 +4,7 @@ use yii\helpers\Inflector; use yii\helpers\StringHelper; /** - * @var yii\base\View $this + * @var yii\web\View $this * @var yii\gii\generators\crud\Generator $generator */ @@ -15,31 +15,34 @@ use yii\helpers\Html; use yii\widgets\ActiveForm; /** - * @var yii\base\View $this - * @var <?php echo ltrim($generator->searchModelClass, '\\'); ?> $model + * @var yii\web\View $this + * @var <?= ltrim($generator->searchModelClass, '\\') ?> $model * @var yii\widgets\ActiveForm $form */ ?> -<div class="<?php echo Inflector::camel2id(StringHelper::basename($generator->modelClass)); ?>-search"> +<div class="<?= Inflector::camel2id(StringHelper::basename($generator->modelClass)) ?>-search"> - <?php echo '<?php'; ?> $form = ActiveForm::begin(array('method' => 'get')); ?> + <?= "<?php " ?>$form = ActiveForm::begin([ + 'action' => ['index'], + 'method' => 'get', + ]); ?> <?php $count = 0; foreach ($generator->getTableSchema()->getColumnNames() as $attribute) { if (++$count < 6) { - echo "\t\t<?php echo " . $generator->generateActiveSearchField($attribute) . " ?>\n"; + echo "\t\t<?= " . $generator->generateActiveSearchField($attribute) . " ?>\n\n"; } else { - echo "\t\t<?php // echo " . $generator->generateActiveSearchField($attribute) . " ?>\n"; + echo "\t\t<?php // echo " . $generator->generateActiveSearchField($attribute) . " ?>\n\n"; } } ?> <div class="form-group"> - <?php echo '<?php'; ?> echo Html::submitButton('Search', array('class' => 'btn btn-primary')); ?> - <?php echo '<?php'; ?> echo Html::resetButton('Reset', array('class' => 'btn btn-default')); ?> + <?= "<?= " ?>Html::submitButton('Search', ['class' => 'btn btn-primary']) ?> + <?= "<?= " ?>Html::resetButton('Reset', ['class' => 'btn btn-default']) ?> </div> - <?php echo '<?php'; ?> ActiveForm::end(); ?> + <?= "<?php " ?>ActiveForm::end(); ?> </div> diff --git a/framework/yii/gii/generators/crud/templates/views/create.php b/extensions/gii/generators/crud/templates/views/create.php similarity index 53% rename from framework/yii/gii/generators/crud/templates/views/create.php rename to extensions/gii/generators/crud/templates/views/create.php index 669b99a..68d08ba 100644 --- a/framework/yii/gii/generators/crud/templates/views/create.php +++ b/extensions/gii/generators/crud/templates/views/create.php @@ -4,7 +4,7 @@ use yii\helpers\Inflector; use yii\helpers\StringHelper; /** - * @var yii\base\View $this + * @var yii\web\View $this * @var yii\gii\generators\crud\Generator $generator */ @@ -14,20 +14,20 @@ echo "<?php\n"; use yii\helpers\Html; /** - * @var yii\base\View $this - * @var <?php echo ltrim($generator->modelClass, '\\'); ?> $model + * @var yii\web\View $this + * @var <?= ltrim($generator->modelClass, '\\') ?> $model */ -$this->title = 'Create <?php echo Inflector::camel2words(StringHelper::basename($generator->modelClass)); ?>'; -$this->params['breadcrumbs'][] = array('label' => '<?php echo Inflector::pluralize(Inflector::camel2words(StringHelper::basename($generator->modelClass))); ?>', 'url' => array('index')); +$this->title = 'Create <?= Inflector::camel2words(StringHelper::basename($generator->modelClass)) ?>'; +$this->params['breadcrumbs'][] = ['label' => '<?= Inflector::pluralize(Inflector::camel2words(StringHelper::basename($generator->modelClass))) ?>', 'url' => ['index']]; $this->params['breadcrumbs'][] = $this->title; ?> -<div class="<?php echo Inflector::camel2id(StringHelper::basename($generator->modelClass)); ?>-create"> +<div class="<?= Inflector::camel2id(StringHelper::basename($generator->modelClass)) ?>-create"> - <h1><?php echo "<?php"; ?> echo Html::encode($this->title); ?></h1> + <h1><?= "<?= " ?>Html::encode($this->title) ?></h1> - <?php echo "<?php"; ?> echo $this->render('_form', array( + <?= "<?php " ?>echo $this->render('_form', [ 'model' => $model, - )); ?> + ]); ?> </div> diff --git a/framework/yii/gii/generators/crud/templates/views/index.php b/extensions/gii/generators/crud/templates/views/index.php similarity index 53% rename from framework/yii/gii/generators/crud/templates/views/index.php rename to extensions/gii/generators/crud/templates/views/index.php index df76581..b47c3f3 100644 --- a/framework/yii/gii/generators/crud/templates/views/index.php +++ b/extensions/gii/generators/crud/templates/views/index.php @@ -4,7 +4,7 @@ use yii\helpers\Inflector; use yii\helpers\StringHelper; /** - * @var yii\base\View $this + * @var yii\web\View $this * @var yii\gii\generators\crud\Generator $generator */ @@ -15,61 +15,57 @@ echo "<?php\n"; ?> use yii\helpers\Html; -use <?php echo $generator->indexWidgetType === 'grid' ? 'yii\grid\GridView' : 'yii\widgets\ListView'; ?>; +use <?= $generator->indexWidgetType === 'grid' ? "yii\\grid\\GridView" : "yii\\widgets\\ListView" ?>; /** - * @var yii\base\View $this + * @var yii\web\View $this * @var yii\data\ActiveDataProvider $dataProvider - * @var <?php echo ltrim($generator->searchModelClass, '\\'); ?> $searchModel + * @var <?= ltrim($generator->searchModelClass, '\\') ?> $searchModel */ -$this->title = '<?php echo Inflector::pluralize(Inflector::camel2words(StringHelper::basename($generator->modelClass))); ?>'; +$this->title = '<?= Inflector::pluralize(Inflector::camel2words(StringHelper::basename($generator->modelClass))) ?>'; $this->params['breadcrumbs'][] = $this->title; ?> -<div class="<?php echo Inflector::camel2id(StringHelper::basename($generator->modelClass)); ?>-index"> +<div class="<?= Inflector::camel2id(StringHelper::basename($generator->modelClass)) ?>-index"> - <h1><?php echo "<?php"; ?> echo Html::encode($this->title); ?></h1> + <h1><?= "<?= " ?>Html::encode($this->title) ?></h1> - <?php echo '<?php'; ?> echo $this->render('_search', array('model' => $searchModel)); ?> + <?= "<?php " . ($generator->indexWidgetType === 'grid' ? "// " : "") ?>echo $this->render('_search', ['model' => $searchModel]); ?> - <hr> - - <div> - <?php echo '<?php'; ?> echo Html::a('Create <?php echo StringHelper::basename($generator->modelClass); ?>', array('create'), array('class' => 'btn btn-danger')); ?> - </div> + <p> + <?= "<?= " ?>Html::a('Create <?= StringHelper::basename($generator->modelClass) ?>', ['create'], ['class' => 'btn btn-success']) ?> + </p> <?php if ($generator->indexWidgetType === 'grid'): ?> - <?php echo "<?php"; ?> echo GridView::widget(array( + <?= "<?php " ?>echo GridView::widget([ 'dataProvider' => $dataProvider, 'filterModel' => $searchModel, - 'columns' => array( - array('class' => 'yii\grid\SerialColumn'), + 'columns' => [ + ['class' => 'yii\grid\SerialColumn'], <?php $count = 0; foreach ($generator->getTableSchema()->columns as $column) { $format = $generator->generateColumnFormat($column); if (++$count < 6) { - echo "\t\t\t'" . $column->name . ($format === 'text' ? '' : ':' . $format) . "',\n"; + echo "\t\t\t'" . $column->name . ($format === 'text' ? "" : ":" . $format) . "',\n"; } else { - echo "\t\t\t// '" . $column->name . ($format === 'text' ? '' : ':' . $format) . "',\n"; + echo "\t\t\t// '" . $column->name . ($format === 'text' ? "" : ":" . $format) . "',\n"; } } ?> - array('class' => 'yii\grid\ActionColumn'), - ), - )); ?> + ['class' => 'yii\grid\ActionColumn'], + ], + ]); ?> <?php else: ?> - <?php echo "<?php"; ?> echo ListView::widget(array( + <?= "<?php " ?>echo ListView::widget([ 'dataProvider' => $dataProvider, - 'itemOptions' => array( - 'class' => 'item', - ), + 'itemOptions' => ['class' => 'item'], 'itemView' => function ($model, $key, $index, $widget) { - return Html::a(Html::encode($model-><?php echo $nameAttribute; ?>), array('view', <?php echo $urlParams; ?>)); + return Html::a(Html::encode($model-><?= $nameAttribute ?>), ['view', <?= $urlParams ?>]); }, - )); ?> + ]); ?> <?php endif; ?> </div> diff --git a/framework/yii/gii/generators/crud/templates/views/update.php b/extensions/gii/generators/crud/templates/views/update.php similarity index 50% rename from framework/yii/gii/generators/crud/templates/views/update.php rename to extensions/gii/generators/crud/templates/views/update.php index cb892c2..2fbbecf 100644 --- a/framework/yii/gii/generators/crud/templates/views/update.php +++ b/extensions/gii/generators/crud/templates/views/update.php @@ -4,7 +4,7 @@ use yii\helpers\Inflector; use yii\helpers\StringHelper; /** - * @var yii\base\View $this + * @var yii\web\View $this * @var yii\gii\generators\crud\Generator $generator */ @@ -16,21 +16,21 @@ echo "<?php\n"; use yii\helpers\Html; /** - * @var yii\base\View $this - * @var <?php echo ltrim($generator->modelClass, '\\'); ?> $model + * @var yii\web\View $this + * @var <?= ltrim($generator->modelClass, '\\') ?> $model */ -$this->title = 'Update <?php echo Inflector::camel2words(StringHelper::basename($generator->modelClass)); ?>: ' . $model-><?php echo $generator->getNameAttribute(); ?>; -$this->params['breadcrumbs'][] = array('label' => '<?php echo Inflector::pluralize(Inflector::camel2words(StringHelper::basename($generator->modelClass))); ?>', 'url' => array('index')); -$this->params['breadcrumbs'][] = array('label' => $model-><?php echo $generator->getNameAttribute(); ?>, 'url' => array('view', <?php echo $urlParams; ?>)); +$this->title = 'Update <?= Inflector::camel2words(StringHelper::basename($generator->modelClass)) ?>: ' . $model-><?= $generator->getNameAttribute() ?>; +$this->params['breadcrumbs'][] = ['label' => '<?= Inflector::pluralize(Inflector::camel2words(StringHelper::basename($generator->modelClass))) ?>', 'url' => ['index']]; +$this->params['breadcrumbs'][] = ['label' => $model-><?= $generator->getNameAttribute() ?>, 'url' => ['view', <?= $urlParams ?>]]; $this->params['breadcrumbs'][] = 'Update'; ?> -<div class="<?php echo Inflector::camel2id(StringHelper::basename($generator->modelClass)); ?>-update"> +<div class="<?= Inflector::camel2id(StringHelper::basename($generator->modelClass)) ?>-update"> - <h1><?php echo "<?php"; ?> echo Html::encode($this->title); ?></h1> + <h1><?= "<?= " ?>Html::encode($this->title) ?></h1> - <?php echo "<?php"; ?> echo $this->render('_form', array( + <?= "<?php " ?>echo $this->render('_form', [ 'model' => $model, - )); ?> + ]); ?> </div> diff --git a/framework/yii/gii/generators/crud/templates/views/view.php b/extensions/gii/generators/crud/templates/views/view.php similarity index 54% rename from framework/yii/gii/generators/crud/templates/views/view.php rename to extensions/gii/generators/crud/templates/views/view.php index a3b6cad..9b5391e 100644 --- a/framework/yii/gii/generators/crud/templates/views/view.php +++ b/extensions/gii/generators/crud/templates/views/view.php @@ -4,7 +4,7 @@ use yii\helpers\Inflector; use yii\helpers\StringHelper; /** - * @var yii\base\View $this + * @var yii\web\View $this * @var yii\gii\generators\crud\Generator $generator */ @@ -17,37 +17,37 @@ use yii\helpers\Html; use yii\widgets\DetailView; /** - * @var yii\base\View $this - * @var <?php echo ltrim($generator->modelClass, '\\'); ?> $model + * @var yii\web\View $this + * @var <?= ltrim($generator->modelClass, '\\') ?> $model */ -$this->title = $model-><?php echo $generator->getNameAttribute(); ?>; -$this->params['breadcrumbs'][] = array('label' => '<?php echo Inflector::pluralize(Inflector::camel2words(StringHelper::basename($generator->modelClass))); ?>', 'url' => array('index')); +$this->title = $model-><?= $generator->getNameAttribute() ?>; +$this->params['breadcrumbs'][] = ['label' => '<?= Inflector::pluralize(Inflector::camel2words(StringHelper::basename($generator->modelClass))) ?>', 'url' => ['index']]; $this->params['breadcrumbs'][] = $this->title; ?> -<div class="<?php echo Inflector::camel2id(StringHelper::basename($generator->modelClass)); ?>-view"> +<div class="<?= Inflector::camel2id(StringHelper::basename($generator->modelClass)) ?>-view"> - <h1><?php echo "<?php"; ?> echo Html::encode($this->title); ?></h1> + <h1><?= "<?= " ?>Html::encode($this->title) ?></h1> - <div> - <?php echo '<?php'; ?> echo Html::a('Update', array('update', <?php echo $urlParams; ?>), array('class' => 'btn btn-danger')); ?> - <?php echo '<?php'; ?> echo Html::a('Delete', array('delete', <?php echo $urlParams; ?>), array( + <p> + <?= "<?= " ?>Html::a('Update', ['update', <?= $urlParams ?>], ['class' => 'btn btn-primary']) ?> + <?= "<?php " ?>echo Html::a('Delete', ['delete', <?= $urlParams ?>], [ 'class' => 'btn btn-danger', 'data-confirm' => Yii::t('app', 'Are you sure to delete this item?'), 'data-method' => 'post', - )); ?> - </div> + ]); ?> + </p> - <?php echo '<?php'; ?> echo DetailView::widget(array( + <?= "<?php " ?>echo DetailView::widget([ 'model' => $model, - 'attributes' => array( + 'attributes' => [ <?php foreach ($generator->getTableSchema()->columns as $column) { $format = $generator->generateColumnFormat($column); - echo "\t\t\t'" . $column->name . ($format === 'text' ? '' : ':' . $format) . "',\n"; + echo "\t\t\t'" . $column->name . ($format === 'text' ? "" : ":" . $format) . "',\n"; } ?> - ), - )); ?> + ], + ]); ?> </div> diff --git a/framework/yii/gii/generators/form/Generator.php b/extensions/gii/generators/form/Generator.php similarity index 76% rename from framework/yii/gii/generators/form/Generator.php rename to extensions/gii/generators/form/Generator.php index b0edb2a..3bc0be6 100644 --- a/framework/yii/gii/generators/form/Generator.php +++ b/extensions/gii/generators/form/Generator.php @@ -46,7 +46,7 @@ class Generator extends \yii\gii\Generator */ public function generate() { - $files = array(); + $files = []; $files[] = new CodeFile( Yii::getAlias($this->viewPath) . '/' . $this->viewName . '.php', $this->render('form.php') @@ -59,16 +59,16 @@ class Generator extends \yii\gii\Generator */ public function rules() { - return array_merge(parent::rules(), array( - array('modelClass, viewName, scenarioName, viewPath', 'filter', 'filter' => 'trim'), - array('modelClass, viewName, viewPath', 'required'), - array('modelClass', 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'), - array('modelClass', 'validateClass', 'params' => array('extends' => Model::className())), - array('viewName', 'match', 'pattern' => '/^\w+[\\-\\/\w]*$/', 'message' => 'Only word characters, dashes and slashes are allowed.'), - array('viewPath', 'match', 'pattern' => '/^@?\w+[\\-\\/\w]*$/', 'message' => 'Only word characters, dashes, slashes and @ are allowed.'), - array('viewPath', 'validateViewPath'), - array('scenarioName', 'match', 'pattern' => '/^[\w\\-]+$/', 'message' => 'Only word characters and dashes are allowed.'), - )); + return array_merge(parent::rules(), [ + [['modelClass', 'viewName', 'scenarioName', 'viewPath'], 'filter', 'filter' => 'trim'], + [['modelClass', 'viewName', 'viewPath'], 'required'], + [['modelClass'], 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'], + [['modelClass'], 'validateClass', 'params' => ['extends' => Model::className()]], + [['viewName'], 'match', 'pattern' => '/^\w+[\\-\\/\w]*$/', 'message' => 'Only word characters, dashes and slashes are allowed.'], + [['viewPath'], 'match', 'pattern' => '/^@?\w+[\\-\\/\w]*$/', 'message' => 'Only word characters, dashes, slashes and @ are allowed.'], + [['viewPath'], 'validateViewPath'], + [['scenarioName'], 'match', 'pattern' => '/^[\w\\-]+$/', 'message' => 'Only word characters and dashes are allowed.'], + ]); } /** @@ -76,12 +76,12 @@ class Generator extends \yii\gii\Generator */ public function attributeLabels() { - return array( + return [ 'modelClass' => 'Model Class', 'viewName' => 'View Name', 'viewPath' => 'View Path', 'scenarioName' => 'Scenario', - ); + ]; } /** @@ -89,10 +89,7 @@ class Generator extends \yii\gii\Generator */ public function requiredTemplates() { - return array( - 'form.php', - 'action.php', - ); + return ['form.php', 'action.php']; } /** @@ -100,7 +97,7 @@ class Generator extends \yii\gii\Generator */ public function stickyAttributes() { - return array('viewPath', 'scenarioName'); + return ['viewPath', 'scenarioName']; } /** @@ -108,12 +105,12 @@ class Generator extends \yii\gii\Generator */ public function hints() { - return array( + return [ 'modelClass' => 'This is the model class for collecting the form input. You should provide a fully qualified class name, e.g., <code>app\models\Post</code>.', 'viewName' => 'This is the view name with respect to the view path. For example, <code>site/index</code> would generate a <code>site/index.php</code> view file under the view path.', 'viewPath' => 'This is the root view path to keep the generated view files. You may provide either a directory or a path alias, e.g., <code>@app/views</code>.', 'scenarioName' => 'This is the scenario to be used by the model when collecting the form input. If empty, the default scenario will be used.', - ); + ]; } /** diff --git a/framework/yii/gii/generators/form/form.php b/extensions/gii/generators/form/form.php similarity index 90% rename from framework/yii/gii/generators/form/form.php rename to extensions/gii/generators/form/form.php index c04a26e..3d46777 100644 --- a/framework/yii/gii/generators/form/form.php +++ b/extensions/gii/generators/form/form.php @@ -1,6 +1,6 @@ <?php /** - * @var yii\base\View $this + * @var yii\web\View $this * @var yii\widgets\ActiveForm $form * @var yii\gii\generators\form\Generator $generator */ diff --git a/framework/yii/gii/generators/form/templates/action.php b/extensions/gii/generators/form/templates/action.php similarity index 68% rename from framework/yii/gii/generators/form/templates/action.php rename to extensions/gii/generators/form/templates/action.php index fc5830e..9e6840d 100644 --- a/framework/yii/gii/generators/form/templates/action.php +++ b/extensions/gii/generators/form/templates/action.php @@ -5,24 +5,24 @@ use yii\helpers\Inflector; /** * This is the template for generating an action view file. * - * @var yii\base\View $this + * @var yii\web\View $this * @var yii\gii\generators\form\Generator $generator */ echo "<?php\n"; ?> -public function action<?php echo Inflector::id2camel(trim(basename($generator->viewName), '_')); ?>() +public function action<?= Inflector::id2camel(trim(basename($generator->viewName), '_')) ?>() { - $model = new <?php echo $generator->modelClass; ?><?php echo empty($generator->scenarioName) ? '' : "(array('scenario' => '{$generator->scenarioName}'))"; ?>; + $model = new <?= $generator->modelClass ?><?= empty($generator->scenarioName) ? "" : "(['scenario' => '{$generator->scenarioName}'])" ?>; if ($model->load($_POST)) { - if($model->validate()) { + if ($model->validate()) { // form inputs are valid, do something here return; } } - return $this->render('<?php echo $generator->viewName; ?>', array( + return $this->render('<?= $generator->viewName ?>', [ 'model' => $model, - )); + ]); } diff --git a/framework/yii/gii/generators/form/templates/form.php b/extensions/gii/generators/form/templates/form.php similarity index 58% rename from framework/yii/gii/generators/form/templates/form.php rename to extensions/gii/generators/form/templates/form.php index ad52fe9..b30570c 100644 --- a/framework/yii/gii/generators/form/templates/form.php +++ b/extensions/gii/generators/form/templates/form.php @@ -2,7 +2,7 @@ /** * This is the template for generating an action view file. * - * @var yii\base\View $this + * @var yii\web\View $this * @var yii\gii\generators\form\Generator $generator */ @@ -13,23 +13,23 @@ use yii\helpers\Html; use yii\widgets\ActiveForm; /** - * @var yii\base\View $this - * @var <?php echo $generator->modelClass; ?> $model + * @var yii\web\View $this + * @var <?= $generator->modelClass ?> $model * @var ActiveForm $form */ -<?php echo "?>"; ?> +<?= "?>" ?> -<div class="<?php echo str_replace('/', '-', trim($generator->viewName, '_')); ?>"> +<div class="<?= str_replace('/', '-', trim($generator->viewName, '_')) ?>"> - <?php echo '<?php'; ?> $form = ActiveForm::begin(); ?> + <?= "<?php " ?>$form = ActiveForm::begin(); ?> <?php foreach ($generator->getModelAttributes() as $attribute): ?> - <?php echo '<?php'; ?> echo $form->field($model, '<?php echo $attribute; ?>'); ?> + <?= "<?= " ?>$form->field($model, '<?= $attribute ?>') ?> <?php endforeach; ?> <div class="form-group"> - <?php echo '<?php'; ?> echo Html::submitButton('Submit', array('class' => 'btn btn-primary')); ?> + <?= "<?= " ?>Html::submitButton('Submit', ['class' => 'btn btn-primary']) ?> </div> - <?php echo '<?php'; ?> ActiveForm::end(); ?> + <?= "<?php " ?>ActiveForm::end(); ?> -</div><!-- <?php echo str_replace('/', '-', trim($generator->viewName, '-')); ?> --> +</div><!-- <?= str_replace('/', '-', trim($generator->viewName, '-')) ?> --> diff --git a/framework/yii/gii/generators/model/Generator.php b/extensions/gii/generators/model/Generator.php similarity index 77% rename from framework/yii/gii/generators/model/Generator.php rename to extensions/gii/generators/model/Generator.php index b9c8f23..cd2fcbf 100644 --- a/framework/yii/gii/generators/model/Generator.php +++ b/extensions/gii/generators/model/Generator.php @@ -52,19 +52,19 @@ class Generator extends \yii\gii\Generator */ public function rules() { - return array_merge(parent::rules(), array( - array('db, ns, tableName, modelClass, baseClass', 'filter', 'filter' => 'trim'), - array('db, ns, tableName, baseClass', 'required'), - array('db, modelClass', 'match', 'pattern' => '/^\w+$/', 'message' => 'Only word characters are allowed.'), - array('ns, baseClass', 'match', 'pattern' => '/^[\w\\\\]+$/', 'message' => 'Only word characters and backslashes are allowed.'), - array('tableName', 'match', 'pattern' => '/^(\w+\.)?([\w\*]+)$/', 'message' => 'Only word characters, and optionally an asterisk and/or a dot are allowed.'), - array('db', 'validateDb'), - array('ns', 'validateNamespace'), - array('tableName', 'validateTableName'), - array('modelClass', 'validateModelClass'), - array('baseClass', 'validateClass', 'params' => array('extends' => ActiveRecord::className())), - array('generateRelations, generateLabelsFromComments', 'boolean'), - )); + return array_merge(parent::rules(), [ + [['db', 'ns', 'tableName', 'modelClass', 'baseClass'], 'filter', 'filter' => 'trim'], + [['db', 'ns', 'tableName', 'baseClass'], 'required'], + [['db', 'modelClass'], 'match', 'pattern' => '/^\w+$/', 'message' => 'Only word characters are allowed.'], + [['ns', 'baseClass'], 'match', 'pattern' => '/^[\w\\\\]+$/', 'message' => 'Only word characters and backslashes are allowed.'], + [['tableName'], 'match', 'pattern' => '/^(\w+\.)?([\w\*]+)$/', 'message' => 'Only word characters, and optionally an asterisk and/or a dot are allowed.'], + [['db'], 'validateDb'], + [['ns'], 'validateNamespace'], + [['tableName'], 'validateTableName'], + [['modelClass'], 'validateModelClass', 'skipOnEmpty' => false], + [['baseClass'], 'validateClass', 'params' => ['extends' => ActiveRecord::className()]], + [['generateRelations', 'generateLabelsFromComments'], 'boolean'], + ]); } /** @@ -72,7 +72,7 @@ class Generator extends \yii\gii\Generator */ public function attributeLabels() { - return array( + return [ 'ns' => 'Namespace', 'db' => 'Database Connection ID', 'tableName' => 'Table Name', @@ -80,7 +80,7 @@ class Generator extends \yii\gii\Generator 'baseClass' => 'Base Class', 'generateRelations' => 'Generate Relations', 'generateLabelsFromComments' => 'Generate Labels from DB Comments', - ); + ]; } /** @@ -88,26 +88,43 @@ class Generator extends \yii\gii\Generator */ public function hints() { - return array( + return [ 'ns' => 'This is the namespace of the ActiveRecord class to be generated, e.g., <code>app\models</code>', 'db' => 'This is the ID of the DB application component.', 'tableName' => 'This is the name of the DB table that the new ActiveRecord class is associated with, e.g. <code>tbl_post</code>. The table name may consist of the DB schema part if needed, e.g. <code>public.tbl_post</code>. - The table name may contain an asterisk to match multiple table names, e.g. <code>tbl_*</code> + The table name may end with asterisk to match multiple table names, e.g. <code>tbl_*</code> will match tables who name starts with <code>tbl_</code>. In this case, multiple ActiveRecord classes will be generated, one for each matching table name; and the class names will be generated from the matching characters. For example, table <code>tbl_post</code> will generate <code>Post</code> class.', 'modelClass' => 'This is the name of the ActiveRecord class to be generated. The class name should not contain the namespace part as it is specified in "Namespace". You do not need to specify the class name - if "Table Name" contains an asterisk at the end, in which case multiple ActiveRecord classes will be generated.', + if "Table Name" ends with asterisk, in which case multiple ActiveRecord classes will be generated.', 'baseClass' => 'This is the base class of the new ActiveRecord class. It should be a fully qualified namespaced class name.', 'generateRelations' => 'This indicates whether the generator should generate relations based on foreign key constraints it detects in the database. Note that if your database contains too many tables, - you may want to uncheck this option to accelerate the code generation proc ess.', + you may want to uncheck this option to accelerate the code generation process.', 'generateLabelsFromComments' => 'This indicates whether the generator should generate attribute labels by using the comments of the corresponding DB columns.', - ); + ]; + } + + /** + * @inheritdoc + */ + public function autoCompleteData() + { + $db = $this->getDbConnection(); + if ($db !== null) { + return [ + 'tableName' => function () use ($db) { + return $db->getSchema()->getTableNames(); + }, + ]; + } else { + return []; + } } /** @@ -115,9 +132,7 @@ class Generator extends \yii\gii\Generator */ public function requiredTemplates() { - return array( - 'model.php', - ); + return ['model.php']; } /** @@ -125,7 +140,7 @@ class Generator extends \yii\gii\Generator */ public function stickyAttributes() { - return array('ns', 'db', 'baseClass', 'generateRelations', 'generateLabelsFromComments'); + return ['ns', 'db', 'baseClass', 'generateRelations', 'generateLabelsFromComments']; } /** @@ -133,20 +148,20 @@ class Generator extends \yii\gii\Generator */ public function generate() { - $files = array(); + $files = []; $relations = $this->generateRelations(); $db = $this->getDbConnection(); foreach ($this->getTableNames() as $tableName) { $className = $this->generateClassName($tableName); $tableSchema = $db->getTableSchema($tableName); - $params = array( + $params = [ 'tableName' => $tableName, 'className' => $className, 'tableSchema' => $tableSchema, 'labels' => $this->generateLabels($tableSchema), 'rules' => $this->generateRules($tableSchema), - 'relations' => isset($relations[$className]) ? $relations[$className] : array(), - ); + 'relations' => isset($relations[$className]) ? $relations[$className] : [], + ]; $files[] = new CodeFile( Yii::getAlias('@' . str_replace('\\', '/', $this->ns)) . '/' . $className . '.php', $this->render('model.php', $params) @@ -163,7 +178,7 @@ class Generator extends \yii\gii\Generator */ public function generateLabels($table) { - $labels = array(); + $labels = []; foreach ($table->columns as $column) { if ($this->generateLabelsFromComments && !empty($column->comment)) { $labels[$column->name] = $column->comment; @@ -187,8 +202,8 @@ class Generator extends \yii\gii\Generator */ public function generateRules($table) { - $types = array(); - $lengths = array(); + $types = []; + $lengths = []; foreach ($table->columns as $column) { if ($column->autoIncrement) { continue; @@ -225,12 +240,12 @@ class Generator extends \yii\gii\Generator } } - $rules = array(); + $rules = []; foreach ($types as $type => $columns) { - $rules[] = "array('" . implode(', ', $columns) . "', '$type')"; + $rules[] = "[['" . implode("', '", $columns) . "'], '$type']"; } foreach ($lengths as $length => $columns) { - $rules[] = "array('" . implode(', ', $columns) . "', 'string', 'max' => $length)"; + $rules[] = "[['" . implode("', '", $columns) . "'], 'string', 'max' => $length]"; } return $rules; @@ -242,7 +257,7 @@ class Generator extends \yii\gii\Generator protected function generateRelations() { if (!$this->generateRelations) { - return array(); + return []; } $db = $this->getDbConnection(); @@ -253,7 +268,7 @@ class Generator extends \yii\gii\Generator $schemaName = ''; } - $relations = array(); + $relations = []; foreach ($db->getSchema()->getTableSchemas($schemaName) as $table) { $tableName = $table->name; $className = $this->generateClassName($tableName); @@ -266,11 +281,11 @@ class Generator extends \yii\gii\Generator // Add relation for this table $link = $this->generateRelationLink(array_flip($refs)); $relationName = $this->generateRelationName($relations, $className, $table, $fks[0], false); - $relations[$className][$relationName] = array( - "return \$this->hasOne('$refClassName', $link);", + $relations[$className][$relationName] = [ + "return \$this->hasOne($refClassName::className(), $link);", $refClassName, false, - ); + ]; // Add relation for the referenced table $hasMany = false; @@ -282,11 +297,11 @@ class Generator extends \yii\gii\Generator } $link = $this->generateRelationLink($refs); $relationName = $this->generateRelationName($relations, $refClassName, $refTable, $className, $hasMany); - $relations[$refClassName][$relationName] = array( - "return \$this->" . ($hasMany ? 'hasMany' : 'hasOne') . "('$className', $link);", + $relations[$refClassName][$relationName] = [ + "return \$this->" . ($hasMany ? 'hasMany' : 'hasOne') . "($className::className(), $link);", $className, $hasMany, - ); + ]; } if (($fks = $this->checkPivotTable($table)) === false) { @@ -297,23 +312,23 @@ class Generator extends \yii\gii\Generator $className0 = $this->generateClassName($table0); $className1 = $this->generateClassName($table1); - $link = $this->generateRelationLink(array($fks[$table->primaryKey[1]][1] => $table->primaryKey[1])); - $viaLink = $this->generateRelationLink(array($table->primaryKey[0] => $fks[$table->primaryKey[0]][1])); + $link = $this->generateRelationLink([$fks[$table->primaryKey[1]][1] => $table->primaryKey[1]]); + $viaLink = $this->generateRelationLink([$table->primaryKey[0] => $fks[$table->primaryKey[0]][1]]); $relationName = $this->generateRelationName($relations, $className0, $db->getTableSchema($table0), $table->primaryKey[1], true); - $relations[$className0][$relationName] = array( - "return \$this->hasMany('$className1', $link)->viaTable('{$table->name}', $viaLink);", + $relations[$className0][$relationName] = [ + "return \$this->hasMany($className1::className(), $link)->viaTable('{$table->name}', $viaLink);", $className0, true, - ); + ]; - $link = $this->generateRelationLink(array($fks[$table->primaryKey[0]][1] => $table->primaryKey[0])); - $viaLink = $this->generateRelationLink(array($table->primaryKey[1] => $fks[$table->primaryKey[1]][1])); + $link = $this->generateRelationLink([$fks[$table->primaryKey[0]][1] => $table->primaryKey[0]]); + $viaLink = $this->generateRelationLink([$table->primaryKey[1] => $fks[$table->primaryKey[1]][1]]); $relationName = $this->generateRelationName($relations, $className1, $db->getTableSchema($table1), $table->primaryKey[0], true); - $relations[$className1][$relationName] = array( - "return \$this->hasMany('$className0', $link)->viaTable('{$table->name}', $viaLink);", + $relations[$className1][$relationName] = [ + "return \$this->hasMany($className0::className(), $link)->viaTable('{$table->name}', $viaLink);", $className1, true, - ); + ]; } return $relations; } @@ -325,11 +340,11 @@ class Generator extends \yii\gii\Generator */ protected function generateRelationLink($refs) { - $pairs = array(); + $pairs = []; foreach ($refs as $a => $b) { $pairs[] = "'$a' => '$b'"; } - return 'array(' . implode(', ', $pairs) . ')'; + return '[' . implode(', ', $pairs) . ']'; } /** @@ -346,13 +361,13 @@ class Generator extends \yii\gii\Generator if (count($pk) !== 2) { return false; } - $fks = array(); + $fks = []; foreach ($table->foreignKeys as $refs) { if (count($refs) === 2) { if (isset($refs[$pk[0]])) { - $fks[$pk[0]] = array($refs[0], $refs[$pk[0]]); + $fks[$pk[0]] = [$refs[0], $refs[$pk[0]]]; } elseif (isset($refs[$pk[1]])) { - $fks[$pk[1]] = array($refs[0], $refs[$pk[1]]); + $fks[$pk[1]] = [$refs[0], $refs[$pk[1]]]; } } } @@ -424,8 +439,8 @@ class Generator extends \yii\gii\Generator if ($this->isReservedKeyword($this->modelClass)) { $this->addError('modelClass', 'Class name cannot be a reserved PHP keyword.'); } - if (strpos($this->tableName, '*') === false && $this->modelClass == '') { - $this->addError('modelClass', 'Model Class cannot be blank.'); + if (substr($this->tableName, -1) !== '*' && $this->modelClass == '') { + $this->addError('modelClass', 'Model Class cannot be blank if table name does not end with asterisk.'); } } @@ -434,8 +449,8 @@ class Generator extends \yii\gii\Generator */ public function validateTableName() { - if (($pos = strpos($this->tableName, '*')) !== false && strpos($this->tableName, '*', $pos + 1) !== false) { - $this->addError('tableName', 'At most one asterisk is allowed.'); + if (($pos = strpos($this->tableName, '*')) !== false && substr($this->tableName, -1) !== '*') { + $this->addError('tableName', 'Asterisk is only allowed as the last character.'); return; } $tables = $this->getTableNames(); @@ -464,7 +479,10 @@ class Generator extends \yii\gii\Generator return $this->_tableNames; } $db = $this->getDbConnection(); - $tableNames = array(); + if ($db === null) { + return []; + } + $tableNames = []; if (strpos($this->tableName, '*') !== false) { if (($pos = strrpos($this->tableName, '.')) !== false) { $schema = substr($this->tableName, 0, $pos); @@ -502,7 +520,7 @@ class Generator extends \yii\gii\Generator } $db = $this->getDbConnection(); - $patterns = array(); + $patterns = []; if (strpos($this->tableName, '*') !== false) { $pattern = $this->tableName; if (($pos = strrpos($pattern, '.')) !== false) { @@ -511,7 +529,8 @@ class Generator extends \yii\gii\Generator $patterns[] = '/^' . str_replace('*', '(\w+)', $pattern) . '$/'; } if (!empty($db->tablePrefix)) { - $patterns[] = "/^{$db->tablePrefix}(.*?)|(.*?){$db->tablePrefix}$/"; + $patterns[] = "/^{$db->tablePrefix}(.*?)$/"; + $patterns[] = "/^(.*?){$db->tablePrefix}$/"; } else { $patterns[] = "/^tbl_(.*?)$/"; } @@ -520,6 +539,7 @@ class Generator extends \yii\gii\Generator foreach ($patterns as $pattern) { if (preg_match($pattern, $tableName, $matches)) { $className = $matches[1]; + break; } } return $this->_classNames[$tableName] = Inflector::id2camel($className, '_'); diff --git a/framework/yii/gii/generators/model/form.php b/extensions/gii/generators/model/form.php similarity index 92% rename from framework/yii/gii/generators/model/form.php rename to extensions/gii/generators/model/form.php index 9eca27c..ddc40f8 100644 --- a/framework/yii/gii/generators/model/form.php +++ b/extensions/gii/generators/model/form.php @@ -1,6 +1,6 @@ <?php /** - * @var yii\base\View $this + * @var yii\web\View $this * @var yii\widgets\ActiveForm $form * @var yii\gii\generators\form\Generator $generator */ diff --git a/framework/yii/gii/generators/model/templates/model.php b/extensions/gii/generators/model/templates/model.php similarity index 77% rename from framework/yii/gii/generators/model/templates/model.php rename to extensions/gii/generators/model/templates/model.php index b194294..dcd1461 100644 --- a/framework/yii/gii/generators/model/templates/model.php +++ b/extensions/gii/generators/model/templates/model.php @@ -2,7 +2,7 @@ /** * This is the template for generating the model class of a specified table. * - * @var yii\base\View $this + * @var yii\web\View $this * @var yii\gii\generators\model\Generator $generator * @var string $tableName full table name * @var string $className class name @@ -15,29 +15,29 @@ echo "<?php\n"; ?> -namespace <?php echo $generator->ns; ?>; +namespace <?= $generator->ns ?>; /** - * This is the model class for table "<?php echo $tableName; ?>". + * This is the model class for table "<?= $tableName ?>". * <?php foreach ($tableSchema->columns as $column): ?> - * @property <?php echo "{$column->phpType} \${$column->name}\n"; ?> + * @property <?= "{$column->phpType} \${$column->name}\n" ?> <?php endforeach; ?> <?php if (!empty($relations)): ?> * <?php foreach ($relations as $name => $relation): ?> - * @property <?php echo $relation[1] . ($relation[2] ? '[]' : '') . ' $' . lcfirst($name) . "\n"; ?> + * @property <?= $relation[1] . ($relation[2] ? '[]' : '') . ' $' . lcfirst($name) . "\n" ?> <?php endforeach; ?> <?php endif; ?> */ -class <?php echo $className; ?> extends <?php echo '\\' . ltrim($generator->baseClass, '\\') . "\n"; ?> +class <?= $className ?> extends <?= '\\' . ltrim($generator->baseClass, '\\') . "\n" ?> { /** * @inheritdoc */ public static function tableName() { - return '<?php echo $tableName; ?>'; + return '<?= $tableName ?>'; } /** @@ -45,7 +45,7 @@ class <?php echo $className; ?> extends <?php echo '\\' . ltrim($generator->base */ public function rules() { - return array(<?php echo "\n\t\t\t" . implode(",\n\t\t\t", $rules) . "\n\t\t"; ?>); + return [<?= "\n\t\t\t" . implode(",\n\t\t\t", $rules) . "\n\t\t" ?>]; } /** @@ -53,20 +53,20 @@ class <?php echo $className; ?> extends <?php echo '\\' . ltrim($generator->base */ public function attributeLabels() { - return array( + return [ <?php foreach ($labels as $name => $label): ?> - <?php echo "'$name' => '" . addslashes($label) . "',\n"; ?> + <?= "'$name' => '" . addslashes($label) . "',\n" ?> <?php endforeach; ?> - ); + ]; } <?php foreach ($relations as $name => $relation): ?> /** * @return \yii\db\ActiveRelation */ - public function get<?php echo $name; ?>() + public function get<?= $name ?>() { - <?php echo $relation[0] . "\n"; ?> + <?= $relation[0] . "\n" ?> } <?php endforeach; ?> } diff --git a/framework/yii/gii/generators/module/Generator.php b/extensions/gii/generators/module/Generator.php similarity index 78% rename from framework/yii/gii/generators/module/Generator.php rename to extensions/gii/generators/module/Generator.php index 52fbfe4..5946e07 100644 --- a/framework/yii/gii/generators/module/Generator.php +++ b/extensions/gii/generators/module/Generator.php @@ -44,13 +44,13 @@ class Generator extends \yii\gii\Generator */ public function rules() { - return array_merge(parent::rules(), array( - array('moduleID, moduleClass', 'filter', 'filter' => 'trim'), - array('moduleID, moduleClass', 'required'), - array('moduleID', 'match', 'pattern' => '/^[\w\\-]+$/', 'message' => 'Only word characters and dashes are allowed.'), - array('moduleClass', 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'), - array('moduleClass', 'validateModuleClass'), - )); + return array_merge(parent::rules(), [ + [['moduleID', 'moduleClass'], 'filter', 'filter' => 'trim'], + [['moduleID', 'moduleClass'], 'required'], + [['moduleID'], 'match', 'pattern' => '/^[\w\\-]+$/', 'message' => 'Only word characters and dashes are allowed.'], + [['moduleClass'], 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'], + [['moduleClass'], 'validateModuleClass'], + ]); } /** @@ -58,10 +58,10 @@ class Generator extends \yii\gii\Generator */ public function attributeLabels() { - return array( + return [ 'moduleID' => 'Module ID', 'moduleClass' => 'Module Class', - ); + ]; } /** @@ -69,10 +69,10 @@ class Generator extends \yii\gii\Generator */ public function hints() { - return array( + return [ 'moduleID' => 'This refers to the ID of the module, e.g., <code>admin</code>.', 'moduleClass' => 'This is the fully qualified class name of the module, e.g., <code>app\modules\admin\Module</code>.', - ); + ]; } /** @@ -81,24 +81,23 @@ class Generator extends \yii\gii\Generator public function successMessage() { if (Yii::$app->hasModule($this->moduleID)) { - $link = Html::a('try it now', Yii::$app->getUrlManager()->createUrl($this->moduleID), array('target' => '_blank')); + $link = Html::a('try it now', Yii::$app->getUrlManager()->createUrl($this->moduleID), ['target' => '_blank']); return "The module has been generated successfully. You may $link."; } $output = <<<EOD <p>The module has been generated successfully.</p> -<p>To access the module, you need to modify the application configuration as follows:</p> +<p>To access the module, you need to add this to your application configuration:</p> EOD; $code = <<<EOD <?php -return array( - 'modules'=>array( - '{$this->moduleID}' => array( + ...... + 'modules' => [ + '{$this->moduleID}' => [ 'class' => '{$this->moduleClass}', - ), - ), - ...... -); + ], + ], + ...... EOD; return $output . '<pre>' . highlight_string($code, true) . '</pre>'; @@ -109,11 +108,7 @@ EOD; */ public function requiredTemplates() { - return array( - 'module.php', - 'controller.php', - 'view.php', - ); + return ['module.php', 'controller.php', 'view.php']; } /** @@ -121,7 +116,7 @@ EOD; */ public function generate() { - $files = array(); + $files = []; $modulePath = $this->getModulePath(); $files[] = new CodeFile( $modulePath . '/' . StringHelper::basename($this->moduleClass) . '.php', @@ -144,9 +139,12 @@ EOD; */ public function validateModuleClass() { - if (strpos($this->moduleClass, '\\') === false || Yii::getAlias('@' . str_replace('\\', '/', $this->moduleClass)) === false) { + if (strpos($this->moduleClass, '\\') === false || Yii::getAlias('@' . str_replace('\\', '/', $this->moduleClass), false) === false) { $this->addError('moduleClass', 'Module class must be properly namespaced.'); } + if (substr($this->moduleClass, -1, 1) == '\\') { + $this->addError('moduleClass', 'Module class name must not be empty. Please enter a fully qualified class name. e.g. "app\\modules\\admin\\Module".'); + } } /** diff --git a/framework/yii/gii/generators/module/form.php b/extensions/gii/generators/module/form.php similarity index 92% rename from framework/yii/gii/generators/module/form.php rename to extensions/gii/generators/module/form.php index 8a0cc88..2874d90 100644 --- a/framework/yii/gii/generators/module/form.php +++ b/extensions/gii/generators/module/form.php @@ -1,6 +1,6 @@ <?php /** - * @var yii\base\View $this + * @var yii\web\View $this * @var yii\widgets\ActiveForm $form * @var yii\gii\generators\module\Generator $generator */ diff --git a/framework/yii/gii/generators/module/templates/controller.php b/extensions/gii/generators/module/templates/controller.php similarity index 88% rename from framework/yii/gii/generators/module/templates/controller.php rename to extensions/gii/generators/module/templates/controller.php index 4d3da93..018450c 100644 --- a/framework/yii/gii/generators/module/templates/controller.php +++ b/extensions/gii/generators/module/templates/controller.php @@ -2,13 +2,13 @@ /** * This is the template for generating a controller class within a module. * - * @var yii\base\View $this + * @var yii\web\View $this * @var yii\gii\generators\module\Generator $generator */ echo "<?php\n"; ?> -namespace <?php echo $generator->getControllerNamespace(); ?>; +namespace <?= $generator->getControllerNamespace() ?>; use yii\web\Controller; diff --git a/framework/yii/gii/generators/module/templates/module.php b/extensions/gii/generators/module/templates/module.php similarity index 80% rename from framework/yii/gii/generators/module/templates/module.php rename to extensions/gii/generators/module/templates/module.php index 40af635..72b32be 100644 --- a/framework/yii/gii/generators/module/templates/module.php +++ b/extensions/gii/generators/module/templates/module.php @@ -2,7 +2,7 @@ /** * This is the template for generating a module class file. * - * @var yii\base\View $this + * @var yii\web\View $this * @var yii\gii\generators\module\Generator $generator */ $className = $generator->moduleClass; @@ -13,12 +13,12 @@ $className = substr($className, $pos + 1); echo "<?php\n"; ?> -namespace <?php echo $ns; ?>; +namespace <?= $ns ?>; -class <?php echo $className; ?> extends \yii\base\Module +class <?= $className ?> extends \yii\base\Module { - public $controllerNamespace = '<?php echo $generator->getControllerNamespace(); ?>'; + public $controllerNamespace = '<?= $generator->getControllerNamespace() ?>'; public function init() { diff --git a/framework/yii/gii/generators/module/templates/view.php b/extensions/gii/generators/module/templates/view.php similarity index 56% rename from framework/yii/gii/generators/module/templates/view.php rename to extensions/gii/generators/module/templates/view.php index d0e1ce6..f5f4248 100644 --- a/framework/yii/gii/generators/module/templates/view.php +++ b/extensions/gii/generators/module/templates/view.php @@ -1,18 +1,18 @@ <?php /** - * @var yii\base\View $this + * @var yii\web\View $this * @var yii\gii\generators\module\Generator $generator */ ?> -<div class="<?php echo $generator->moduleID . '-default-index'; ?>"> - <h1><?php echo "<?php"; ?> echo $this->context->action->uniqueId; ?></h1> +<div class="<?= $generator->moduleID . '-default-index' ?>"> + <h1><?= "<?= " ?>$this->context->action->uniqueId ?></h1> <p> - This is the view content for action "<?php echo "<?php"; ?> echo $this->context->action->id; ?>". - The action belongs to the controller "<?php echo "<?php"; ?> echo get_class($this->context); ?>" - in the "<?php echo "<?php"; ?> echo $this->context->module->id; ?>" module. + This is the view content for action "<?= "<?= " ?>$this->context->action->id ?>". + The action belongs to the controller "<?= "<?= " ?>get_class($this->context) ?>" + in the "<?= "<?= " ?>$this->context->module->id ?>" module. </p> <p> You may customize this page by editing the following file:<br> - <code><?php echo "<?php"; ?> echo __FILE__; ?></code> + <code><?= "<?= " ?>__FILE__ ?></code> </p> </div> diff --git a/framework/yii/gii/views/default/diff.php b/extensions/gii/views/default/diff.php similarity index 86% rename from framework/yii/gii/views/default/diff.php rename to extensions/gii/views/default/diff.php index bb4e455..6ee91a1 100644 --- a/framework/yii/gii/views/default/diff.php +++ b/extensions/gii/views/default/diff.php @@ -1,6 +1,6 @@ <?php /** - * @var yii\base\View $this + * @var yii\web\View $this * @var mixed $diff */ ?> @@ -10,6 +10,6 @@ <?php elseif (empty($diff)): ?> <div class="alert alert-success">Identical.</div> <?php else: ?> - <div class="content"><?php echo $diff; ?></div> + <div class="content"><?= $diff ?></div> <?php endif; ?> </div> diff --git a/framework/yii/gii/views/default/index.php b/extensions/gii/views/default/index.php similarity index 73% rename from framework/yii/gii/views/default/index.php rename to extensions/gii/views/default/index.php index 7dd1fdd..2e48816 100644 --- a/framework/yii/gii/views/default/index.php +++ b/extensions/gii/views/default/index.php @@ -2,10 +2,10 @@ use yii\helpers\Html; /** - * @var $this \yii\base\View - * @var $content string - * @var yii\gii\Generator[] $generators - * @var yii\gii\Generator $activeGenerator + * @var \yii\web\View $this + * @var \yii\gii\Generator[] $generators + * @var \yii\gii\Generator $activeGenerator + * @var string $content */ $generators = Yii::$app->controller->module->generators; $activeGenerator = Yii::$app->controller->generator; @@ -21,9 +21,9 @@ $this->title = 'Welcome to Gii'; <div class="row"> <?php foreach ($generators as $id => $generator): ?> <div class="generator col-lg-4"> - <h3><?php echo Html::encode($generator->getName()); ?></h3> - <p><?php echo $generator->getDescription(); ?></p> - <p><?php echo Html::a('Start »', array('default/view', 'id' => $id), array('class' => 'btn btn-default')); ?></p> + <h3><?= Html::encode($generator->getName()) ?></h3> + <p><?= $generator->getDescription() ?></p> + <p><?= Html::a('Start »', ['default/view', 'id' => $id], ['class' => 'btn btn-default']) ?></p> </div> <?php endforeach; ?> </div> diff --git a/framework/yii/gii/views/default/view.php b/extensions/gii/views/default/view.php similarity index 69% rename from framework/yii/gii/views/default/view.php rename to extensions/gii/views/default/view.php index 9754918..dabcf39 100644 --- a/framework/yii/gii/views/default/view.php +++ b/extensions/gii/views/default/view.php @@ -1,13 +1,12 @@ <?php -use yii\gii\Generator; use yii\helpers\Html; use yii\widgets\ActiveForm; use yii\gii\components\ActiveField; use yii\gii\CodeFile; /** - * @var yii\base\View $this + * @var yii\web\View $this * @var yii\gii\Generator $generator * @var string $id * @var yii\widgets\ActiveForm $form @@ -18,37 +17,37 @@ use yii\gii\CodeFile; */ $this->title = $generator->getName(); -$templates = array(); +$templates = []; foreach ($generator->templates as $name => $path) { $templates[$name] = "$name ($path)"; } ?> <div class="default-view"> - <h1><?php echo Html::encode($this->title); ?></h1> + <h1><?= Html::encode($this->title) ?></h1> - <p><?php echo $generator->getDescription(); ?></p> + <p><?= $generator->getDescription() ?></p> - <?php $form = ActiveForm::begin(array( + <?php $form = ActiveForm::begin([ 'id' => "$id-generator", 'successCssClass' => '', - 'fieldConfig' => array('class' => ActiveField::className()), - )); ?> + 'fieldConfig' => ['class' => ActiveField::className()], + ]); ?> <div class="row"> <div class="col-lg-8"> - <?php echo $this->renderFile($generator->formView(), array( + <?= $this->renderFile($generator->formView(), [ 'generator' => $generator, 'form' => $form, - )); ?> - <?php echo $form->field($generator, 'template')->sticky() + ]) ?> + <?= $form->field($generator, 'template')->sticky() ->label('Code Template') ->dropDownList($templates)->hint(' Please select which set of the templates should be used to generated the code. - '); ?> + ') ?> <div class="form-group"> - <?php echo Html::submitButton('Preview', array('name' => 'preview', 'class' => 'btn btn-success')); ?> + <?= Html::submitButton('Preview', ['name' => 'preview', 'class' => 'btn btn-primary']) ?> - <?php if(isset($files)): ?> - <?php echo Html::submitButton('Generate', array('name' => 'generate', 'class' => 'btn btn-danger')); ?> + <?php if (isset($files)): ?> + <?= Html::submitButton('Generate', ['name' => 'generate', 'class' => 'btn btn-success']) ?> <?php endif; ?> </div> </div> @@ -56,17 +55,17 @@ foreach ($generator->templates as $name => $path) { <?php if (isset($results)) { - echo $this->render('view/results', array( + echo $this->render('view/results', [ 'generator' => $generator, 'results' => $results, 'hasError' => $hasError, - )); + ]); } elseif (isset($files)) { - echo $this->render('view/files', array( + echo $this->render('view/files', [ 'generator' => $generator, 'files' => $files, 'answers' => $answers, - )); + ]); } ?> <?php ActiveForm::end(); ?> diff --git a/framework/yii/gii/views/default/view/files.php b/extensions/gii/views/default/view/files.php similarity index 88% rename from framework/yii/gii/views/default/view/files.php rename to extensions/gii/views/default/view/files.php index 5ba08e8..d8d5f0f 100644 --- a/framework/yii/gii/views/default/view/files.php +++ b/extensions/gii/views/default/view/files.php @@ -1,12 +1,11 @@ <?php -use yii\gii\Generator; use yii\helpers\Html; use yii\gii\CodeFile; /** - * @var $this \yii\base\View - * @var $generator \yii\gii\Generator + * @var \yii\web\View $this + * @var \yii\gii\Generator $generator * @var CodeFile[] $files * @var array $answers */ @@ -33,11 +32,11 @@ use yii\gii\CodeFile; </thead> <tbody> <?php foreach ($files as $file): ?> - <tr class="<?php echo $file->operation; ?>"> + <tr class="<?= $file->operation ?>"> <td class="file"> - <?php echo Html::a(Html::encode($file->getRelativePath()), array('preview', 'file' => $file->id), array('class' => 'preview-code', 'data-title' => $file->getRelativePath())); ?> + <?= Html::a(Html::encode($file->getRelativePath()), ['preview', 'file' => $file->id], ['class' => 'preview-code', 'data-title' => $file->getRelativePath()]) ?> <?php if ($file->operation === CodeFile::OP_OVERWRITE): ?> - <?php echo Html::a('diff', array('diff', 'file' => $file->id), array('class' => 'diff-code label label-warning', 'data-title' => $file->getRelativePath())); ?> + <?= Html::a('diff', ['diff', 'file' => $file->id], ['class' => 'diff-code label label-warning', 'data-title' => $file->getRelativePath()]) ?> <?php endif; ?> </td> <td class="action"> @@ -54,7 +53,7 @@ use yii\gii\CodeFile; if ($file->operation === CodeFile::OP_SKIP) { echo ' '; } else { - echo Html::checkBox("answers[{$file->id}]", isset($answers) ? isset($answers[$file->id]) : ($file->operation === CodeFile::OP_NEW)); + echo Html::checkBox("answers[{$file->id}]", isset($answers) ? isset($answers[$file->id]) : ($file->operation === CodeFile::OP_CREATE)); } ?> </td> diff --git a/framework/yii/gii/views/default/view/results.php b/extensions/gii/views/default/view/results.php similarity index 85% rename from framework/yii/gii/views/default/view/results.php rename to extensions/gii/views/default/view/results.php index caca404..0e9b7d9 100644 --- a/framework/yii/gii/views/default/view/results.php +++ b/extensions/gii/views/default/view/results.php @@ -1,10 +1,6 @@ <?php - -use yii\gii\Generator; -use yii\gii\CodeFile; - /** - * @var yii\base\View $this + * @var yii\web\View $this * @var yii\gii\Generator $generator * @var string $results * @var boolean $hasError @@ -18,5 +14,5 @@ use yii\gii\CodeFile; echo '<div class="alert alert-success">' . $generator->successMessage() . '</div>'; } ?> - <pre><?php echo nl2br($results); ?></pre> + <pre><?= nl2br($results) ?></pre> </div> diff --git a/framework/yii/gii/views/layouts/generator.php b/extensions/gii/views/layouts/generator.php similarity index 78% rename from framework/yii/gii/views/layouts/generator.php rename to extensions/gii/views/layouts/generator.php index fcf9fbc..245cd29 100644 --- a/framework/yii/gii/views/layouts/generator.php +++ b/extensions/gii/views/layouts/generator.php @@ -2,10 +2,10 @@ use yii\helpers\Html; /** - * @var $this \yii\base\View - * @var $content string - * @var yii\gii\Generator[] $generators - * @var yii\gii\Generator $activeGenerator + * @var \yii\web\View $this + * @var \yii\gii\Generator[] $generators + * @var \yii\gii\Generator $activeGenerator + * @var string $content */ $generators = Yii::$app->controller->module->generators; $activeGenerator = Yii::$app->controller->generator; @@ -17,15 +17,15 @@ $activeGenerator = Yii::$app->controller->generator; <?php foreach ($generators as $id => $generator) { $label = '<i class="glyphicon glyphicon-chevron-right"></i>' . Html::encode($generator->getName()); - echo Html::a($label, array('default/view', 'id' => $id), array( + echo Html::a($label, ['default/view', 'id' => $id], [ 'class' => $generator === $activeGenerator ? 'list-group-item active' : 'list-group-item', - )); + ]); } ?> </div> </div> <div class="col-lg-9"> - <?php echo $content; ?> + <?= $content ?> </div> </div> <?php $this->endContent(); ?> diff --git a/framework/yii/gii/views/layouts/main.php b/extensions/gii/views/layouts/main.php similarity index 63% rename from framework/yii/gii/views/layouts/main.php rename to extensions/gii/views/layouts/main.php index 5a70ac4..983475b 100644 --- a/framework/yii/gii/views/layouts/main.php +++ b/extensions/gii/views/layouts/main.php @@ -4,8 +4,8 @@ use yii\bootstrap\Nav; use yii\helpers\Html; /** - * @var $this \yii\base\View - * @var $content string + * @var \yii\web\View $this + * @var string $content */ $asset = yii\gii\GiiAsset::register($this); ?> @@ -14,38 +14,36 @@ $asset = yii\gii\GiiAsset::register($this); <html lang="en"> <head> <meta charset="utf-8"/> - <title><?php echo Html::encode($this->title); ?></title> + <title><?= Html::encode($this->title) ?></title> <?php $this->head(); ?> </head> <body> <?php $this->beginBody(); ?> <?php -NavBar::begin(array( +NavBar::begin([ 'brandLabel' => Html::img($asset->baseUrl . '/logo.png'), - 'brandUrl' => array('default/index'), - 'options' => array( - 'class' => 'navbar-inverse navbar-fixed-top', - ), -)); -echo Nav::widget(array( - 'options' => array('class' => 'nav navbar-nav pull-right'), - 'items' => array( - array('label' => 'Home', 'url' => array('default/index')), - array('label' => 'Help', 'url' => 'http://www.yiiframework.com/doc/guide/topics.gii'), - array('label' => 'Application', 'url' => Yii::$app->homeUrl), - ), -)); + 'brandUrl' => ['default/index'], + 'options' => ['class' => 'navbar-inverse navbar-fixed-top'], +]); +echo Nav::widget([ + 'options' => ['class' => 'nav navbar-nav navbar-right'], + 'items' => [ + ['label' => 'Home', 'url' => ['default/index']], + ['label' => 'Help', 'url' => 'http://www.yiiframework.com/doc/guide/topics.gii'], + ['label' => 'Application', 'url' => Yii::$app->homeUrl], + ], +]); NavBar::end(); ?> <div class="container"> - <?php echo $content; ?> + <?= $content ?> </div> <footer class="footer"> <div class="container"> <p class="pull-left">A Product of <a href="http://www.yiisoft.com/">Yii Software LLC</a></p> - <p class="pull-right"><?php echo Yii::powered(); ?></p> + <p class="pull-right"><?= Yii::powered() ?></p> </div> </footer> diff --git a/extensions/jui/yii/jui/Accordion.php b/extensions/jui/Accordion.php similarity index 71% rename from extensions/jui/yii/jui/Accordion.php rename to extensions/jui/Accordion.php index 7f32bc2..42897a9 100644 --- a/extensions/jui/yii/jui/Accordion.php +++ b/extensions/jui/Accordion.php @@ -17,36 +17,24 @@ use yii\helpers\Html; * For example: * * ```php - * echo Accordion::widget(array( - * 'items' => array( - * array( + * echo Accordion::widget([ + * 'items' => [ + * [ * 'header' => 'Section 1', * 'content' => 'Mauris mauris ante, blandit et, ultrices a, suscipit eget...', - * ), - * array( + * ], + * [ * 'header' => 'Section 2', - * 'headerOptions' => array( - * 'tag' => 'h3', - * ), + * 'headerOptions' => ['tag' => 'h3'], * 'content' => 'Sed non urna. Phasellus eu ligula. Vestibulum sit amet purus...', - * 'options' => array( - * 'tag' => 'div', - * ), - * ), - * ), - * 'options' => array( - * 'tag' => 'div', - * ), - * 'itemOptions' => array( - * 'tag' => 'div', - * ), - * 'headerOptions' => array( - * 'tag' => 'h3', - * ), - * 'clientOptions' => array( - * 'collapsible' => false, - * ), - * )); + * 'options' => ['tag' => 'div'], + * ], + * ], + * 'options' => ['tag' => 'div'], + * 'itemOptions' => ['tag' => 'div'], + * 'headerOptions' => ['tag' => 'h3'], + * 'clientOptions' => ['collapsible' => false], + * ]); * ``` * * @see http://api.jqueryui.com/accordion/ @@ -60,36 +48,36 @@ class Accordion extends Widget * * - tag: string, defaults to "div", the tag name of the container tag of this widget */ - public $options = array(); + public $options = []; /** * @var array list of collapsible items. Each item can be an array of the following structure: * * ~~~ - * array( + * [ * 'header' => 'Item header', * 'content' => 'Item content', * // the HTML attributes of the item header container tag. This will overwrite "headerOptions". - * 'headerOptions' => array(), + * 'headerOptions' => [], * // the HTML attributes of the item container tag. This will overwrite "itemOptions". - * 'options' => array(), - * ) + * 'options' => [], + * ] * ~~~ */ - public $items = array(); + public $items = []; /** * @var array list of HTML attributes for the item container tags. This will be overwritten * by the "options" set in individual [[items]]. The following special options are recognized: * * - tag: string, defaults to "div", the tag name of the item container tags. */ - public $itemOptions = array(); + public $itemOptions = []; /** * @var array list of HTML attributes for the item header container tags. This will be overwritten * by the "headerOptions" set in individual [[items]]. The following special options are recognized: * * - tag: string, defaults to "h3", the tag name of the item container tags. */ - public $headerOptions = array(); + public $headerOptions = []; /** @@ -112,7 +100,7 @@ class Accordion extends Widget */ protected function renderItems() { - $items = array(); + $items = []; foreach ($this->items as $item) { if (!isset($item['header'])) { throw new InvalidConfigException("The 'header' option is required."); @@ -120,10 +108,10 @@ class Accordion extends Widget if (!isset($item['content'])) { throw new InvalidConfigException("The 'content' option is required."); } - $headerOptions = array_merge($this->headerOptions, ArrayHelper::getValue($item, 'headerOptions', array())); + $headerOptions = array_merge($this->headerOptions, ArrayHelper::getValue($item, 'headerOptions', [])); $headerTag = ArrayHelper::remove($headerOptions, 'tag', 'h3'); $items[] = Html::tag($headerTag, $item['header'], $headerOptions); - $options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', array())); + $options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', [])); $tag = ArrayHelper::remove($options, 'tag', 'div'); $items[] = Html::tag($tag, $item['content'], $options); } diff --git a/extensions/jui/yii/jui/AccordionAsset.php b/extensions/jui/AccordionAsset.php similarity index 82% rename from extensions/jui/yii/jui/AccordionAsset.php rename to extensions/jui/AccordionAsset.php index ae6accb..05c1e20 100644 --- a/extensions/jui/yii/jui/AccordionAsset.php +++ b/extensions/jui/AccordionAsset.php @@ -6,6 +6,7 @@ */ namespace yii\jui; + use yii\web\AssetBundle; /** @@ -15,11 +16,11 @@ use yii\web\AssetBundle; class AccordionAsset extends AssetBundle { public $sourcePath = '@yii/jui/assets'; - public $js = array( + public $js = [ 'jquery.ui.accordion.js', - ); - public $depends = array( + ]; + public $depends = [ 'yii\jui\CoreAsset', 'yii\jui\EffectAsset', - ); + ]; } diff --git a/extensions/jui/yii/jui/AutoComplete.php b/extensions/jui/AutoComplete.php similarity index 82% rename from extensions/jui/yii/jui/AutoComplete.php rename to extensions/jui/AutoComplete.php index be31c55..ac0c997 100644 --- a/extensions/jui/yii/jui/AutoComplete.php +++ b/extensions/jui/AutoComplete.php @@ -16,24 +16,24 @@ use yii\helpers\Html; * For example: * * ```php - * echo AutoComplete::widget(array( + * echo AutoComplete::widget([ * 'model' => $model, * 'attribute' => 'country', - * 'clientOptions' => array( - * 'source' => array('USA', 'RUS'), - * ), - * )); + * 'clientOptions' => [ + * 'source' => ['USA', 'RUS'], + * ], + * ]); * ``` * * The following example will use the name property instead: * * ```php - * echo AutoComplete::widget(array( - * 'name' => 'country', - * 'clientOptions' => array( - * 'source' => array('USA', 'RUS'), - * ), - * )); + * echo AutoComplete::widget([ + * 'name' => 'country', + * 'clientOptions' => [ + * 'source' => ['USA', 'RUS'], + * ], + * ]); *``` * * @see http://api.jqueryui.com/autocomplete/ diff --git a/extensions/jui/yii/jui/AutoCompleteAsset.php b/extensions/jui/AutoCompleteAsset.php similarity index 82% rename from extensions/jui/yii/jui/AutoCompleteAsset.php rename to extensions/jui/AutoCompleteAsset.php index d0af190..f48e064 100644 --- a/extensions/jui/yii/jui/AutoCompleteAsset.php +++ b/extensions/jui/AutoCompleteAsset.php @@ -15,11 +15,11 @@ use yii\web\AssetBundle; class AutoCompleteAsset extends AssetBundle { public $sourcePath = '@yii/jui/assets'; - public $js = array( + public $js = [ 'jquery.ui.autocomplete.js', - ); - public $depends = array( + ]; + public $depends = [ 'yii\jui\CoreAsset', 'yii\jui\MenuAsset', - ); + ]; } diff --git a/extensions/jui/yii/jui/ButtonAsset.php b/extensions/jui/ButtonAsset.php similarity index 81% rename from extensions/jui/yii/jui/ButtonAsset.php rename to extensions/jui/ButtonAsset.php index 1676e8e..6616b34 100644 --- a/extensions/jui/yii/jui/ButtonAsset.php +++ b/extensions/jui/ButtonAsset.php @@ -15,10 +15,10 @@ use yii\web\AssetBundle; class ButtonAsset extends AssetBundle { public $sourcePath = '@yii/jui/assets'; - public $js = array( + public $js = [ 'jquery.ui.button.js', - ); - public $depends = array( + ]; + public $depends = [ 'yii\jui\CoreAsset', - ); + ]; } diff --git a/extensions/jui/yii/jui/CoreAsset.php b/extensions/jui/CoreAsset.php similarity index 84% rename from extensions/jui/yii/jui/CoreAsset.php rename to extensions/jui/CoreAsset.php index 2f0d62a..d77a25f 100644 --- a/extensions/jui/yii/jui/CoreAsset.php +++ b/extensions/jui/CoreAsset.php @@ -15,13 +15,13 @@ use yii\web\AssetBundle; class CoreAsset extends AssetBundle { public $sourcePath = '@yii/jui/assets'; - public $js = array( + public $js = [ 'jquery.ui.core.js', 'jquery.ui.widget.js', 'jquery.ui.position.js', 'jquery.ui.mouse.js', - ); - public $depends = array( + ]; + public $depends = [ 'yii\web\JqueryAsset', - ); + ]; } diff --git a/extensions/jui/yii/jui/DatePicker.php b/extensions/jui/DatePicker.php similarity index 85% rename from extensions/jui/yii/jui/DatePicker.php rename to extensions/jui/DatePicker.php index c275190..06ca356 100644 --- a/extensions/jui/yii/jui/DatePicker.php +++ b/extensions/jui/DatePicker.php @@ -9,6 +9,7 @@ namespace yii\jui; use Yii; use yii\helpers\Html; +use yii\helpers\Json; /** * DatePicker renders an datepicker jQuery UI widget. @@ -16,26 +17,26 @@ use yii\helpers\Html; * For example: * * ```php - * echo DatePicker::widget(array( + * echo DatePicker::widget([ * 'language' => 'ru', * 'model' => $model, * 'attribute' => 'country', - * 'clientOptions' => array( + * 'clientOptions' => [ * 'dateFormat' => 'yy-mm-dd', - * ), - * )); + * ], + * ]); * ``` * * The following example will use the name property instead: * * ```php - * echo DatePicker::widget(array( + * echo DatePicker::widget([ * 'language' => 'ru', * 'name' => 'country', - * 'clientOptions' => array( + * 'clientOptions' => [ * 'dateFormat' => 'yy-mm-dd', - * ), - * )); + * ], + * ]); *``` * * @see http://api.jqueryui.com/datepicker/ @@ -61,11 +62,19 @@ class DatePicker extends InputWidget public function run() { echo $this->renderWidget() . "\n"; - $this->registerWidget('datepicker', DatePickerAsset::className()); if ($this->language !== false) { $view = $this->getView(); DatePickerRegionalAsset::register($view); - $view->registerJs("$('#{$this->options['id']}').datepicker('option', $.datepicker.regional['{$this->language}']);"); + + $options = Json::encode($this->clientOptions); + $view->registerJs("$('#{$this->options['id']}').datepicker($.extend({}, $.datepicker.regional['{$this->language}'], $options));"); + + $options = $this->clientOptions; + $this->clientOptions = false; // the datepicker js widget is already registered + $this->registerWidget('datepicker', DatePickerAsset::className()); + $this->clientOptions = $options; + } else { + $this->registerWidget('datepicker', DatePickerAsset::className()); } } @@ -75,7 +84,7 @@ class DatePicker extends InputWidget */ protected function renderWidget() { - $contents = array(); + $contents = []; if ($this->inline === false) { if ($this->hasModel()) { diff --git a/extensions/jui/yii/jui/DatePickerAsset.php b/extensions/jui/DatePickerAsset.php similarity index 82% rename from extensions/jui/yii/jui/DatePickerAsset.php rename to extensions/jui/DatePickerAsset.php index 4102675..fddd8df 100644 --- a/extensions/jui/yii/jui/DatePickerAsset.php +++ b/extensions/jui/DatePickerAsset.php @@ -15,11 +15,11 @@ use yii\web\AssetBundle; class DatePickerAsset extends AssetBundle { public $sourcePath = '@yii/jui/assets'; - public $js = array( + public $js = [ 'jquery.ui.datepicker.js', - ); - public $depends = array( + ]; + public $depends = [ 'yii\jui\CoreAsset', 'yii\jui\EffectAsset', - ); + ]; } diff --git a/extensions/jui/yii/jui/DatePickerRegionalAsset.php b/extensions/jui/DatePickerRegionalAsset.php similarity index 81% rename from extensions/jui/yii/jui/DatePickerRegionalAsset.php rename to extensions/jui/DatePickerRegionalAsset.php index 44fc60a..249373a 100644 --- a/extensions/jui/yii/jui/DatePickerRegionalAsset.php +++ b/extensions/jui/DatePickerRegionalAsset.php @@ -15,10 +15,10 @@ use yii\web\AssetBundle; class DatePickerRegionalAsset extends AssetBundle { public $sourcePath = '@yii/jui/assets'; - public $js = array( + public $js = [ 'jquery.ui.datepicker-i18n.js', - ); - public $depends = array( + ]; + public $depends = [ 'yii\jui\DatePickerAsset', - ); + ]; } diff --git a/extensions/jui/yii/jui/Dialog.php b/extensions/jui/Dialog.php similarity index 91% rename from extensions/jui/yii/jui/Dialog.php rename to extensions/jui/Dialog.php index 1285a90..a5cbaf2 100644 --- a/extensions/jui/yii/jui/Dialog.php +++ b/extensions/jui/Dialog.php @@ -15,11 +15,11 @@ use yii\helpers\Html; * For example: * * ```php - * Dialog::begin(array( - * 'clientOptions' => array( + * Dialog::begin([ + * 'clientOptions' => [ * 'modal' => true, - * ), - * )); + * ], + * ]); * * echo 'Dialog contents here...'; * diff --git a/extensions/jui/yii/jui/DialogAsset.php b/extensions/jui/DialogAsset.php similarity index 84% rename from extensions/jui/yii/jui/DialogAsset.php rename to extensions/jui/DialogAsset.php index 04ba950..109243e 100644 --- a/extensions/jui/yii/jui/DialogAsset.php +++ b/extensions/jui/DialogAsset.php @@ -15,13 +15,13 @@ use yii\web\AssetBundle; class DialogAsset extends AssetBundle { public $sourcePath = '@yii/jui/assets'; - public $js = array( + public $js = [ 'jquery.ui.dialog.js', - ); - public $depends = array( + ]; + public $depends = [ 'yii\jui\CoreAsset', 'yii\jui\ButtonAsset', 'yii\jui\DraggableAsset', 'yii\jui\ResizableAsset', - ); + ]; } diff --git a/extensions/jui/yii/jui/Draggable.php b/extensions/jui/Draggable.php similarity index 91% rename from extensions/jui/yii/jui/Draggable.php rename to extensions/jui/Draggable.php index 5fa6491..02e4973 100644 --- a/extensions/jui/yii/jui/Draggable.php +++ b/extensions/jui/Draggable.php @@ -15,11 +15,9 @@ use yii\helpers\Html; * For example: * * ```php - * Draggable::begin(array( - * 'clientOptions' => array( - * 'grid' => array(50, 20), - * ), - * )); + * Draggable::begin([ + * 'clientOptions' => ['grid' => [50, 20]], + * ]); * * echo 'Draggable contents here...'; * diff --git a/extensions/jui/yii/jui/DraggableAsset.php b/extensions/jui/DraggableAsset.php similarity index 81% rename from extensions/jui/yii/jui/DraggableAsset.php rename to extensions/jui/DraggableAsset.php index 44f8e66..f3286a5 100644 --- a/extensions/jui/yii/jui/DraggableAsset.php +++ b/extensions/jui/DraggableAsset.php @@ -15,10 +15,10 @@ use yii\web\AssetBundle; class DraggableAsset extends AssetBundle { public $sourcePath = '@yii/jui/assets'; - public $js = array( + public $js = [ 'jquery.ui.draggable.js', - ); - public $depends = array( + ]; + public $depends = [ 'yii\jui\CoreAsset', - ); + ]; } diff --git a/extensions/jui/yii/jui/Droppable.php b/extensions/jui/Droppable.php similarity index 91% rename from extensions/jui/yii/jui/Droppable.php rename to extensions/jui/Droppable.php index d9f366c..530e736 100644 --- a/extensions/jui/yii/jui/Droppable.php +++ b/extensions/jui/Droppable.php @@ -15,11 +15,9 @@ use yii\helpers\Html; * For example: * * ```php - * Droppable::begin(array( - * 'clientOptions' => array( - * 'accept' => '.special', - * ), - * )); + * Droppable::begin([ + * 'clientOptions' => ['accept' => '.special'], + * ]); * * echo 'Droppable body here...'; * diff --git a/extensions/jui/yii/jui/DroppableAsset.php b/extensions/jui/DroppableAsset.php similarity index 81% rename from extensions/jui/yii/jui/DroppableAsset.php rename to extensions/jui/DroppableAsset.php index 3274c74..84b64b8 100644 --- a/extensions/jui/yii/jui/DroppableAsset.php +++ b/extensions/jui/DroppableAsset.php @@ -15,10 +15,10 @@ use yii\web\AssetBundle; class DroppableAsset extends AssetBundle { public $sourcePath = '@yii/jui/assets'; - public $js = array( + public $js = [ 'jquery.ui.droppable.js', - ); - public $depends = array( + ]; + public $depends = [ 'yii\jui\DraggableAsset', - ); + ]; } diff --git a/extensions/jui/yii/jui/EffectAsset.php b/extensions/jui/EffectAsset.php similarity index 81% rename from extensions/jui/yii/jui/EffectAsset.php rename to extensions/jui/EffectAsset.php index b882f45..79c5aaa 100644 --- a/extensions/jui/yii/jui/EffectAsset.php +++ b/extensions/jui/EffectAsset.php @@ -15,10 +15,10 @@ use yii\web\AssetBundle; class EffectAsset extends AssetBundle { public $sourcePath = '@yii/jui/assets'; - public $js = array( + public $js = [ 'jquery.ui.effect-all.js', - ); - public $depends = array( + ]; + public $depends = [ 'yii\web\JqueryAsset', - ); + ]; } diff --git a/extensions/jui/yii/jui/InputWidget.php b/extensions/jui/InputWidget.php similarity index 100% rename from extensions/jui/yii/jui/InputWidget.php rename to extensions/jui/InputWidget.php diff --git a/extensions/jui/LICENSE.md b/extensions/jui/LICENSE.md index 0bb1a8d..e98f03d 100644 --- a/extensions/jui/LICENSE.md +++ b/extensions/jui/LICENSE.md @@ -1,7 +1,7 @@ The Yii framework is free software. It is released under the terms of the following BSD License. -Copyright © 2008-2013 by Yii Software LLC (http://www.yiisoft.com) +Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com) All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/extensions/jui/yii/jui/Menu.php b/extensions/jui/Menu.php similarity index 95% rename from extensions/jui/yii/jui/Menu.php rename to extensions/jui/Menu.php index a2aadd9..46c7ee0 100644 --- a/extensions/jui/yii/jui/Menu.php +++ b/extensions/jui/Menu.php @@ -25,14 +25,14 @@ class Menu extends \yii\widgets\Menu * For example, [this page](http://api.jqueryui.com/accordion/) shows * how to use the "Accordion" widget and the supported options (e.g. "header"). */ - public $clientOptions = array(); + public $clientOptions = []; /** * @var array the event handlers for the underlying jQuery UI widget. * Please refer to the corresponding jQuery UI widget Web page for possible events. * For example, [this page](http://api.jqueryui.com/accordion/) shows * how to use the "Accordion" widget and the supported events (e.g. "create"). */ - public $clientEvents = array(); + public $clientEvents = []; /** @@ -68,7 +68,7 @@ class Menu extends \yii\widgets\Menu } if (!empty($this->clientEvents)) { - $js = array(); + $js = []; foreach ($this->clientEvents as $event => $handler) { $js[] = "jQuery('#$id').on('menu$event', $handler);"; } diff --git a/extensions/jui/yii/jui/MenuAsset.php b/extensions/jui/MenuAsset.php similarity index 81% rename from extensions/jui/yii/jui/MenuAsset.php rename to extensions/jui/MenuAsset.php index 96a11a5..8b840a8 100644 --- a/extensions/jui/yii/jui/MenuAsset.php +++ b/extensions/jui/MenuAsset.php @@ -15,10 +15,10 @@ use yii\web\AssetBundle; class MenuAsset extends AssetBundle { public $sourcePath = '@yii/jui/assets'; - public $js = array( + public $js = [ 'jquery.ui.menu.js', - ); - public $depends = array( + ]; + public $depends = [ 'yii\jui\CoreAsset', - ); + ]; } diff --git a/extensions/jui/yii/jui/ProgressBar.php b/extensions/jui/ProgressBar.php similarity index 85% rename from extensions/jui/yii/jui/ProgressBar.php rename to extensions/jui/ProgressBar.php index b06c06a..1c555f1 100644 --- a/extensions/jui/yii/jui/ProgressBar.php +++ b/extensions/jui/ProgressBar.php @@ -15,22 +15,20 @@ use yii\helpers\Html; * For example: * * ```php - * echo ProgressBar::widget(array( - * 'clientOptions' => array( + * echo ProgressBar::widget([ + * 'clientOptions' => [ * 'value' => 75, - * ), - * )); + * ], + * ]); * ``` * * The following example will show the content enclosed between the [[begin()]] * and [[end()]] calls within the widget container: * * ~~~php - * ProgressBar::widget(array( - * 'clientOptions' => array( - * 'value' => 75, - * ), - * )); + * ProgressBar::widget([ + * 'clientOptions' => ['value' => 75], + * ]); * * echo '<div class="progress-label">Loading...</div>'; * diff --git a/extensions/jui/yii/jui/ProgressBarAsset.php b/extensions/jui/ProgressBarAsset.php similarity index 81% rename from extensions/jui/yii/jui/ProgressBarAsset.php rename to extensions/jui/ProgressBarAsset.php index cc36d03..d485fbd 100644 --- a/extensions/jui/yii/jui/ProgressBarAsset.php +++ b/extensions/jui/ProgressBarAsset.php @@ -15,10 +15,10 @@ use yii\web\AssetBundle; class ProgressBarAsset extends AssetBundle { public $sourcePath = '@yii/jui/assets'; - public $js = array( + public $js = [ 'jquery.ui.progressbar.js', - ); - public $depends = array( + ]; + public $depends = [ 'yii\jui\CoreAsset', - ); + ]; } diff --git a/extensions/jui/README.md b/extensions/jui/README.md index 0403acb..f197c8c 100644 --- a/extensions/jui/README.md +++ b/extensions/jui/README.md @@ -1,55 +1,41 @@ -Yii 2.0 Public Preview - JUI Extension -====================================== +JUI Extension for Yii 2 +======================= -Thank you for choosing Yii - a high-performance component-based PHP framework. +This is the JQuery UI extension for Yii 2. It encapsulates JQuery UI widgets as Yii widgets, +and makes using JQuery UI widgets in Yii applications extremely easy. For example, the following +single line of code in a view file would render a JQuery UI DatePicker widget: -If you are looking for a production-ready PHP framework, please use -[Yii v1.1](https://github.com/yiisoft/yii). - -Yii 2.0 is still under heavy development. We may make significant changes -without prior notices. **Yii 2.0 is not ready for production use yet.** +```php +<?= yii\jui\DatePicker::widget(['name' => 'attributeName']) ?> +``` -[](http://travis-ci.org/yiisoft/yii2) +Configuring the Jquery UI options should be done using the clientOptions attribute: +```php +<?= yii\jui\DatePicker::widget(['name' => 'attributeName', 'clientOptions' => ['dateFormat' => 'yy-mm-dd']]) ?> +``` -This is the yii2-jui extension. +If you want to use the JUI widget in an ActiveRecord form, it can be done like this: +```php +<?= $form->field($model,'attributeName')->widget(DatePicker::className(),['clientOptions' => ['dateFormat' => 'yy-mm-dd']]) ?> +``` Installation ------------ -The preferred way to install this extension is [composer](http://getcomposer.org/download/). +The preferred way to install this extension is through [composer](http://getcomposer.org/download/). Either run + ``` php composer.phar require yiisoft/yii2-jui "*" ``` or add + ``` "yiisoft/yii2-jui": "*" ``` -to the require section of your composer.json. - - -*Note: You might have to run `php composer.phar selfupdate`* - - -Usage & Documentation ---------------------- - -This extension provides multiple widgets to work with jquery.ui, as well as a set of compatible jquery.ui files. - -You can use these widgets in your view files after you have registered the corresponding assets. - -Example: - -```php -echo ProgressBar::widget(array( - 'clientOptions' => array( - 'value' => 75, - ), -)); -``` -For further instructions refer to the yii guide. +to the require section of your `composer.json` file. diff --git a/extensions/jui/yii/jui/Resizable.php b/extensions/jui/Resizable.php similarity index 89% rename from extensions/jui/yii/jui/Resizable.php rename to extensions/jui/Resizable.php index 21ec70c..bcff9a8 100644 --- a/extensions/jui/yii/jui/Resizable.php +++ b/extensions/jui/Resizable.php @@ -15,11 +15,11 @@ use yii\helpers\Html; * For example: * * ```php - * Resizable::begin(array( - * 'clientOptions' => array( - * 'grid' => array(20, 10), - * ), - * )); + * Resizable::begin([ + * 'clientOptions' => [ + * 'grid' => [20, 10], + * ], + * ]); * * echo 'Resizable contents here...'; * diff --git a/extensions/jui/yii/jui/ResizableAsset.php b/extensions/jui/ResizableAsset.php similarity index 81% rename from extensions/jui/yii/jui/ResizableAsset.php rename to extensions/jui/ResizableAsset.php index 35b4849..acf4c73 100644 --- a/extensions/jui/yii/jui/ResizableAsset.php +++ b/extensions/jui/ResizableAsset.php @@ -15,10 +15,10 @@ use yii\web\AssetBundle; class ResizableAsset extends AssetBundle { public $sourcePath = '@yii/jui/assets'; - public $js = array( + public $js = [ 'jquery.ui.resizable.js', - ); - public $depends = array( + ]; + public $depends = [ 'yii\jui\CoreAsset', - ); + ]; } diff --git a/extensions/jui/yii/jui/Selectable.php b/extensions/jui/Selectable.php similarity index 79% rename from extensions/jui/yii/jui/Selectable.php rename to extensions/jui/Selectable.php index b77cfa9..94e4faf 100644 --- a/extensions/jui/yii/jui/Selectable.php +++ b/extensions/jui/Selectable.php @@ -17,29 +17,29 @@ use yii\helpers\Html; * For example: * * ```php - * echo Selectable::widget(array( - * 'items' => array( + * echo Selectable::widget([ + * 'items' => [ * 'Item 1', - * array( + * [ * 'content' => 'Item2', - * ), - * array( + * ], + * [ * 'content' => 'Item3', - * 'options' => array( + * 'options' => [ * 'tag' => 'li', - * ), - * ), + * ], + * ], * ), - * 'options' => array( + * 'options' => [ * 'tag' => 'ul', - * ), - * 'itemOptions' => array( + * ], + * 'itemOptions' => [ * 'tag' => 'li', - * ), - * 'clientOptions' => array( + * ], + * 'clientOptions' => [ * 'tolerance' => 'fit', - * ), - * )); + * ], + * ]); * ``` * * @see http://api.jqueryui.com/selectable/ @@ -53,27 +53,27 @@ class Selectable extends Widget * * - tag: string, defaults to "ul", the tag name of the container tag of this widget */ - public $options = array(); + public $options = []; /** * @var array list of selectable items. Each item can be a string representing the item content * or an array of the following structure: * * ~~~ - * array( + * [ * 'content' => 'item content', * // the HTML attributes of the item container tag. This will overwrite "itemOptions". - * 'options' => array(), - * ) + * 'options' => [], + * ] * ~~~ */ - public $items = array(); + public $items = []; /** * @var array list of HTML attributes for the item container tags. This will be overwritten * by the "options" set in individual [[items]]. The following special options are recognized: * * - tag: string, defaults to "li", the tag name of the item container tags. */ - public $itemOptions = array(); + public $itemOptions = []; /** @@ -96,7 +96,7 @@ class Selectable extends Widget */ public function renderItems() { - $items = array(); + $items = []; foreach ($this->items as $item) { $options = $this->itemOptions; $tag = ArrayHelper::remove($options, 'tag', 'li'); @@ -104,7 +104,7 @@ class Selectable extends Widget if (!isset($item['content'])) { throw new InvalidConfigException("The 'content' option is required."); } - $options = array_merge($options, ArrayHelper::getValue($item, 'options', array())); + $options = array_merge($options, ArrayHelper::getValue($item, 'options', [])); $tag = ArrayHelper::remove($options, 'tag', $tag); $items[] = Html::tag($tag, $item['content'], $options); } else { diff --git a/extensions/jui/yii/jui/SelectableAsset.php b/extensions/jui/SelectableAsset.php similarity index 81% rename from extensions/jui/yii/jui/SelectableAsset.php rename to extensions/jui/SelectableAsset.php index 59cd485..61f405f 100644 --- a/extensions/jui/yii/jui/SelectableAsset.php +++ b/extensions/jui/SelectableAsset.php @@ -15,10 +15,10 @@ use yii\web\AssetBundle; class SelectableAsset extends AssetBundle { public $sourcePath = '@yii/jui/assets'; - public $js = array( + public $js = [ 'jquery.ui.selectable.js', - ); - public $depends = array( + ]; + public $depends = [ 'yii\jui\CoreAsset', - ); + ]; } diff --git a/extensions/jui/Slider.php b/extensions/jui/Slider.php new file mode 100644 index 0000000..f051b9e --- /dev/null +++ b/extensions/jui/Slider.php @@ -0,0 +1,45 @@ +<?php +/** + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\jui; + +use yii\helpers\Html; + +/** + * Slider renders a slider jQuery UI widget. + * + * echo Slider::widget([ + * 'clientOptions' => [ + * 'min' => 1, + * 'max' => 10, + * ], + * ]); + * ``` + * + * @see http://api.jqueryui.com/slider/ + * @author Alexander Makarov <sam@rmcreative.ru> + * @since 2.0 + */ +class Slider extends Widget +{ + protected $clientEventsMap = [ + 'change' => 'slidechange', + 'create' => 'slidecreate', + 'slide' => 'slide', + 'start' => 'slidestart', + 'stop' => 'slidestop', + ]; + + /** + * Executes the widget. + */ + public function run() + { + echo Html::tag('div', '', $this->options); + $this->registerWidget('slider', SliderAsset::className()); + } +} \ No newline at end of file diff --git a/extensions/jui/yii/jui/SliderAsset.php b/extensions/jui/SliderAsset.php similarity index 81% rename from extensions/jui/yii/jui/SliderAsset.php rename to extensions/jui/SliderAsset.php index f0c7dc2..56c2451 100644 --- a/extensions/jui/yii/jui/SliderAsset.php +++ b/extensions/jui/SliderAsset.php @@ -15,10 +15,10 @@ use yii\web\AssetBundle; class SliderAsset extends AssetBundle { public $sourcePath = '@yii/jui/assets'; - public $js = array( + public $js = [ 'jquery.ui.slider.js', - ); - public $depends = array( + ]; + public $depends = [ 'yii\jui\CoreAsset', - ); + ]; } diff --git a/extensions/jui/yii/jui/Slider.php b/extensions/jui/SliderInput.php similarity index 55% rename from extensions/jui/yii/jui/Slider.php rename to extensions/jui/SliderInput.php index e1adb2b..cc77103 100644 --- a/extensions/jui/yii/jui/Slider.php +++ b/extensions/jui/SliderInput.php @@ -7,62 +7,73 @@ namespace yii\jui; -use Yii; use yii\helpers\Html; /** - * Slider renders a slider jQuery UI widget. + * SliderInput renders a slider jQuery UI widget that writes its value into hidden input. * - * For example: + * For example, * - * ```php - * echo Slider::widget(array( + * ``` + * echo Slider::widget([ * 'model' => $model, * 'attribute' => 'amount', - * 'clientOptions' => array( + * 'clientOptions' => [ * 'min' => 1, * 'max' => 10, - * ), - * )); + * ], + * ]); * ``` * * The following example will use the name property instead: * - * ```php - * echo Slider::widget(array( - * 'name' => 'amount', - * 'clientOptions' => array( + * ``` + * echo Slider::widget([ + * 'name' => 'amount', + * 'clientOptions' => [ * 'min' => 1, * 'max' => 10, - * ), - * )); - *``` + * ], + * ]); + * ``` * * @see http://api.jqueryui.com/slider/ * @author Alexander Makarov <sam@rmcreative.ru> * @since 2.0 */ -class Slider extends InputWidget +class SliderInput extends InputWidget { + protected $clientEventsMap = [ + 'change' => 'slidechange', + 'create' => 'slidecreate', + 'slide' => 'slide', + 'start' => 'slidestart', + 'stop' => 'slidestop', + ]; + /** - * Renders the widget. + * Executes the widget. */ public function run() { - echo $this->renderWidget(); - $this->registerWidget('slider', SliderAsset::className()); - } + echo Html::tag('div', '', $this->options); - /** - * Renders the Slider widget. - * @return string the rendering result. - */ - public function renderWidget() - { + $inputId = $this->id.'-input'; + $inputOptions = $this->options; + $inputOptions['id'] = $inputId; if ($this->hasModel()) { - return Html::activeTextInput($this->model, $this->attribute, $this->options); + echo Html::activeHiddenInput($this->model, $this->attribute, $inputOptions); } else { - return Html::textInput($this->name, $this->value, $this->options); + echo Html::hiddenInput($this->name, $this->value, $inputOptions); + } + + if (!isset($this->clientEvents['slide'])) { + $this->clientEvents['slide'] = 'function(event, ui) { + $("#'.$inputId.'").val(ui.value); + }'; } + + $this->registerWidget('slider', SliderAsset::className()); + $this->getView()->registerJs('$("#'.$inputId.'").val($("#'.$this->id.'").slider("value"));'); } -} +} \ No newline at end of file diff --git a/extensions/jui/yii/jui/Sortable.php b/extensions/jui/Sortable.php similarity index 78% rename from extensions/jui/yii/jui/Sortable.php rename to extensions/jui/Sortable.php index 6c0d48a..6209cb6 100644 --- a/extensions/jui/yii/jui/Sortable.php +++ b/extensions/jui/Sortable.php @@ -17,28 +17,18 @@ use yii\helpers\Html; * For example: * * ```php - * echo Sortable::widget(array( - * 'items' => array( + * echo Sortable::widget([ + * 'items' => [ * 'Item 1', - * array( - * 'content' => 'Item2', - * ), - * array( + * ['content' => 'Item2'], + * [ * 'content' => 'Item3', - * 'options' => array( - * 'tag' => 'li', - * ), - * ), - * ), - * 'options' => array( - * 'tag' => 'ul', - * ), - * 'itemOptions' => array( - * 'tag' => 'li', - * ), - * 'clientOptions' => array( - * 'cursor' => 'move', - * ), + * 'options' => ['tag' => 'li'], + * ], + * ], + * 'options' => ['tag' => 'ul'], + * 'itemOptions' => ['tag' => 'li'], + * 'clientOptions' => ['cursor' => 'move'], * )); * ``` * @@ -53,27 +43,27 @@ class Sortable extends Widget * * - tag: string, defaults to "ul", the tag name of the container tag of this widget */ - public $options = array(); + public $options = []; /** * @var array list of sortable items. Each item can be a string representing the item content * or an array of the following structure: * * ~~~ - * array( + * [ * 'content' => 'item content', * // the HTML attributes of the item container tag. This will overwrite "itemOptions". - * 'options' => array(), - * ) + * 'options' => [], + * ] * ~~~ */ - public $items = array(); + public $items = []; /** * @var array list of HTML attributes for the item container tags. This will be overwritten * by the "options" set in individual [[items]]. The following special options are recognized: * * - tag: string, defaults to "li", the tag name of the item container tags. */ - public $itemOptions = array(); + public $itemOptions = []; /** @@ -96,7 +86,7 @@ class Sortable extends Widget */ public function renderItems() { - $items = array(); + $items = []; foreach ($this->items as $item) { $options = $this->itemOptions; $tag = ArrayHelper::remove($options, 'tag', 'li'); @@ -104,7 +94,7 @@ class Sortable extends Widget if (!isset($item['content'])) { throw new InvalidConfigException("The 'content' option is required."); } - $options = array_merge($options, ArrayHelper::getValue($item, 'options', array())); + $options = array_merge($options, ArrayHelper::getValue($item, 'options', [])); $tag = ArrayHelper::remove($options, 'tag', $tag); $items[] = Html::tag($tag, $item['content'], $options); } else { diff --git a/extensions/jui/yii/jui/SortableAsset.php b/extensions/jui/SortableAsset.php similarity index 81% rename from extensions/jui/yii/jui/SortableAsset.php rename to extensions/jui/SortableAsset.php index 9a19e64..69c9ba3 100644 --- a/extensions/jui/yii/jui/SortableAsset.php +++ b/extensions/jui/SortableAsset.php @@ -15,10 +15,10 @@ use yii\web\AssetBundle; class SortableAsset extends AssetBundle { public $sourcePath = '@yii/jui/assets'; - public $js = array( + public $js = [ 'jquery.ui.sortable.js', - ); - public $depends = array( + ]; + public $depends = [ 'yii\jui\CoreAsset', - ); + ]; } diff --git a/extensions/jui/yii/jui/Spinner.php b/extensions/jui/Spinner.php similarity index 86% rename from extensions/jui/yii/jui/Spinner.php rename to extensions/jui/Spinner.php index 29e947e..caf73f3 100644 --- a/extensions/jui/yii/jui/Spinner.php +++ b/extensions/jui/Spinner.php @@ -16,24 +16,20 @@ use yii\helpers\Html; * For example: * * ```php - * echo Spinner::widget(array( + * echo Spinner::widget([ * 'model' => $model, * 'attribute' => 'country', - * 'clientOptions' => array( - * 'step' => 2, - * ), - * )); + * 'clientOptions' => ['step' => 2], + * ]); * ``` * * The following example will use the name property instead: * * ```php - * echo Spinner::widget(array( + * echo Spinner::widget([ * 'name' => 'country', - * 'clientOptions' => array( - * 'step' => 2, - * ), - * )); + * 'clientOptions' => ['step' => 2], + * ]); *``` * * @see http://api.jqueryui.com/spinner/ diff --git a/extensions/jui/yii/jui/SpinnerAsset.php b/extensions/jui/SpinnerAsset.php similarity index 82% rename from extensions/jui/yii/jui/SpinnerAsset.php rename to extensions/jui/SpinnerAsset.php index 2eb67f8..89a8c59 100644 --- a/extensions/jui/yii/jui/SpinnerAsset.php +++ b/extensions/jui/SpinnerAsset.php @@ -15,11 +15,11 @@ use yii\web\AssetBundle; class SpinnerAsset extends AssetBundle { public $sourcePath = '@yii/jui/assets'; - public $js = array( + public $js = [ 'jquery.ui.spinner.js', - ); - public $depends = array( + ]; + public $depends = [ 'yii\jui\CoreAsset', 'yii\jui\ButtonAsset', - ); + ]; } diff --git a/extensions/jui/yii/jui/Tabs.php b/extensions/jui/Tabs.php similarity index 70% rename from extensions/jui/yii/jui/Tabs.php rename to extensions/jui/Tabs.php index 7ddf7bb..9d2a2be 100644 --- a/extensions/jui/yii/jui/Tabs.php +++ b/extensions/jui/Tabs.php @@ -17,47 +17,33 @@ use yii\helpers\Html; * For example: * * ```php - * echo Tabs::widget(array( - * 'items' => array( - * array( + * echo Tabs::widget([ + * 'items' => [ + * [ * 'label' => 'Tab one', * 'content' => 'Mauris mauris ante, blandit et, ultrices a, suscipit eget...', - * ), - * array( + * ], + * [ * 'label' => 'Tab two', * 'content' => 'Sed non urna. Phasellus eu ligula. Vestibulum sit amet purus...', - * 'options' => array( - * 'tag' => 'div', - * ), - * 'headerOptions' => array( - * 'class' => 'my-class', - * ), - * ), - * array( + * 'options' => ['tag' => 'div'], + * 'headerOptions' => ['class' => 'my-class'], + * ], + * [ * 'label' => 'Tab with custom id', * 'content' => 'Morbi tincidunt, dui sit amet facilisis feugiat...', - * 'options' => array( - * 'id' => 'my-tab', - * ), - * ), - * array( + * 'options' => ['id' => 'my-tab'], + * ], + * [ * 'label' => 'Ajax tab', - * 'url' => array('ajax/content'), - * ), + * 'url' => ['ajax/content'], + * ], * ), - * 'options' => array( - * 'tag' => 'div', - * ), - * 'itemOptions' => array( - * 'tag' => 'div', - * ), - * 'headerOptions' => array( - * 'class' => 'my-class', - * ), - * 'clientOptions' => array( - * 'collapsible' => false, - * ), - * )); + * 'options' => ['tag' => 'div'], + * 'itemOptions' => ['tag' => 'div'], + * 'headerOptions' => ['class' => 'my-class'], + * 'clientOptions' => ['collapsible' => false], + * ]); * ``` * * @see http://api.jqueryui.com/tabs/ @@ -71,7 +57,7 @@ class Tabs extends Widget * * - tag: string, defaults to "div", the tag name of the container tag of this widget */ - public $options = array(); + public $options = []; /** * @var array list of tab items. Each item can be an array of the following structure: * @@ -84,19 +70,19 @@ class Tabs extends Widget * - options: array, optional, the HTML attributes of the header. * - headerOptions: array, optional, the HTML attributes for the header container tag. */ - public $items = array(); + public $items = []; /** * @var array list of HTML attributes for the item container tags. This will be overwritten * by the "options" set in individual [[items]]. The following special options are recognized: * * - tag: string, defaults to "div", the tag name of the item container tags. */ - public $itemOptions = array(); + public $itemOptions = []; /** * @var array list of HTML attributes for the header container tags. This will be overwritten * by the "headerOptions" set in individual [[items]]. */ - public $headerOptions = array(); + public $headerOptions = []; /** * @var string the default header template to render the link. */ @@ -127,8 +113,8 @@ class Tabs extends Widget */ protected function renderItems() { - $headers = array(); - $items = array(); + $headers = []; + $items = []; foreach ($this->items as $n => $item) { if (!isset($item['label'])) { throw new InvalidConfigException("The 'label' option is required."); @@ -139,7 +125,7 @@ class Tabs extends Widget if (!isset($item['content'])) { throw new InvalidConfigException("The 'content' or 'url' option is required."); } - $options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', array())); + $options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', [])); $tag = ArrayHelper::remove($options, 'tag', 'div'); if (!isset($options['id'])) { $options['id'] = $this->options['id'] . '-tab' . $n; @@ -147,12 +133,12 @@ class Tabs extends Widget $url = '#' . $options['id']; $items[] = Html::tag($tag, $item['content'], $options); } - $headerOptions = array_merge($this->headerOptions, ArrayHelper::getValue($item, 'headerOptions', array())); + $headerOptions = array_merge($this->headerOptions, ArrayHelper::getValue($item, 'headerOptions', [])); $template = ArrayHelper::getValue($item, 'template', $this->linkTemplate); - $headers[] = Html::tag('li', strtr($template, array( + $headers[] = Html::tag('li', strtr($template, [ '{label}' => $this->encodeLabels ? Html::encode($item['label']) : $item['label'], '{url}' => $url, - )), $headerOptions); + ]), $headerOptions); } return Html::tag('ul', implode("\n", $headers)) . "\n" . implode("\n", $items); } diff --git a/extensions/jui/yii/jui/TabsAsset.php b/extensions/jui/TabsAsset.php similarity index 82% rename from extensions/jui/yii/jui/TabsAsset.php rename to extensions/jui/TabsAsset.php index 2cdf477..5bef4c0 100644 --- a/extensions/jui/yii/jui/TabsAsset.php +++ b/extensions/jui/TabsAsset.php @@ -15,11 +15,11 @@ use yii\web\AssetBundle; class TabsAsset extends AssetBundle { public $sourcePath = '@yii/jui/assets'; - public $js = array( + public $js = [ 'jquery.ui.tabs.js', - ); - public $depends = array( + ]; + public $depends = [ 'yii\jui\CoreAsset', 'yii\jui\EffectAsset', - ); + ]; } diff --git a/extensions/jui/yii/jui/ThemeAsset.php b/extensions/jui/ThemeAsset.php similarity index 89% rename from extensions/jui/yii/jui/ThemeAsset.php rename to extensions/jui/ThemeAsset.php index 2ca0631..dedcb00 100644 --- a/extensions/jui/yii/jui/ThemeAsset.php +++ b/extensions/jui/ThemeAsset.php @@ -15,7 +15,7 @@ use yii\web\AssetBundle; class ThemeAsset extends AssetBundle { public $sourcePath = '@yii/jui/assets'; - public $css = array( + public $css = [ 'theme/jquery.ui.css', - ); + ]; } diff --git a/extensions/jui/yii/jui/TooltipAsset.php b/extensions/jui/TooltipAsset.php similarity index 82% rename from extensions/jui/yii/jui/TooltipAsset.php rename to extensions/jui/TooltipAsset.php index 9d6f781..1fa4490 100644 --- a/extensions/jui/yii/jui/TooltipAsset.php +++ b/extensions/jui/TooltipAsset.php @@ -15,11 +15,11 @@ use yii\web\AssetBundle; class TooltipAsset extends AssetBundle { public $sourcePath = '@yii/jui/assets'; - public $js = array( + public $js = [ 'jquery.ui.tooltip.js', - ); - public $depends = array( + ]; + public $depends = [ 'yii\jui\CoreAsset', 'yii\jui\EffectAsset', - ); + ]; } diff --git a/extensions/jui/yii/jui/Widget.php b/extensions/jui/Widget.php similarity index 70% rename from extensions/jui/yii/jui/Widget.php rename to extensions/jui/Widget.php index 815bbe9..ced00fb 100644 --- a/extensions/jui/yii/jui/Widget.php +++ b/extensions/jui/Widget.php @@ -26,22 +26,27 @@ class Widget extends \yii\base\Widget /** * @var array the HTML attributes for the widget container tag. */ - public $options = array(); + public $options = []; /** * @var array the options for the underlying jQuery UI widget. * Please refer to the corresponding jQuery UI widget Web page for possible options. * For example, [this page](http://api.jqueryui.com/accordion/) shows * how to use the "Accordion" widget and the supported options (e.g. "header"). */ - public $clientOptions = array(); + public $clientOptions = []; /** * @var array the event handlers for the underlying jQuery UI widget. * Please refer to the corresponding jQuery UI widget Web page for possible events. * For example, [this page](http://api.jqueryui.com/accordion/) shows * how to use the "Accordion" widget and the supported events (e.g. "create"). */ - public $clientEvents = array(); + public $clientEvents = []; + /** + * @var array event names mapped to what should be specified in .on( + * If empty, it is assumed that event passed to clientEvents is prefixed with widget name. + */ + protected $clientEventsMap = []; /** * Initializes the widget. @@ -56,32 +61,62 @@ class Widget extends \yii\base\Widget } /** - * Registers a specific jQuery UI widget and the related events - * @param string $name the name of the jQuery UI widget + * Registers a specific jQuery UI widget assets * @param string $assetBundle the asset bundle for the widget */ - protected function registerWidget($name, $assetBundle) + protected function registerAssets($assetBundle) { - $view = $this->getView(); /** @var \yii\web\AssetBundle $assetBundle */ - $assetBundle::register($view); + $assetBundle::register($this->getView()); /** @var \yii\web\AssetBundle $themeAsset */ - $themeAsset = self::$theme; - $themeAsset::register($view); + $themeAsset = static::$theme; + $themeAsset::register($this->getView()); + } - $id = $this->options['id']; + /** + * Registers a specific jQuery UI widget options + * @param string $name the name of the jQuery UI widget + */ + protected function registerClientOptions($name) + { if ($this->clientOptions !== false) { + $id = $this->options['id']; $options = empty($this->clientOptions) ? '' : Json::encode($this->clientOptions); $js = "jQuery('#$id').$name($options);"; - $view->registerJs($js); + $this->getView()->registerJs($js); } + } + /** + * Registers a specific jQuery UI widget events + * @param string $name the name of the jQuery UI widget + */ + protected function registerClientEvents($name) + { if (!empty($this->clientEvents)) { - $js = array(); + $id = $this->options['id']; + $js = []; foreach ($this->clientEvents as $event => $handler) { - $js[] = "jQuery('#$id').on('$name$event', $handler);"; + if (isset($this->clientEventsMap[$event])) { + $eventName = $this->clientEventsMap[$event]; + } else { + $eventName = $name.$event; + } + $js[] = "jQuery('#$id').on('$eventName', $handler);"; } - $view->registerJs(implode("\n", $js)); + $this->getView()->registerJs(implode("\n", $js)); } } + + /** + * Registers a specific jQuery UI widget asset bundle, initializes it with client options and registers related events + * @param string $name the name of the jQuery UI widget + * @param string $assetBundle the asset bundle for the widget + */ + protected function registerWidget($name, $assetBundle) + { + $this->registerAssets($assetBundle); + $this->registerClientOptions($name); + $this->registerClientEvents($name); + } } diff --git a/extensions/jui/yii/jui/assets.php b/extensions/jui/assets.php similarity index 90% rename from extensions/jui/yii/jui/assets.php rename to extensions/jui/assets.php index e6c5151..ab4c930 100644 --- a/extensions/jui/yii/jui/assets.php +++ b/extensions/jui/assets.php @@ -1,6 +1,6 @@ <?php -return array( +return [ yii\jui\CoreAsset::className(), yii\jui\EffectAsset::className(), yii\jui\AccordionAsset::className(), @@ -20,4 +20,4 @@ return array( yii\jui\DraggableAsset::className(), yii\jui\DroppableAsset::className(), yii\jui\MenuAsset::className(), -); +]; diff --git a/extensions/jui/yii/jui/assets/UPGRADE.md b/extensions/jui/assets/UPGRADE.md similarity index 100% rename from extensions/jui/yii/jui/assets/UPGRADE.md rename to extensions/jui/assets/UPGRADE.md diff --git a/extensions/jui/yii/jui/assets/jquery.ui.accordion.js b/extensions/jui/assets/jquery.ui.accordion.js similarity index 100% rename from extensions/jui/yii/jui/assets/jquery.ui.accordion.js rename to extensions/jui/assets/jquery.ui.accordion.js diff --git a/extensions/jui/yii/jui/assets/jquery.ui.autocomplete.js b/extensions/jui/assets/jquery.ui.autocomplete.js similarity index 100% rename from extensions/jui/yii/jui/assets/jquery.ui.autocomplete.js rename to extensions/jui/assets/jquery.ui.autocomplete.js diff --git a/extensions/jui/yii/jui/assets/jquery.ui.button.js b/extensions/jui/assets/jquery.ui.button.js similarity index 100% rename from extensions/jui/yii/jui/assets/jquery.ui.button.js rename to extensions/jui/assets/jquery.ui.button.js diff --git a/extensions/jui/yii/jui/assets/jquery.ui.core.js b/extensions/jui/assets/jquery.ui.core.js similarity index 100% rename from extensions/jui/yii/jui/assets/jquery.ui.core.js rename to extensions/jui/assets/jquery.ui.core.js diff --git a/extensions/jui/yii/jui/assets/jquery.ui.datepicker-i18n.js b/extensions/jui/assets/jquery.ui.datepicker-i18n.js similarity index 100% rename from extensions/jui/yii/jui/assets/jquery.ui.datepicker-i18n.js rename to extensions/jui/assets/jquery.ui.datepicker-i18n.js diff --git a/extensions/jui/yii/jui/assets/jquery.ui.datepicker.js b/extensions/jui/assets/jquery.ui.datepicker.js similarity index 100% rename from extensions/jui/yii/jui/assets/jquery.ui.datepicker.js rename to extensions/jui/assets/jquery.ui.datepicker.js diff --git a/extensions/jui/yii/jui/assets/jquery.ui.dialog.js b/extensions/jui/assets/jquery.ui.dialog.js similarity index 100% rename from extensions/jui/yii/jui/assets/jquery.ui.dialog.js rename to extensions/jui/assets/jquery.ui.dialog.js diff --git a/extensions/jui/yii/jui/assets/jquery.ui.draggable.js b/extensions/jui/assets/jquery.ui.draggable.js similarity index 100% rename from extensions/jui/yii/jui/assets/jquery.ui.draggable.js rename to extensions/jui/assets/jquery.ui.draggable.js diff --git a/extensions/jui/yii/jui/assets/jquery.ui.droppable.js b/extensions/jui/assets/jquery.ui.droppable.js similarity index 100% rename from extensions/jui/yii/jui/assets/jquery.ui.droppable.js rename to extensions/jui/assets/jquery.ui.droppable.js diff --git a/extensions/jui/yii/jui/assets/jquery.ui.effect-all.js b/extensions/jui/assets/jquery.ui.effect-all.js similarity index 100% rename from extensions/jui/yii/jui/assets/jquery.ui.effect-all.js rename to extensions/jui/assets/jquery.ui.effect-all.js diff --git a/extensions/jui/yii/jui/assets/jquery.ui.menu.js b/extensions/jui/assets/jquery.ui.menu.js similarity index 100% rename from extensions/jui/yii/jui/assets/jquery.ui.menu.js rename to extensions/jui/assets/jquery.ui.menu.js diff --git a/extensions/jui/yii/jui/assets/jquery.ui.mouse.js b/extensions/jui/assets/jquery.ui.mouse.js similarity index 100% rename from extensions/jui/yii/jui/assets/jquery.ui.mouse.js rename to extensions/jui/assets/jquery.ui.mouse.js diff --git a/extensions/jui/yii/jui/assets/jquery.ui.position.js b/extensions/jui/assets/jquery.ui.position.js similarity index 100% rename from extensions/jui/yii/jui/assets/jquery.ui.position.js rename to extensions/jui/assets/jquery.ui.position.js diff --git a/extensions/jui/yii/jui/assets/jquery.ui.progressbar.js b/extensions/jui/assets/jquery.ui.progressbar.js similarity index 100% rename from extensions/jui/yii/jui/assets/jquery.ui.progressbar.js rename to extensions/jui/assets/jquery.ui.progressbar.js diff --git a/extensions/jui/yii/jui/assets/jquery.ui.resizable.js b/extensions/jui/assets/jquery.ui.resizable.js similarity index 100% rename from extensions/jui/yii/jui/assets/jquery.ui.resizable.js rename to extensions/jui/assets/jquery.ui.resizable.js diff --git a/extensions/jui/yii/jui/assets/jquery.ui.selectable.js b/extensions/jui/assets/jquery.ui.selectable.js similarity index 100% rename from extensions/jui/yii/jui/assets/jquery.ui.selectable.js rename to extensions/jui/assets/jquery.ui.selectable.js diff --git a/extensions/jui/yii/jui/assets/jquery.ui.slider.js b/extensions/jui/assets/jquery.ui.slider.js similarity index 100% rename from extensions/jui/yii/jui/assets/jquery.ui.slider.js rename to extensions/jui/assets/jquery.ui.slider.js diff --git a/extensions/jui/yii/jui/assets/jquery.ui.sortable.js b/extensions/jui/assets/jquery.ui.sortable.js similarity index 100% rename from extensions/jui/yii/jui/assets/jquery.ui.sortable.js rename to extensions/jui/assets/jquery.ui.sortable.js diff --git a/extensions/jui/yii/jui/assets/jquery.ui.spinner.js b/extensions/jui/assets/jquery.ui.spinner.js similarity index 100% rename from extensions/jui/yii/jui/assets/jquery.ui.spinner.js rename to extensions/jui/assets/jquery.ui.spinner.js diff --git a/extensions/jui/yii/jui/assets/jquery.ui.tabs.js b/extensions/jui/assets/jquery.ui.tabs.js similarity index 100% rename from extensions/jui/yii/jui/assets/jquery.ui.tabs.js rename to extensions/jui/assets/jquery.ui.tabs.js diff --git a/extensions/jui/yii/jui/assets/jquery.ui.tooltip.js b/extensions/jui/assets/jquery.ui.tooltip.js similarity index 100% rename from extensions/jui/yii/jui/assets/jquery.ui.tooltip.js rename to extensions/jui/assets/jquery.ui.tooltip.js diff --git a/extensions/jui/yii/jui/assets/jquery.ui.widget.js b/extensions/jui/assets/jquery.ui.widget.js similarity index 100% rename from extensions/jui/yii/jui/assets/jquery.ui.widget.js rename to extensions/jui/assets/jquery.ui.widget.js diff --git a/extensions/jui/yii/jui/assets/theme/images/animated-overlay.gif b/extensions/jui/assets/theme/images/animated-overlay.gif similarity index 100% rename from extensions/jui/yii/jui/assets/theme/images/animated-overlay.gif rename to extensions/jui/assets/theme/images/animated-overlay.gif Binary files a/extensions/jui/yii/jui/assets/theme/images/animated-overlay.gif and b/extensions/jui/assets/theme/images/animated-overlay.gif differ diff --git a/extensions/jui/yii/jui/assets/theme/images/ui-bg_flat_0_aaaaaa_40x100.png b/extensions/jui/assets/theme/images/ui-bg_flat_0_aaaaaa_40x100.png similarity index 100% rename from extensions/jui/yii/jui/assets/theme/images/ui-bg_flat_0_aaaaaa_40x100.png rename to extensions/jui/assets/theme/images/ui-bg_flat_0_aaaaaa_40x100.png Binary files a/extensions/jui/yii/jui/assets/theme/images/ui-bg_flat_0_aaaaaa_40x100.png and b/extensions/jui/assets/theme/images/ui-bg_flat_0_aaaaaa_40x100.png differ diff --git a/extensions/jui/yii/jui/assets/theme/images/ui-bg_flat_75_ffffff_40x100.png b/extensions/jui/assets/theme/images/ui-bg_flat_75_ffffff_40x100.png similarity index 100% rename from extensions/jui/yii/jui/assets/theme/images/ui-bg_flat_75_ffffff_40x100.png rename to extensions/jui/assets/theme/images/ui-bg_flat_75_ffffff_40x100.png Binary files a/extensions/jui/yii/jui/assets/theme/images/ui-bg_flat_75_ffffff_40x100.png and b/extensions/jui/assets/theme/images/ui-bg_flat_75_ffffff_40x100.png differ diff --git a/extensions/jui/yii/jui/assets/theme/images/ui-bg_glass_55_fbf9ee_1x400.png b/extensions/jui/assets/theme/images/ui-bg_glass_55_fbf9ee_1x400.png similarity index 100% rename from extensions/jui/yii/jui/assets/theme/images/ui-bg_glass_55_fbf9ee_1x400.png rename to extensions/jui/assets/theme/images/ui-bg_glass_55_fbf9ee_1x400.png Binary files a/extensions/jui/yii/jui/assets/theme/images/ui-bg_glass_55_fbf9ee_1x400.png and b/extensions/jui/assets/theme/images/ui-bg_glass_55_fbf9ee_1x400.png differ diff --git a/extensions/jui/yii/jui/assets/theme/images/ui-bg_glass_65_ffffff_1x400.png b/extensions/jui/assets/theme/images/ui-bg_glass_65_ffffff_1x400.png similarity index 100% rename from extensions/jui/yii/jui/assets/theme/images/ui-bg_glass_65_ffffff_1x400.png rename to extensions/jui/assets/theme/images/ui-bg_glass_65_ffffff_1x400.png Binary files a/extensions/jui/yii/jui/assets/theme/images/ui-bg_glass_65_ffffff_1x400.png and b/extensions/jui/assets/theme/images/ui-bg_glass_65_ffffff_1x400.png differ diff --git a/extensions/jui/yii/jui/assets/theme/images/ui-bg_glass_75_dadada_1x400.png b/extensions/jui/assets/theme/images/ui-bg_glass_75_dadada_1x400.png similarity index 100% rename from extensions/jui/yii/jui/assets/theme/images/ui-bg_glass_75_dadada_1x400.png rename to extensions/jui/assets/theme/images/ui-bg_glass_75_dadada_1x400.png Binary files a/extensions/jui/yii/jui/assets/theme/images/ui-bg_glass_75_dadada_1x400.png and b/extensions/jui/assets/theme/images/ui-bg_glass_75_dadada_1x400.png differ diff --git a/extensions/jui/yii/jui/assets/theme/images/ui-bg_glass_75_e6e6e6_1x400.png b/extensions/jui/assets/theme/images/ui-bg_glass_75_e6e6e6_1x400.png similarity index 100% rename from extensions/jui/yii/jui/assets/theme/images/ui-bg_glass_75_e6e6e6_1x400.png rename to extensions/jui/assets/theme/images/ui-bg_glass_75_e6e6e6_1x400.png Binary files a/extensions/jui/yii/jui/assets/theme/images/ui-bg_glass_75_e6e6e6_1x400.png and b/extensions/jui/assets/theme/images/ui-bg_glass_75_e6e6e6_1x400.png differ diff --git a/extensions/jui/yii/jui/assets/theme/images/ui-bg_glass_95_fef1ec_1x400.png b/extensions/jui/assets/theme/images/ui-bg_glass_95_fef1ec_1x400.png similarity index 100% rename from extensions/jui/yii/jui/assets/theme/images/ui-bg_glass_95_fef1ec_1x400.png rename to extensions/jui/assets/theme/images/ui-bg_glass_95_fef1ec_1x400.png Binary files a/extensions/jui/yii/jui/assets/theme/images/ui-bg_glass_95_fef1ec_1x400.png and b/extensions/jui/assets/theme/images/ui-bg_glass_95_fef1ec_1x400.png differ diff --git a/extensions/jui/yii/jui/assets/theme/images/ui-bg_highlight-soft_75_cccccc_1x100.png b/extensions/jui/assets/theme/images/ui-bg_highlight-soft_75_cccccc_1x100.png similarity index 100% rename from extensions/jui/yii/jui/assets/theme/images/ui-bg_highlight-soft_75_cccccc_1x100.png rename to extensions/jui/assets/theme/images/ui-bg_highlight-soft_75_cccccc_1x100.png Binary files a/extensions/jui/yii/jui/assets/theme/images/ui-bg_highlight-soft_75_cccccc_1x100.png and b/extensions/jui/assets/theme/images/ui-bg_highlight-soft_75_cccccc_1x100.png differ diff --git a/extensions/jui/yii/jui/assets/theme/images/ui-icons_222222_256x240.png b/extensions/jui/assets/theme/images/ui-icons_222222_256x240.png similarity index 100% rename from extensions/jui/yii/jui/assets/theme/images/ui-icons_222222_256x240.png rename to extensions/jui/assets/theme/images/ui-icons_222222_256x240.png Binary files a/extensions/jui/yii/jui/assets/theme/images/ui-icons_222222_256x240.png and b/extensions/jui/assets/theme/images/ui-icons_222222_256x240.png differ diff --git a/extensions/jui/yii/jui/assets/theme/images/ui-icons_2e83ff_256x240.png b/extensions/jui/assets/theme/images/ui-icons_2e83ff_256x240.png similarity index 100% rename from extensions/jui/yii/jui/assets/theme/images/ui-icons_2e83ff_256x240.png rename to extensions/jui/assets/theme/images/ui-icons_2e83ff_256x240.png Binary files a/extensions/jui/yii/jui/assets/theme/images/ui-icons_2e83ff_256x240.png and b/extensions/jui/assets/theme/images/ui-icons_2e83ff_256x240.png differ diff --git a/extensions/jui/yii/jui/assets/theme/images/ui-icons_454545_256x240.png b/extensions/jui/assets/theme/images/ui-icons_454545_256x240.png similarity index 100% rename from extensions/jui/yii/jui/assets/theme/images/ui-icons_454545_256x240.png rename to extensions/jui/assets/theme/images/ui-icons_454545_256x240.png Binary files a/extensions/jui/yii/jui/assets/theme/images/ui-icons_454545_256x240.png and b/extensions/jui/assets/theme/images/ui-icons_454545_256x240.png differ diff --git a/extensions/jui/yii/jui/assets/theme/images/ui-icons_888888_256x240.png b/extensions/jui/assets/theme/images/ui-icons_888888_256x240.png similarity index 100% rename from extensions/jui/yii/jui/assets/theme/images/ui-icons_888888_256x240.png rename to extensions/jui/assets/theme/images/ui-icons_888888_256x240.png Binary files a/extensions/jui/yii/jui/assets/theme/images/ui-icons_888888_256x240.png and b/extensions/jui/assets/theme/images/ui-icons_888888_256x240.png differ diff --git a/extensions/jui/yii/jui/assets/theme/images/ui-icons_cd0a0a_256x240.png b/extensions/jui/assets/theme/images/ui-icons_cd0a0a_256x240.png similarity index 100% rename from extensions/jui/yii/jui/assets/theme/images/ui-icons_cd0a0a_256x240.png rename to extensions/jui/assets/theme/images/ui-icons_cd0a0a_256x240.png Binary files a/extensions/jui/yii/jui/assets/theme/images/ui-icons_cd0a0a_256x240.png and b/extensions/jui/assets/theme/images/ui-icons_cd0a0a_256x240.png differ diff --git a/extensions/jui/yii/jui/assets/theme/jquery.ui.css b/extensions/jui/assets/theme/jquery.ui.css similarity index 100% rename from extensions/jui/yii/jui/assets/theme/jquery.ui.css rename to extensions/jui/assets/theme/jquery.ui.css diff --git a/extensions/jui/composer.json b/extensions/jui/composer.json index f5e259f..ff54422 100644 --- a/extensions/jui/composer.json +++ b/extensions/jui/composer.json @@ -2,7 +2,7 @@ "name": "yiisoft/yii2-jui", "description": "The Jquery UI extension for the Yii framework", "keywords": ["yii", "Jquery UI", "renderer"], - "type": "library", + "type": "yii2-extension", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/yiisoft/yii2/issues?state=open", @@ -11,11 +11,17 @@ "irc": "irc://irc.freenode.net/yii", "source": "https://github.com/yiisoft/yii2" }, - "minimum-stability": "dev", + "authors": [ + { + "name": "Qiang Xue", + "email": "qiang.xue@gmail.com" + } + ], "require": { "yiisoft/yii2": "*" }, "autoload": { - "psr-0": { "yii\\jui": "" } - } + "psr-0": { "yii\\jui\\": "" } + }, + "target-dir": "yii/jui" } diff --git a/extensions/mutex/README.md b/extensions/mutex/README.md deleted file mode 100644 index 161ee8a..0000000 --- a/extensions/mutex/README.md +++ /dev/null @@ -1,42 +0,0 @@ -Yii 2.0 Public Preview - Mutex Extension -======================================== - -Thank you for choosing Yii - a high-performance component-based PHP framework. - -If you are looking for a production-ready PHP framework, please use -[Yii v1.1](https://github.com/yiisoft/yii). - -Yii 2.0 is still under heavy development. We may make significant changes -without prior notices. **Yii 2.0 is not ready for production use yet.** - -[](http://travis-ci.org/yiisoft/yii2) - -This is the yii2-mutex extension. - - -Installation ------------- - -The prefered way to install this extension is through [composer](http://getcomposer.org/download/). - -Either run -``` -php composer.phar require yiisoft/yii2-mutex "*" -``` - -or add -``` -"yiisoft/yii2-mutex": "*" -``` -to the require section of your composer.json. - - -*Note: You might have to run `php composer.phar selfupdate`* - - -Usage & Documentation ---------------------- - -This component can be used to perform actions similar to the concept of [mutual exclusion](http://en.wikipedia.org/wiki/Mutual_exclusion). - -For concrete examples and advanced usage refer to the yii guide. diff --git a/extensions/smarty/LICENSE.md b/extensions/smarty/LICENSE.md index 0bb1a8d..e98f03d 100644 --- a/extensions/smarty/LICENSE.md +++ b/extensions/smarty/LICENSE.md @@ -1,7 +1,7 @@ The Yii framework is free software. It is released under the terms of the following BSD License. -Copyright © 2008-2013 by Yii Software LLC (http://www.yiisoft.com) +Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com) All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/extensions/smarty/README.md b/extensions/smarty/README.md index adf764f..06617f0 100644 --- a/extensions/smarty/README.md +++ b/extensions/smarty/README.md @@ -1,64 +1,41 @@ -Yii 2.0 Public Preview - Smarty View Renderer -============================================= +Smarty Extension for Yii 2 +========================== -Thank you for choosing Yii - a high-performance component-based PHP framework. +This extension provides a `ViewRender` that would allow you to use Smarty view template engine. -If you are looking for a production-ready PHP framework, please use -[Yii v1.1](https://github.com/yiisoft/yii). - -Yii 2.0 is still under heavy development. We may make significant changes -without prior notices. **Yii 2.0 is not ready for production use yet.** - -[](http://travis-ci.org/yiisoft/yii2) - -This is the yii2-smarty extension. +To use this extension, simply add the following code in your application configuration: +```php +return [ + //.... + 'components' => [ + 'view' => [ + 'renderers' => [ + 'tpl' => [ + 'class' => 'yii\smarty\ViewRenderer', + //'cachePath' => '@runtime/Smarty/cache', + ], + ], + ], + ], +]; +``` Installation ------------ -The prefered way to install this extension is through [composer](http://getcomposer.org/download/). +The preferred way to install this extension is through [composer](http://getcomposer.org/download/). Either run + ``` php composer.phar require yiisoft/yii2-smarty "*" ``` or add + ```json "yiisoft/yii2-smarty": "*" ``` -to the require section of your composer.json. - - -*Note: You might have to run `php composer.phar selfupdate`* - - -Usage & Documentation ---------------------- - -This extension has to be registered prior to usage. -To enable this view renderer add it to the $rendereres property of your view object. - -Example: - -```php -<?php -// config.php -return array( - //.... - 'components' => array( - 'view' => array( - 'class' => 'yii\base\View', - 'renderers' => array( - 'tpl' => array( - 'class' => 'yii\smarty\ViewRenderer', - //'cachePath' => '@runtime/Smarty/cache', - ), - ), - ), - ), -); -``` -For further instructions refer to the related section in the yii guide. +to the require section of your composer.json. diff --git a/extensions/smarty/yii/smarty/ViewRenderer.php b/extensions/smarty/ViewRenderer.php similarity index 97% rename from extensions/smarty/yii/smarty/ViewRenderer.php rename to extensions/smarty/ViewRenderer.php index 7a9554e..2cfb216 100644 --- a/extensions/smarty/yii/smarty/ViewRenderer.php +++ b/extensions/smarty/ViewRenderer.php @@ -44,7 +44,7 @@ class ViewRenderer extends BaseViewRenderer $this->smarty->setCompileDir(Yii::getAlias($this->compilePath)); $this->smarty->setCacheDir(Yii::getAlias($this->cachePath)); - $this->smarty->registerPlugin('function', 'path', array($this, 'smarty_function_path')); + $this->smarty->registerPlugin('function', 'path', [$this, 'smarty_function_path']); } /** diff --git a/extensions/smarty/composer.json b/extensions/smarty/composer.json index b1e1681..88b75a3 100644 --- a/extensions/smarty/composer.json +++ b/extensions/smarty/composer.json @@ -2,7 +2,7 @@ "name": "yiisoft/yii2-smarty", "description": "The Smarty integration for the Yii framework", "keywords": ["yii", "smarty", "renderer"], - "type": "library", + "type": "yii2-extension", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/yiisoft/yii2/issues?state=open", @@ -13,16 +13,16 @@ }, "authors": [ { - "name": "Alenxader Makarov", + "name": "Alexander Makarov", "email": "sam@rmcreative.ru" } ], - "minimum-stability": "dev", "require": { "yiisoft/yii2": "*", - "smarty/smarty": "v3.1.13" + "smarty/smarty": "*" }, "autoload": { - "psr-0": { "yii\\smarty": "" } - } + "psr-0": { "yii\\smarty\\": "" } + }, + "target-dir": "yii/smarty" } diff --git a/extensions/mutex/LICENSE.md b/extensions/swiftmailer/LICENSE.md similarity index 100% rename from extensions/mutex/LICENSE.md rename to extensions/swiftmailer/LICENSE.md diff --git a/extensions/swiftmailer/Mailer.php b/extensions/swiftmailer/Mailer.php new file mode 100644 index 0000000..e43798a --- /dev/null +++ b/extensions/swiftmailer/Mailer.php @@ -0,0 +1,216 @@ +<?php +/** + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\swiftmailer; + +use Yii; +use yii\base\InvalidConfigException; +use yii\mail\BaseMailer; + +/** + * Mailer implements a mailer based on SwiftMailer. + * + * To use Mailer, you should configure it in the application configuration like the following, + * + * ~~~ + * 'components' => [ + * ... + * 'email' => [ + * 'class' => 'yii\swiftmailer\Mailer', + * 'transport' => [ + * 'class' => 'Swift_SmtpTransport', + * 'host' => 'localhost', + * 'username' => 'username', + * 'password' => 'password', + * 'port' => '587', + * 'encryption' => 'tls', + * ], + * ], + * ... + * ], + * ~~~ + * + * You may also skip the configuration of the [[transport]] property. In that case, the default + * PHP `mail()` function will be used to send emails. + * + * You specify the transport constructor arguments using 'constructArgs' key in the config. + * You can also specify the list of plugins, which should be registered to the transport using + * 'plugins' key. For example: + * + * ~~~ + * 'transport' => [ + * 'class' => 'Swift_SmtpTransport', + * 'constructArgs' => ['localhost', 25] + * 'plugins' => [ + * [ + * 'class' => 'Swift_Plugins_ThrottlerPlugin', + * 'constructArgs' => [20], + * ], + * ], + * ], + * ~~~ + * + * To send an email, you may use the following code: + * + * ~~~ + * Yii::$app->mail->compose('contact/html', ['contactForm' => $form]) + * ->setFrom('from@domain.com') + * ->setTo($form->email) + * ->setSubject($form->subject) + * ->send(); + * ~~~ + * + * @see http://swiftmailer.org + * + * @author Paul Klimov <klimov.paul@gmail.com> + * @since 2.0 + */ +class Mailer extends BaseMailer +{ + /** + * @var string message default class name. + */ + public $messageClass = 'yii\swiftmailer\Message'; + /** + * @var \Swift_Mailer Swift mailer instance. + */ + private $_swiftMailer; + /** + * @var \Swift_Transport|array Swift transport instance or its array configuration. + */ + private $_transport = []; + + /** + * @return array|\Swift_Mailer Swift mailer instance or array configuration. + */ + public function getSwiftMailer() + { + if (!is_object($this->_swiftMailer)) { + $this->_swiftMailer = $this->createSwiftMailer(); + } + return $this->_swiftMailer; + } + + /** + * @param array|\Swift_Transport $transport + * @throws InvalidConfigException on invalid argument. + */ + public function setTransport($transport) + { + if (!is_array($transport) && !is_object($transport)) { + throw new InvalidConfigException('"' . get_class($this) . '::transport" should be either object or array, "' . gettype($transport) . '" given.'); + } + $this->_transport = $transport; + } + + /** + * @return array|\Swift_Transport + */ + public function getTransport() + { + if (!is_object($this->_transport)) { + $this->_transport = $this->createTransport($this->_transport); + } + return $this->_transport; + } + + /** + * @inheritdoc + */ + protected function sendMessage($message) + { + $address = $message->getTo(); + if (is_array($address)) { + $address = implode(', ', array_keys($address)); + } + Yii::info('Sending email "' . $message->getSubject() . '" to "' . $address . '"', __METHOD__); + return $this->getSwiftMailer()->send($message->getSwiftMessage()) > 0; + } + + /** + * Creates Swift mailer instance. + * @return \Swift_Mailer mailer instance. + */ + protected function createSwiftMailer() + { + return \Swift_Mailer::newInstance($this->getTransport()); + } + + /** + * Creates email transport instance by its array configuration. + * @param array $config transport configuration. + * @throws \yii\base\InvalidConfigException on invalid transport configuration. + * @return \Swift_Transport transport instance. + */ + protected function createTransport(array $config) + { + if (!isset($config['class'])) { + $config['class'] = 'Swift_MailTransport'; + } + if (isset($config['plugins'])) { + $plugins = $config['plugins']; + unset($config['plugins']); + } + /** @var \Swift_MailTransport $transport */ + $transport = $this->createSwiftObject($config); + if (isset($plugins)) { + foreach ($plugins as $plugin) { + if (is_array($plugin) && isset($plugin['class'])) { + $plugin = $this->createSwiftObject($plugin); + } + $transport->registerPlugin($plugin); + } + } + return $transport; + } + + /** + * Creates Swift library object, from given array configuration. + * @param array $config object configuration + * @return Object created object + * @throws \yii\base\InvalidConfigException on invalid configuration. + */ + protected function createSwiftObject(array $config) + { + if (isset($config['class'])) { + $className = $config['class']; + unset($config['class']); + } else { + throw new InvalidConfigException('Object configuration must be an array containing a "class" element.'); + } + if (isset($config['constructArgs'])) { + $args = []; + foreach ($config['constructArgs'] as $arg) { + if (is_array($arg) && isset($arg['class'])) { + $args[] = $this->createSwiftObject($arg); + } else { + $args[] = $arg; + } + } + unset($config['constructArgs']); + array_unshift($args, ['class' => $className]); + $object = call_user_func_array(['Yii', 'createObject'], $args); + } else { + $object = new $className; + } + if (!empty($config)) { + foreach ($config as $name => $value) { + if (property_exists($object, $name)) { + $object->$name = $value; + } else { + $setter = 'set' . $name; + if (method_exists($object, $setter) || method_exists($object, '__call')) { + $object->$setter($value); + } else { + throw new InvalidConfigException('Setting unknown property: ' . $className . '::' . $name); + } + } + } + } + return $object; + } +} diff --git a/extensions/swiftmailer/Message.php b/extensions/swiftmailer/Message.php new file mode 100644 index 0000000..b0ebd63 --- /dev/null +++ b/extensions/swiftmailer/Message.php @@ -0,0 +1,301 @@ +<?php +/** + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\swiftmailer; + +use yii\mail\BaseMessage; + +/** + * Message implements a message class based on SwiftMailer. + * + * @see http://swiftmailer.org/docs/messages.html + * @see Mailer + * + * @method Mailer getMailer() returns mailer instance. + * @property \Swift_Message $swiftMessage vendor message instance. + * + * @author Paul Klimov <klimov.paul@gmail.com> + * @since 2.0 + */ +class Message extends BaseMessage +{ + /** + * @var \Swift_Message Swift message instance. + */ + private $_swiftMessage; + + /** + * @return \Swift_Message Swift message instance. + */ + public function getSwiftMessage() + { + if (!is_object($this->_swiftMessage)) { + $this->_swiftMessage = $this->createSwiftMessage(); + } + return $this->_swiftMessage; + } + + /** + * @inheritdoc + */ + public function getCharset() + { + return $this->getSwiftMessage()->getCharset(); + } + + /** + * @inheritdoc + */ + public function setCharset($charset) + { + $this->getSwiftMessage()->setCharset($charset); + return $this; + } + + /** + * @inheritdoc + */ + public function getFrom() + { + return $this->getSwiftMessage()->getFrom(); + } + + /** + * @inheritdoc + */ + public function setFrom($from) + { + $this->getSwiftMessage()->setFrom($from); + return $this; + } + + /** + * @inheritdoc + */ + public function getReplyTo() + { + return $this->getSwiftMessage()->getReplyTo(); + } + + /** + * @inheritdoc + */ + public function setReplyTo($replyTo) + { + $this->getSwiftMessage()->setReplyTo($replyTo); + return $this; + } + + /** + * @inheritdoc + */ + public function getTo() + { + return $this->getSwiftMessage()->getTo(); + } + + /** + * @inheritdoc + */ + public function setTo($to) + { + $this->getSwiftMessage()->setTo($to); + return $this; + } + + /** + * @inheritdoc + */ + public function getCc() + { + return $this->getSwiftMessage()->getCc(); + } + + /** + * @inheritdoc + */ + public function setCc($cc) + { + $this->getSwiftMessage()->setCc($cc); + return $this; + } + + /** + * @inheritdoc + */ + public function getBcc() + { + return $this->getSwiftMessage()->getBcc(); + } + + /** + * @inheritdoc + */ + public function setBcc($bcc) + { + $this->getSwiftMessage()->setBcc($bcc); + return $this; + } + + /** + * @inheritdoc + */ + public function getSubject() + { + return $this->getSwiftMessage()->getSubject(); + } + + /** + * @inheritdoc + */ + public function setSubject($subject) + { + $this->getSwiftMessage()->setSubject($subject); + return $this; + } + + /** + * @inheritdoc + */ + public function setTextBody($text) + { + $this->setBody($text, 'text/plain'); + return $this; + } + + /** + * @inheritdoc + */ + public function setHtmlBody($html) + { + $this->setBody($html, 'text/html'); + return $this; + } + + /** + * Sets the message body. + * If body is already set and its content type matches given one, it will + * be overridden, if content type miss match the multipart message will be composed. + * @param string $body body content. + * @param string $contentType body content type. + */ + protected function setBody($body, $contentType) + { + $message = $this->getSwiftMessage(); + $oldBody = $message->getBody(); + if (empty($oldBody)) { + $parts = $message->getChildren(); + $partFound = false; + foreach ($parts as $key => $part) { + if (!($part instanceof \Swift_Mime_Attachment)) { + /* @var \Swift_Mime_MimePart $part */ + if ($part->getContentType() == $contentType) { + unset($parts[$key]); + $partFound = true; + break; + } + } + } + if ($partFound) { + reset($parts); + $message->setChildren($parts); + $message->addPart($body, $contentType); + } else { + $message->setBody($body, $contentType); + } + } else { + $oldContentType = $message->getContentType(); + if ($oldContentType == $contentType) { + $message->setBody($body, $contentType); + } else { + $message->setBody(null); + $message->setContentType(null); + $message->addPart($oldBody, $oldContentType); + $message->addPart($body, $contentType); + } + } + } + + /** + * @inheritdoc + */ + public function attach($fileName, array $options = []) + { + $attachment = \Swift_Attachment::fromPath($fileName); + if (!empty($options['fileName'])) { + $attachment->setFilename($options['fileName']); + } + if (!empty($options['contentType'])) { + $attachment->setContentType($options['contentType']); + } + $this->getSwiftMessage()->attach($attachment); + return $this; + } + + /** + * @inheritdoc + */ + public function attachContent($content, array $options = []) + { + $attachment = \Swift_Attachment::newInstance($content); + if (!empty($options['fileName'])) { + $attachment->setFilename($options['fileName']); + } + if (!empty($options['contentType'])) { + $attachment->setContentType($options['contentType']); + } + $this->getSwiftMessage()->attach($attachment); + return $this; + } + + /** + * @inheritdoc + */ + public function embed($fileName, array $options = []) + { + $embedFile = \Swift_EmbeddedFile::fromPath($fileName); + if (!empty($options['fileName'])) { + $embedFile->setFilename($options['fileName']); + } + if (!empty($options['contentType'])) { + $embedFile->setContentType($options['contentType']); + } + return $this->getSwiftMessage()->embed($embedFile); + } + + /** + * @inheritdoc + */ + public function embedContent($content, array $options = []) + { + $embedFile = \Swift_EmbeddedFile::newInstance($content); + if (!empty($options['fileName'])) { + $embedFile->setFilename($options['fileName']); + } + if (!empty($options['contentType'])) { + $embedFile->setContentType($options['contentType']); + } + return $this->getSwiftMessage()->embed($embedFile); + } + + /** + * @inheritdoc + */ + public function toString() + { + return $this->getSwiftMessage()->toString(); + } + + /** + * Creates the Swift email message instance. + * @return \Swift_Message email message instance. + */ + protected function createSwiftMessage() + { + return new \Swift_Message(); + } +} diff --git a/extensions/swiftmailer/README.md b/extensions/swiftmailer/README.md new file mode 100644 index 0000000..3edd4a4 --- /dev/null +++ b/extensions/swiftmailer/README.md @@ -0,0 +1,49 @@ +SwiftMailer Extension for Yii 2 +=============================== + +This extension provides a `SwiftMailer` mail solution for Yii 2. + +To use this extension, simply add the following code in your application configuration: + +```php +return [ + //.... + 'components' => [ + 'mail' => [ + 'class' => 'yii\swiftmailer\Mailer', + ], + ], +]; +``` + +You can then send an email as follows: + +```php +Yii::$app->mail->compose('contact/html') + ->from('from@domain.com') + ->to($form->email) + ->subject($form->subject) + ->send(); +``` + +For further instructions refer to the related section in the Yii Definitive Guide. + + +Installation +------------ + +The preferred way to install this extension is through [composer](http://getcomposer.org/download/). + +Either run + +``` +php composer.phar require yiisoft/yii2-swiftmailer "*" +``` + +or add + +```json +"yiisoft/yii2-swiftmailer": "*" +``` + +to the require section of your composer.json. diff --git a/extensions/swiftmailer/composer.json b/extensions/swiftmailer/composer.json new file mode 100644 index 0000000..5a47397 --- /dev/null +++ b/extensions/swiftmailer/composer.json @@ -0,0 +1,28 @@ +{ + "name": "yiisoft/yii2-swiftmailer", + "description": "The SwiftMailer integration for the Yii framework", + "keywords": ["yii", "swift", "swiftmailer", "mail", "email", "mailer"], + "type": "yii2-extension", + "license": "BSD-3-Clause", + "support": { + "issues": "https://github.com/yiisoft/yii2/issues?state=open", + "forum": "http://www.yiiframework.com/forum/", + "wiki": "http://www.yiiframework.com/wiki/", + "irc": "irc://irc.freenode.net/yii", + "source": "https://github.com/yiisoft/yii2" + }, + "authors": [ + { + "name": "Paul Klimov", + "email": "klimov.paul@gmail.com" + } + ], + "require": { + "yiisoft/yii2": "*", + "swiftmailer/swiftmailer": "*" + }, + "autoload": { + "psr-0": { "yii\\swiftmailer\\": "" } + }, + "target-dir": "yii/swiftmailer" +} diff --git a/extensions/twig/LICENSE.md b/extensions/twig/LICENSE.md index 0bb1a8d..e98f03d 100644 --- a/extensions/twig/LICENSE.md +++ b/extensions/twig/LICENSE.md @@ -1,7 +1,7 @@ The Yii framework is free software. It is released under the terms of the following BSD License. -Copyright © 2008-2013 by Yii Software LLC (http://www.yiisoft.com) +Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com) All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/extensions/twig/README.md b/extensions/twig/README.md index cc6bab0..8099e1c 100644 --- a/extensions/twig/README.md +++ b/extensions/twig/README.md @@ -1,64 +1,43 @@ -Yii 2.0 Public Preview - Twig View Renderer -=========================================== +Twig Extension for Yii 2 +======================== -Thank you for choosing Yii - a high-performance component-based PHP framework. +This extension provides a `ViewRender` that would allow you to use Twig view template engine. -If you are looking for a production-ready PHP framework, please use -[Yii v1.1](https://github.com/yiisoft/yii). +To use this extension, simply add the following code in your application configuration: -Yii 2.0 is still under heavy development. We may make significant changes -without prior notices. **Yii 2.0 is not ready for production use yet.** - -[](http://travis-ci.org/yiisoft/yii2) - -This is the yii2-twig extension. +```php +return [ + //.... + 'components' => [ + 'view' => [ + 'renderers' => [ + 'twig' => [ + 'class' => 'yii\twig\ViewRenderer', + //'cachePath' => '@runtime/Twig/cache', + //'options' => [], /* Array of twig options */ + ], + ], + ], + ], +]; +``` Installation ------------ -The prefered way to install this extension is through [composer](http://getcomposer.org/download/). +The preferred way to install this extension is through [composer](http://getcomposer.org/download/). Either run + ``` php composer.phar require yiisoft/yii2-twig "*" ``` or add + ``` "yiisoft/yii2-twig": "*" ``` -to the require section of your composer.json. - - -*Note: You might have to run `php composer.phar selfupdate`* - - -Usage & Documentation ---------------------- - -This extension has to be registered prior to usage. -To enable this view renderer add it to the $rendereres property of your view object. - -Example: -```php -<?php -// config.php -return array( - //.... - 'components' => array( - 'view' => array( - 'class' => 'yii\base\View', - 'renderers' => array( - 'twig' => array( - 'class' => 'yii\twig\ViewRenderer', - //'cachePath' => '@runtime/Twig/cache', - //'options' => array(), /* Array of twig options */ - ), - ), - ), - ), -); -``` -For further instructions refer to the related section in the yii guide. +to the require section of your composer.json. diff --git a/extensions/twig/yii/twig/ViewRenderer.php b/extensions/twig/ViewRenderer.php similarity index 90% rename from extensions/twig/yii/twig/ViewRenderer.php rename to extensions/twig/ViewRenderer.php index 06ad2d2..b1598cf 100644 --- a/extensions/twig/yii/twig/ViewRenderer.php +++ b/extensions/twig/ViewRenderer.php @@ -7,7 +7,7 @@ * @license http://www.yiiframework.com/license/ */ -namespace yii\renderers; +namespace yii\twig; use Yii; use yii\base\View; @@ -31,7 +31,7 @@ class ViewRenderer extends BaseViewRenderer * @var array Twig options * @see http://twig.sensiolabs.org/doc/api.html#environment-options */ - public $options = array(); + public $options = []; /** * @var \Twig_Environment @@ -42,12 +42,12 @@ class ViewRenderer extends BaseViewRenderer { $loader = new \Twig_Loader_String(); - $this->twig = new \Twig_Environment($loader, array_merge(array( + $this->twig = new \Twig_Environment($loader, array_merge([ 'cache' => Yii::getAlias($this->cachePath), - ), $this->options)); + ], $this->options)); - $this->twig->addFunction('path', new \Twig_Function_Function(function ($path, $args = array()) { - return Html::url(array_merge(array($path), $args)); + $this->twig->addFunction('path', new \Twig_Function_Function(function ($path, $args = []) { + return Html::url(array_merge([$path], $args)); })); $this->twig->addGlobal('app', \Yii::$app); diff --git a/extensions/twig/composer.json b/extensions/twig/composer.json index 0c1644a..8fe6431 100644 --- a/extensions/twig/composer.json +++ b/extensions/twig/composer.json @@ -2,7 +2,7 @@ "name": "yiisoft/yii2-twig", "description": "The Twig integration for the Yii framework", "keywords": ["yii", "twig", "renderer"], - "type": "library", + "type": "yii2-extension", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/yiisoft/yii2/issues?state=open", @@ -13,16 +13,16 @@ }, "authors": [ { - "name": "Alenxader Makarov", + "name": "Alexander Makarov", "email": "sam@rmcreative.ru" } ], - "minimum-stability": "dev", "require": { "yiisoft/yii2": "*", - "twig/twig": "1.13.*" + "twig/twig": "*" }, "autoload": { - "psr-0": { "yii\\twig": "" } - } + "psr-0": { "yii\\twig\\": "" } + }, + "target-dir": "yii/twig" } diff --git a/framework/LICENSE.md b/framework/LICENSE.md index 6edcc4f..e98f03d 100644 --- a/framework/LICENSE.md +++ b/framework/LICENSE.md @@ -1,7 +1,7 @@ The Yii framework is free software. It is released under the terms of the following BSD License. -Copyright © 2008-2013 by Yii Software LLC (http://www.yiisoft.com) +Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com) All rights reserved. Redistribution and use in source and binary forms, with or without @@ -29,4 +29,4 @@ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file +POSSIBILITY OF SUCH DAMAGE. diff --git a/framework/README.md b/framework/README.md index 1cbfdf8..b5c754f 100644 --- a/framework/README.md +++ b/framework/README.md @@ -16,6 +16,6 @@ without prior notices. **Yii 2.0 is not ready for production use yet.** REQUIREMENTS ------------ -The minimum requirement by Yii is that your Web server supports PHP 5.3.?. +The minimum requirement by Yii is that your Web server supports PHP 5.4.0. diff --git a/framework/composer.json b/framework/composer.json index 89d6064..637471c 100644 --- a/framework/composer.json +++ b/framework/composer.json @@ -64,18 +64,16 @@ "source": "https://github.com/yiisoft/yii2" }, "require": { - "php": ">=5.3.7", + "php": ">=5.4.0", "ext-mbstring": "*", "lib-pcre": "*", - "phpspec/php-diff": "1.0.*", - "ezyang/htmlpurifier": "4.5.*" + "yiisoft/yii2-composer": "*", + "yiisoft/jquery": "1.10.*", + "phpspec/php-diff": ">=1.0.2", + "ezyang/htmlpurifier": "4.5.*", + "michelf/php-markdown": "1.3.*" }, "autoload": { "psr-0": { "yii\\": "/" } - }, - "suggest": { - "michelf/php-markdown": "Required by Markdown.", - "twig/twig": "Required by TwigViewRenderer.", - "smarty/smarty": "Required by SmartyViewRenderer." } } diff --git a/framework/yii/YiiBase.php b/framework/yii/BaseYii.php similarity index 77% rename from framework/yii/YiiBase.php rename to framework/yii/BaseYii.php index f04903d..ee49c8e 100644 --- a/framework/yii/YiiBase.php +++ b/framework/yii/BaseYii.php @@ -6,7 +6,6 @@ */ namespace yii; -use yii\base\Exception; use yii\base\InvalidConfigException; use yii\base\InvalidParamException; use yii\base\UnknownClassException; @@ -49,56 +48,55 @@ defined('YII_ENABLE_ERROR_HANDLER') or define('YII_ENABLE_ERROR_HANDLER', true); /** - * YiiBase is the core helper class for the Yii framework. + * BaseYii is the core helper class for the Yii framework. * - * Do not use YiiBase directly. Instead, use its child class [[Yii]] where - * you can customize methods of YiiBase. + * Do not use BaseYii directly. Instead, use its child class [[Yii]] where + * you can customize methods of BaseYii. * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ -class YiiBase +class BaseYii { /** * @var array class map used by the Yii autoloading mechanism. * The array keys are the class names (without leading backslashes), and the array values * are the corresponding class file paths (or path aliases). This property mainly affects * how [[autoload()]] works. - * @see import - * @see autoload + * @see autoload() */ - public static $classMap = array(); + public static $classMap = []; /** * @var \yii\console\Application|\yii\web\Application the application instance */ public static $app; /** * @var array registered path aliases - * @see getAlias - * @see setAlias + * @see getAlias() + * @see setAlias() */ - public static $aliases = array('@yii' => __DIR__); + public static $aliases = ['@yii' => __DIR__]; /** * @var array initial property values that will be applied to objects newly created via [[createObject]]. * The array keys are class names without leading backslashes "\", and the array values are the corresponding * name-value pairs for initializing the created class instances. For example, * * ~~~ - * array( - * 'Bar' => array( + * [ + * 'Bar' => [ * 'prop1' => 'value1', * 'prop2' => 'value2', - * ), - * 'mycompany\foo\Car' => array( + * ], + * 'mycompany\foo\Car' => [ * 'prop1' => 'value1', * 'prop2' => 'value2', - * ), - * ) + * ], + * ] * ~~~ * - * @see createObject + * @see createObject() */ - public static $objectConfig = array(); + public static $objectConfig = []; /** @@ -110,33 +108,6 @@ class YiiBase } /** - * Imports a set of namespaces. - * - * By importing a namespace, the method will create an alias for the directory corresponding - * to the namespace. For example, if "foo\bar" is a namespace associated with the directory - * "path/to/foo/bar", then an alias "@foo/bar" will be created for this directory. - * - * This method is typically invoked in the bootstrap file to import the namespaces of - * the installed extensions. By default, Composer, when installing new extensions, will - * generate such a mapping file which can be loaded and passed to this method. - * - * @param array $namespaces the namespaces to be imported. The keys are the namespaces, - * and the values are the corresponding directories. - */ - public static function importNamespaces($namespaces) - { - foreach ($namespaces as $name => $path) { - if ($name !== '') { - $name = trim(strtr($name, array('\\' => '/', '_' => '/')), '/'); - if (is_array($path)) { - $path = reset($path); - } - static::setAlias('@' . $name, rtrim($path, '/\\') . '/' . $name); - } - } - } - - /** * Translates a path alias into an actual path. * * The translation is done according to the following procedure: @@ -164,7 +135,7 @@ class YiiBase * If this is false and an invalid alias is given, false will be returned by this method. * @return string|boolean the path corresponding to the alias, false if the root alias is not previously registered. * @throws InvalidParamException if the alias is invalid while $throwException is true. - * @see setAlias + * @see setAlias() */ public static function getAlias($alias, $throwException = true) { @@ -176,11 +147,11 @@ class YiiBase $pos = strpos($alias, '/'); $root = $pos === false ? $alias : substr($alias, 0, $pos); - if (isset(self::$aliases[$root])) { - if (is_string(self::$aliases[$root])) { - return $pos === false ? self::$aliases[$root] : self::$aliases[$root] . substr($alias, $pos); + if (isset(static::$aliases[$root])) { + if (is_string(static::$aliases[$root])) { + return $pos === false ? static::$aliases[$root] : static::$aliases[$root] . substr($alias, $pos); } else { - foreach (self::$aliases[$root] as $name => $path) { + foreach (static::$aliases[$root] as $name => $path) { if (strpos($alias . '/', $name . '/') === 0) { return $path . substr($alias, strlen($name)); } @@ -207,11 +178,11 @@ class YiiBase $pos = strpos($alias, '/'); $root = $pos === false ? $alias : substr($alias, 0, $pos); - if (isset(self::$aliases[$root])) { - if (is_string(self::$aliases[$root])) { + if (isset(static::$aliases[$root])) { + if (is_string(static::$aliases[$root])) { return $root; } else { - foreach (self::$aliases[$root] as $name => $path) { + foreach (static::$aliases[$root] as $name => $path) { if (strpos($alias . '/', $name . '/') === 0) { return $name; } @@ -247,7 +218,7 @@ class YiiBase * actual path first by calling [[getAlias()]]. * * @throws InvalidParamException if $path is an invalid alias. - * @see getAlias + * @see getAlias() */ public static function setAlias($alias, $path) { @@ -258,30 +229,30 @@ class YiiBase $root = $pos === false ? $alias : substr($alias, 0, $pos); if ($path !== null) { $path = strncmp($path, '@', 1) ? rtrim($path, '\\/') : static::getAlias($path); - if (!isset(self::$aliases[$root])) { + if (!isset(static::$aliases[$root])) { if ($pos === false) { - self::$aliases[$root] = $path; + static::$aliases[$root] = $path; } else { - self::$aliases[$root] = array($alias => $path); + static::$aliases[$root] = [$alias => $path]; } - } elseif (is_string(self::$aliases[$root])) { + } elseif (is_string(static::$aliases[$root])) { if ($pos === false) { - self::$aliases[$root] = $path; + static::$aliases[$root] = $path; } else { - self::$aliases[$root] = array( + static::$aliases[$root] = [ $alias => $path, - $root => self::$aliases[$root], - ); + $root => static::$aliases[$root], + ]; } } else { - self::$aliases[$root][$alias] = $path; - krsort(self::$aliases[$root]); + static::$aliases[$root][$alias] = $path; + krsort(static::$aliases[$root]); } - } elseif (isset(self::$aliases[$root])) { - if (is_array(self::$aliases[$root])) { - unset(self::$aliases[$root][$alias]); + } elseif (isset(static::$aliases[$root])) { + if (is_array(static::$aliases[$root])) { + unset(static::$aliases[$root][$alias]); } elseif ($pos === false) { - unset(self::$aliases[$root]); + unset(static::$aliases[$root]); } } } @@ -307,8 +278,8 @@ class YiiBase */ public static function autoload($className) { - if (isset(self::$classMap[$className])) { - $classFile = self::$classMap[$className]; + if (isset(static::$classMap[$className])) { + $classFile = static::$classMap[$className]; if ($classFile[0] === '@') { $classFile = static::getAlias($classFile); } @@ -335,8 +306,7 @@ class YiiBase include($classFile); - if (YII_DEBUG && !class_exists($className, false) && !interface_exists($className, false) && - (!function_exists('trait_exists') || !trait_exists($className, false))) { + if (YII_DEBUG && !class_exists($className, false) && !interface_exists($className, false) && !trait_exists($className, false)) { throw new UnknownClassException("Unable to find '$className' in file: $classFile"); } } @@ -350,27 +320,21 @@ class YiiBase * the rest of the name-value pairs in the array will be used to initialize * the corresponding object properties. * - * The object type can be either a class name or the [[getAlias()|alias]] of - * the class. For example, - * - * - `app\components\GoogleMap`: fully-qualified namespaced class. - * - `@app/components/GoogleMap`: an alias, used for non-namespaced class. - * * Below are some usage examples: * * ~~~ - * $object = \Yii::createObject('@app/components/GoogleMap'); - * $object = \Yii::createObject(array( - * 'class' => '\app\components\GoogleMap', + * $object = \Yii::createObject('app\components\GoogleMap'); + * $object = \Yii::createObject([ + * 'class' => 'app\components\GoogleMap', * 'apiKey' => 'xyz', - * )); + * ]); * ~~~ * * This method can be used to create any object as long as the object's constructor is * defined like the following: * * ~~~ - * public function __construct(..., $config = array()) { + * public function __construct(..., $config = []) { * } * ~~~ * @@ -384,11 +348,11 @@ class YiiBase */ public static function createObject($config) { - static $reflections = array(); + static $reflections = []; if (is_string($config)) { $class = $config; - $config = array(); + $config = []; } elseif (isset($config['class'])) { $class = $config['class']; unset($config['class']); @@ -398,12 +362,12 @@ class YiiBase $class = ltrim($class, '\\'); - if (isset(self::$objectConfig[$class])) { - $config = array_merge(self::$objectConfig[$class], $config); + if (isset(static::$objectConfig[$class])) { + $config = array_merge(static::$objectConfig[$class], $config); } if (($n = func_num_args()) > 1) { - /** @var $reflection \ReflectionClass */ + /** @var \ReflectionClass $reflection */ if (isset($reflections[$class])) { $reflection = $reflections[$class]; } else { @@ -430,7 +394,7 @@ class YiiBase public static function trace($message, $category = 'application') { if (YII_DEBUG) { - self::$app->getLog()->log($message, Logger::LEVEL_TRACE, $category); + static::$app->getLog()->log($message, Logger::LEVEL_TRACE, $category); } } @@ -443,7 +407,7 @@ class YiiBase */ public static function error($message, $category = 'application') { - self::$app->getLog()->log($message, Logger::LEVEL_ERROR, $category); + static::$app->getLog()->log($message, Logger::LEVEL_ERROR, $category); } /** @@ -455,7 +419,7 @@ class YiiBase */ public static function warning($message, $category = 'application') { - self::$app->getLog()->log($message, Logger::LEVEL_WARNING, $category); + static::$app->getLog()->log($message, Logger::LEVEL_WARNING, $category); } /** @@ -467,7 +431,7 @@ class YiiBase */ public static function info($message, $category = 'application') { - self::$app->getLog()->log($message, Logger::LEVEL_INFO, $category); + static::$app->getLog()->log($message, Logger::LEVEL_INFO, $category); } /** @@ -485,11 +449,11 @@ class YiiBase * ~~~ * @param string $token token for the code block * @param string $category the category of this log message - * @see endProfile + * @see endProfile() */ public static function beginProfile($token, $category = 'application') { - self::$app->getLog()->log($token, Logger::LEVEL_PROFILE_BEGIN, $category); + static::$app->getLog()->log($token, Logger::LEVEL_PROFILE_BEGIN, $category); } /** @@ -497,11 +461,11 @@ class YiiBase * This has to be matched with a previous call to [[beginProfile]] with the same category name. * @param string $token token for the code block * @param string $category the category of this log message - * @see beginProfile + * @see beginProfile() */ public static function endProfile($token, $category = 'application') { - self::$app->getLog()->log($token, Logger::LEVEL_PROFILE_END, $category); + static::$app->getLog()->log($token, Logger::LEVEL_PROFILE_END, $category); } /** @@ -534,16 +498,20 @@ class YiiBase * @param string $category the message category. * @param string $message the message to be translated. * @param array $params the parameters that will be used to replace the corresponding placeholders in the message. - * @param string $language the language code (e.g. `en_US`, `en`). If this is null, the current + * @param string $language the language code (e.g. `en-US`, `en`). If this is null, the current * [[\yii\base\Application::language|application language]] will be used. * @return string the translated message. */ - public static function t($category, $message, $params = array(), $language = null) + public static function t($category, $message, $params = [], $language = null) { - if (self::$app !== null) { - return self::$app->getI18N()->translate($category, $message, $params, $language ?: self::$app->language); + if (static::$app !== null) { + return static::$app->getI18n()->translate($category, $message, $params, $language ?: static::$app->language); } else { - return is_array($params) ? strtr($message, $params) : $message; + $p = []; + foreach ((array) $params as $name => $value) { + $p['{' . $name . '}'] = $value; + } + return ($p === []) ? $message : strtr($message, $p); } } diff --git a/framework/yii/Yii.php b/framework/yii/Yii.php index bde15cc..272b35f 100644 --- a/framework/yii/Yii.php +++ b/framework/yii/Yii.php @@ -7,20 +7,20 @@ * @license http://www.yiiframework.com/license/ */ -require(__DIR__ . '/YiiBase.php'); +require(__DIR__ . '/BaseYii.php'); /** * Yii is a helper class serving common framework functionalities. * - * It extends from [[YiiBase]] which provides the actual implementation. - * By writing your own Yii class, you can customize some functionalities of [[YiiBase]]. + * It extends from [[BaseYii]] which provides the actual implementation. + * By writing your own Yii class, you can customize some functionalities of [[BaseYii]]. * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ -class Yii extends \yii\YiiBase +class Yii extends \yii\BaseYii { } -spl_autoload_register(array('Yii', 'autoload'), true, true); +spl_autoload_register(['Yii', 'autoload'], true, true); Yii::$classMap = include(__DIR__ . '/classes.php'); diff --git a/framework/yii/assets.php b/framework/yii/assets.php index c52bca6..df4ee10 100644 --- a/framework/yii/assets.php +++ b/framework/yii/assets.php @@ -1,6 +1,6 @@ <?php -return array( +return [ yii\web\YiiAsset::className(), yii\web\JqueryAsset::className(), yii\validators\PunycodeAsset::className(), @@ -8,4 +8,4 @@ return array( yii\widgets\ActiveFormAsset::className(), yii\captcha\CaptchaAsset::className(), yii\widgets\MaskedInputAsset::className(), -); +]; diff --git a/framework/yii/assets/jquery.js b/framework/yii/assets/jquery.js deleted file mode 100644 index e2c203f..0000000 --- a/framework/yii/assets/jquery.js +++ /dev/null @@ -1,9597 +0,0 @@ -/*! - * jQuery JavaScript Library v1.9.1 - * http://jquery.com/ - * - * Includes Sizzle.js - * http://sizzlejs.com/ - * - * Copyright 2005, 2012 jQuery Foundation, Inc. and other contributors - * Released under the MIT license - * http://jquery.org/license - * - * Date: 2013-2-4 - */ -(function( window, undefined ) { - -// Can't do this because several apps including ASP.NET trace -// the stack via arguments.caller.callee and Firefox dies if -// you try to trace through "use strict" call chains. (#13335) -// Support: Firefox 18+ -//"use strict"; -var - // The deferred used on DOM ready - readyList, - - // A central reference to the root jQuery(document) - rootjQuery, - - // Support: IE<9 - // For `typeof node.method` instead of `node.method !== undefined` - core_strundefined = typeof undefined, - - // Use the correct document accordingly with window argument (sandbox) - document = window.document, - location = window.location, - - // Map over jQuery in case of overwrite - _jQuery = window.jQuery, - - // Map over the $ in case of overwrite - _$ = window.$, - - // [[Class]] -> type pairs - class2type = {}, - - // List of deleted data cache ids, so we can reuse them - core_deletedIds = [], - - core_version = "1.9.1", - - // Save a reference to some core methods - core_concat = core_deletedIds.concat, - core_push = core_deletedIds.push, - core_slice = core_deletedIds.slice, - core_indexOf = core_deletedIds.indexOf, - core_toString = class2type.toString, - core_hasOwn = class2type.hasOwnProperty, - core_trim = core_version.trim, - - // Define a local copy of jQuery - jQuery = function( selector, context ) { - // The jQuery object is actually just the init constructor 'enhanced' - return new jQuery.fn.init( selector, context, rootjQuery ); - }, - - // Used for matching numbers - core_pnum = /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source, - - // Used for splitting on whitespace - core_rnotwhite = /\S+/g, - - // Make sure we trim BOM and NBSP (here's looking at you, Safari 5.0 and IE) - rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, - - // A simple way to check for HTML strings - // Prioritize #id over <tag> to avoid XSS via location.hash (#9521) - // Strict HTML recognition (#11290: must start with <) - rquickExpr = /^(?:(<[\w\W]+>)[^>]*|#([\w-]*))$/, - - // Match a standalone tag - rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/, - - // JSON RegExp - rvalidchars = /^[\],:{}\s]*$/, - rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, - rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g, - rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g, - - // Matches dashed string for camelizing - rmsPrefix = /^-ms-/, - rdashAlpha = /-([\da-z])/gi, - - // Used by jQuery.camelCase as callback to replace() - fcamelCase = function( all, letter ) { - return letter.toUpperCase(); - }, - - // The ready event handler - completed = function( event ) { - - // readyState === "complete" is good enough for us to call the dom ready in oldIE - if ( document.addEventListener || event.type === "load" || document.readyState === "complete" ) { - detach(); - jQuery.ready(); - } - }, - // Clean-up method for dom ready events - detach = function() { - if ( document.addEventListener ) { - document.removeEventListener( "DOMContentLoaded", completed, false ); - window.removeEventListener( "load", completed, false ); - - } else { - document.detachEvent( "onreadystatechange", completed ); - window.detachEvent( "onload", completed ); - } - }; - -jQuery.fn = jQuery.prototype = { - // The current version of jQuery being used - jquery: core_version, - - constructor: jQuery, - init: function( selector, context, rootjQuery ) { - var match, elem; - - // HANDLE: $(""), $(null), $(undefined), $(false) - if ( !selector ) { - return this; - } - - // Handle HTML strings - if ( typeof selector === "string" ) { - if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { - // Assume that strings that start and end with <> are HTML and skip the regex check - match = [ null, selector, null ]; - - } else { - match = rquickExpr.exec( selector ); - } - - // Match html or make sure no context is specified for #id - if ( match && (match[1] || !context) ) { - - // HANDLE: $(html) -> $(array) - if ( match[1] ) { - context = context instanceof jQuery ? context[0] : context; - - // scripts is true for back-compat - jQuery.merge( this, jQuery.parseHTML( - match[1], - context && context.nodeType ? context.ownerDocument || context : document, - true - ) ); - - // HANDLE: $(html, props) - if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) { - for ( match in context ) { - // Properties of context are called as methods if possible - if ( jQuery.isFunction( this[ match ] ) ) { - this[ match ]( context[ match ] ); - - // ...and otherwise set as attributes - } else { - this.attr( match, context[ match ] ); - } - } - } - - return this; - - // HANDLE: $(#id) - } else { - elem = document.getElementById( match[2] ); - - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - if ( elem && elem.parentNode ) { - // Handle the case where IE and Opera return items - // by name instead of ID - if ( elem.id !== match[2] ) { - return rootjQuery.find( selector ); - } - - // Otherwise, we inject the element directly into the jQuery object - this.length = 1; - this[0] = elem; - } - - this.context = document; - this.selector = selector; - return this; - } - - // HANDLE: $(expr, $(...)) - } else if ( !context || context.jquery ) { - return ( context || rootjQuery ).find( selector ); - - // HANDLE: $(expr, context) - // (which is just equivalent to: $(context).find(expr) - } else { - return this.constructor( context ).find( selector ); - } - - // HANDLE: $(DOMElement) - } else if ( selector.nodeType ) { - this.context = this[0] = selector; - this.length = 1; - return this; - - // HANDLE: $(function) - // Shortcut for document ready - } else if ( jQuery.isFunction( selector ) ) { - return rootjQuery.ready( selector ); - } - - if ( selector.selector !== undefined ) { - this.selector = selector.selector; - this.context = selector.context; - } - - return jQuery.makeArray( selector, this ); - }, - - // Start with an empty selector - selector: "", - - // The default length of a jQuery object is 0 - length: 0, - - // The number of elements contained in the matched element set - size: function() { - return this.length; - }, - - toArray: function() { - return core_slice.call( this ); - }, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function( num ) { - return num == null ? - - // Return a 'clean' array - this.toArray() : - - // Return just the object - ( num < 0 ? this[ this.length + num ] : this[ num ] ); - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function( elems ) { - - // Build a new jQuery matched element set - var ret = jQuery.merge( this.constructor(), elems ); - - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - ret.context = this.context; - - // Return the newly-formed element set - return ret; - }, - - // Execute a callback for every element in the matched set. - // (You can seed the arguments with an array of args, but this is - // only used internally.) - each: function( callback, args ) { - return jQuery.each( this, callback, args ); - }, - - ready: function( fn ) { - // Add the callback - jQuery.ready.promise().done( fn ); - - return this; - }, - - slice: function() { - return this.pushStack( core_slice.apply( this, arguments ) ); - }, - - first: function() { - return this.eq( 0 ); - }, - - last: function() { - return this.eq( -1 ); - }, - - eq: function( i ) { - var len = this.length, - j = +i + ( i < 0 ? len : 0 ); - return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] ); - }, - - map: function( callback ) { - return this.pushStack( jQuery.map(this, function( elem, i ) { - return callback.call( elem, i, elem ); - })); - }, - - end: function() { - return this.prevObject || this.constructor(null); - }, - - // For internal use only. - // Behaves like an Array's method, not like a jQuery method. - push: core_push, - sort: [].sort, - splice: [].splice -}; - -// Give the init function the jQuery prototype for later instantiation -jQuery.fn.init.prototype = jQuery.fn; - -jQuery.extend = jQuery.fn.extend = function() { - var src, copyIsArray, copy, name, options, clone, - target = arguments[0] || {}, - i = 1, - length = arguments.length, - deep = false; - - // Handle a deep copy situation - if ( typeof target === "boolean" ) { - deep = target; - target = arguments[1] || {}; - // skip the boolean and the target - i = 2; - } - - // Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && !jQuery.isFunction(target) ) { - target = {}; - } - - // extend jQuery itself if only one argument is passed - if ( length === i ) { - target = this; - --i; - } - - for ( ; i < length; i++ ) { - // Only deal with non-null/undefined values - if ( (options = arguments[ i ]) != null ) { - // Extend the base object - for ( name in options ) { - src = target[ name ]; - copy = options[ name ]; - - // Prevent never-ending loop - if ( target === copy ) { - continue; - } - - // Recurse if we're merging plain objects or arrays - if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { - if ( copyIsArray ) { - copyIsArray = false; - clone = src && jQuery.isArray(src) ? src : []; - - } else { - clone = src && jQuery.isPlainObject(src) ? src : {}; - } - - // Never move original objects, clone them - target[ name ] = jQuery.extend( deep, clone, copy ); - - // Don't bring in undefined values - } else if ( copy !== undefined ) { - target[ name ] = copy; - } - } - } - } - - // Return the modified object - return target; -}; - -jQuery.extend({ - noConflict: function( deep ) { - if ( window.$ === jQuery ) { - window.$ = _$; - } - - if ( deep && window.jQuery === jQuery ) { - window.jQuery = _jQuery; - } - - return jQuery; - }, - - // Is the DOM ready to be used? Set to true once it occurs. - isReady: false, - - // A counter to track how many items to wait for before - // the ready event fires. See #6781 - readyWait: 1, - - // Hold (or release) the ready event - holdReady: function( hold ) { - if ( hold ) { - jQuery.readyWait++; - } else { - jQuery.ready( true ); - } - }, - - // Handle when the DOM is ready - ready: function( wait ) { - - // Abort if there are pending holds or we're already ready - if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { - return; - } - - // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). - if ( !document.body ) { - return setTimeout( jQuery.ready ); - } - - // Remember that the DOM is ready - jQuery.isReady = true; - - // If a normal DOM Ready event fired, decrement, and wait if need be - if ( wait !== true && --jQuery.readyWait > 0 ) { - return; - } - - // If there are functions bound, to execute - readyList.resolveWith( document, [ jQuery ] ); - - // Trigger any bound ready events - if ( jQuery.fn.trigger ) { - jQuery( document ).trigger("ready").off("ready"); - } - }, - - // See test/unit/core.js for details concerning isFunction. - // Since version 1.3, DOM methods and functions like alert - // aren't supported. They return false on IE (#2968). - isFunction: function( obj ) { - return jQuery.type(obj) === "function"; - }, - - isArray: Array.isArray || function( obj ) { - return jQuery.type(obj) === "array"; - }, - - isWindow: function( obj ) { - return obj != null && obj == obj.window; - }, - - isNumeric: function( obj ) { - return !isNaN( parseFloat(obj) ) && isFinite( obj ); - }, - - type: function( obj ) { - if ( obj == null ) { - return String( obj ); - } - return typeof obj === "object" || typeof obj === "function" ? - class2type[ core_toString.call(obj) ] || "object" : - typeof obj; - }, - - isPlainObject: function( obj ) { - // Must be an Object. - // Because of IE, we also have to check the presence of the constructor property. - // Make sure that DOM nodes and window objects don't pass through, as well - if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { - return false; - } - - try { - // Not own constructor property must be Object - if ( obj.constructor && - !core_hasOwn.call(obj, "constructor") && - !core_hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { - return false; - } - } catch ( e ) { - // IE8,9 Will throw exceptions on certain host objects #9897 - return false; - } - - // Own properties are enumerated firstly, so to speed up, - // if last one is own, then all properties are own. - - var key; - for ( key in obj ) {} - - return key === undefined || core_hasOwn.call( obj, key ); - }, - - isEmptyObject: function( obj ) { - var name; - for ( name in obj ) { - return false; - } - return true; - }, - - error: function( msg ) { - throw new Error( msg ); - }, - - // data: string of html - // context (optional): If specified, the fragment will be created in this context, defaults to document - // keepScripts (optional): If true, will include scripts passed in the html string - parseHTML: function( data, context, keepScripts ) { - if ( !data || typeof data !== "string" ) { - return null; - } - if ( typeof context === "boolean" ) { - keepScripts = context; - context = false; - } - context = context || document; - - var parsed = rsingleTag.exec( data ), - scripts = !keepScripts && []; - - // Single tag - if ( parsed ) { - return [ context.createElement( parsed[1] ) ]; - } - - parsed = jQuery.buildFragment( [ data ], context, scripts ); - if ( scripts ) { - jQuery( scripts ).remove(); - } - return jQuery.merge( [], parsed.childNodes ); - }, - - parseJSON: function( data ) { - // Attempt to parse using the native JSON parser first - if ( window.JSON && window.JSON.parse ) { - return window.JSON.parse( data ); - } - - if ( data === null ) { - return data; - } - - if ( typeof data === "string" ) { - - // Make sure leading/trailing whitespace is removed (IE can't handle it) - data = jQuery.trim( data ); - - if ( data ) { - // Make sure the incoming data is actual JSON - // Logic borrowed from http://json.org/json2.js - if ( rvalidchars.test( data.replace( rvalidescape, "@" ) - .replace( rvalidtokens, "]" ) - .replace( rvalidbraces, "")) ) { - - return ( new Function( "return " + data ) )(); - } - } - } - - jQuery.error( "Invalid JSON: " + data ); - }, - - // Cross-browser xml parsing - parseXML: function( data ) { - var xml, tmp; - if ( !data || typeof data !== "string" ) { - return null; - } - try { - if ( window.DOMParser ) { // Standard - tmp = new DOMParser(); - xml = tmp.parseFromString( data , "text/xml" ); - } else { // IE - xml = new ActiveXObject( "Microsoft.XMLDOM" ); - xml.async = "false"; - xml.loadXML( data ); - } - } catch( e ) { - xml = undefined; - } - if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) { - jQuery.error( "Invalid XML: " + data ); - } - return xml; - }, - - noop: function() {}, - - // Evaluates a script in a global context - // Workarounds based on findings by Jim Driscoll - // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context - globalEval: function( data ) { - if ( data && jQuery.trim( data ) ) { - // We use execScript on Internet Explorer - // We use an anonymous function so that context is window - // rather than jQuery in Firefox - ( window.execScript || function( data ) { - window[ "eval" ].call( window, data ); - } )( data ); - } - }, - - // Convert dashed to camelCase; used by the css and data modules - // Microsoft forgot to hump their vendor prefix (#9572) - camelCase: function( string ) { - return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); - }, - - nodeName: function( elem, name ) { - return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); - }, - - // args is for internal usage only - each: function( obj, callback, args ) { - var value, - i = 0, - length = obj.length, - isArray = isArraylike( obj ); - - if ( args ) { - if ( isArray ) { - for ( ; i < length; i++ ) { - value = callback.apply( obj[ i ], args ); - - if ( value === false ) { - break; - } - } - } else { - for ( i in obj ) { - value = callback.apply( obj[ i ], args ); - - if ( value === false ) { - break; - } - } - } - - // A special, fast, case for the most common use of each - } else { - if ( isArray ) { - for ( ; i < length; i++ ) { - value = callback.call( obj[ i ], i, obj[ i ] ); - - if ( value === false ) { - break; - } - } - } else { - for ( i in obj ) { - value = callback.call( obj[ i ], i, obj[ i ] ); - - if ( value === false ) { - break; - } - } - } - } - - return obj; - }, - - // Use native String.trim function wherever possible - trim: core_trim && !core_trim.call("\uFEFF\xA0") ? - function( text ) { - return text == null ? - "" : - core_trim.call( text ); - } : - - // Otherwise use our own trimming functionality - function( text ) { - return text == null ? - "" : - ( text + "" ).replace( rtrim, "" ); - }, - - // results is for internal usage only - makeArray: function( arr, results ) { - var ret = results || []; - - if ( arr != null ) { - if ( isArraylike( Object(arr) ) ) { - jQuery.merge( ret, - typeof arr === "string" ? - [ arr ] : arr - ); - } else { - core_push.call( ret, arr ); - } - } - - return ret; - }, - - inArray: function( elem, arr, i ) { - var len; - - if ( arr ) { - if ( core_indexOf ) { - return core_indexOf.call( arr, elem, i ); - } - - len = arr.length; - i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; - - for ( ; i < len; i++ ) { - // Skip accessing in sparse arrays - if ( i in arr && arr[ i ] === elem ) { - return i; - } - } - } - - return -1; - }, - - merge: function( first, second ) { - var l = second.length, - i = first.length, - j = 0; - - if ( typeof l === "number" ) { - for ( ; j < l; j++ ) { - first[ i++ ] = second[ j ]; - } - } else { - while ( second[j] !== undefined ) { - first[ i++ ] = second[ j++ ]; - } - } - - first.length = i; - - return first; - }, - - grep: function( elems, callback, inv ) { - var retVal, - ret = [], - i = 0, - length = elems.length; - inv = !!inv; - - // Go through the array, only saving the items - // that pass the validator function - for ( ; i < length; i++ ) { - retVal = !!callback( elems[ i ], i ); - if ( inv !== retVal ) { - ret.push( elems[ i ] ); - } - } - - return ret; - }, - - // arg is for internal usage only - map: function( elems, callback, arg ) { - var value, - i = 0, - length = elems.length, - isArray = isArraylike( elems ), - ret = []; - - // Go through the array, translating each of the items to their - if ( isArray ) { - for ( ; i < length; i++ ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret[ ret.length ] = value; - } - } - - // Go through every key on the object, - } else { - for ( i in elems ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret[ ret.length ] = value; - } - } - } - - // Flatten any nested arrays - return core_concat.apply( [], ret ); - }, - - // A global GUID counter for objects - guid: 1, - - // Bind a function to a context, optionally partially applying any - // arguments. - proxy: function( fn, context ) { - var args, proxy, tmp; - - if ( typeof context === "string" ) { - tmp = fn[ context ]; - context = fn; - fn = tmp; - } - - // Quick check to determine if target is callable, in the spec - // this throws a TypeError, but we will just return undefined. - if ( !jQuery.isFunction( fn ) ) { - return undefined; - } - - // Simulated bind - args = core_slice.call( arguments, 2 ); - proxy = function() { - return fn.apply( context || this, args.concat( core_slice.call( arguments ) ) ); - }; - - // Set the guid of unique handler to the same of original handler, so it can be removed - proxy.guid = fn.guid = fn.guid || jQuery.guid++; - - return proxy; - }, - - // Multifunctional method to get and set values of a collection - // The value/s can optionally be executed if it's a function - access: function( elems, fn, key, value, chainable, emptyGet, raw ) { - var i = 0, - length = elems.length, - bulk = key == null; - - // Sets many values - if ( jQuery.type( key ) === "object" ) { - chainable = true; - for ( i in key ) { - jQuery.access( elems, fn, i, key[i], true, emptyGet, raw ); - } - - // Sets one value - } else if ( value !== undefined ) { - chainable = true; - - if ( !jQuery.isFunction( value ) ) { - raw = true; - } - - if ( bulk ) { - // Bulk operations run against the entire set - if ( raw ) { - fn.call( elems, value ); - fn = null; - - // ...except when executing function values - } else { - bulk = fn; - fn = function( elem, key, value ) { - return bulk.call( jQuery( elem ), value ); - }; - } - } - - if ( fn ) { - for ( ; i < length; i++ ) { - fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) ); - } - } - } - - return chainable ? - elems : - - // Gets - bulk ? - fn.call( elems ) : - length ? fn( elems[0], key ) : emptyGet; - }, - - now: function() { - return ( new Date() ).getTime(); - } -}); - -jQuery.ready.promise = function( obj ) { - if ( !readyList ) { - - readyList = jQuery.Deferred(); - - // Catch cases where $(document).ready() is called after the browser event has already occurred. - // we once tried to use readyState "interactive" here, but it caused issues like the one - // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15 - if ( document.readyState === "complete" ) { - // Handle it asynchronously to allow scripts the opportunity to delay ready - setTimeout( jQuery.ready ); - - // Standards-based browsers support DOMContentLoaded - } else if ( document.addEventListener ) { - // Use the handy event callback - document.addEventListener( "DOMContentLoaded", completed, false ); - - // A fallback to window.onload, that will always work - window.addEventListener( "load", completed, false ); - - // If IE event model is used - } else { - // Ensure firing before onload, maybe late but safe also for iframes - document.attachEvent( "onreadystatechange", completed ); - - // A fallback to window.onload, that will always work - window.attachEvent( "onload", completed ); - - // If IE and not a frame - // continually check to see if the document is ready - var top = false; - - try { - top = window.frameElement == null && document.documentElement; - } catch(e) {} - - if ( top && top.doScroll ) { - (function doScrollCheck() { - if ( !jQuery.isReady ) { - - try { - // Use the trick by Diego Perini - // http://javascript.nwbox.com/IEContentLoaded/ - top.doScroll("left"); - } catch(e) { - return setTimeout( doScrollCheck, 50 ); - } - - // detach all dom ready events - detach(); - - // and execute any waiting functions - jQuery.ready(); - } - })(); - } - } - } - return readyList.promise( obj ); -}; - -// Populate the class2type map -jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) { - class2type[ "[object " + name + "]" ] = name.toLowerCase(); -}); - -function isArraylike( obj ) { - var length = obj.length, - type = jQuery.type( obj ); - - if ( jQuery.isWindow( obj ) ) { - return false; - } - - if ( obj.nodeType === 1 && length ) { - return true; - } - - return type === "array" || type !== "function" && - ( length === 0 || - typeof length === "number" && length > 0 && ( length - 1 ) in obj ); -} - -// All jQuery objects should point back to these -rootjQuery = jQuery(document); -// String to Object options format cache -var optionsCache = {}; - -// Convert String-formatted options into Object-formatted ones and store in cache -function createOptions( options ) { - var object = optionsCache[ options ] = {}; - jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) { - object[ flag ] = true; - }); - return object; -} - -/* - * Create a callback list using the following parameters: - * - * options: an optional list of space-separated options that will change how - * the callback list behaves or a more traditional option object - * - * By default a callback list will act like an event callback list and can be - * "fired" multiple times. - * - * Possible options: - * - * once: will ensure the callback list can only be fired once (like a Deferred) - * - * memory: will keep track of previous values and will call any callback added - * after the list has been fired right away with the latest "memorized" - * values (like a Deferred) - * - * unique: will ensure a callback can only be added once (no duplicate in the list) - * - * stopOnFalse: interrupt callings when a callback returns false - * - */ -jQuery.Callbacks = function( options ) { - - // Convert options from String-formatted to Object-formatted if needed - // (we check in cache first) - options = typeof options === "string" ? - ( optionsCache[ options ] || createOptions( options ) ) : - jQuery.extend( {}, options ); - - var // Flag to know if list is currently firing - firing, - // Last fire value (for non-forgettable lists) - memory, - // Flag to know if list was already fired - fired, - // End of the loop when firing - firingLength, - // Index of currently firing callback (modified by remove if needed) - firingIndex, - // First callback to fire (used internally by add and fireWith) - firingStart, - // Actual callback list - list = [], - // Stack of fire calls for repeatable lists - stack = !options.once && [], - // Fire callbacks - fire = function( data ) { - memory = options.memory && data; - fired = true; - firingIndex = firingStart || 0; - firingStart = 0; - firingLength = list.length; - firing = true; - for ( ; list && firingIndex < firingLength; firingIndex++ ) { - if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { - memory = false; // To prevent further calls using add - break; - } - } - firing = false; - if ( list ) { - if ( stack ) { - if ( stack.length ) { - fire( stack.shift() ); - } - } else if ( memory ) { - list = []; - } else { - self.disable(); - } - } - }, - // Actual Callbacks object - self = { - // Add a callback or a collection of callbacks to the list - add: function() { - if ( list ) { - // First, we save the current length - var start = list.length; - (function add( args ) { - jQuery.each( args, function( _, arg ) { - var type = jQuery.type( arg ); - if ( type === "function" ) { - if ( !options.unique || !self.has( arg ) ) { - list.push( arg ); - } - } else if ( arg && arg.length && type !== "string" ) { - // Inspect recursively - add( arg ); - } - }); - })( arguments ); - // Do we need to add the callbacks to the - // current firing batch? - if ( firing ) { - firingLength = list.length; - // With memory, if we're not firing then - // we should call right away - } else if ( memory ) { - firingStart = start; - fire( memory ); - } - } - return this; - }, - // Remove a callback from the list - remove: function() { - if ( list ) { - jQuery.each( arguments, function( _, arg ) { - var index; - while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { - list.splice( index, 1 ); - // Handle firing indexes - if ( firing ) { - if ( index <= firingLength ) { - firingLength--; - } - if ( index <= firingIndex ) { - firingIndex--; - } - } - } - }); - } - return this; - }, - // Check if a given callback is in the list. - // If no argument is given, return whether or not list has callbacks attached. - has: function( fn ) { - return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length ); - }, - // Remove all callbacks from the list - empty: function() { - list = []; - return this; - }, - // Have the list do nothing anymore - disable: function() { - list = stack = memory = undefined; - return this; - }, - // Is it disabled? - disabled: function() { - return !list; - }, - // Lock the list in its current state - lock: function() { - stack = undefined; - if ( !memory ) { - self.disable(); - } - return this; - }, - // Is it locked? - locked: function() { - return !stack; - }, - // Call all callbacks with the given context and arguments - fireWith: function( context, args ) { - args = args || []; - args = [ context, args.slice ? args.slice() : args ]; - if ( list && ( !fired || stack ) ) { - if ( firing ) { - stack.push( args ); - } else { - fire( args ); - } - } - return this; - }, - // Call all the callbacks with the given arguments - fire: function() { - self.fireWith( this, arguments ); - return this; - }, - // To know if the callbacks have already been called at least once - fired: function() { - return !!fired; - } - }; - - return self; -}; -jQuery.extend({ - - Deferred: function( func ) { - var tuples = [ - // action, add listener, listener list, final state - [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], - [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], - [ "notify", "progress", jQuery.Callbacks("memory") ] - ], - state = "pending", - promise = { - state: function() { - return state; - }, - always: function() { - deferred.done( arguments ).fail( arguments ); - return this; - }, - then: function( /* fnDone, fnFail, fnProgress */ ) { - var fns = arguments; - return jQuery.Deferred(function( newDefer ) { - jQuery.each( tuples, function( i, tuple ) { - var action = tuple[ 0 ], - fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; - // deferred[ done | fail | progress ] for forwarding actions to newDefer - deferred[ tuple[1] ](function() { - var returned = fn && fn.apply( this, arguments ); - if ( returned && jQuery.isFunction( returned.promise ) ) { - returned.promise() - .done( newDefer.resolve ) - .fail( newDefer.reject ) - .progress( newDefer.notify ); - } else { - newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments ); - } - }); - }); - fns = null; - }).promise(); - }, - // Get a promise for this deferred - // If obj is provided, the promise aspect is added to the object - promise: function( obj ) { - return obj != null ? jQuery.extend( obj, promise ) : promise; - } - }, - deferred = {}; - - // Keep pipe for back-compat - promise.pipe = promise.then; - - // Add list-specific methods - jQuery.each( tuples, function( i, tuple ) { - var list = tuple[ 2 ], - stateString = tuple[ 3 ]; - - // promise[ done | fail | progress ] = list.add - promise[ tuple[1] ] = list.add; - - // Handle state - if ( stateString ) { - list.add(function() { - // state = [ resolved | rejected ] - state = stateString; - - // [ reject_list | resolve_list ].disable; progress_list.lock - }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); - } - - // deferred[ resolve | reject | notify ] - deferred[ tuple[0] ] = function() { - deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments ); - return this; - }; - deferred[ tuple[0] + "With" ] = list.fireWith; - }); - - // Make the deferred a promise - promise.promise( deferred ); - - // Call given func if any - if ( func ) { - func.call( deferred, deferred ); - } - - // All done! - return deferred; - }, - - // Deferred helper - when: function( subordinate /* , ..., subordinateN */ ) { - var i = 0, - resolveValues = core_slice.call( arguments ), - length = resolveValues.length, - - // the count of uncompleted subordinates - remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, - - // the master Deferred. If resolveValues consist of only a single Deferred, just use that. - deferred = remaining === 1 ? subordinate : jQuery.Deferred(), - - // Update function for both resolve and progress values - updateFunc = function( i, contexts, values ) { - return function( value ) { - contexts[ i ] = this; - values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value; - if( values === progressValues ) { - deferred.notifyWith( contexts, values ); - } else if ( !( --remaining ) ) { - deferred.resolveWith( contexts, values ); - } - }; - }, - - progressValues, progressContexts, resolveContexts; - - // add listeners to Deferred subordinates; treat others as resolved - if ( length > 1 ) { - progressValues = new Array( length ); - progressContexts = new Array( length ); - resolveContexts = new Array( length ); - for ( ; i < length; i++ ) { - if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) { - resolveValues[ i ].promise() - .done( updateFunc( i, resolveContexts, resolveValues ) ) - .fail( deferred.reject ) - .progress( updateFunc( i, progressContexts, progressValues ) ); - } else { - --remaining; - } - } - } - - // if we're not waiting on anything, resolve the master - if ( !remaining ) { - deferred.resolveWith( resolveContexts, resolveValues ); - } - - return deferred.promise(); - } -}); -jQuery.support = (function() { - - var support, all, a, - input, select, fragment, - opt, eventName, isSupported, i, - div = document.createElement("div"); - - // Setup - div.setAttribute( "className", "t" ); - div.innerHTML = " <link/><table></table><a href='/a'>a</a><input type='checkbox'/>"; - - // Support tests won't run in some limited or non-browser environments - all = div.getElementsByTagName("*"); - a = div.getElementsByTagName("a")[ 0 ]; - if ( !all || !a || !all.length ) { - return {}; - } - - // First batch of tests - select = document.createElement("select"); - opt = select.appendChild( document.createElement("option") ); - input = div.getElementsByTagName("input")[ 0 ]; - - a.style.cssText = "top:1px;float:left;opacity:.5"; - support = { - // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) - getSetAttribute: div.className !== "t", - - // IE strips leading whitespace when .innerHTML is used - leadingWhitespace: div.firstChild.nodeType === 3, - - // Make sure that tbody elements aren't automatically inserted - // IE will insert them into empty tables - tbody: !div.getElementsByTagName("tbody").length, - - // Make sure that link elements get serialized correctly by innerHTML - // This requires a wrapper element in IE - htmlSerialize: !!div.getElementsByTagName("link").length, - - // Get the style information from getAttribute - // (IE uses .cssText instead) - style: /top/.test( a.getAttribute("style") ), - - // Make sure that URLs aren't manipulated - // (IE normalizes it by default) - hrefNormalized: a.getAttribute("href") === "/a", - - // Make sure that element opacity exists - // (IE uses filter instead) - // Use a regex to work around a WebKit issue. See #5145 - opacity: /^0.5/.test( a.style.opacity ), - - // Verify style float existence - // (IE uses styleFloat instead of cssFloat) - cssFloat: !!a.style.cssFloat, - - // Check the default checkbox/radio value ("" on WebKit; "on" elsewhere) - checkOn: !!input.value, - - // Make sure that a selected-by-default option has a working selected property. - // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) - optSelected: opt.selected, - - // Tests for enctype support on a form (#6743) - enctype: !!document.createElement("form").enctype, - - // Makes sure cloning an html5 element does not cause problems - // Where outerHTML is undefined, this still works - html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav></:nav>", - - // jQuery.support.boxModel DEPRECATED in 1.8 since we don't support Quirks Mode - boxModel: document.compatMode === "CSS1Compat", - - // Will be defined later - deleteExpando: true, - noCloneEvent: true, - inlineBlockNeedsLayout: false, - shrinkWrapBlocks: false, - reliableMarginRight: true, - boxSizingReliable: true, - pixelPosition: false - }; - - // Make sure checked status is properly cloned - input.checked = true; - support.noCloneChecked = input.cloneNode( true ).checked; - - // Make sure that the options inside disabled selects aren't marked as disabled - // (WebKit marks them as disabled) - select.disabled = true; - support.optDisabled = !opt.disabled; - - // Support: IE<9 - try { - delete div.test; - } catch( e ) { - support.deleteExpando = false; - } - - // Check if we can trust getAttribute("value") - input = document.createElement("input"); - input.setAttribute( "value", "" ); - support.input = input.getAttribute( "value" ) === ""; - - // Check if an input maintains its value after becoming a radio - input.value = "t"; - input.setAttribute( "type", "radio" ); - support.radioValue = input.value === "t"; - - // #11217 - WebKit loses check when the name is after the checked attribute - input.setAttribute( "checked", "t" ); - input.setAttribute( "name", "t" ); - - fragment = document.createDocumentFragment(); - fragment.appendChild( input ); - - // Check if a disconnected checkbox will retain its checked - // value of true after appended to the DOM (IE6/7) - support.appendChecked = input.checked; - - // WebKit doesn't clone checked state correctly in fragments - support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; - - // Support: IE<9 - // Opera does not clone events (and typeof div.attachEvent === undefined). - // IE9-10 clones events bound via attachEvent, but they don't trigger with .click() - if ( div.attachEvent ) { - div.attachEvent( "onclick", function() { - support.noCloneEvent = false; - }); - - div.cloneNode( true ).click(); - } - - // Support: IE<9 (lack submit/change bubble), Firefox 17+ (lack focusin event) - // Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP), test/csp.php - for ( i in { submit: true, change: true, focusin: true }) { - div.setAttribute( eventName = "on" + i, "t" ); - - support[ i + "Bubbles" ] = eventName in window || div.attributes[ eventName ].expando === false; - } - - div.style.backgroundClip = "content-box"; - div.cloneNode( true ).style.backgroundClip = ""; - support.clearCloneStyle = div.style.backgroundClip === "content-box"; - - // Run tests that need a body at doc ready - jQuery(function() { - var container, marginDiv, tds, - divReset = "padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;", - body = document.getElementsByTagName("body")[0]; - - if ( !body ) { - // Return for frameset docs that don't have a body - return; - } - - container = document.createElement("div"); - container.style.cssText = "border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px"; - - body.appendChild( container ).appendChild( div ); - - // Support: IE8 - // Check if table cells still have offsetWidth/Height when they are set - // to display:none and there are still other visible table cells in a - // table row; if so, offsetWidth/Height are not reliable for use when - // determining if an element has been hidden directly using - // display:none (it is still safe to use offsets if a parent element is - // hidden; don safety goggles and see bug #4512 for more information). - div.innerHTML = "<table><tr><td></td><td>t</td></tr></table>"; - tds = div.getElementsByTagName("td"); - tds[ 0 ].style.cssText = "padding:0;margin:0;border:0;display:none"; - isSupported = ( tds[ 0 ].offsetHeight === 0 ); - - tds[ 0 ].style.display = ""; - tds[ 1 ].style.display = "none"; - - // Support: IE8 - // Check if empty table cells still have offsetWidth/Height - support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); - - // Check box-sizing and margin behavior - div.innerHTML = ""; - div.style.cssText = "box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;"; - support.boxSizing = ( div.offsetWidth === 4 ); - support.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== 1 ); - - // Use window.getComputedStyle because jsdom on node.js will break without it. - if ( window.getComputedStyle ) { - support.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== "1%"; - support.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px"; - - // Check if div with explicit width and no margin-right incorrectly - // gets computed margin-right based on width of container. (#3333) - // Fails in WebKit before Feb 2011 nightlies - // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right - marginDiv = div.appendChild( document.createElement("div") ); - marginDiv.style.cssText = div.style.cssText = divReset; - marginDiv.style.marginRight = marginDiv.style.width = "0"; - div.style.width = "1px"; - - support.reliableMarginRight = - !parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight ); - } - - if ( typeof div.style.zoom !== core_strundefined ) { - // Support: IE<8 - // Check if natively block-level elements act like inline-block - // elements when setting their display to 'inline' and giving - // them layout - div.innerHTML = ""; - div.style.cssText = divReset + "width:1px;padding:1px;display:inline;zoom:1"; - support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 ); - - // Support: IE6 - // Check if elements with layout shrink-wrap their children - div.style.display = "block"; - div.innerHTML = "<div></div>"; - div.firstChild.style.width = "5px"; - support.shrinkWrapBlocks = ( div.offsetWidth !== 3 ); - - if ( support.inlineBlockNeedsLayout ) { - // Prevent IE 6 from affecting layout for positioned elements #11048 - // Prevent IE from shrinking the body in IE 7 mode #12869 - // Support: IE<8 - body.style.zoom = 1; - } - } - - body.removeChild( container ); - - // Null elements to avoid leaks in IE - container = div = tds = marginDiv = null; - }); - - // Null elements to avoid leaks in IE - all = select = fragment = opt = a = input = null; - - return support; -})(); - -var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/, - rmultiDash = /([A-Z])/g; - -function internalData( elem, name, data, pvt /* Internal Use Only */ ){ - if ( !jQuery.acceptData( elem ) ) { - return; - } - - var thisCache, ret, - internalKey = jQuery.expando, - getByName = typeof name === "string", - - // We have to handle DOM nodes and JS objects differently because IE6-7 - // can't GC object references properly across the DOM-JS boundary - isNode = elem.nodeType, - - // Only DOM nodes need the global jQuery cache; JS object data is - // attached directly to the object so GC can occur automatically - cache = isNode ? jQuery.cache : elem, - - // Only defining an ID for JS objects if its cache already exists allows - // the code to shortcut on the same path as a DOM node with no cache - id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey; - - // Avoid doing any more work than we need to when trying to get data on an - // object that has no data at all - if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && getByName && data === undefined ) { - return; - } - - if ( !id ) { - // Only DOM nodes need a new unique ID for each element since their data - // ends up in the global cache - if ( isNode ) { - elem[ internalKey ] = id = core_deletedIds.pop() || jQuery.guid++; - } else { - id = internalKey; - } - } - - if ( !cache[ id ] ) { - cache[ id ] = {}; - - // Avoids exposing jQuery metadata on plain JS objects when the object - // is serialized using JSON.stringify - if ( !isNode ) { - cache[ id ].toJSON = jQuery.noop; - } - } - - // An object can be passed to jQuery.data instead of a key/value pair; this gets - // shallow copied over onto the existing cache - if ( typeof name === "object" || typeof name === "function" ) { - if ( pvt ) { - cache[ id ] = jQuery.extend( cache[ id ], name ); - } else { - cache[ id ].data = jQuery.extend( cache[ id ].data, name ); - } - } - - thisCache = cache[ id ]; - - // jQuery data() is stored in a separate object inside the object's internal data - // cache in order to avoid key collisions between internal data and user-defined - // data. - if ( !pvt ) { - if ( !thisCache.data ) { - thisCache.data = {}; - } - - thisCache = thisCache.data; - } - - if ( data !== undefined ) { - thisCache[ jQuery.camelCase( name ) ] = data; - } - - // Check for both converted-to-camel and non-converted data property names - // If a data property was specified - if ( getByName ) { - - // First Try to find as-is property data - ret = thisCache[ name ]; - - // Test for null|undefined property data - if ( ret == null ) { - - // Try to find the camelCased property - ret = thisCache[ jQuery.camelCase( name ) ]; - } - } else { - ret = thisCache; - } - - return ret; -} - -function internalRemoveData( elem, name, pvt ) { - if ( !jQuery.acceptData( elem ) ) { - return; - } - - var i, l, thisCache, - isNode = elem.nodeType, - - // See jQuery.data for more information - cache = isNode ? jQuery.cache : elem, - id = isNode ? elem[ jQuery.expando ] : jQuery.expando; - - // If there is already no cache entry for this object, there is no - // purpose in continuing - if ( !cache[ id ] ) { - return; - } - - if ( name ) { - - thisCache = pvt ? cache[ id ] : cache[ id ].data; - - if ( thisCache ) { - - // Support array or space separated string names for data keys - if ( !jQuery.isArray( name ) ) { - - // try the string as a key before any manipulation - if ( name in thisCache ) { - name = [ name ]; - } else { - - // split the camel cased version by spaces unless a key with the spaces exists - name = jQuery.camelCase( name ); - if ( name in thisCache ) { - name = [ name ]; - } else { - name = name.split(" "); - } - } - } else { - // If "name" is an array of keys... - // When data is initially created, via ("key", "val") signature, - // keys will be converted to camelCase. - // Since there is no way to tell _how_ a key was added, remove - // both plain key and camelCase key. #12786 - // This will only penalize the array argument path. - name = name.concat( jQuery.map( name, jQuery.camelCase ) ); - } - - for ( i = 0, l = name.length; i < l; i++ ) { - delete thisCache[ name[i] ]; - } - - // If there is no data left in the cache, we want to continue - // and let the cache object itself get destroyed - if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) { - return; - } - } - } - - // See jQuery.data for more information - if ( !pvt ) { - delete cache[ id ].data; - - // Don't destroy the parent cache unless the internal data object - // had been the only thing left in it - if ( !isEmptyDataObject( cache[ id ] ) ) { - return; - } - } - - // Destroy the cache - if ( isNode ) { - jQuery.cleanData( [ elem ], true ); - - // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080) - } else if ( jQuery.support.deleteExpando || cache != cache.window ) { - delete cache[ id ]; - - // When all else fails, null - } else { - cache[ id ] = null; - } -} - -jQuery.extend({ - cache: {}, - - // Unique for each copy of jQuery on the page - // Non-digits removed to match rinlinejQuery - expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" ), - - // The following elements throw uncatchable exceptions if you - // attempt to add expando properties to them. - noData: { - "embed": true, - // Ban all objects except for Flash (which handle expandos) - "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", - "applet": true - }, - - hasData: function( elem ) { - elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; - return !!elem && !isEmptyDataObject( elem ); - }, - - data: function( elem, name, data ) { - return internalData( elem, name, data ); - }, - - removeData: function( elem, name ) { - return internalRemoveData( elem, name ); - }, - - // For internal use only. - _data: function( elem, name, data ) { - return internalData( elem, name, data, true ); - }, - - _removeData: function( elem, name ) { - return internalRemoveData( elem, name, true ); - }, - - // A method for determining if a DOM node can handle the data expando - acceptData: function( elem ) { - // Do not set data on non-element because it will not be cleared (#8335). - if ( elem.nodeType && elem.nodeType !== 1 && elem.nodeType !== 9 ) { - return false; - } - - var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ]; - - // nodes accept data unless otherwise specified; rejection can be conditional - return !noData || noData !== true && elem.getAttribute("classid") === noData; - } -}); - -jQuery.fn.extend({ - data: function( key, value ) { - var attrs, name, - elem = this[0], - i = 0, - data = null; - - // Gets all values - if ( key === undefined ) { - if ( this.length ) { - data = jQuery.data( elem ); - - if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) { - attrs = elem.attributes; - for ( ; i < attrs.length; i++ ) { - name = attrs[i].name; - - if ( !name.indexOf( "data-" ) ) { - name = jQuery.camelCase( name.slice(5) ); - - dataAttr( elem, name, data[ name ] ); - } - } - jQuery._data( elem, "parsedAttrs", true ); - } - } - - return data; - } - - // Sets multiple values - if ( typeof key === "object" ) { - return this.each(function() { - jQuery.data( this, key ); - }); - } - - return jQuery.access( this, function( value ) { - - if ( value === undefined ) { - // Try to fetch any internally stored data first - return elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : null; - } - - this.each(function() { - jQuery.data( this, key, value ); - }); - }, null, value, arguments.length > 1, null, true ); - }, - - removeData: function( key ) { - return this.each(function() { - jQuery.removeData( this, key ); - }); - } -}); - -function dataAttr( elem, key, data ) { - // If nothing was found internally, try to fetch any - // data from the HTML5 data-* attribute - if ( data === undefined && elem.nodeType === 1 ) { - - var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); - - data = elem.getAttribute( name ); - - if ( typeof data === "string" ) { - try { - data = data === "true" ? true : - data === "false" ? false : - data === "null" ? null : - // Only convert to a number if it doesn't change the string - +data + "" === data ? +data : - rbrace.test( data ) ? jQuery.parseJSON( data ) : - data; - } catch( e ) {} - - // Make sure we set the data so it isn't changed later - jQuery.data( elem, key, data ); - - } else { - data = undefined; - } - } - - return data; -} - -// checks a cache object for emptiness -function isEmptyDataObject( obj ) { - var name; - for ( name in obj ) { - - // if the public data object is empty, the private is still empty - if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { - continue; - } - if ( name !== "toJSON" ) { - return false; - } - } - - return true; -} -jQuery.extend({ - queue: function( elem, type, data ) { - var queue; - - if ( elem ) { - type = ( type || "fx" ) + "queue"; - queue = jQuery._data( elem, type ); - - // Speed up dequeue by getting out quickly if this is just a lookup - if ( data ) { - if ( !queue || jQuery.isArray(data) ) { - queue = jQuery._data( elem, type, jQuery.makeArray(data) ); - } else { - queue.push( data ); - } - } - return queue || []; - } - }, - - dequeue: function( elem, type ) { - type = type || "fx"; - - var queue = jQuery.queue( elem, type ), - startLength = queue.length, - fn = queue.shift(), - hooks = jQuery._queueHooks( elem, type ), - next = function() { - jQuery.dequeue( elem, type ); - }; - - // If the fx queue is dequeued, always remove the progress sentinel - if ( fn === "inprogress" ) { - fn = queue.shift(); - startLength--; - } - - hooks.cur = fn; - if ( fn ) { - - // Add a progress sentinel to prevent the fx queue from being - // automatically dequeued - if ( type === "fx" ) { - queue.unshift( "inprogress" ); - } - - // clear up the last queue stop function - delete hooks.stop; - fn.call( elem, next, hooks ); - } - - if ( !startLength && hooks ) { - hooks.empty.fire(); - } - }, - - // not intended for public consumption - generates a queueHooks object, or returns the current one - _queueHooks: function( elem, type ) { - var key = type + "queueHooks"; - return jQuery._data( elem, key ) || jQuery._data( elem, key, { - empty: jQuery.Callbacks("once memory").add(function() { - jQuery._removeData( elem, type + "queue" ); - jQuery._removeData( elem, key ); - }) - }); - } -}); - -jQuery.fn.extend({ - queue: function( type, data ) { - var setter = 2; - - if ( typeof type !== "string" ) { - data = type; - type = "fx"; - setter--; - } - - if ( arguments.length < setter ) { - return jQuery.queue( this[0], type ); - } - - return data === undefined ? - this : - this.each(function() { - var queue = jQuery.queue( this, type, data ); - - // ensure a hooks for this queue - jQuery._queueHooks( this, type ); - - if ( type === "fx" && queue[0] !== "inprogress" ) { - jQuery.dequeue( this, type ); - } - }); - }, - dequeue: function( type ) { - return this.each(function() { - jQuery.dequeue( this, type ); - }); - }, - // Based off of the plugin by Clint Helfers, with permission. - // http://blindsignals.com/index.php/2009/07/jquery-delay/ - delay: function( time, type ) { - time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; - type = type || "fx"; - - return this.queue( type, function( next, hooks ) { - var timeout = setTimeout( next, time ); - hooks.stop = function() { - clearTimeout( timeout ); - }; - }); - }, - clearQueue: function( type ) { - return this.queue( type || "fx", [] ); - }, - // Get a promise resolved when queues of a certain type - // are emptied (fx is the type by default) - promise: function( type, obj ) { - var tmp, - count = 1, - defer = jQuery.Deferred(), - elements = this, - i = this.length, - resolve = function() { - if ( !( --count ) ) { - defer.resolveWith( elements, [ elements ] ); - } - }; - - if ( typeof type !== "string" ) { - obj = type; - type = undefined; - } - type = type || "fx"; - - while( i-- ) { - tmp = jQuery._data( elements[ i ], type + "queueHooks" ); - if ( tmp && tmp.empty ) { - count++; - tmp.empty.add( resolve ); - } - } - resolve(); - return defer.promise( obj ); - } -}); -var nodeHook, boolHook, - rclass = /[\t\r\n]/g, - rreturn = /\r/g, - rfocusable = /^(?:input|select|textarea|button|object)$/i, - rclickable = /^(?:a|area)$/i, - rboolean = /^(?:checked|selected|autofocus|autoplay|async|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped)$/i, - ruseDefault = /^(?:checked|selected)$/i, - getSetAttribute = jQuery.support.getSetAttribute, - getSetInput = jQuery.support.input; - -jQuery.fn.extend({ - attr: function( name, value ) { - return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 ); - }, - - removeAttr: function( name ) { - return this.each(function() { - jQuery.removeAttr( this, name ); - }); - }, - - prop: function( name, value ) { - return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 ); - }, - - removeProp: function( name ) { - name = jQuery.propFix[ name ] || name; - return this.each(function() { - // try/catch handles cases where IE balks (such as removing a property on window) - try { - this[ name ] = undefined; - delete this[ name ]; - } catch( e ) {} - }); - }, - - addClass: function( value ) { - var classes, elem, cur, clazz, j, - i = 0, - len = this.length, - proceed = typeof value === "string" && value; - - if ( jQuery.isFunction( value ) ) { - return this.each(function( j ) { - jQuery( this ).addClass( value.call( this, j, this.className ) ); - }); - } - - if ( proceed ) { - // The disjunction here is for better compressibility (see removeClass) - classes = ( value || "" ).match( core_rnotwhite ) || []; - - for ( ; i < len; i++ ) { - elem = this[ i ]; - cur = elem.nodeType === 1 && ( elem.className ? - ( " " + elem.className + " " ).replace( rclass, " " ) : - " " - ); - - if ( cur ) { - j = 0; - while ( (clazz = classes[j++]) ) { - if ( cur.indexOf( " " + clazz + " " ) < 0 ) { - cur += clazz + " "; - } - } - elem.className = jQuery.trim( cur ); - - } - } - } - - return this; - }, - - removeClass: function( value ) { - var classes, elem, cur, clazz, j, - i = 0, - len = this.length, - proceed = arguments.length === 0 || typeof value === "string" && value; - - if ( jQuery.isFunction( value ) ) { - return this.each(function( j ) { - jQuery( this ).removeClass( value.call( this, j, this.className ) ); - }); - } - if ( proceed ) { - classes = ( value || "" ).match( core_rnotwhite ) || []; - - for ( ; i < len; i++ ) { - elem = this[ i ]; - // This expression is here for better compressibility (see addClass) - cur = elem.nodeType === 1 && ( elem.className ? - ( " " + elem.className + " " ).replace( rclass, " " ) : - "" - ); - - if ( cur ) { - j = 0; - while ( (clazz = classes[j++]) ) { - // Remove *all* instances - while ( cur.indexOf( " " + clazz + " " ) >= 0 ) { - cur = cur.replace( " " + clazz + " ", " " ); - } - } - elem.className = value ? jQuery.trim( cur ) : ""; - } - } - } - - return this; - }, - - toggleClass: function( value, stateVal ) { - var type = typeof value, - isBool = typeof stateVal === "boolean"; - - if ( jQuery.isFunction( value ) ) { - return this.each(function( i ) { - jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal ); - }); - } - - return this.each(function() { - if ( type === "string" ) { - // toggle individual class names - var className, - i = 0, - self = jQuery( this ), - state = stateVal, - classNames = value.match( core_rnotwhite ) || []; - - while ( (className = classNames[ i++ ]) ) { - // check each className given, space separated list - state = isBool ? state : !self.hasClass( className ); - self[ state ? "addClass" : "removeClass" ]( className ); - } - - // Toggle whole class name - } else if ( type === core_strundefined || type === "boolean" ) { - if ( this.className ) { - // store className if set - jQuery._data( this, "__className__", this.className ); - } - - // If the element has a class name or if we're passed "false", - // then remove the whole classname (if there was one, the above saved it). - // Otherwise bring back whatever was previously saved (if anything), - // falling back to the empty string if nothing was stored. - this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; - } - }); - }, - - hasClass: function( selector ) { - var className = " " + selector + " ", - i = 0, - l = this.length; - for ( ; i < l; i++ ) { - if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) { - return true; - } - } - - return false; - }, - - val: function( value ) { - var ret, hooks, isFunction, - elem = this[0]; - - if ( !arguments.length ) { - if ( elem ) { - hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ]; - - if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { - return ret; - } - - ret = elem.value; - - return typeof ret === "string" ? - // handle most common string cases - ret.replace(rreturn, "") : - // handle cases where value is null/undef or number - ret == null ? "" : ret; - } - - return; - } - - isFunction = jQuery.isFunction( value ); - - return this.each(function( i ) { - var val, - self = jQuery(this); - - if ( this.nodeType !== 1 ) { - return; - } - - if ( isFunction ) { - val = value.call( this, i, self.val() ); - } else { - val = value; - } - - // Treat null/undefined as ""; convert numbers to string - if ( val == null ) { - val = ""; - } else if ( typeof val === "number" ) { - val += ""; - } else if ( jQuery.isArray( val ) ) { - val = jQuery.map(val, function ( value ) { - return value == null ? "" : value + ""; - }); - } - - hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; - - // If set returns undefined, fall back to normal setting - if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) { - this.value = val; - } - }); - } -}); - -jQuery.extend({ - valHooks: { - option: { - get: function( elem ) { - // attributes.value is undefined in Blackberry 4.7 but - // uses .value. See #6932 - var val = elem.attributes.value; - return !val || val.specified ? elem.value : elem.text; - } - }, - select: { - get: function( elem ) { - var value, option, - options = elem.options, - index = elem.selectedIndex, - one = elem.type === "select-one" || index < 0, - values = one ? null : [], - max = one ? index + 1 : options.length, - i = index < 0 ? - max : - one ? index : 0; - - // Loop through all the selected options - for ( ; i < max; i++ ) { - option = options[ i ]; - - // oldIE doesn't update selected after form reset (#2551) - if ( ( option.selected || i === index ) && - // Don't return options that are disabled or in a disabled optgroup - ( jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null ) && - ( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) { - - // Get the specific value for the option - value = jQuery( option ).val(); - - // We don't need an array for one selects - if ( one ) { - return value; - } - - // Multi-Selects return an array - values.push( value ); - } - } - - return values; - }, - - set: function( elem, value ) { - var values = jQuery.makeArray( value ); - - jQuery(elem).find("option").each(function() { - this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; - }); - - if ( !values.length ) { - elem.selectedIndex = -1; - } - return values; - } - } - }, - - attr: function( elem, name, value ) { - var hooks, notxml, ret, - nType = elem.nodeType; - - // don't get/set attributes on text, comment and attribute nodes - if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - // Fallback to prop when attributes are not supported - if ( typeof elem.getAttribute === core_strundefined ) { - return jQuery.prop( elem, name, value ); - } - - notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); - - // All attributes are lowercase - // Grab necessary hook if one is defined - if ( notxml ) { - name = name.toLowerCase(); - hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook ); - } - - if ( value !== undefined ) { - - if ( value === null ) { - jQuery.removeAttr( elem, name ); - - } else if ( hooks && notxml && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { - return ret; - - } else { - elem.setAttribute( name, value + "" ); - return value; - } - - } else if ( hooks && notxml && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { - return ret; - - } else { - - // In IE9+, Flash objects don't have .getAttribute (#12945) - // Support: IE9+ - if ( typeof elem.getAttribute !== core_strundefined ) { - ret = elem.getAttribute( name ); - } - - // Non-existent attributes return null, we normalize to undefined - return ret == null ? - undefined : - ret; - } - }, - - removeAttr: function( elem, value ) { - var name, propName, - i = 0, - attrNames = value && value.match( core_rnotwhite ); - - if ( attrNames && elem.nodeType === 1 ) { - while ( (name = attrNames[i++]) ) { - propName = jQuery.propFix[ name ] || name; - - // Boolean attributes get special treatment (#10870) - if ( rboolean.test( name ) ) { - // Set corresponding property to false for boolean attributes - // Also clear defaultChecked/defaultSelected (if appropriate) for IE<8 - if ( !getSetAttribute && ruseDefault.test( name ) ) { - elem[ jQuery.camelCase( "default-" + name ) ] = - elem[ propName ] = false; - } else { - elem[ propName ] = false; - } - - // See #9699 for explanation of this approach (setting first, then removal) - } else { - jQuery.attr( elem, name, "" ); - } - - elem.removeAttribute( getSetAttribute ? name : propName ); - } - } - }, - - attrHooks: { - type: { - set: function( elem, value ) { - if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) { - // Setting the type on a radio button after the value resets the value in IE6-9 - // Reset value to default in case type is set after value during creation - var val = elem.value; - elem.setAttribute( "type", value ); - if ( val ) { - elem.value = val; - } - return value; - } - } - } - }, - - propFix: { - tabindex: "tabIndex", - readonly: "readOnly", - "for": "htmlFor", - "class": "className", - maxlength: "maxLength", - cellspacing: "cellSpacing", - cellpadding: "cellPadding", - rowspan: "rowSpan", - colspan: "colSpan", - usemap: "useMap", - frameborder: "frameBorder", - contenteditable: "contentEditable" - }, - - prop: function( elem, name, value ) { - var ret, hooks, notxml, - nType = elem.nodeType; - - // don't get/set properties on text, comment and attribute nodes - if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); - - if ( notxml ) { - // Fix name and attach hooks - name = jQuery.propFix[ name ] || name; - hooks = jQuery.propHooks[ name ]; - } - - if ( value !== undefined ) { - if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { - return ret; - - } else { - return ( elem[ name ] = value ); - } - - } else { - if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { - return ret; - - } else { - return elem[ name ]; - } - } - }, - - propHooks: { - tabIndex: { - get: function( elem ) { - // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set - // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ - var attributeNode = elem.getAttributeNode("tabindex"); - - return attributeNode && attributeNode.specified ? - parseInt( attributeNode.value, 10 ) : - rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? - 0 : - undefined; - } - } - } -}); - -// Hook for boolean attributes -boolHook = { - get: function( elem, name ) { - var - // Use .prop to determine if this attribute is understood as boolean - prop = jQuery.prop( elem, name ), - - // Fetch it accordingly - attr = typeof prop === "boolean" && elem.getAttribute( name ), - detail = typeof prop === "boolean" ? - - getSetInput && getSetAttribute ? - attr != null : - // oldIE fabricates an empty string for missing boolean attributes - // and conflates checked/selected into attroperties - ruseDefault.test( name ) ? - elem[ jQuery.camelCase( "default-" + name ) ] : - !!attr : - - // fetch an attribute node for properties not recognized as boolean - elem.getAttributeNode( name ); - - return detail && detail.value !== false ? - name.toLowerCase() : - undefined; - }, - set: function( elem, value, name ) { - if ( value === false ) { - // Remove boolean attributes when set to false - jQuery.removeAttr( elem, name ); - } else if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) { - // IE<8 needs the *property* name - elem.setAttribute( !getSetAttribute && jQuery.propFix[ name ] || name, name ); - - // Use defaultChecked and defaultSelected for oldIE - } else { - elem[ jQuery.camelCase( "default-" + name ) ] = elem[ name ] = true; - } - - return name; - } -}; - -// fix oldIE value attroperty -if ( !getSetInput || !getSetAttribute ) { - jQuery.attrHooks.value = { - get: function( elem, name ) { - var ret = elem.getAttributeNode( name ); - return jQuery.nodeName( elem, "input" ) ? - - // Ignore the value *property* by using defaultValue - elem.defaultValue : - - ret && ret.specified ? ret.value : undefined; - }, - set: function( elem, value, name ) { - if ( jQuery.nodeName( elem, "input" ) ) { - // Does not return so that setAttribute is also used - elem.defaultValue = value; - } else { - // Use nodeHook if defined (#1954); otherwise setAttribute is fine - return nodeHook && nodeHook.set( elem, value, name ); - } - } - }; -} - -// IE6/7 do not support getting/setting some attributes with get/setAttribute -if ( !getSetAttribute ) { - - // Use this for any attribute in IE6/7 - // This fixes almost every IE6/7 issue - nodeHook = jQuery.valHooks.button = { - get: function( elem, name ) { - var ret = elem.getAttributeNode( name ); - return ret && ( name === "id" || name === "name" || name === "coords" ? ret.value !== "" : ret.specified ) ? - ret.value : - undefined; - }, - set: function( elem, value, name ) { - // Set the existing or create a new attribute node - var ret = elem.getAttributeNode( name ); - if ( !ret ) { - elem.setAttributeNode( - (ret = elem.ownerDocument.createAttribute( name )) - ); - } - - ret.value = value += ""; - - // Break association with cloned elements by also using setAttribute (#9646) - return name === "value" || value === elem.getAttribute( name ) ? - value : - undefined; - } - }; - - // Set contenteditable to false on removals(#10429) - // Setting to empty string throws an error as an invalid value - jQuery.attrHooks.contenteditable = { - get: nodeHook.get, - set: function( elem, value, name ) { - nodeHook.set( elem, value === "" ? false : value, name ); - } - }; - - // Set width and height to auto instead of 0 on empty string( Bug #8150 ) - // This is for removals - jQuery.each([ "width", "height" ], function( i, name ) { - jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { - set: function( elem, value ) { - if ( value === "" ) { - elem.setAttribute( name, "auto" ); - return value; - } - } - }); - }); -} - - -// Some attributes require a special call on IE -// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx -if ( !jQuery.support.hrefNormalized ) { - jQuery.each([ "href", "src", "width", "height" ], function( i, name ) { - jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { - get: function( elem ) { - var ret = elem.getAttribute( name, 2 ); - return ret == null ? undefined : ret; - } - }); - }); - - // href/src property should get the full normalized URL (#10299/#12915) - jQuery.each([ "href", "src" ], function( i, name ) { - jQuery.propHooks[ name ] = { - get: function( elem ) { - return elem.getAttribute( name, 4 ); - } - }; - }); -} - -if ( !jQuery.support.style ) { - jQuery.attrHooks.style = { - get: function( elem ) { - // Return undefined in the case of empty string - // Note: IE uppercases css property names, but if we were to .toLowerCase() - // .cssText, that would destroy case senstitivity in URL's, like in "background" - return elem.style.cssText || undefined; - }, - set: function( elem, value ) { - return ( elem.style.cssText = value + "" ); - } - }; -} - -// Safari mis-reports the default selected property of an option -// Accessing the parent's selectedIndex property fixes it -if ( !jQuery.support.optSelected ) { - jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, { - get: function( elem ) { - var parent = elem.parentNode; - - if ( parent ) { - parent.selectedIndex; - - // Make sure that it also works with optgroups, see #5701 - if ( parent.parentNode ) { - parent.parentNode.selectedIndex; - } - } - return null; - } - }); -} - -// IE6/7 call enctype encoding -if ( !jQuery.support.enctype ) { - jQuery.propFix.enctype = "encoding"; -} - -// Radios and checkboxes getter/setter -if ( !jQuery.support.checkOn ) { - jQuery.each([ "radio", "checkbox" ], function() { - jQuery.valHooks[ this ] = { - get: function( elem ) { - // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified - return elem.getAttribute("value") === null ? "on" : elem.value; - } - }; - }); -} -jQuery.each([ "radio", "checkbox" ], function() { - jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], { - set: function( elem, value ) { - if ( jQuery.isArray( value ) ) { - return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 ); - } - } - }); -}); -var rformElems = /^(?:input|select|textarea)$/i, - rkeyEvent = /^key/, - rmouseEvent = /^(?:mouse|contextmenu)|click/, - rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, - rtypenamespace = /^([^.]*)(?:\.(.+)|)$/; - -function returnTrue() { - return true; -} - -function returnFalse() { - return false; -} - -/* - * Helper functions for managing events -- not part of the public interface. - * Props to Dean Edwards' addEvent library for many of the ideas. - */ -jQuery.event = { - - global: {}, - - add: function( elem, types, handler, data, selector ) { - var tmp, events, t, handleObjIn, - special, eventHandle, handleObj, - handlers, type, namespaces, origType, - elemData = jQuery._data( elem ); - - // Don't attach events to noData or text/comment nodes (but allow plain objects) - if ( !elemData ) { - return; - } - - // Caller can pass in an object of custom data in lieu of the handler - if ( handler.handler ) { - handleObjIn = handler; - handler = handleObjIn.handler; - selector = handleObjIn.selector; - } - - // Make sure that the handler has a unique ID, used to find/remove it later - if ( !handler.guid ) { - handler.guid = jQuery.guid++; - } - - // Init the element's event structure and main handler, if this is the first - if ( !(events = elemData.events) ) { - events = elemData.events = {}; - } - if ( !(eventHandle = elemData.handle) ) { - eventHandle = elemData.handle = function( e ) { - // Discard the second event of a jQuery.event.trigger() and - // when an event is called after a page has unloaded - return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ? - jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : - undefined; - }; - // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events - eventHandle.elem = elem; - } - - // Handle multiple events separated by a space - // jQuery(...).bind("mouseover mouseout", fn); - types = ( types || "" ).match( core_rnotwhite ) || [""]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[t] ) || []; - type = origType = tmp[1]; - namespaces = ( tmp[2] || "" ).split( "." ).sort(); - - // If event changes its type, use the special event handlers for the changed type - special = jQuery.event.special[ type ] || {}; - - // If selector defined, determine special event api type, otherwise given type - type = ( selector ? special.delegateType : special.bindType ) || type; - - // Update special based on newly reset type - special = jQuery.event.special[ type ] || {}; - - // handleObj is passed to all event handlers - handleObj = jQuery.extend({ - type: type, - origType: origType, - data: data, - handler: handler, - guid: handler.guid, - selector: selector, - needsContext: selector && jQuery.expr.match.needsContext.test( selector ), - namespace: namespaces.join(".") - }, handleObjIn ); - - // Init the event handler queue if we're the first - if ( !(handlers = events[ type ]) ) { - handlers = events[ type ] = []; - handlers.delegateCount = 0; - - // Only use addEventListener/attachEvent if the special events handler returns false - if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { - // Bind the global event handler to the element - if ( elem.addEventListener ) { - elem.addEventListener( type, eventHandle, false ); - - } else if ( elem.attachEvent ) { - elem.attachEvent( "on" + type, eventHandle ); - } - } - } - - if ( special.add ) { - special.add.call( elem, handleObj ); - - if ( !handleObj.handler.guid ) { - handleObj.handler.guid = handler.guid; - } - } - - // Add to the element's handler list, delegates in front - if ( selector ) { - handlers.splice( handlers.delegateCount++, 0, handleObj ); - } else { - handlers.push( handleObj ); - } - - // Keep track of which events have ever been used, for event optimization - jQuery.event.global[ type ] = true; - } - - // Nullify elem to prevent memory leaks in IE - elem = null; - }, - - // Detach an event or set of events from an element - remove: function( elem, types, handler, selector, mappedTypes ) { - var j, handleObj, tmp, - origCount, t, events, - special, handlers, type, - namespaces, origType, - elemData = jQuery.hasData( elem ) && jQuery._data( elem ); - - if ( !elemData || !(events = elemData.events) ) { - return; - } - - // Once for each type.namespace in types; type may be omitted - types = ( types || "" ).match( core_rnotwhite ) || [""]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[t] ) || []; - type = origType = tmp[1]; - namespaces = ( tmp[2] || "" ).split( "." ).sort(); - - // Unbind all events (on this namespace, if provided) for the element - if ( !type ) { - for ( type in events ) { - jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); - } - continue; - } - - special = jQuery.event.special[ type ] || {}; - type = ( selector ? special.delegateType : special.bindType ) || type; - handlers = events[ type ] || []; - tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ); - - // Remove matching events - origCount = j = handlers.length; - while ( j-- ) { - handleObj = handlers[ j ]; - - if ( ( mappedTypes || origType === handleObj.origType ) && - ( !handler || handler.guid === handleObj.guid ) && - ( !tmp || tmp.test( handleObj.namespace ) ) && - ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { - handlers.splice( j, 1 ); - - if ( handleObj.selector ) { - handlers.delegateCount--; - } - if ( special.remove ) { - special.remove.call( elem, handleObj ); - } - } - } - - // Remove generic event handler if we removed something and no more handlers exist - // (avoids potential for endless recursion during removal of special event handlers) - if ( origCount && !handlers.length ) { - if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { - jQuery.removeEvent( elem, type, elemData.handle ); - } - - delete events[ type ]; - } - } - - // Remove the expando if it's no longer used - if ( jQuery.isEmptyObject( events ) ) { - delete elemData.handle; - - // removeData also checks for emptiness and clears the expando if empty - // so use it instead of delete - jQuery._removeData( elem, "events" ); - } - }, - - trigger: function( event, data, elem, onlyHandlers ) { - var handle, ontype, cur, - bubbleType, special, tmp, i, - eventPath = [ elem || document ], - type = core_hasOwn.call( event, "type" ) ? event.type : event, - namespaces = core_hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : []; - - cur = tmp = elem = elem || document; - - // Don't do events on text and comment nodes - if ( elem.nodeType === 3 || elem.nodeType === 8 ) { - return; - } - - // focus/blur morphs to focusin/out; ensure we're not firing them right now - if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { - return; - } - - if ( type.indexOf(".") >= 0 ) { - // Namespaced trigger; create a regexp to match event type in handle() - namespaces = type.split("."); - type = namespaces.shift(); - namespaces.sort(); - } - ontype = type.indexOf(":") < 0 && "on" + type; - - // Caller can pass in a jQuery.Event object, Object, or just an event type string - event = event[ jQuery.expando ] ? - event : - new jQuery.Event( type, typeof event === "object" && event ); - - event.isTrigger = true; - event.namespace = namespaces.join("."); - event.namespace_re = event.namespace ? - new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) : - null; - - // Clean up the event in case it is being reused - event.result = undefined; - if ( !event.target ) { - event.target = elem; - } - - // Clone any incoming data and prepend the event, creating the handler arg list - data = data == null ? - [ event ] : - jQuery.makeArray( data, [ event ] ); - - // Allow special events to draw outside the lines - special = jQuery.event.special[ type ] || {}; - if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { - return; - } - - // Determine event propagation path in advance, per W3C events spec (#9951) - // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) - if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { - - bubbleType = special.delegateType || type; - if ( !rfocusMorph.test( bubbleType + type ) ) { - cur = cur.parentNode; - } - for ( ; cur; cur = cur.parentNode ) { - eventPath.push( cur ); - tmp = cur; - } - - // Only add window if we got to document (e.g., not plain obj or detached DOM) - if ( tmp === (elem.ownerDocument || document) ) { - eventPath.push( tmp.defaultView || tmp.parentWindow || window ); - } - } - - // Fire handlers on the event path - i = 0; - while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) { - - event.type = i > 1 ? - bubbleType : - special.bindType || type; - - // jQuery handler - handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); - if ( handle ) { - handle.apply( cur, data ); - } - - // Native handler - handle = ontype && cur[ ontype ]; - if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) { - event.preventDefault(); - } - } - event.type = type; - - // If nobody prevented the default action, do it now - if ( !onlyHandlers && !event.isDefaultPrevented() ) { - - if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) && - !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) { - - // Call a native DOM method on the target with the same name name as the event. - // Can't use an .isFunction() check here because IE6/7 fails that test. - // Don't do default actions on window, that's where global variables be (#6170) - if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) { - - // Don't re-trigger an onFOO event when we call its FOO() method - tmp = elem[ ontype ]; - - if ( tmp ) { - elem[ ontype ] = null; - } - - // Prevent re-triggering of the same event, since we already bubbled it above - jQuery.event.triggered = type; - try { - elem[ type ](); - } catch ( e ) { - // IE<9 dies on focus/blur to hidden element (#1486,#12518) - // only reproducible on winXP IE8 native, not IE9 in IE8 mode - } - jQuery.event.triggered = undefined; - - if ( tmp ) { - elem[ ontype ] = tmp; - } - } - } - } - - return event.result; - }, - - dispatch: function( event ) { - - // Make a writable jQuery.Event from the native event object - event = jQuery.event.fix( event ); - - var i, ret, handleObj, matched, j, - handlerQueue = [], - args = core_slice.call( arguments ), - handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [], - special = jQuery.event.special[ event.type ] || {}; - - // Use the fix-ed jQuery.Event rather than the (read-only) native event - args[0] = event; - event.delegateTarget = this; - - // Call the preDispatch hook for the mapped type, and let it bail if desired - if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { - return; - } - - // Determine handlers - handlerQueue = jQuery.event.handlers.call( this, event, handlers ); - - // Run delegates first; they may want to stop propagation beneath us - i = 0; - while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) { - event.currentTarget = matched.elem; - - j = 0; - while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) { - - // Triggered event must either 1) have no namespace, or - // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). - if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) { - - event.handleObj = handleObj; - event.data = handleObj.data; - - ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) - .apply( matched.elem, args ); - - if ( ret !== undefined ) { - if ( (event.result = ret) === false ) { - event.preventDefault(); - event.stopPropagation(); - } - } - } - } - } - - // Call the postDispatch hook for the mapped type - if ( special.postDispatch ) { - special.postDispatch.call( this, event ); - } - - return event.result; - }, - - handlers: function( event, handlers ) { - var sel, handleObj, matches, i, - handlerQueue = [], - delegateCount = handlers.delegateCount, - cur = event.target; - - // Find delegate handlers - // Black-hole SVG <use> instance trees (#13180) - // Avoid non-left-click bubbling in Firefox (#3861) - if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) { - - for ( ; cur != this; cur = cur.parentNode || this ) { - - // Don't check non-elements (#13208) - // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) - if ( cur.nodeType === 1 && (cur.disabled !== true || event.type !== "click") ) { - matches = []; - for ( i = 0; i < delegateCount; i++ ) { - handleObj = handlers[ i ]; - - // Don't conflict with Object.prototype properties (#13203) - sel = handleObj.selector + " "; - - if ( matches[ sel ] === undefined ) { - matches[ sel ] = handleObj.needsContext ? - jQuery( sel, this ).index( cur ) >= 0 : - jQuery.find( sel, this, null, [ cur ] ).length; - } - if ( matches[ sel ] ) { - matches.push( handleObj ); - } - } - if ( matches.length ) { - handlerQueue.push({ elem: cur, handlers: matches }); - } - } - } - } - - // Add the remaining (directly-bound) handlers - if ( delegateCount < handlers.length ) { - handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) }); - } - - return handlerQueue; - }, - - fix: function( event ) { - if ( event[ jQuery.expando ] ) { - return event; - } - - // Create a writable copy of the event object and normalize some properties - var i, prop, copy, - type = event.type, - originalEvent = event, - fixHook = this.fixHooks[ type ]; - - if ( !fixHook ) { - this.fixHooks[ type ] = fixHook = - rmouseEvent.test( type ) ? this.mouseHooks : - rkeyEvent.test( type ) ? this.keyHooks : - {}; - } - copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; - - event = new jQuery.Event( originalEvent ); - - i = copy.length; - while ( i-- ) { - prop = copy[ i ]; - event[ prop ] = originalEvent[ prop ]; - } - - // Support: IE<9 - // Fix target property (#1925) - if ( !event.target ) { - event.target = originalEvent.srcElement || document; - } - - // Support: Chrome 23+, Safari? - // Target should not be a text node (#504, #13143) - if ( event.target.nodeType === 3 ) { - event.target = event.target.parentNode; - } - - // Support: IE<9 - // For mouse/key events, metaKey==false if it's undefined (#3368, #11328) - event.metaKey = !!event.metaKey; - - return fixHook.filter ? fixHook.filter( event, originalEvent ) : event; - }, - - // Includes some event props shared by KeyEvent and MouseEvent - props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), - - fixHooks: {}, - - keyHooks: { - props: "char charCode key keyCode".split(" "), - filter: function( event, original ) { - - // Add which for key events - if ( event.which == null ) { - event.which = original.charCode != null ? original.charCode : original.keyCode; - } - - return event; - } - }, - - mouseHooks: { - props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), - filter: function( event, original ) { - var body, eventDoc, doc, - button = original.button, - fromElement = original.fromElement; - - // Calculate pageX/Y if missing and clientX/Y available - if ( event.pageX == null && original.clientX != null ) { - eventDoc = event.target.ownerDocument || document; - doc = eventDoc.documentElement; - body = eventDoc.body; - - event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); - event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); - } - - // Add relatedTarget, if necessary - if ( !event.relatedTarget && fromElement ) { - event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; - } - - // Add which for click: 1 === left; 2 === middle; 3 === right - // Note: button is not normalized, so don't use it - if ( !event.which && button !== undefined ) { - event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); - } - - return event; - } - }, - - special: { - load: { - // Prevent triggered image.load events from bubbling to window.load - noBubble: true - }, - click: { - // For checkbox, fire native event so checked state will be right - trigger: function() { - if ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) { - this.click(); - return false; - } - } - }, - focus: { - // Fire native event if possible so blur/focus sequence is correct - trigger: function() { - if ( this !== document.activeElement && this.focus ) { - try { - this.focus(); - return false; - } catch ( e ) { - // Support: IE<9 - // If we error on focus to hidden element (#1486, #12518), - // let .trigger() run the handlers - } - } - }, - delegateType: "focusin" - }, - blur: { - trigger: function() { - if ( this === document.activeElement && this.blur ) { - this.blur(); - return false; - } - }, - delegateType: "focusout" - }, - - beforeunload: { - postDispatch: function( event ) { - - // Even when returnValue equals to undefined Firefox will still show alert - if ( event.result !== undefined ) { - event.originalEvent.returnValue = event.result; - } - } - } - }, - - simulate: function( type, elem, event, bubble ) { - // Piggyback on a donor event to simulate a different one. - // Fake originalEvent to avoid donor's stopPropagation, but if the - // simulated event prevents default then we do the same on the donor. - var e = jQuery.extend( - new jQuery.Event(), - event, - { type: type, - isSimulated: true, - originalEvent: {} - } - ); - if ( bubble ) { - jQuery.event.trigger( e, null, elem ); - } else { - jQuery.event.dispatch.call( elem, e ); - } - if ( e.isDefaultPrevented() ) { - event.preventDefault(); - } - } -}; - -jQuery.removeEvent = document.removeEventListener ? - function( elem, type, handle ) { - if ( elem.removeEventListener ) { - elem.removeEventListener( type, handle, false ); - } - } : - function( elem, type, handle ) { - var name = "on" + type; - - if ( elem.detachEvent ) { - - // #8545, #7054, preventing memory leaks for custom events in IE6-8 - // detachEvent needed property on element, by name of that event, to properly expose it to GC - if ( typeof elem[ name ] === core_strundefined ) { - elem[ name ] = null; - } - - elem.detachEvent( name, handle ); - } - }; - -jQuery.Event = function( src, props ) { - // Allow instantiation without the 'new' keyword - if ( !(this instanceof jQuery.Event) ) { - return new jQuery.Event( src, props ); - } - - // Event object - if ( src && src.type ) { - this.originalEvent = src; - this.type = src.type; - - // Events bubbling up the document may have been marked as prevented - // by a handler lower down the tree; reflect the correct value. - this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false || - src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse; - - // Event type - } else { - this.type = src; - } - - // Put explicitly provided properties onto the event object - if ( props ) { - jQuery.extend( this, props ); - } - - // Create a timestamp if incoming event doesn't have one - this.timeStamp = src && src.timeStamp || jQuery.now(); - - // Mark it as fixed - this[ jQuery.expando ] = true; -}; - -// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding -// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html -jQuery.Event.prototype = { - isDefaultPrevented: returnFalse, - isPropagationStopped: returnFalse, - isImmediatePropagationStopped: returnFalse, - - preventDefault: function() { - var e = this.originalEvent; - - this.isDefaultPrevented = returnTrue; - if ( !e ) { - return; - } - - // If preventDefault exists, run it on the original event - if ( e.preventDefault ) { - e.preventDefault(); - - // Support: IE - // Otherwise set the returnValue property of the original event to false - } else { - e.returnValue = false; - } - }, - stopPropagation: function() { - var e = this.originalEvent; - - this.isPropagationStopped = returnTrue; - if ( !e ) { - return; - } - // If stopPropagation exists, run it on the original event - if ( e.stopPropagation ) { - e.stopPropagation(); - } - - // Support: IE - // Set the cancelBubble property of the original event to true - e.cancelBubble = true; - }, - stopImmediatePropagation: function() { - this.isImmediatePropagationStopped = returnTrue; - this.stopPropagation(); - } -}; - -// Create mouseenter/leave events using mouseover/out and event-time checks -jQuery.each({ - mouseenter: "mouseover", - mouseleave: "mouseout" -}, function( orig, fix ) { - jQuery.event.special[ orig ] = { - delegateType: fix, - bindType: fix, - - handle: function( event ) { - var ret, - target = this, - related = event.relatedTarget, - handleObj = event.handleObj; - - // For mousenter/leave call the handler if related is outside the target. - // NB: No relatedTarget if the mouse left/entered the browser window - if ( !related || (related !== target && !jQuery.contains( target, related )) ) { - event.type = handleObj.origType; - ret = handleObj.handler.apply( this, arguments ); - event.type = fix; - } - return ret; - } - }; -}); - -// IE submit delegation -if ( !jQuery.support.submitBubbles ) { - - jQuery.event.special.submit = { - setup: function() { - // Only need this for delegated form submit events - if ( jQuery.nodeName( this, "form" ) ) { - return false; - } - - // Lazy-add a submit handler when a descendant form may potentially be submitted - jQuery.event.add( this, "click._submit keypress._submit", function( e ) { - // Node name check avoids a VML-related crash in IE (#9807) - var elem = e.target, - form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; - if ( form && !jQuery._data( form, "submitBubbles" ) ) { - jQuery.event.add( form, "submit._submit", function( event ) { - event._submit_bubble = true; - }); - jQuery._data( form, "submitBubbles", true ); - } - }); - // return undefined since we don't need an event listener - }, - - postDispatch: function( event ) { - // If form was submitted by the user, bubble the event up the tree - if ( event._submit_bubble ) { - delete event._submit_bubble; - if ( this.parentNode && !event.isTrigger ) { - jQuery.event.simulate( "submit", this.parentNode, event, true ); - } - } - }, - - teardown: function() { - // Only need this for delegated form submit events - if ( jQuery.nodeName( this, "form" ) ) { - return false; - } - - // Remove delegated handlers; cleanData eventually reaps submit handlers attached above - jQuery.event.remove( this, "._submit" ); - } - }; -} - -// IE change delegation and checkbox/radio fix -if ( !jQuery.support.changeBubbles ) { - - jQuery.event.special.change = { - - setup: function() { - - if ( rformElems.test( this.nodeName ) ) { - // IE doesn't fire change on a check/radio until blur; trigger it on click - // after a propertychange. Eat the blur-change in special.change.handle. - // This still fires onchange a second time for check/radio after blur. - if ( this.type === "checkbox" || this.type === "radio" ) { - jQuery.event.add( this, "propertychange._change", function( event ) { - if ( event.originalEvent.propertyName === "checked" ) { - this._just_changed = true; - } - }); - jQuery.event.add( this, "click._change", function( event ) { - if ( this._just_changed && !event.isTrigger ) { - this._just_changed = false; - } - // Allow triggered, simulated change events (#11500) - jQuery.event.simulate( "change", this, event, true ); - }); - } - return false; - } - // Delegated event; lazy-add a change handler on descendant inputs - jQuery.event.add( this, "beforeactivate._change", function( e ) { - var elem = e.target; - - if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "changeBubbles" ) ) { - jQuery.event.add( elem, "change._change", function( event ) { - if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { - jQuery.event.simulate( "change", this.parentNode, event, true ); - } - }); - jQuery._data( elem, "changeBubbles", true ); - } - }); - }, - - handle: function( event ) { - var elem = event.target; - - // Swallow native change events from checkbox/radio, we already triggered them above - if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) { - return event.handleObj.handler.apply( this, arguments ); - } - }, - - teardown: function() { - jQuery.event.remove( this, "._change" ); - - return !rformElems.test( this.nodeName ); - } - }; -} - -// Create "bubbling" focus and blur events -if ( !jQuery.support.focusinBubbles ) { - jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { - - // Attach a single capturing handler while someone wants focusin/focusout - var attaches = 0, - handler = function( event ) { - jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); - }; - - jQuery.event.special[ fix ] = { - setup: function() { - if ( attaches++ === 0 ) { - document.addEventListener( orig, handler, true ); - } - }, - teardown: function() { - if ( --attaches === 0 ) { - document.removeEventListener( orig, handler, true ); - } - } - }; - }); -} - -jQuery.fn.extend({ - - on: function( types, selector, data, fn, /*INTERNAL*/ one ) { - var type, origFn; - - // Types can be a map of types/handlers - if ( typeof types === "object" ) { - // ( types-Object, selector, data ) - if ( typeof selector !== "string" ) { - // ( types-Object, data ) - data = data || selector; - selector = undefined; - } - for ( type in types ) { - this.on( type, selector, data, types[ type ], one ); - } - return this; - } - - if ( data == null && fn == null ) { - // ( types, fn ) - fn = selector; - data = selector = undefined; - } else if ( fn == null ) { - if ( typeof selector === "string" ) { - // ( types, selector, fn ) - fn = data; - data = undefined; - } else { - // ( types, data, fn ) - fn = data; - data = selector; - selector = undefined; - } - } - if ( fn === false ) { - fn = returnFalse; - } else if ( !fn ) { - return this; - } - - if ( one === 1 ) { - origFn = fn; - fn = function( event ) { - // Can use an empty set, since event contains the info - jQuery().off( event ); - return origFn.apply( this, arguments ); - }; - // Use same guid so caller can remove using origFn - fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); - } - return this.each( function() { - jQuery.event.add( this, types, fn, data, selector ); - }); - }, - one: function( types, selector, data, fn ) { - return this.on( types, selector, data, fn, 1 ); - }, - off: function( types, selector, fn ) { - var handleObj, type; - if ( types && types.preventDefault && types.handleObj ) { - // ( event ) dispatched jQuery.Event - handleObj = types.handleObj; - jQuery( types.delegateTarget ).off( - handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, - handleObj.selector, - handleObj.handler - ); - return this; - } - if ( typeof types === "object" ) { - // ( types-object [, selector] ) - for ( type in types ) { - this.off( type, selector, types[ type ] ); - } - return this; - } - if ( selector === false || typeof selector === "function" ) { - // ( types [, fn] ) - fn = selector; - selector = undefined; - } - if ( fn === false ) { - fn = returnFalse; - } - return this.each(function() { - jQuery.event.remove( this, types, fn, selector ); - }); - }, - - bind: function( types, data, fn ) { - return this.on( types, null, data, fn ); - }, - unbind: function( types, fn ) { - return this.off( types, null, fn ); - }, - - delegate: function( selector, types, data, fn ) { - return this.on( types, selector, data, fn ); - }, - undelegate: function( selector, types, fn ) { - // ( namespace ) or ( selector, types [, fn] ) - return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn ); - }, - - trigger: function( type, data ) { - return this.each(function() { - jQuery.event.trigger( type, data, this ); - }); - }, - triggerHandler: function( type, data ) { - var elem = this[0]; - if ( elem ) { - return jQuery.event.trigger( type, data, elem, true ); - } - } -}); -/*! - * Sizzle CSS Selector Engine - * Copyright 2012 jQuery Foundation and other contributors - * Released under the MIT license - * http://sizzlejs.com/ - */ -(function( window, undefined ) { - -var i, - cachedruns, - Expr, - getText, - isXML, - compile, - hasDuplicate, - outermostContext, - - // Local document vars - setDocument, - document, - docElem, - documentIsXML, - rbuggyQSA, - rbuggyMatches, - matches, - contains, - sortOrder, - - // Instance-specific data - expando = "sizzle" + -(new Date()), - preferredDoc = window.document, - support = {}, - dirruns = 0, - done = 0, - classCache = createCache(), - tokenCache = createCache(), - compilerCache = createCache(), - - // General-purpose constants - strundefined = typeof undefined, - MAX_NEGATIVE = 1 << 31, - - // Array methods - arr = [], - pop = arr.pop, - push = arr.push, - slice = arr.slice, - // Use a stripped-down indexOf if we can't use a native one - indexOf = arr.indexOf || function( elem ) { - var i = 0, - len = this.length; - for ( ; i < len; i++ ) { - if ( this[i] === elem ) { - return i; - } - } - return -1; - }, - - - // Regular expressions - - // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace - whitespace = "[\\x20\\t\\r\\n\\f]", - // http://www.w3.org/TR/css3-syntax/#characters - characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+", - - // Loosely modeled on CSS identifier characters - // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors - // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier - identifier = characterEncoding.replace( "w", "w#" ), - - // Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors - operators = "([*^$|!~]?=)", - attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace + - "*(?:" + operators + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]", - - // Prefer arguments quoted, - // then not containing pseudos/brackets, - // then attribute selectors/non-parenthetical expressions, - // then anything else - // These preferences are here to reduce the number of selectors - // needing tokenize in the PSEUDO preFilter - pseudos = ":(" + characterEncoding + ")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|" + attributes.replace( 3, 8 ) + ")*)|.*)\\)|)", - - // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter - rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), - - rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), - rcombinators = new RegExp( "^" + whitespace + "*([\\x20\\t\\r\\n\\f>+~])" + whitespace + "*" ), - rpseudo = new RegExp( pseudos ), - ridentifier = new RegExp( "^" + identifier + "$" ), - - matchExpr = { - "ID": new RegExp( "^#(" + characterEncoding + ")" ), - "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ), - "NAME": new RegExp( "^\\[name=['\"]?(" + characterEncoding + ")['\"]?\\]" ), - "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ), - "ATTR": new RegExp( "^" + attributes ), - "PSEUDO": new RegExp( "^" + pseudos ), - "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + - "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + - "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), - // For use in libraries implementing .is() - // We use this for POS matching in `select` - "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + - whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) - }, - - rsibling = /[\x20\t\r\n\f]*[+~]/, - - rnative = /^[^{]+\{\s*\[native code/, - - // Easily-parseable/retrievable ID or TAG or CLASS selectors - rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, - - rinputs = /^(?:input|select|textarea|button)$/i, - rheader = /^h\d$/i, - - rescape = /'|\\/g, - rattributeQuotes = /\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g, - - // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters - runescape = /\\([\da-fA-F]{1,6}[\x20\t\r\n\f]?|.)/g, - funescape = function( _, escaped ) { - var high = "0x" + escaped - 0x10000; - // NaN means non-codepoint - return high !== high ? - escaped : - // BMP codepoint - high < 0 ? - String.fromCharCode( high + 0x10000 ) : - // Supplemental Plane codepoint (surrogate pair) - String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); - }; - -// Use a stripped-down slice if we can't use a native one -try { - slice.call( preferredDoc.documentElement.childNodes, 0 )[0].nodeType; -} catch ( e ) { - slice = function( i ) { - var elem, - results = []; - while ( (elem = this[i++]) ) { - results.push( elem ); - } - return results; - }; -} - -/** - * For feature detection - * @param {Function} fn The function to test for native support - */ -function isNative( fn ) { - return rnative.test( fn + "" ); -} - -/** - * Create key-value caches of limited size - * @returns {Function(string, Object)} Returns the Object data after storing it on itself with - * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) - * deleting the oldest entry - */ -function createCache() { - var cache, - keys = []; - - return (cache = function( key, value ) { - // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) - if ( keys.push( key += " " ) > Expr.cacheLength ) { - // Only keep the most recent entries - delete cache[ keys.shift() ]; - } - return (cache[ key ] = value); - }); -} - -/** - * Mark a function for special use by Sizzle - * @param {Function} fn The function to mark - */ -function markFunction( fn ) { - fn[ expando ] = true; - return fn; -} - -/** - * Support testing using an element - * @param {Function} fn Passed the created div and expects a boolean result - */ -function assert( fn ) { - var div = document.createElement("div"); - - try { - return fn( div ); - } catch (e) { - return false; - } finally { - // release memory in IE - div = null; - } -} - -function Sizzle( selector, context, results, seed ) { - var match, elem, m, nodeType, - // QSA vars - i, groups, old, nid, newContext, newSelector; - - if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { - setDocument( context ); - } - - context = context || document; - results = results || []; - - if ( !selector || typeof selector !== "string" ) { - return results; - } - - if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) { - return []; - } - - if ( !documentIsXML && !seed ) { - - // Shortcuts - if ( (match = rquickExpr.exec( selector )) ) { - // Speed-up: Sizzle("#ID") - if ( (m = match[1]) ) { - if ( nodeType === 9 ) { - elem = context.getElementById( m ); - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - if ( elem && elem.parentNode ) { - // Handle the case where IE, Opera, and Webkit return items - // by name instead of ID - if ( elem.id === m ) { - results.push( elem ); - return results; - } - } else { - return results; - } - } else { - // Context is not a document - if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) && - contains( context, elem ) && elem.id === m ) { - results.push( elem ); - return results; - } - } - - // Speed-up: Sizzle("TAG") - } else if ( match[2] ) { - push.apply( results, slice.call(context.getElementsByTagName( selector ), 0) ); - return results; - - // Speed-up: Sizzle(".CLASS") - } else if ( (m = match[3]) && support.getByClassName && context.getElementsByClassName ) { - push.apply( results, slice.call(context.getElementsByClassName( m ), 0) ); - return results; - } - } - - // QSA path - if ( support.qsa && !rbuggyQSA.test(selector) ) { - old = true; - nid = expando; - newContext = context; - newSelector = nodeType === 9 && selector; - - // qSA works strangely on Element-rooted queries - // We can work around this by specifying an extra ID on the root - // and working up from there (Thanks to Andrew Dupont for the technique) - // IE 8 doesn't work on object elements - if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { - groups = tokenize( selector ); - - if ( (old = context.getAttribute("id")) ) { - nid = old.replace( rescape, "\\$&" ); - } else { - context.setAttribute( "id", nid ); - } - nid = "[id='" + nid + "'] "; - - i = groups.length; - while ( i-- ) { - groups[i] = nid + toSelector( groups[i] ); - } - newContext = rsibling.test( selector ) && context.parentNode || context; - newSelector = groups.join(","); - } - - if ( newSelector ) { - try { - push.apply( results, slice.call( newContext.querySelectorAll( - newSelector - ), 0 ) ); - return results; - } catch(qsaError) { - } finally { - if ( !old ) { - context.removeAttribute("id"); - } - } - } - } - } - - // All others - return select( selector.replace( rtrim, "$1" ), context, results, seed ); -} - -/** - * Detect xml - * @param {Element|Object} elem An element or a document - */ -isXML = Sizzle.isXML = function( elem ) { - // documentElement is verified for cases where it doesn't yet exist - // (such as loading iframes in IE - #4833) - var documentElement = elem && (elem.ownerDocument || elem).documentElement; - return documentElement ? documentElement.nodeName !== "HTML" : false; -}; - -/** - * Sets document-related variables once based on the current document - * @param {Element|Object} [doc] An element or document object to use to set the document - * @returns {Object} Returns the current document - */ -setDocument = Sizzle.setDocument = function( node ) { - var doc = node ? node.ownerDocument || node : preferredDoc; - - // If no document and documentElement is available, return - if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { - return document; - } - - // Set our document - document = doc; - docElem = doc.documentElement; - - // Support tests - documentIsXML = isXML( doc ); - - // Check if getElementsByTagName("*") returns only elements - support.tagNameNoComments = assert(function( div ) { - div.appendChild( doc.createComment("") ); - return !div.getElementsByTagName("*").length; - }); - - // Check if attributes should be retrieved by attribute nodes - support.attributes = assert(function( div ) { - div.innerHTML = "<select></select>"; - var type = typeof div.lastChild.getAttribute("multiple"); - // IE8 returns a string for some attributes even when not present - return type !== "boolean" && type !== "string"; - }); - - // Check if getElementsByClassName can be trusted - support.getByClassName = assert(function( div ) { - // Opera can't find a second classname (in 9.6) - div.innerHTML = "<div class='hidden e'></div><div class='hidden'></div>"; - if ( !div.getElementsByClassName || !div.getElementsByClassName("e").length ) { - return false; - } - - // Safari 3.2 caches class attributes and doesn't catch changes - div.lastChild.className = "e"; - return div.getElementsByClassName("e").length === 2; - }); - - // Check if getElementById returns elements by name - // Check if getElementsByName privileges form controls or returns elements by ID - support.getByName = assert(function( div ) { - // Inject content - div.id = expando + 0; - div.innerHTML = "<a name='" + expando + "'></a><div name='" + expando + "'></div>"; - docElem.insertBefore( div, docElem.firstChild ); - - // Test - var pass = doc.getElementsByName && - // buggy browsers will return fewer than the correct 2 - doc.getElementsByName( expando ).length === 2 + - // buggy browsers will return more than the correct 0 - doc.getElementsByName( expando + 0 ).length; - support.getIdNotName = !doc.getElementById( expando ); - - // Cleanup - docElem.removeChild( div ); - - return pass; - }); - - // IE6/7 return modified attributes - Expr.attrHandle = assert(function( div ) { - div.innerHTML = "<a href='#'></a>"; - return div.firstChild && typeof div.firstChild.getAttribute !== strundefined && - div.firstChild.getAttribute("href") === "#"; - }) ? - {} : - { - "href": function( elem ) { - return elem.getAttribute( "href", 2 ); - }, - "type": function( elem ) { - return elem.getAttribute("type"); - } - }; - - // ID find and filter - if ( support.getIdNotName ) { - Expr.find["ID"] = function( id, context ) { - if ( typeof context.getElementById !== strundefined && !documentIsXML ) { - var m = context.getElementById( id ); - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - return m && m.parentNode ? [m] : []; - } - }; - Expr.filter["ID"] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - return elem.getAttribute("id") === attrId; - }; - }; - } else { - Expr.find["ID"] = function( id, context ) { - if ( typeof context.getElementById !== strundefined && !documentIsXML ) { - var m = context.getElementById( id ); - - return m ? - m.id === id || typeof m.getAttributeNode !== strundefined && m.getAttributeNode("id").value === id ? - [m] : - undefined : - []; - } - }; - Expr.filter["ID"] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id"); - return node && node.value === attrId; - }; - }; - } - - // Tag - Expr.find["TAG"] = support.tagNameNoComments ? - function( tag, context ) { - if ( typeof context.getElementsByTagName !== strundefined ) { - return context.getElementsByTagName( tag ); - } - } : - function( tag, context ) { - var elem, - tmp = [], - i = 0, - results = context.getElementsByTagName( tag ); - - // Filter out possible comments - if ( tag === "*" ) { - while ( (elem = results[i++]) ) { - if ( elem.nodeType === 1 ) { - tmp.push( elem ); - } - } - - return tmp; - } - return results; - }; - - // Name - Expr.find["NAME"] = support.getByName && function( tag, context ) { - if ( typeof context.getElementsByName !== strundefined ) { - return context.getElementsByName( name ); - } - }; - - // Class - Expr.find["CLASS"] = support.getByClassName && function( className, context ) { - if ( typeof context.getElementsByClassName !== strundefined && !documentIsXML ) { - return context.getElementsByClassName( className ); - } - }; - - // QSA and matchesSelector support - - // matchesSelector(:active) reports false when true (IE9/Opera 11.5) - rbuggyMatches = []; - - // qSa(:focus) reports false when true (Chrome 21), - // no need to also add to buggyMatches since matches checks buggyQSA - // A support test would require too much code (would include document ready) - rbuggyQSA = [ ":focus" ]; - - if ( (support.qsa = isNative(doc.querySelectorAll)) ) { - // Build QSA regex - // Regex strategy adopted from Diego Perini - assert(function( div ) { - // Select is set to empty string on purpose - // This is to test IE's treatment of not explictly - // setting a boolean content attribute, - // since its presence should be enough - // http://bugs.jquery.com/ticket/12359 - div.innerHTML = "<select><option selected=''></option></select>"; - - // IE8 - Some boolean attributes are not treated correctly - if ( !div.querySelectorAll("[selected]").length ) { - rbuggyQSA.push( "\\[" + whitespace + "*(?:checked|disabled|ismap|multiple|readonly|selected|value)" ); - } - - // Webkit/Opera - :checked should return selected option elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - // IE8 throws error here and will not see later tests - if ( !div.querySelectorAll(":checked").length ) { - rbuggyQSA.push(":checked"); - } - }); - - assert(function( div ) { - - // Opera 10-12/IE8 - ^= $= *= and empty values - // Should not select anything - div.innerHTML = "<input type='hidden' i=''/>"; - if ( div.querySelectorAll("[i^='']").length ) { - rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:\"\"|'')" ); - } - - // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) - // IE8 throws error here and will not see later tests - if ( !div.querySelectorAll(":enabled").length ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Opera 10-11 does not throw on post-comma invalid pseudos - div.querySelectorAll("*,:x"); - rbuggyQSA.push(",.*:"); - }); - } - - if ( (support.matchesSelector = isNative( (matches = docElem.matchesSelector || - docElem.mozMatchesSelector || - docElem.webkitMatchesSelector || - docElem.oMatchesSelector || - docElem.msMatchesSelector) )) ) { - - assert(function( div ) { - // Check to see if it's possible to do matchesSelector - // on a disconnected node (IE 9) - support.disconnectedMatch = matches.call( div, "div" ); - - // This should fail with an exception - // Gecko does not error, returns false instead - matches.call( div, "[s!='']:x" ); - rbuggyMatches.push( "!=", pseudos ); - }); - } - - rbuggyQSA = new RegExp( rbuggyQSA.join("|") ); - rbuggyMatches = new RegExp( rbuggyMatches.join("|") ); - - // Element contains another - // Purposefully does not implement inclusive descendent - // As in, an element does not contain itself - contains = isNative(docElem.contains) || docElem.compareDocumentPosition ? - function( a, b ) { - var adown = a.nodeType === 9 ? a.documentElement : a, - bup = b && b.parentNode; - return a === bup || !!( bup && bup.nodeType === 1 && ( - adown.contains ? - adown.contains( bup ) : - a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 - )); - } : - function( a, b ) { - if ( b ) { - while ( (b = b.parentNode) ) { - if ( b === a ) { - return true; - } - } - } - return false; - }; - - // Document order sorting - sortOrder = docElem.compareDocumentPosition ? - function( a, b ) { - var compare; - - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - if ( (compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition( b )) ) { - if ( compare & 1 || a.parentNode && a.parentNode.nodeType === 11 ) { - if ( a === doc || contains( preferredDoc, a ) ) { - return -1; - } - if ( b === doc || contains( preferredDoc, b ) ) { - return 1; - } - return 0; - } - return compare & 4 ? -1 : 1; - } - - return a.compareDocumentPosition ? -1 : 1; - } : - function( a, b ) { - var cur, - i = 0, - aup = a.parentNode, - bup = b.parentNode, - ap = [ a ], - bp = [ b ]; - - // Exit early if the nodes are identical - if ( a === b ) { - hasDuplicate = true; - return 0; - - // Parentless nodes are either documents or disconnected - } else if ( !aup || !bup ) { - return a === doc ? -1 : - b === doc ? 1 : - aup ? -1 : - bup ? 1 : - 0; - - // If the nodes are siblings, we can do a quick check - } else if ( aup === bup ) { - return siblingCheck( a, b ); - } - - // Otherwise we need full lists of their ancestors for comparison - cur = a; - while ( (cur = cur.parentNode) ) { - ap.unshift( cur ); - } - cur = b; - while ( (cur = cur.parentNode) ) { - bp.unshift( cur ); - } - - // Walk down the tree looking for a discrepancy - while ( ap[i] === bp[i] ) { - i++; - } - - return i ? - // Do a sibling check if the nodes have a common ancestor - siblingCheck( ap[i], bp[i] ) : - - // Otherwise nodes in our document sort first - ap[i] === preferredDoc ? -1 : - bp[i] === preferredDoc ? 1 : - 0; - }; - - // Always assume the presence of duplicates if sort doesn't - // pass them to our comparison function (as in Google Chrome). - hasDuplicate = false; - [0, 0].sort( sortOrder ); - support.detectDuplicates = hasDuplicate; - - return document; -}; - -Sizzle.matches = function( expr, elements ) { - return Sizzle( expr, null, null, elements ); -}; - -Sizzle.matchesSelector = function( elem, expr ) { - // Set document vars if needed - if ( ( elem.ownerDocument || elem ) !== document ) { - setDocument( elem ); - } - - // Make sure that attribute selectors are quoted - expr = expr.replace( rattributeQuotes, "='$1']" ); - - // rbuggyQSA always contains :focus, so no need for an existence check - if ( support.matchesSelector && !documentIsXML && (!rbuggyMatches || !rbuggyMatches.test(expr)) && !rbuggyQSA.test(expr) ) { - try { - var ret = matches.call( elem, expr ); - - // IE 9's matchesSelector returns false on disconnected nodes - if ( ret || support.disconnectedMatch || - // As well, disconnected nodes are said to be in a document - // fragment in IE 9 - elem.document && elem.document.nodeType !== 11 ) { - return ret; - } - } catch(e) {} - } - - return Sizzle( expr, document, null, [elem] ).length > 0; -}; - -Sizzle.contains = function( context, elem ) { - // Set document vars if needed - if ( ( context.ownerDocument || context ) !== document ) { - setDocument( context ); - } - return contains( context, elem ); -}; - -Sizzle.attr = function( elem, name ) { - var val; - - // Set document vars if needed - if ( ( elem.ownerDocument || elem ) !== document ) { - setDocument( elem ); - } - - if ( !documentIsXML ) { - name = name.toLowerCase(); - } - if ( (val = Expr.attrHandle[ name ]) ) { - return val( elem ); - } - if ( documentIsXML || support.attributes ) { - return elem.getAttribute( name ); - } - return ( (val = elem.getAttributeNode( name )) || elem.getAttribute( name ) ) && elem[ name ] === true ? - name : - val && val.specified ? val.value : null; -}; - -Sizzle.error = function( msg ) { - throw new Error( "Syntax error, unrecognized expression: " + msg ); -}; - -// Document sorting and removing duplicates -Sizzle.uniqueSort = function( results ) { - var elem, - duplicates = [], - i = 1, - j = 0; - - // Unless we *know* we can detect duplicates, assume their presence - hasDuplicate = !support.detectDuplicates; - results.sort( sortOrder ); - - if ( hasDuplicate ) { - for ( ; (elem = results[i]); i++ ) { - if ( elem === results[ i - 1 ] ) { - j = duplicates.push( i ); - } - } - while ( j-- ) { - results.splice( duplicates[ j ], 1 ); - } - } - - return results; -}; - -function siblingCheck( a, b ) { - var cur = b && a, - diff = cur && ( ~b.sourceIndex || MAX_NEGATIVE ) - ( ~a.sourceIndex || MAX_NEGATIVE ); - - // Use IE sourceIndex if available on both nodes - if ( diff ) { - return diff; - } - - // Check if b follows a - if ( cur ) { - while ( (cur = cur.nextSibling) ) { - if ( cur === b ) { - return -1; - } - } - } - - return a ? 1 : -1; -} - -// Returns a function to use in pseudos for input types -function createInputPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === type; - }; -} - -// Returns a function to use in pseudos for buttons -function createButtonPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return (name === "input" || name === "button") && elem.type === type; - }; -} - -// Returns a function to use in pseudos for positionals -function createPositionalPseudo( fn ) { - return markFunction(function( argument ) { - argument = +argument; - return markFunction(function( seed, matches ) { - var j, - matchIndexes = fn( [], seed.length, argument ), - i = matchIndexes.length; - - // Match elements found at the specified indexes - while ( i-- ) { - if ( seed[ (j = matchIndexes[i]) ] ) { - seed[j] = !(matches[j] = seed[j]); - } - } - }); - }); -} - -/** - * Utility function for retrieving the text value of an array of DOM nodes - * @param {Array|Element} elem - */ -getText = Sizzle.getText = function( elem ) { - var node, - ret = "", - i = 0, - nodeType = elem.nodeType; - - if ( !nodeType ) { - // If no nodeType, this is expected to be an array - for ( ; (node = elem[i]); i++ ) { - // Do not traverse comment nodes - ret += getText( node ); - } - } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { - // Use textContent for elements - // innerText usage removed for consistency of new lines (see #11153) - if ( typeof elem.textContent === "string" ) { - return elem.textContent; - } else { - // Traverse its children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - ret += getText( elem ); - } - } - } else if ( nodeType === 3 || nodeType === 4 ) { - return elem.nodeValue; - } - // Do not include comment or processing instruction nodes - - return ret; -}; - -Expr = Sizzle.selectors = { - - // Can be adjusted by the user - cacheLength: 50, - - createPseudo: markFunction, - - match: matchExpr, - - find: {}, - - relative: { - ">": { dir: "parentNode", first: true }, - " ": { dir: "parentNode" }, - "+": { dir: "previousSibling", first: true }, - "~": { dir: "previousSibling" } - }, - - preFilter: { - "ATTR": function( match ) { - match[1] = match[1].replace( runescape, funescape ); - - // Move the given value to match[3] whether quoted or unquoted - match[3] = ( match[4] || match[5] || "" ).replace( runescape, funescape ); - - if ( match[2] === "~=" ) { - match[3] = " " + match[3] + " "; - } - - return match.slice( 0, 4 ); - }, - - "CHILD": function( match ) { - /* matches from matchExpr["CHILD"] - 1 type (only|nth|...) - 2 what (child|of-type) - 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) - 4 xn-component of xn+y argument ([+-]?\d*n|) - 5 sign of xn-component - 6 x of xn-component - 7 sign of y-component - 8 y of y-component - */ - match[1] = match[1].toLowerCase(); - - if ( match[1].slice( 0, 3 ) === "nth" ) { - // nth-* requires argument - if ( !match[3] ) { - Sizzle.error( match[0] ); - } - - // numeric x and y parameters for Expr.filter.CHILD - // remember that false/true cast respectively to 0/1 - match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); - match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); - - // other types prohibit arguments - } else if ( match[3] ) { - Sizzle.error( match[0] ); - } - - return match; - }, - - "PSEUDO": function( match ) { - var excess, - unquoted = !match[5] && match[2]; - - if ( matchExpr["CHILD"].test( match[0] ) ) { - return null; - } - - // Accept quoted arguments as-is - if ( match[4] ) { - match[2] = match[4]; - - // Strip excess characters from unquoted arguments - } else if ( unquoted && rpseudo.test( unquoted ) && - // Get excess from tokenize (recursively) - (excess = tokenize( unquoted, true )) && - // advance to the next closing parenthesis - (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { - - // excess is a negative index - match[0] = match[0].slice( 0, excess ); - match[2] = unquoted.slice( 0, excess ); - } - - // Return only captures needed by the pseudo filter method (type and argument) - return match.slice( 0, 3 ); - } - }, - - filter: { - - "TAG": function( nodeName ) { - if ( nodeName === "*" ) { - return function() { return true; }; - } - - nodeName = nodeName.replace( runescape, funescape ).toLowerCase(); - return function( elem ) { - return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; - }; - }, - - "CLASS": function( className ) { - var pattern = classCache[ className + " " ]; - - return pattern || - (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && - classCache( className, function( elem ) { - return pattern.test( elem.className || (typeof elem.getAttribute !== strundefined && elem.getAttribute("class")) || "" ); - }); - }, - - "ATTR": function( name, operator, check ) { - return function( elem ) { - var result = Sizzle.attr( elem, name ); - - if ( result == null ) { - return operator === "!="; - } - if ( !operator ) { - return true; - } - - result += ""; - - return operator === "=" ? result === check : - operator === "!=" ? result !== check : - operator === "^=" ? check && result.indexOf( check ) === 0 : - operator === "*=" ? check && result.indexOf( check ) > -1 : - operator === "$=" ? check && result.slice( -check.length ) === check : - operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 : - operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : - false; - }; - }, - - "CHILD": function( type, what, argument, first, last ) { - var simple = type.slice( 0, 3 ) !== "nth", - forward = type.slice( -4 ) !== "last", - ofType = what === "of-type"; - - return first === 1 && last === 0 ? - - // Shortcut for :nth-*(n) - function( elem ) { - return !!elem.parentNode; - } : - - function( elem, context, xml ) { - var cache, outerCache, node, diff, nodeIndex, start, - dir = simple !== forward ? "nextSibling" : "previousSibling", - parent = elem.parentNode, - name = ofType && elem.nodeName.toLowerCase(), - useCache = !xml && !ofType; - - if ( parent ) { - - // :(first|last|only)-(child|of-type) - if ( simple ) { - while ( dir ) { - node = elem; - while ( (node = node[ dir ]) ) { - if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { - return false; - } - } - // Reverse direction for :only-* (if we haven't yet done so) - start = dir = type === "only" && !start && "nextSibling"; - } - return true; - } - - start = [ forward ? parent.firstChild : parent.lastChild ]; - - // non-xml :nth-child(...) stores cache data on `parent` - if ( forward && useCache ) { - // Seek `elem` from a previously-cached index - outerCache = parent[ expando ] || (parent[ expando ] = {}); - cache = outerCache[ type ] || []; - nodeIndex = cache[0] === dirruns && cache[1]; - diff = cache[0] === dirruns && cache[2]; - node = nodeIndex && parent.childNodes[ nodeIndex ]; - - while ( (node = ++nodeIndex && node && node[ dir ] || - - // Fallback to seeking `elem` from the start - (diff = nodeIndex = 0) || start.pop()) ) { - - // When found, cache indexes on `parent` and break - if ( node.nodeType === 1 && ++diff && node === elem ) { - outerCache[ type ] = [ dirruns, nodeIndex, diff ]; - break; - } - } - - // Use previously-cached element index if available - } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) { - diff = cache[1]; - - // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...) - } else { - // Use the same loop as above to seek `elem` from the start - while ( (node = ++nodeIndex && node && node[ dir ] || - (diff = nodeIndex = 0) || start.pop()) ) { - - if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) { - // Cache the index of each encountered element - if ( useCache ) { - (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ]; - } - - if ( node === elem ) { - break; - } - } - } - } - - // Incorporate the offset, then check against cycle size - diff -= last; - return diff === first || ( diff % first === 0 && diff / first >= 0 ); - } - }; - }, - - "PSEUDO": function( pseudo, argument ) { - // pseudo-class names are case-insensitive - // http://www.w3.org/TR/selectors/#pseudo-classes - // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters - // Remember that setFilters inherits from pseudos - var args, - fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || - Sizzle.error( "unsupported pseudo: " + pseudo ); - - // The user may use createPseudo to indicate that - // arguments are needed to create the filter function - // just as Sizzle does - if ( fn[ expando ] ) { - return fn( argument ); - } - - // But maintain support for old signatures - if ( fn.length > 1 ) { - args = [ pseudo, pseudo, "", argument ]; - return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? - markFunction(function( seed, matches ) { - var idx, - matched = fn( seed, argument ), - i = matched.length; - while ( i-- ) { - idx = indexOf.call( seed, matched[i] ); - seed[ idx ] = !( matches[ idx ] = matched[i] ); - } - }) : - function( elem ) { - return fn( elem, 0, args ); - }; - } - - return fn; - } - }, - - pseudos: { - // Potentially complex pseudos - "not": markFunction(function( selector ) { - // Trim the selector passed to compile - // to avoid treating leading and trailing - // spaces as combinators - var input = [], - results = [], - matcher = compile( selector.replace( rtrim, "$1" ) ); - - return matcher[ expando ] ? - markFunction(function( seed, matches, context, xml ) { - var elem, - unmatched = matcher( seed, null, xml, [] ), - i = seed.length; - - // Match elements unmatched by `matcher` - while ( i-- ) { - if ( (elem = unmatched[i]) ) { - seed[i] = !(matches[i] = elem); - } - } - }) : - function( elem, context, xml ) { - input[0] = elem; - matcher( input, null, xml, results ); - return !results.pop(); - }; - }), - - "has": markFunction(function( selector ) { - return function( elem ) { - return Sizzle( selector, elem ).length > 0; - }; - }), - - "contains": markFunction(function( text ) { - return function( elem ) { - return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; - }; - }), - - // "Whether an element is represented by a :lang() selector - // is based solely on the element's language value - // being equal to the identifier C, - // or beginning with the identifier C immediately followed by "-". - // The matching of C against the element's language value is performed case-insensitively. - // The identifier C does not have to be a valid language name." - // http://www.w3.org/TR/selectors/#lang-pseudo - "lang": markFunction( function( lang ) { - // lang value must be a valid identifider - if ( !ridentifier.test(lang || "") ) { - Sizzle.error( "unsupported lang: " + lang ); - } - lang = lang.replace( runescape, funescape ).toLowerCase(); - return function( elem ) { - var elemLang; - do { - if ( (elemLang = documentIsXML ? - elem.getAttribute("xml:lang") || elem.getAttribute("lang") : - elem.lang) ) { - - elemLang = elemLang.toLowerCase(); - return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; - } - } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); - return false; - }; - }), - - // Miscellaneous - "target": function( elem ) { - var hash = window.location && window.location.hash; - return hash && hash.slice( 1 ) === elem.id; - }, - - "root": function( elem ) { - return elem === docElem; - }, - - "focus": function( elem ) { - return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); - }, - - // Boolean properties - "enabled": function( elem ) { - return elem.disabled === false; - }, - - "disabled": function( elem ) { - return elem.disabled === true; - }, - - "checked": function( elem ) { - // In CSS3, :checked should return both checked and selected elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - var nodeName = elem.nodeName.toLowerCase(); - return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); - }, - - "selected": function( elem ) { - // Accessing this property makes selected-by-default - // options in Safari work properly - if ( elem.parentNode ) { - elem.parentNode.selectedIndex; - } - - return elem.selected === true; - }, - - // Contents - "empty": function( elem ) { - // http://www.w3.org/TR/selectors/#empty-pseudo - // :empty is only affected by element nodes and content nodes(including text(3), cdata(4)), - // not comment, processing instructions, or others - // Thanks to Diego Perini for the nodeName shortcut - // Greater than "@" means alpha characters (specifically not starting with "#" or "?") - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - if ( elem.nodeName > "@" || elem.nodeType === 3 || elem.nodeType === 4 ) { - return false; - } - } - return true; - }, - - "parent": function( elem ) { - return !Expr.pseudos["empty"]( elem ); - }, - - // Element/input types - "header": function( elem ) { - return rheader.test( elem.nodeName ); - }, - - "input": function( elem ) { - return rinputs.test( elem.nodeName ); - }, - - "button": function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === "button" || name === "button"; - }, - - "text": function( elem ) { - var attr; - // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) - // use getAttribute instead to test this case - return elem.nodeName.toLowerCase() === "input" && - elem.type === "text" && - ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === elem.type ); - }, - - // Position-in-collection - "first": createPositionalPseudo(function() { - return [ 0 ]; - }), - - "last": createPositionalPseudo(function( matchIndexes, length ) { - return [ length - 1 ]; - }), - - "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { - return [ argument < 0 ? argument + length : argument ]; - }), - - "even": createPositionalPseudo(function( matchIndexes, length ) { - var i = 0; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "odd": createPositionalPseudo(function( matchIndexes, length ) { - var i = 1; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; - for ( ; --i >= 0; ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; - for ( ; ++i < length; ) { - matchIndexes.push( i ); - } - return matchIndexes; - }) - } -}; - -// Add button/input type pseudos -for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { - Expr.pseudos[ i ] = createInputPseudo( i ); -} -for ( i in { submit: true, reset: true } ) { - Expr.pseudos[ i ] = createButtonPseudo( i ); -} - -function tokenize( selector, parseOnly ) { - var matched, match, tokens, type, - soFar, groups, preFilters, - cached = tokenCache[ selector + " " ]; - - if ( cached ) { - return parseOnly ? 0 : cached.slice( 0 ); - } - - soFar = selector; - groups = []; - preFilters = Expr.preFilter; - - while ( soFar ) { - - // Comma and first run - if ( !matched || (match = rcomma.exec( soFar )) ) { - if ( match ) { - // Don't consume trailing commas as valid - soFar = soFar.slice( match[0].length ) || soFar; - } - groups.push( tokens = [] ); - } - - matched = false; - - // Combinators - if ( (match = rcombinators.exec( soFar )) ) { - matched = match.shift(); - tokens.push( { - value: matched, - // Cast descendant combinators to space - type: match[0].replace( rtrim, " " ) - } ); - soFar = soFar.slice( matched.length ); - } - - // Filters - for ( type in Expr.filter ) { - if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || - (match = preFilters[ type ]( match ))) ) { - matched = match.shift(); - tokens.push( { - value: matched, - type: type, - matches: match - } ); - soFar = soFar.slice( matched.length ); - } - } - - if ( !matched ) { - break; - } - } - - // Return the length of the invalid excess - // if we're just parsing - // Otherwise, throw an error or return tokens - return parseOnly ? - soFar.length : - soFar ? - Sizzle.error( selector ) : - // Cache the tokens - tokenCache( selector, groups ).slice( 0 ); -} - -function toSelector( tokens ) { - var i = 0, - len = tokens.length, - selector = ""; - for ( ; i < len; i++ ) { - selector += tokens[i].value; - } - return selector; -} - -function addCombinator( matcher, combinator, base ) { - var dir = combinator.dir, - checkNonElements = base && dir === "parentNode", - doneName = done++; - - return combinator.first ? - // Check against closest ancestor/preceding element - function( elem, context, xml ) { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - return matcher( elem, context, xml ); - } - } - } : - - // Check against all ancestor/preceding elements - function( elem, context, xml ) { - var data, cache, outerCache, - dirkey = dirruns + " " + doneName; - - // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching - if ( xml ) { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - if ( matcher( elem, context, xml ) ) { - return true; - } - } - } - } else { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - outerCache = elem[ expando ] || (elem[ expando ] = {}); - if ( (cache = outerCache[ dir ]) && cache[0] === dirkey ) { - if ( (data = cache[1]) === true || data === cachedruns ) { - return data === true; - } - } else { - cache = outerCache[ dir ] = [ dirkey ]; - cache[1] = matcher( elem, context, xml ) || cachedruns; - if ( cache[1] === true ) { - return true; - } - } - } - } - } - }; -} - -function elementMatcher( matchers ) { - return matchers.length > 1 ? - function( elem, context, xml ) { - var i = matchers.length; - while ( i-- ) { - if ( !matchers[i]( elem, context, xml ) ) { - return false; - } - } - return true; - } : - matchers[0]; -} - -function condense( unmatched, map, filter, context, xml ) { - var elem, - newUnmatched = [], - i = 0, - len = unmatched.length, - mapped = map != null; - - for ( ; i < len; i++ ) { - if ( (elem = unmatched[i]) ) { - if ( !filter || filter( elem, context, xml ) ) { - newUnmatched.push( elem ); - if ( mapped ) { - map.push( i ); - } - } - } - } - - return newUnmatched; -} - -function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { - if ( postFilter && !postFilter[ expando ] ) { - postFilter = setMatcher( postFilter ); - } - if ( postFinder && !postFinder[ expando ] ) { - postFinder = setMatcher( postFinder, postSelector ); - } - return markFunction(function( seed, results, context, xml ) { - var temp, i, elem, - preMap = [], - postMap = [], - preexisting = results.length, - - // Get initial elements from seed or context - elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), - - // Prefilter to get matcher input, preserving a map for seed-results synchronization - matcherIn = preFilter && ( seed || !selector ) ? - condense( elems, preMap, preFilter, context, xml ) : - elems, - - matcherOut = matcher ? - // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, - postFinder || ( seed ? preFilter : preexisting || postFilter ) ? - - // ...intermediate processing is necessary - [] : - - // ...otherwise use results directly - results : - matcherIn; - - // Find primary matches - if ( matcher ) { - matcher( matcherIn, matcherOut, context, xml ); - } - - // Apply postFilter - if ( postFilter ) { - temp = condense( matcherOut, postMap ); - postFilter( temp, [], context, xml ); - - // Un-match failing elements by moving them back to matcherIn - i = temp.length; - while ( i-- ) { - if ( (elem = temp[i]) ) { - matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); - } - } - } - - if ( seed ) { - if ( postFinder || preFilter ) { - if ( postFinder ) { - // Get the final matcherOut by condensing this intermediate into postFinder contexts - temp = []; - i = matcherOut.length; - while ( i-- ) { - if ( (elem = matcherOut[i]) ) { - // Restore matcherIn since elem is not yet a final match - temp.push( (matcherIn[i] = elem) ); - } - } - postFinder( null, (matcherOut = []), temp, xml ); - } - - // Move matched elements from seed to results to keep them synchronized - i = matcherOut.length; - while ( i-- ) { - if ( (elem = matcherOut[i]) && - (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) { - - seed[temp] = !(results[temp] = elem); - } - } - } - - // Add elements to results, through postFinder if defined - } else { - matcherOut = condense( - matcherOut === results ? - matcherOut.splice( preexisting, matcherOut.length ) : - matcherOut - ); - if ( postFinder ) { - postFinder( null, results, matcherOut, xml ); - } else { - push.apply( results, matcherOut ); - } - } - }); -} - -function matcherFromTokens( tokens ) { - var checkContext, matcher, j, - len = tokens.length, - leadingRelative = Expr.relative[ tokens[0].type ], - implicitRelative = leadingRelative || Expr.relative[" "], - i = leadingRelative ? 1 : 0, - - // The foundational matcher ensures that elements are reachable from top-level context(s) - matchContext = addCombinator( function( elem ) { - return elem === checkContext; - }, implicitRelative, true ), - matchAnyContext = addCombinator( function( elem ) { - return indexOf.call( checkContext, elem ) > -1; - }, implicitRelative, true ), - matchers = [ function( elem, context, xml ) { - return ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( - (checkContext = context).nodeType ? - matchContext( elem, context, xml ) : - matchAnyContext( elem, context, xml ) ); - } ]; - - for ( ; i < len; i++ ) { - if ( (matcher = Expr.relative[ tokens[i].type ]) ) { - matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; - } else { - matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); - - // Return special upon seeing a positional matcher - if ( matcher[ expando ] ) { - // Find the next relative operator (if any) for proper handling - j = ++i; - for ( ; j < len; j++ ) { - if ( Expr.relative[ tokens[j].type ] ) { - break; - } - } - return setMatcher( - i > 1 && elementMatcher( matchers ), - i > 1 && toSelector( tokens.slice( 0, i - 1 ) ).replace( rtrim, "$1" ), - matcher, - i < j && matcherFromTokens( tokens.slice( i, j ) ), - j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), - j < len && toSelector( tokens ) - ); - } - matchers.push( matcher ); - } - } - - return elementMatcher( matchers ); -} - -function matcherFromGroupMatchers( elementMatchers, setMatchers ) { - // A counter to specify which element is currently being matched - var matcherCachedRuns = 0, - bySet = setMatchers.length > 0, - byElement = elementMatchers.length > 0, - superMatcher = function( seed, context, xml, results, expandContext ) { - var elem, j, matcher, - setMatched = [], - matchedCount = 0, - i = "0", - unmatched = seed && [], - outermost = expandContext != null, - contextBackup = outermostContext, - // We must always have either seed elements or context - elems = seed || byElement && Expr.find["TAG"]( "*", expandContext && context.parentNode || context ), - // Use integer dirruns iff this is the outermost matcher - dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1); - - if ( outermost ) { - outermostContext = context !== document && context; - cachedruns = matcherCachedRuns; - } - - // Add elements passing elementMatchers directly to results - // Keep `i` a string if there are no elements so `matchedCount` will be "00" below - for ( ; (elem = elems[i]) != null; i++ ) { - if ( byElement && elem ) { - j = 0; - while ( (matcher = elementMatchers[j++]) ) { - if ( matcher( elem, context, xml ) ) { - results.push( elem ); - break; - } - } - if ( outermost ) { - dirruns = dirrunsUnique; - cachedruns = ++matcherCachedRuns; - } - } - - // Track unmatched elements for set filters - if ( bySet ) { - // They will have gone through all possible matchers - if ( (elem = !matcher && elem) ) { - matchedCount--; - } - - // Lengthen the array for every element, matched or not - if ( seed ) { - unmatched.push( elem ); - } - } - } - - // Apply set filters to unmatched elements - matchedCount += i; - if ( bySet && i !== matchedCount ) { - j = 0; - while ( (matcher = setMatchers[j++]) ) { - matcher( unmatched, setMatched, context, xml ); - } - - if ( seed ) { - // Reintegrate element matches to eliminate the need for sorting - if ( matchedCount > 0 ) { - while ( i-- ) { - if ( !(unmatched[i] || setMatched[i]) ) { - setMatched[i] = pop.call( results ); - } - } - } - - // Discard index placeholder values to get only actual matches - setMatched = condense( setMatched ); - } - - // Add matches to results - push.apply( results, setMatched ); - - // Seedless set matches succeeding multiple successful matchers stipulate sorting - if ( outermost && !seed && setMatched.length > 0 && - ( matchedCount + setMatchers.length ) > 1 ) { - - Sizzle.uniqueSort( results ); - } - } - - // Override manipulation of globals by nested matchers - if ( outermost ) { - dirruns = dirrunsUnique; - outermostContext = contextBackup; - } - - return unmatched; - }; - - return bySet ? - markFunction( superMatcher ) : - superMatcher; -} - -compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) { - var i, - setMatchers = [], - elementMatchers = [], - cached = compilerCache[ selector + " " ]; - - if ( !cached ) { - // Generate a function of recursive functions that can be used to check each element - if ( !group ) { - group = tokenize( selector ); - } - i = group.length; - while ( i-- ) { - cached = matcherFromTokens( group[i] ); - if ( cached[ expando ] ) { - setMatchers.push( cached ); - } else { - elementMatchers.push( cached ); - } - } - - // Cache the compiled function - cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); - } - return cached; -}; - -function multipleContexts( selector, contexts, results ) { - var i = 0, - len = contexts.length; - for ( ; i < len; i++ ) { - Sizzle( selector, contexts[i], results ); - } - return results; -} - -function select( selector, context, results, seed ) { - var i, tokens, token, type, find, - match = tokenize( selector ); - - if ( !seed ) { - // Try to minimize operations if there is only one group - if ( match.length === 1 ) { - - // Take a shortcut and set the context if the root selector is an ID - tokens = match[0] = match[0].slice( 0 ); - if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && - context.nodeType === 9 && !documentIsXML && - Expr.relative[ tokens[1].type ] ) { - - context = Expr.find["ID"]( token.matches[0].replace( runescape, funescape ), context )[0]; - if ( !context ) { - return results; - } - - selector = selector.slice( tokens.shift().value.length ); - } - - // Fetch a seed set for right-to-left matching - i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; - while ( i-- ) { - token = tokens[i]; - - // Abort if we hit a combinator - if ( Expr.relative[ (type = token.type) ] ) { - break; - } - if ( (find = Expr.find[ type ]) ) { - // Search, expanding context for leading sibling combinators - if ( (seed = find( - token.matches[0].replace( runescape, funescape ), - rsibling.test( tokens[0].type ) && context.parentNode || context - )) ) { - - // If seed is empty or no tokens remain, we can return early - tokens.splice( i, 1 ); - selector = seed.length && toSelector( tokens ); - if ( !selector ) { - push.apply( results, slice.call( seed, 0 ) ); - return results; - } - - break; - } - } - } - } - } - - // Compile and execute a filtering function - // Provide `match` to avoid retokenization if we modified the selector above - compile( selector, match )( - seed, - context, - documentIsXML, - results, - rsibling.test( selector ) - ); - return results; -} - -// Deprecated -Expr.pseudos["nth"] = Expr.pseudos["eq"]; - -// Easy API for creating new setFilters -function setFilters() {} -Expr.filters = setFilters.prototype = Expr.pseudos; -Expr.setFilters = new setFilters(); - -// Initialize with the default document -setDocument(); - -// Override sizzle attribute retrieval -Sizzle.attr = jQuery.attr; -jQuery.find = Sizzle; -jQuery.expr = Sizzle.selectors; -jQuery.expr[":"] = jQuery.expr.pseudos; -jQuery.unique = Sizzle.uniqueSort; -jQuery.text = Sizzle.getText; -jQuery.isXMLDoc = Sizzle.isXML; -jQuery.contains = Sizzle.contains; - - -})( window ); -var runtil = /Until$/, - rparentsprev = /^(?:parents|prev(?:Until|All))/, - isSimple = /^.[^:#\[\.,]*$/, - rneedsContext = jQuery.expr.match.needsContext, - // methods guaranteed to produce a unique set when starting from a unique set - guaranteedUnique = { - children: true, - contents: true, - next: true, - prev: true - }; - -jQuery.fn.extend({ - find: function( selector ) { - var i, ret, self, - len = this.length; - - if ( typeof selector !== "string" ) { - self = this; - return this.pushStack( jQuery( selector ).filter(function() { - for ( i = 0; i < len; i++ ) { - if ( jQuery.contains( self[ i ], this ) ) { - return true; - } - } - }) ); - } - - ret = []; - for ( i = 0; i < len; i++ ) { - jQuery.find( selector, this[ i ], ret ); - } - - // Needed because $( selector, context ) becomes $( context ).find( selector ) - ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret ); - ret.selector = ( this.selector ? this.selector + " " : "" ) + selector; - return ret; - }, - - has: function( target ) { - var i, - targets = jQuery( target, this ), - len = targets.length; - - return this.filter(function() { - for ( i = 0; i < len; i++ ) { - if ( jQuery.contains( this, targets[i] ) ) { - return true; - } - } - }); - }, - - not: function( selector ) { - return this.pushStack( winnow(this, selector, false) ); - }, - - filter: function( selector ) { - return this.pushStack( winnow(this, selector, true) ); - }, - - is: function( selector ) { - return !!selector && ( - typeof selector === "string" ? - // If this is a positional/relative selector, check membership in the returned set - // so $("p:first").is("p:last") won't return true for a doc with two "p". - rneedsContext.test( selector ) ? - jQuery( selector, this.context ).index( this[0] ) >= 0 : - jQuery.filter( selector, this ).length > 0 : - this.filter( selector ).length > 0 ); - }, - - closest: function( selectors, context ) { - var cur, - i = 0, - l = this.length, - ret = [], - pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ? - jQuery( selectors, context || this.context ) : - 0; - - for ( ; i < l; i++ ) { - cur = this[i]; - - while ( cur && cur.ownerDocument && cur !== context && cur.nodeType !== 11 ) { - if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) { - ret.push( cur ); - break; - } - cur = cur.parentNode; - } - } - - return this.pushStack( ret.length > 1 ? jQuery.unique( ret ) : ret ); - }, - - // Determine the position of an element within - // the matched set of elements - index: function( elem ) { - - // No argument, return index in parent - if ( !elem ) { - return ( this[0] && this[0].parentNode ) ? this.first().prevAll().length : -1; - } - - // index in selector - if ( typeof elem === "string" ) { - return jQuery.inArray( this[0], jQuery( elem ) ); - } - - // Locate the position of the desired element - return jQuery.inArray( - // If it receives a jQuery object, the first element is used - elem.jquery ? elem[0] : elem, this ); - }, - - add: function( selector, context ) { - var set = typeof selector === "string" ? - jQuery( selector, context ) : - jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ), - all = jQuery.merge( this.get(), set ); - - return this.pushStack( jQuery.unique(all) ); - }, - - addBack: function( selector ) { - return this.add( selector == null ? - this.prevObject : this.prevObject.filter(selector) - ); - } -}); - -jQuery.fn.andSelf = jQuery.fn.addBack; - -function sibling( cur, dir ) { - do { - cur = cur[ dir ]; - } while ( cur && cur.nodeType !== 1 ); - - return cur; -} - -jQuery.each({ - parent: function( elem ) { - var parent = elem.parentNode; - return parent && parent.nodeType !== 11 ? parent : null; - }, - parents: function( elem ) { - return jQuery.dir( elem, "parentNode" ); - }, - parentsUntil: function( elem, i, until ) { - return jQuery.dir( elem, "parentNode", until ); - }, - next: function( elem ) { - return sibling( elem, "nextSibling" ); - }, - prev: function( elem ) { - return sibling( elem, "previousSibling" ); - }, - nextAll: function( elem ) { - return jQuery.dir( elem, "nextSibling" ); - }, - prevAll: function( elem ) { - return jQuery.dir( elem, "previousSibling" ); - }, - nextUntil: function( elem, i, until ) { - return jQuery.dir( elem, "nextSibling", until ); - }, - prevUntil: function( elem, i, until ) { - return jQuery.dir( elem, "previousSibling", until ); - }, - siblings: function( elem ) { - return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); - }, - children: function( elem ) { - return jQuery.sibling( elem.firstChild ); - }, - contents: function( elem ) { - return jQuery.nodeName( elem, "iframe" ) ? - elem.contentDocument || elem.contentWindow.document : - jQuery.merge( [], elem.childNodes ); - } -}, function( name, fn ) { - jQuery.fn[ name ] = function( until, selector ) { - var ret = jQuery.map( this, fn, until ); - - if ( !runtil.test( name ) ) { - selector = until; - } - - if ( selector && typeof selector === "string" ) { - ret = jQuery.filter( selector, ret ); - } - - ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; - - if ( this.length > 1 && rparentsprev.test( name ) ) { - ret = ret.reverse(); - } - - return this.pushStack( ret ); - }; -}); - -jQuery.extend({ - filter: function( expr, elems, not ) { - if ( not ) { - expr = ":not(" + expr + ")"; - } - - return elems.length === 1 ? - jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : - jQuery.find.matches(expr, elems); - }, - - dir: function( elem, dir, until ) { - var matched = [], - cur = elem[ dir ]; - - while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { - if ( cur.nodeType === 1 ) { - matched.push( cur ); - } - cur = cur[dir]; - } - return matched; - }, - - sibling: function( n, elem ) { - var r = []; - - for ( ; n; n = n.nextSibling ) { - if ( n.nodeType === 1 && n !== elem ) { - r.push( n ); - } - } - - return r; - } -}); - -// Implement the identical functionality for filter and not -function winnow( elements, qualifier, keep ) { - - // Can't pass null or undefined to indexOf in Firefox 4 - // Set to 0 to skip string check - qualifier = qualifier || 0; - - if ( jQuery.isFunction( qualifier ) ) { - return jQuery.grep(elements, function( elem, i ) { - var retVal = !!qualifier.call( elem, i, elem ); - return retVal === keep; - }); - - } else if ( qualifier.nodeType ) { - return jQuery.grep(elements, function( elem ) { - return ( elem === qualifier ) === keep; - }); - - } else if ( typeof qualifier === "string" ) { - var filtered = jQuery.grep(elements, function( elem ) { - return elem.nodeType === 1; - }); - - if ( isSimple.test( qualifier ) ) { - return jQuery.filter(qualifier, filtered, !keep); - } else { - qualifier = jQuery.filter( qualifier, filtered ); - } - } - - return jQuery.grep(elements, function( elem ) { - return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep; - }); -} -function createSafeFragment( document ) { - var list = nodeNames.split( "|" ), - safeFrag = document.createDocumentFragment(); - - if ( safeFrag.createElement ) { - while ( list.length ) { - safeFrag.createElement( - list.pop() - ); - } - } - return safeFrag; -} - -var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" + - "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", - rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g, - rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"), - rleadingWhitespace = /^\s+/, - rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi, - rtagName = /<([\w:]+)/, - rtbody = /<tbody/i, - rhtml = /<|&#?\w+;/, - rnoInnerhtml = /<(?:script|style|link)/i, - manipulation_rcheckableType = /^(?:checkbox|radio)$/i, - // checked="checked" or checked - rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, - rscriptType = /^$|\/(?:java|ecma)script/i, - rscriptTypeMasked = /^true\/(.*)/, - rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g, - - // We have to close these tags to support XHTML (#13200) - wrapMap = { - option: [ 1, "<select multiple='multiple'>", "</select>" ], - legend: [ 1, "<fieldset>", "</fieldset>" ], - area: [ 1, "<map>", "</map>" ], - param: [ 1, "<object>", "</object>" ], - thead: [ 1, "<table>", "</table>" ], - tr: [ 2, "<table><tbody>", "</tbody></table>" ], - col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ], - td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ], - - // IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags, - // unless wrapped in a div with non-breaking characters in front of it. - _default: jQuery.support.htmlSerialize ? [ 0, "", "" ] : [ 1, "X<div>", "</div>" ] - }, - safeFragment = createSafeFragment( document ), - fragmentDiv = safeFragment.appendChild( document.createElement("div") ); - -wrapMap.optgroup = wrapMap.option; -wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; -wrapMap.th = wrapMap.td; - -jQuery.fn.extend({ - text: function( value ) { - return jQuery.access( this, function( value ) { - return value === undefined ? - jQuery.text( this ) : - this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) ); - }, null, value, arguments.length ); - }, - - wrapAll: function( html ) { - if ( jQuery.isFunction( html ) ) { - return this.each(function(i) { - jQuery(this).wrapAll( html.call(this, i) ); - }); - } - - if ( this[0] ) { - // The elements to wrap the target around - var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true); - - if ( this[0].parentNode ) { - wrap.insertBefore( this[0] ); - } - - wrap.map(function() { - var elem = this; - - while ( elem.firstChild && elem.firstChild.nodeType === 1 ) { - elem = elem.firstChild; - } - - return elem; - }).append( this ); - } - - return this; - }, - - wrapInner: function( html ) { - if ( jQuery.isFunction( html ) ) { - return this.each(function(i) { - jQuery(this).wrapInner( html.call(this, i) ); - }); - } - - return this.each(function() { - var self = jQuery( this ), - contents = self.contents(); - - if ( contents.length ) { - contents.wrapAll( html ); - - } else { - self.append( html ); - } - }); - }, - - wrap: function( html ) { - var isFunction = jQuery.isFunction( html ); - - return this.each(function(i) { - jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html ); - }); - }, - - unwrap: function() { - return this.parent().each(function() { - if ( !jQuery.nodeName( this, "body" ) ) { - jQuery( this ).replaceWith( this.childNodes ); - } - }).end(); - }, - - append: function() { - return this.domManip(arguments, true, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - this.appendChild( elem ); - } - }); - }, - - prepend: function() { - return this.domManip(arguments, true, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - this.insertBefore( elem, this.firstChild ); - } - }); - }, - - before: function() { - return this.domManip( arguments, false, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this ); - } - }); - }, - - after: function() { - return this.domManip( arguments, false, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this.nextSibling ); - } - }); - }, - - // keepData is for internal use only--do not document - remove: function( selector, keepData ) { - var elem, - i = 0; - - for ( ; (elem = this[i]) != null; i++ ) { - if ( !selector || jQuery.filter( selector, [ elem ] ).length > 0 ) { - if ( !keepData && elem.nodeType === 1 ) { - jQuery.cleanData( getAll( elem ) ); - } - - if ( elem.parentNode ) { - if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) { - setGlobalEval( getAll( elem, "script" ) ); - } - elem.parentNode.removeChild( elem ); - } - } - } - - return this; - }, - - empty: function() { - var elem, - i = 0; - - for ( ; (elem = this[i]) != null; i++ ) { - // Remove element nodes and prevent memory leaks - if ( elem.nodeType === 1 ) { - jQuery.cleanData( getAll( elem, false ) ); - } - - // Remove any remaining nodes - while ( elem.firstChild ) { - elem.removeChild( elem.firstChild ); - } - - // If this is a select, ensure that it displays empty (#12336) - // Support: IE<9 - if ( elem.options && jQuery.nodeName( elem, "select" ) ) { - elem.options.length = 0; - } - } - - return this; - }, - - clone: function( dataAndEvents, deepDataAndEvents ) { - dataAndEvents = dataAndEvents == null ? false : dataAndEvents; - deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; - - return this.map( function () { - return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); - }); - }, - - html: function( value ) { - return jQuery.access( this, function( value ) { - var elem = this[0] || {}, - i = 0, - l = this.length; - - if ( value === undefined ) { - return elem.nodeType === 1 ? - elem.innerHTML.replace( rinlinejQuery, "" ) : - undefined; - } - - // See if we can take a shortcut and just use innerHTML - if ( typeof value === "string" && !rnoInnerhtml.test( value ) && - ( jQuery.support.htmlSerialize || !rnoshimcache.test( value ) ) && - ( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) && - !wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) { - - value = value.replace( rxhtmlTag, "<$1></$2>" ); - - try { - for (; i < l; i++ ) { - // Remove element nodes and prevent memory leaks - elem = this[i] || {}; - if ( elem.nodeType === 1 ) { - jQuery.cleanData( getAll( elem, false ) ); - elem.innerHTML = value; - } - } - - elem = 0; - - // If using innerHTML throws an exception, use the fallback method - } catch(e) {} - } - - if ( elem ) { - this.empty().append( value ); - } - }, null, value, arguments.length ); - }, - - replaceWith: function( value ) { - var isFunc = jQuery.isFunction( value ); - - // Make sure that the elements are removed from the DOM before they are inserted - // this can help fix replacing a parent with child elements - if ( !isFunc && typeof value !== "string" ) { - value = jQuery( value ).not( this ).detach(); - } - - return this.domManip( [ value ], true, function( elem ) { - var next = this.nextSibling, - parent = this.parentNode; - - if ( parent ) { - jQuery( this ).remove(); - parent.insertBefore( elem, next ); - } - }); - }, - - detach: function( selector ) { - return this.remove( selector, true ); - }, - - domManip: function( args, table, callback ) { - - // Flatten any nested arrays - args = core_concat.apply( [], args ); - - var first, node, hasScripts, - scripts, doc, fragment, - i = 0, - l = this.length, - set = this, - iNoClone = l - 1, - value = args[0], - isFunction = jQuery.isFunction( value ); - - // We can't cloneNode fragments that contain checked, in WebKit - if ( isFunction || !( l <= 1 || typeof value !== "string" || jQuery.support.checkClone || !rchecked.test( value ) ) ) { - return this.each(function( index ) { - var self = set.eq( index ); - if ( isFunction ) { - args[0] = value.call( this, index, table ? self.html() : undefined ); - } - self.domManip( args, table, callback ); - }); - } - - if ( l ) { - fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this ); - first = fragment.firstChild; - - if ( fragment.childNodes.length === 1 ) { - fragment = first; - } - - if ( first ) { - table = table && jQuery.nodeName( first, "tr" ); - scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); - hasScripts = scripts.length; - - // Use the original fragment for the last item instead of the first because it can end up - // being emptied incorrectly in certain situations (#8070). - for ( ; i < l; i++ ) { - node = fragment; - - if ( i !== iNoClone ) { - node = jQuery.clone( node, true, true ); - - // Keep references to cloned scripts for later restoration - if ( hasScripts ) { - jQuery.merge( scripts, getAll( node, "script" ) ); - } - } - - callback.call( - table && jQuery.nodeName( this[i], "table" ) ? - findOrAppend( this[i], "tbody" ) : - this[i], - node, - i - ); - } - - if ( hasScripts ) { - doc = scripts[ scripts.length - 1 ].ownerDocument; - - // Reenable scripts - jQuery.map( scripts, restoreScript ); - - // Evaluate executable scripts on first document insertion - for ( i = 0; i < hasScripts; i++ ) { - node = scripts[ i ]; - if ( rscriptType.test( node.type || "" ) && - !jQuery._data( node, "globalEval" ) && jQuery.contains( doc, node ) ) { - - if ( node.src ) { - // Hope ajax is available... - jQuery.ajax({ - url: node.src, - type: "GET", - dataType: "script", - async: false, - global: false, - "throws": true - }); - } else { - jQuery.globalEval( ( node.text || node.textContent || node.innerHTML || "" ).replace( rcleanScript, "" ) ); - } - } - } - } - - // Fix #11809: Avoid leaking memory - fragment = first = null; - } - } - - return this; - } -}); - -function findOrAppend( elem, tag ) { - return elem.getElementsByTagName( tag )[0] || elem.appendChild( elem.ownerDocument.createElement( tag ) ); -} - -// Replace/restore the type attribute of script elements for safe DOM manipulation -function disableScript( elem ) { - var attr = elem.getAttributeNode("type"); - elem.type = ( attr && attr.specified ) + "/" + elem.type; - return elem; -} -function restoreScript( elem ) { - var match = rscriptTypeMasked.exec( elem.type ); - if ( match ) { - elem.type = match[1]; - } else { - elem.removeAttribute("type"); - } - return elem; -} - -// Mark scripts as having already been evaluated -function setGlobalEval( elems, refElements ) { - var elem, - i = 0; - for ( ; (elem = elems[i]) != null; i++ ) { - jQuery._data( elem, "globalEval", !refElements || jQuery._data( refElements[i], "globalEval" ) ); - } -} - -function cloneCopyEvent( src, dest ) { - - if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) { - return; - } - - var type, i, l, - oldData = jQuery._data( src ), - curData = jQuery._data( dest, oldData ), - events = oldData.events; - - if ( events ) { - delete curData.handle; - curData.events = {}; - - for ( type in events ) { - for ( i = 0, l = events[ type ].length; i < l; i++ ) { - jQuery.event.add( dest, type, events[ type ][ i ] ); - } - } - } - - // make the cloned public data object a copy from the original - if ( curData.data ) { - curData.data = jQuery.extend( {}, curData.data ); - } -} - -function fixCloneNodeIssues( src, dest ) { - var nodeName, e, data; - - // We do not need to do anything for non-Elements - if ( dest.nodeType !== 1 ) { - return; - } - - nodeName = dest.nodeName.toLowerCase(); - - // IE6-8 copies events bound via attachEvent when using cloneNode. - if ( !jQuery.support.noCloneEvent && dest[ jQuery.expando ] ) { - data = jQuery._data( dest ); - - for ( e in data.events ) { - jQuery.removeEvent( dest, e, data.handle ); - } - - // Event data gets referenced instead of copied if the expando gets copied too - dest.removeAttribute( jQuery.expando ); - } - - // IE blanks contents when cloning scripts, and tries to evaluate newly-set text - if ( nodeName === "script" && dest.text !== src.text ) { - disableScript( dest ).text = src.text; - restoreScript( dest ); - - // IE6-10 improperly clones children of object elements using classid. - // IE10 throws NoModificationAllowedError if parent is null, #12132. - } else if ( nodeName === "object" ) { - if ( dest.parentNode ) { - dest.outerHTML = src.outerHTML; - } - - // This path appears unavoidable for IE9. When cloning an object - // element in IE9, the outerHTML strategy above is not sufficient. - // If the src has innerHTML and the destination does not, - // copy the src.innerHTML into the dest.innerHTML. #10324 - if ( jQuery.support.html5Clone && ( src.innerHTML && !jQuery.trim(dest.innerHTML) ) ) { - dest.innerHTML = src.innerHTML; - } - - } else if ( nodeName === "input" && manipulation_rcheckableType.test( src.type ) ) { - // IE6-8 fails to persist the checked state of a cloned checkbox - // or radio button. Worse, IE6-7 fail to give the cloned element - // a checked appearance if the defaultChecked value isn't also set - - dest.defaultChecked = dest.checked = src.checked; - - // IE6-7 get confused and end up setting the value of a cloned - // checkbox/radio button to an empty string instead of "on" - if ( dest.value !== src.value ) { - dest.value = src.value; - } - - // IE6-8 fails to return the selected option to the default selected - // state when cloning options - } else if ( nodeName === "option" ) { - dest.defaultSelected = dest.selected = src.defaultSelected; - - // IE6-8 fails to set the defaultValue to the correct value when - // cloning other types of input fields - } else if ( nodeName === "input" || nodeName === "textarea" ) { - dest.defaultValue = src.defaultValue; - } -} - -jQuery.each({ - appendTo: "append", - prependTo: "prepend", - insertBefore: "before", - insertAfter: "after", - replaceAll: "replaceWith" -}, function( name, original ) { - jQuery.fn[ name ] = function( selector ) { - var elems, - i = 0, - ret = [], - insert = jQuery( selector ), - last = insert.length - 1; - - for ( ; i <= last; i++ ) { - elems = i === last ? this : this.clone(true); - jQuery( insert[i] )[ original ]( elems ); - - // Modern browsers can apply jQuery collections as arrays, but oldIE needs a .get() - core_push.apply( ret, elems.get() ); - } - - return this.pushStack( ret ); - }; -}); - -function getAll( context, tag ) { - var elems, elem, - i = 0, - found = typeof context.getElementsByTagName !== core_strundefined ? context.getElementsByTagName( tag || "*" ) : - typeof context.querySelectorAll !== core_strundefined ? context.querySelectorAll( tag || "*" ) : - undefined; - - if ( !found ) { - for ( found = [], elems = context.childNodes || context; (elem = elems[i]) != null; i++ ) { - if ( !tag || jQuery.nodeName( elem, tag ) ) { - found.push( elem ); - } else { - jQuery.merge( found, getAll( elem, tag ) ); - } - } - } - - return tag === undefined || tag && jQuery.nodeName( context, tag ) ? - jQuery.merge( [ context ], found ) : - found; -} - -// Used in buildFragment, fixes the defaultChecked property -function fixDefaultChecked( elem ) { - if ( manipulation_rcheckableType.test( elem.type ) ) { - elem.defaultChecked = elem.checked; - } -} - -jQuery.extend({ - clone: function( elem, dataAndEvents, deepDataAndEvents ) { - var destElements, node, clone, i, srcElements, - inPage = jQuery.contains( elem.ownerDocument, elem ); - - if ( jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) { - clone = elem.cloneNode( true ); - - // IE<=8 does not properly clone detached, unknown element nodes - } else { - fragmentDiv.innerHTML = elem.outerHTML; - fragmentDiv.removeChild( clone = fragmentDiv.firstChild ); - } - - if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) && - (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) { - - // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2 - destElements = getAll( clone ); - srcElements = getAll( elem ); - - // Fix all IE cloning issues - for ( i = 0; (node = srcElements[i]) != null; ++i ) { - // Ensure that the destination node is not null; Fixes #9587 - if ( destElements[i] ) { - fixCloneNodeIssues( node, destElements[i] ); - } - } - } - - // Copy the events from the original to the clone - if ( dataAndEvents ) { - if ( deepDataAndEvents ) { - srcElements = srcElements || getAll( elem ); - destElements = destElements || getAll( clone ); - - for ( i = 0; (node = srcElements[i]) != null; i++ ) { - cloneCopyEvent( node, destElements[i] ); - } - } else { - cloneCopyEvent( elem, clone ); - } - } - - // Preserve script evaluation history - destElements = getAll( clone, "script" ); - if ( destElements.length > 0 ) { - setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); - } - - destElements = srcElements = node = null; - - // Return the cloned set - return clone; - }, - - buildFragment: function( elems, context, scripts, selection ) { - var j, elem, contains, - tmp, tag, tbody, wrap, - l = elems.length, - - // Ensure a safe fragment - safe = createSafeFragment( context ), - - nodes = [], - i = 0; - - for ( ; i < l; i++ ) { - elem = elems[ i ]; - - if ( elem || elem === 0 ) { - - // Add nodes directly - if ( jQuery.type( elem ) === "object" ) { - jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); - - // Convert non-html into a text node - } else if ( !rhtml.test( elem ) ) { - nodes.push( context.createTextNode( elem ) ); - - // Convert html into DOM nodes - } else { - tmp = tmp || safe.appendChild( context.createElement("div") ); - - // Deserialize a standard representation - tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase(); - wrap = wrapMap[ tag ] || wrapMap._default; - - tmp.innerHTML = wrap[1] + elem.replace( rxhtmlTag, "<$1></$2>" ) + wrap[2]; - - // Descend through wrappers to the right content - j = wrap[0]; - while ( j-- ) { - tmp = tmp.lastChild; - } - - // Manually add leading whitespace removed by IE - if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { - nodes.push( context.createTextNode( rleadingWhitespace.exec( elem )[0] ) ); - } - - // Remove IE's autoinserted <tbody> from table fragments - if ( !jQuery.support.tbody ) { - - // String was a <table>, *may* have spurious <tbody> - elem = tag === "table" && !rtbody.test( elem ) ? - tmp.firstChild : - - // String was a bare <thead> or <tfoot> - wrap[1] === "<table>" && !rtbody.test( elem ) ? - tmp : - 0; - - j = elem && elem.childNodes.length; - while ( j-- ) { - if ( jQuery.nodeName( (tbody = elem.childNodes[j]), "tbody" ) && !tbody.childNodes.length ) { - elem.removeChild( tbody ); - } - } - } - - jQuery.merge( nodes, tmp.childNodes ); - - // Fix #12392 for WebKit and IE > 9 - tmp.textContent = ""; - - // Fix #12392 for oldIE - while ( tmp.firstChild ) { - tmp.removeChild( tmp.firstChild ); - } - - // Remember the top-level container for proper cleanup - tmp = safe.lastChild; - } - } - } - - // Fix #11356: Clear elements from fragment - if ( tmp ) { - safe.removeChild( tmp ); - } - - // Reset defaultChecked for any radios and checkboxes - // about to be appended to the DOM in IE 6/7 (#8060) - if ( !jQuery.support.appendChecked ) { - jQuery.grep( getAll( nodes, "input" ), fixDefaultChecked ); - } - - i = 0; - while ( (elem = nodes[ i++ ]) ) { - - // #4087 - If origin and destination elements are the same, and this is - // that element, do not do anything - if ( selection && jQuery.inArray( elem, selection ) !== -1 ) { - continue; - } - - contains = jQuery.contains( elem.ownerDocument, elem ); - - // Append to fragment - tmp = getAll( safe.appendChild( elem ), "script" ); - - // Preserve script evaluation history - if ( contains ) { - setGlobalEval( tmp ); - } - - // Capture executables - if ( scripts ) { - j = 0; - while ( (elem = tmp[ j++ ]) ) { - if ( rscriptType.test( elem.type || "" ) ) { - scripts.push( elem ); - } - } - } - } - - tmp = null; - - return safe; - }, - - cleanData: function( elems, /* internal */ acceptData ) { - var elem, type, id, data, - i = 0, - internalKey = jQuery.expando, - cache = jQuery.cache, - deleteExpando = jQuery.support.deleteExpando, - special = jQuery.event.special; - - for ( ; (elem = elems[i]) != null; i++ ) { - - if ( acceptData || jQuery.acceptData( elem ) ) { - - id = elem[ internalKey ]; - data = id && cache[ id ]; - - if ( data ) { - if ( data.events ) { - for ( type in data.events ) { - if ( special[ type ] ) { - jQuery.event.remove( elem, type ); - - // This is a shortcut to avoid jQuery.event.remove's overhead - } else { - jQuery.removeEvent( elem, type, data.handle ); - } - } - } - - // Remove cache only if it was not already removed by jQuery.event.remove - if ( cache[ id ] ) { - - delete cache[ id ]; - - // IE does not allow us to delete expando properties from nodes, - // nor does it have a removeAttribute function on Document nodes; - // we must handle all of these cases - if ( deleteExpando ) { - delete elem[ internalKey ]; - - } else if ( typeof elem.removeAttribute !== core_strundefined ) { - elem.removeAttribute( internalKey ); - - } else { - elem[ internalKey ] = null; - } - - core_deletedIds.push( id ); - } - } - } - } - } -}); -var iframe, getStyles, curCSS, - ralpha = /alpha\([^)]*\)/i, - ropacity = /opacity\s*=\s*([^)]*)/, - rposition = /^(top|right|bottom|left)$/, - // swappable if display is none or starts with table except "table", "table-cell", or "table-caption" - // see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display - rdisplayswap = /^(none|table(?!-c[ea]).+)/, - rmargin = /^margin/, - rnumsplit = new RegExp( "^(" + core_pnum + ")(.*)$", "i" ), - rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ), - rrelNum = new RegExp( "^([+-])=(" + core_pnum + ")", "i" ), - elemdisplay = { BODY: "block" }, - - cssShow = { position: "absolute", visibility: "hidden", display: "block" }, - cssNormalTransform = { - letterSpacing: 0, - fontWeight: 400 - }, - - cssExpand = [ "Top", "Right", "Bottom", "Left" ], - cssPrefixes = [ "Webkit", "O", "Moz", "ms" ]; - -// return a css property mapped to a potentially vendor prefixed property -function vendorPropName( style, name ) { - - // shortcut for names that are not vendor prefixed - if ( name in style ) { - return name; - } - - // check for vendor prefixed names - var capName = name.charAt(0).toUpperCase() + name.slice(1), - origName = name, - i = cssPrefixes.length; - - while ( i-- ) { - name = cssPrefixes[ i ] + capName; - if ( name in style ) { - return name; - } - } - - return origName; -} - -function isHidden( elem, el ) { - // isHidden might be called from jQuery#filter function; - // in that case, element will be second argument - elem = el || elem; - return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem ); -} - -function showHide( elements, show ) { - var display, elem, hidden, - values = [], - index = 0, - length = elements.length; - - for ( ; index < length; index++ ) { - elem = elements[ index ]; - if ( !elem.style ) { - continue; - } - - values[ index ] = jQuery._data( elem, "olddisplay" ); - display = elem.style.display; - if ( show ) { - // Reset the inline display of this element to learn if it is - // being hidden by cascaded rules or not - if ( !values[ index ] && display === "none" ) { - elem.style.display = ""; - } - - // Set elements which have been overridden with display: none - // in a stylesheet to whatever the default browser style is - // for such an element - if ( elem.style.display === "" && isHidden( elem ) ) { - values[ index ] = jQuery._data( elem, "olddisplay", css_defaultDisplay(elem.nodeName) ); - } - } else { - - if ( !values[ index ] ) { - hidden = isHidden( elem ); - - if ( display && display !== "none" || !hidden ) { - jQuery._data( elem, "olddisplay", hidden ? display : jQuery.css( elem, "display" ) ); - } - } - } - } - - // Set the display of most of the elements in a second loop - // to avoid the constant reflow - for ( index = 0; index < length; index++ ) { - elem = elements[ index ]; - if ( !elem.style ) { - continue; - } - if ( !show || elem.style.display === "none" || elem.style.display === "" ) { - elem.style.display = show ? values[ index ] || "" : "none"; - } - } - - return elements; -} - -jQuery.fn.extend({ - css: function( name, value ) { - return jQuery.access( this, function( elem, name, value ) { - var len, styles, - map = {}, - i = 0; - - if ( jQuery.isArray( name ) ) { - styles = getStyles( elem ); - len = name.length; - - for ( ; i < len; i++ ) { - map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); - } - - return map; - } - - return value !== undefined ? - jQuery.style( elem, name, value ) : - jQuery.css( elem, name ); - }, name, value, arguments.length > 1 ); - }, - show: function() { - return showHide( this, true ); - }, - hide: function() { - return showHide( this ); - }, - toggle: function( state ) { - var bool = typeof state === "boolean"; - - return this.each(function() { - if ( bool ? state : isHidden( this ) ) { - jQuery( this ).show(); - } else { - jQuery( this ).hide(); - } - }); - } -}); - -jQuery.extend({ - // Add in style property hooks for overriding the default - // behavior of getting and setting a style property - cssHooks: { - opacity: { - get: function( elem, computed ) { - if ( computed ) { - // We should always get a number back from opacity - var ret = curCSS( elem, "opacity" ); - return ret === "" ? "1" : ret; - } - } - } - }, - - // Exclude the following css properties to add px - cssNumber: { - "columnCount": true, - "fillOpacity": true, - "fontWeight": true, - "lineHeight": true, - "opacity": true, - "orphans": true, - "widows": true, - "zIndex": true, - "zoom": true - }, - - // Add in properties whose names you wish to fix before - // setting or getting the value - cssProps: { - // normalize float css property - "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat" - }, - - // Get and set the style property on a DOM Node - style: function( elem, name, value, extra ) { - // Don't set styles on text and comment nodes - if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { - return; - } - - // Make sure that we're working with the right name - var ret, type, hooks, - origName = jQuery.camelCase( name ), - style = elem.style; - - name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) ); - - // gets hook for the prefixed version - // followed by the unprefixed version - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - - // Check if we're setting a value - if ( value !== undefined ) { - type = typeof value; - - // convert relative number strings (+= or -=) to relative numbers. #7345 - if ( type === "string" && (ret = rrelNum.exec( value )) ) { - value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) ); - // Fixes bug #9237 - type = "number"; - } - - // Make sure that NaN and null values aren't set. See: #7116 - if ( value == null || type === "number" && isNaN( value ) ) { - return; - } - - // If a number was passed in, add 'px' to the (except for certain CSS properties) - if ( type === "number" && !jQuery.cssNumber[ origName ] ) { - value += "px"; - } - - // Fixes #8908, it can be done more correctly by specifing setters in cssHooks, - // but it would mean to define eight (for every problematic property) identical functions - if ( !jQuery.support.clearCloneStyle && value === "" && name.indexOf("background") === 0 ) { - style[ name ] = "inherit"; - } - - // If a hook was provided, use that value, otherwise just set the specified value - if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) { - - // Wrapped to prevent IE from throwing errors when 'invalid' values are provided - // Fixes bug #5509 - try { - style[ name ] = value; - } catch(e) {} - } - - } else { - // If a hook was provided get the non-computed value from there - if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) { - return ret; - } - - // Otherwise just get the value from the style object - return style[ name ]; - } - }, - - css: function( elem, name, extra, styles ) { - var num, val, hooks, - origName = jQuery.camelCase( name ); - - // Make sure that we're working with the right name - name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) ); - - // gets hook for the prefixed version - // followed by the unprefixed version - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - - // If a hook was provided get the computed value from there - if ( hooks && "get" in hooks ) { - val = hooks.get( elem, true, extra ); - } - - // Otherwise, if a way to get the computed value exists, use that - if ( val === undefined ) { - val = curCSS( elem, name, styles ); - } - - //convert "normal" to computed value - if ( val === "normal" && name in cssNormalTransform ) { - val = cssNormalTransform[ name ]; - } - - // Return, converting to number if forced or a qualifier was provided and val looks numeric - if ( extra === "" || extra ) { - num = parseFloat( val ); - return extra === true || jQuery.isNumeric( num ) ? num || 0 : val; - } - return val; - }, - - // A method for quickly swapping in/out CSS properties to get correct calculations - swap: function( elem, options, callback, args ) { - var ret, name, - old = {}; - - // Remember the old values, and insert the new ones - for ( name in options ) { - old[ name ] = elem.style[ name ]; - elem.style[ name ] = options[ name ]; - } - - ret = callback.apply( elem, args || [] ); - - // Revert the old values - for ( name in options ) { - elem.style[ name ] = old[ name ]; - } - - return ret; - } -}); - -// NOTE: we've included the "window" in window.getComputedStyle -// because jsdom on node.js will break without it. -if ( window.getComputedStyle ) { - getStyles = function( elem ) { - return window.getComputedStyle( elem, null ); - }; - - curCSS = function( elem, name, _computed ) { - var width, minWidth, maxWidth, - computed = _computed || getStyles( elem ), - - // getPropertyValue is only needed for .css('filter') in IE9, see #12537 - ret = computed ? computed.getPropertyValue( name ) || computed[ name ] : undefined, - style = elem.style; - - if ( computed ) { - - if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) { - ret = jQuery.style( elem, name ); - } - - // A tribute to the "awesome hack by Dean Edwards" - // Chrome < 17 and Safari 5.0 uses "computed value" instead of "used value" for margin-right - // Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels - // this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values - if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) { - - // Remember the original values - width = style.width; - minWidth = style.minWidth; - maxWidth = style.maxWidth; - - // Put in the new values to get a computed value out - style.minWidth = style.maxWidth = style.width = ret; - ret = computed.width; - - // Revert the changed values - style.width = width; - style.minWidth = minWidth; - style.maxWidth = maxWidth; - } - } - - return ret; - }; -} else if ( document.documentElement.currentStyle ) { - getStyles = function( elem ) { - return elem.currentStyle; - }; - - curCSS = function( elem, name, _computed ) { - var left, rs, rsLeft, - computed = _computed || getStyles( elem ), - ret = computed ? computed[ name ] : undefined, - style = elem.style; - - // Avoid setting ret to empty string here - // so we don't default to auto - if ( ret == null && style && style[ name ] ) { - ret = style[ name ]; - } - - // From the awesome hack by Dean Edwards - // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 - - // If we're not dealing with a regular pixel number - // but a number that has a weird ending, we need to convert it to pixels - // but not position css attributes, as those are proportional to the parent element instead - // and we can't measure the parent instead because it might trigger a "stacking dolls" problem - if ( rnumnonpx.test( ret ) && !rposition.test( name ) ) { - - // Remember the original values - left = style.left; - rs = elem.runtimeStyle; - rsLeft = rs && rs.left; - - // Put in the new values to get a computed value out - if ( rsLeft ) { - rs.left = elem.currentStyle.left; - } - style.left = name === "fontSize" ? "1em" : ret; - ret = style.pixelLeft + "px"; - - // Revert the changed values - style.left = left; - if ( rsLeft ) { - rs.left = rsLeft; - } - } - - return ret === "" ? "auto" : ret; - }; -} - -function setPositiveNumber( elem, value, subtract ) { - var matches = rnumsplit.exec( value ); - return matches ? - // Guard against undefined "subtract", e.g., when used as in cssHooks - Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) : - value; -} - -function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) { - var i = extra === ( isBorderBox ? "border" : "content" ) ? - // If we already have the right measurement, avoid augmentation - 4 : - // Otherwise initialize for horizontal or vertical properties - name === "width" ? 1 : 0, - - val = 0; - - for ( ; i < 4; i += 2 ) { - // both box models exclude margin, so add it if we want it - if ( extra === "margin" ) { - val += jQuery.css( elem, extra + cssExpand[ i ], true, styles ); - } - - if ( isBorderBox ) { - // border-box includes padding, so remove it if we want content - if ( extra === "content" ) { - val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - } - - // at this point, extra isn't border nor margin, so remove border - if ( extra !== "margin" ) { - val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - } - } else { - // at this point, extra isn't content, so add padding - val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - - // at this point, extra isn't content nor padding, so add border - if ( extra !== "padding" ) { - val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - } - } - } - - return val; -} - -function getWidthOrHeight( elem, name, extra ) { - - // Start with offset property, which is equivalent to the border-box value - var valueIsBorderBox = true, - val = name === "width" ? elem.offsetWidth : elem.offsetHeight, - styles = getStyles( elem ), - isBorderBox = jQuery.support.boxSizing && jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; - - // some non-html elements return undefined for offsetWidth, so check for null/undefined - // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285 - // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668 - if ( val <= 0 || val == null ) { - // Fall back to computed then uncomputed css if necessary - val = curCSS( elem, name, styles ); - if ( val < 0 || val == null ) { - val = elem.style[ name ]; - } - - // Computed unit is not pixels. Stop here and return. - if ( rnumnonpx.test(val) ) { - return val; - } - - // we need the check for style in case a browser which returns unreliable values - // for getComputedStyle silently falls back to the reliable elem.style - valueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] ); - - // Normalize "", auto, and prepare for extra - val = parseFloat( val ) || 0; - } - - // use the active box-sizing model to add/subtract irrelevant styles - return ( val + - augmentWidthOrHeight( - elem, - name, - extra || ( isBorderBox ? "border" : "content" ), - valueIsBorderBox, - styles - ) - ) + "px"; -} - -// Try to determine the default display value of an element -function css_defaultDisplay( nodeName ) { - var doc = document, - display = elemdisplay[ nodeName ]; - - if ( !display ) { - display = actualDisplay( nodeName, doc ); - - // If the simple way fails, read from inside an iframe - if ( display === "none" || !display ) { - // Use the already-created iframe if possible - iframe = ( iframe || - jQuery("<iframe frameborder='0' width='0' height='0'/>") - .css( "cssText", "display:block !important" ) - ).appendTo( doc.documentElement ); - - // Always write a new HTML skeleton so Webkit and Firefox don't choke on reuse - doc = ( iframe[0].contentWindow || iframe[0].contentDocument ).document; - doc.write("<!doctype html><html><body>"); - doc.close(); - - display = actualDisplay( nodeName, doc ); - iframe.detach(); - } - - // Store the correct default display - elemdisplay[ nodeName ] = display; - } - - return display; -} - -// Called ONLY from within css_defaultDisplay -function actualDisplay( name, doc ) { - var elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ), - display = jQuery.css( elem[0], "display" ); - elem.remove(); - return display; -} - -jQuery.each([ "height", "width" ], function( i, name ) { - jQuery.cssHooks[ name ] = { - get: function( elem, computed, extra ) { - if ( computed ) { - // certain elements can have dimension info if we invisibly show them - // however, it must have a current display style that would benefit from this - return elem.offsetWidth === 0 && rdisplayswap.test( jQuery.css( elem, "display" ) ) ? - jQuery.swap( elem, cssShow, function() { - return getWidthOrHeight( elem, name, extra ); - }) : - getWidthOrHeight( elem, name, extra ); - } - }, - - set: function( elem, value, extra ) { - var styles = extra && getStyles( elem ); - return setPositiveNumber( elem, value, extra ? - augmentWidthOrHeight( - elem, - name, - extra, - jQuery.support.boxSizing && jQuery.css( elem, "boxSizing", false, styles ) === "border-box", - styles - ) : 0 - ); - } - }; -}); - -if ( !jQuery.support.opacity ) { - jQuery.cssHooks.opacity = { - get: function( elem, computed ) { - // IE uses filters for opacity - return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ? - ( 0.01 * parseFloat( RegExp.$1 ) ) + "" : - computed ? "1" : ""; - }, - - set: function( elem, value ) { - var style = elem.style, - currentStyle = elem.currentStyle, - opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "", - filter = currentStyle && currentStyle.filter || style.filter || ""; - - // IE has trouble with opacity if it does not have layout - // Force it by setting the zoom level - style.zoom = 1; - - // if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652 - // if value === "", then remove inline opacity #12685 - if ( ( value >= 1 || value === "" ) && - jQuery.trim( filter.replace( ralpha, "" ) ) === "" && - style.removeAttribute ) { - - // Setting style.filter to null, "" & " " still leave "filter:" in the cssText - // if "filter:" is present at all, clearType is disabled, we want to avoid this - // style.removeAttribute is IE Only, but so apparently is this code path... - style.removeAttribute( "filter" ); - - // if there is no filter style applied in a css rule or unset inline opacity, we are done - if ( value === "" || currentStyle && !currentStyle.filter ) { - return; - } - } - - // otherwise, set new filter values - style.filter = ralpha.test( filter ) ? - filter.replace( ralpha, opacity ) : - filter + " " + opacity; - } - }; -} - -// These hooks cannot be added until DOM ready because the support test -// for it is not run until after DOM ready -jQuery(function() { - if ( !jQuery.support.reliableMarginRight ) { - jQuery.cssHooks.marginRight = { - get: function( elem, computed ) { - if ( computed ) { - // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right - // Work around by temporarily setting element display to inline-block - return jQuery.swap( elem, { "display": "inline-block" }, - curCSS, [ elem, "marginRight" ] ); - } - } - }; - } - - // Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084 - // getComputedStyle returns percent when specified for top/left/bottom/right - // rather than make the css module depend on the offset module, we just check for it here - if ( !jQuery.support.pixelPosition && jQuery.fn.position ) { - jQuery.each( [ "top", "left" ], function( i, prop ) { - jQuery.cssHooks[ prop ] = { - get: function( elem, computed ) { - if ( computed ) { - computed = curCSS( elem, prop ); - // if curCSS returns percentage, fallback to offset - return rnumnonpx.test( computed ) ? - jQuery( elem ).position()[ prop ] + "px" : - computed; - } - } - }; - }); - } - -}); - -if ( jQuery.expr && jQuery.expr.filters ) { - jQuery.expr.filters.hidden = function( elem ) { - // Support: Opera <= 12.12 - // Opera reports offsetWidths and offsetHeights less than zero on some elements - return elem.offsetWidth <= 0 && elem.offsetHeight <= 0 || - (!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || jQuery.css( elem, "display" )) === "none"); - }; - - jQuery.expr.filters.visible = function( elem ) { - return !jQuery.expr.filters.hidden( elem ); - }; -} - -// These hooks are used by animate to expand properties -jQuery.each({ - margin: "", - padding: "", - border: "Width" -}, function( prefix, suffix ) { - jQuery.cssHooks[ prefix + suffix ] = { - expand: function( value ) { - var i = 0, - expanded = {}, - - // assumes a single number if not a string - parts = typeof value === "string" ? value.split(" ") : [ value ]; - - for ( ; i < 4; i++ ) { - expanded[ prefix + cssExpand[ i ] + suffix ] = - parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; - } - - return expanded; - } - }; - - if ( !rmargin.test( prefix ) ) { - jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; - } -}); -var r20 = /%20/g, - rbracket = /\[\]$/, - rCRLF = /\r?\n/g, - rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, - rsubmittable = /^(?:input|select|textarea|keygen)/i; - -jQuery.fn.extend({ - serialize: function() { - return jQuery.param( this.serializeArray() ); - }, - serializeArray: function() { - return this.map(function(){ - // Can add propHook for "elements" to filter or add form elements - var elements = jQuery.prop( this, "elements" ); - return elements ? jQuery.makeArray( elements ) : this; - }) - .filter(function(){ - var type = this.type; - // Use .is(":disabled") so that fieldset[disabled] works - return this.name && !jQuery( this ).is( ":disabled" ) && - rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && - ( this.checked || !manipulation_rcheckableType.test( type ) ); - }) - .map(function( i, elem ){ - var val = jQuery( this ).val(); - - return val == null ? - null : - jQuery.isArray( val ) ? - jQuery.map( val, function( val ){ - return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - }) : - { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - }).get(); - } -}); - -//Serialize an array of form elements or a set of -//key/values into a query string -jQuery.param = function( a, traditional ) { - var prefix, - s = [], - add = function( key, value ) { - // If value is a function, invoke it and return its value - value = jQuery.isFunction( value ) ? value() : ( value == null ? "" : value ); - s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value ); - }; - - // Set traditional to true for jQuery <= 1.3.2 behavior. - if ( traditional === undefined ) { - traditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional; - } - - // If an array was passed in, assume that it is an array of form elements. - if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { - // Serialize the form elements - jQuery.each( a, function() { - add( this.name, this.value ); - }); - - } else { - // If traditional, encode the "old" way (the way 1.3.2 or older - // did it), otherwise encode params recursively. - for ( prefix in a ) { - buildParams( prefix, a[ prefix ], traditional, add ); - } - } - - // Return the resulting serialization - return s.join( "&" ).replace( r20, "+" ); -}; - -function buildParams( prefix, obj, traditional, add ) { - var name; - - if ( jQuery.isArray( obj ) ) { - // Serialize array item. - jQuery.each( obj, function( i, v ) { - if ( traditional || rbracket.test( prefix ) ) { - // Treat each array item as a scalar. - add( prefix, v ); - - } else { - // Item is non-scalar (array or object), encode its numeric index. - buildParams( prefix + "[" + ( typeof v === "object" ? i : "" ) + "]", v, traditional, add ); - } - }); - - } else if ( !traditional && jQuery.type( obj ) === "object" ) { - // Serialize object item. - for ( name in obj ) { - buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); - } - - } else { - // Serialize scalar item. - add( prefix, obj ); - } -} -jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + - "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + - "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) { - - // Handle event binding - jQuery.fn[ name ] = function( data, fn ) { - return arguments.length > 0 ? - this.on( name, null, data, fn ) : - this.trigger( name ); - }; -}); - -jQuery.fn.hover = function( fnOver, fnOut ) { - return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); -}; -var - // Document location - ajaxLocParts, - ajaxLocation, - ajax_nonce = jQuery.now(), - - ajax_rquery = /\?/, - rhash = /#.*$/, - rts = /([?&])_=[^&]*/, - rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL - // #7653, #8125, #8152: local protocol detection - rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, - rnoContent = /^(?:GET|HEAD)$/, - rprotocol = /^\/\//, - rurl = /^([\w.+-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/, - - // Keep a copy of the old load method - _load = jQuery.fn.load, - - /* Prefilters - * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) - * 2) These are called: - * - BEFORE asking for a transport - * - AFTER param serialization (s.data is a string if s.processData is true) - * 3) key is the dataType - * 4) the catchall symbol "*" can be used - * 5) execution will start with transport dataType and THEN continue down to "*" if needed - */ - prefilters = {}, - - /* Transports bindings - * 1) key is the dataType - * 2) the catchall symbol "*" can be used - * 3) selection will start with transport dataType and THEN go to "*" if needed - */ - transports = {}, - - // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression - allTypes = "*/".concat("*"); - -// #8138, IE may throw an exception when accessing -// a field from window.location if document.domain has been set -try { - ajaxLocation = location.href; -} catch( e ) { - // Use the href attribute of an A element - // since IE will modify it given document.location - ajaxLocation = document.createElement( "a" ); - ajaxLocation.href = ""; - ajaxLocation = ajaxLocation.href; -} - -// Segment location into parts -ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || []; - -// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport -function addToPrefiltersOrTransports( structure ) { - - // dataTypeExpression is optional and defaults to "*" - return function( dataTypeExpression, func ) { - - if ( typeof dataTypeExpression !== "string" ) { - func = dataTypeExpression; - dataTypeExpression = "*"; - } - - var dataType, - i = 0, - dataTypes = dataTypeExpression.toLowerCase().match( core_rnotwhite ) || []; - - if ( jQuery.isFunction( func ) ) { - // For each dataType in the dataTypeExpression - while ( (dataType = dataTypes[i++]) ) { - // Prepend if requested - if ( dataType[0] === "+" ) { - dataType = dataType.slice( 1 ) || "*"; - (structure[ dataType ] = structure[ dataType ] || []).unshift( func ); - - // Otherwise append - } else { - (structure[ dataType ] = structure[ dataType ] || []).push( func ); - } - } - } - }; -} - -// Base inspection function for prefilters and transports -function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { - - var inspected = {}, - seekingTransport = ( structure === transports ); - - function inspect( dataType ) { - var selected; - inspected[ dataType ] = true; - jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { - var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); - if( typeof dataTypeOrTransport === "string" && !seekingTransport && !inspected[ dataTypeOrTransport ] ) { - options.dataTypes.unshift( dataTypeOrTransport ); - inspect( dataTypeOrTransport ); - return false; - } else if ( seekingTransport ) { - return !( selected = dataTypeOrTransport ); - } - }); - return selected; - } - - return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); -} - -// A special extend for ajax options -// that takes "flat" options (not to be deep extended) -// Fixes #9887 -function ajaxExtend( target, src ) { - var deep, key, - flatOptions = jQuery.ajaxSettings.flatOptions || {}; - - for ( key in src ) { - if ( src[ key ] !== undefined ) { - ( flatOptions[ key ] ? target : ( deep || (deep = {}) ) )[ key ] = src[ key ]; - } - } - if ( deep ) { - jQuery.extend( true, target, deep ); - } - - return target; -} - -jQuery.fn.load = function( url, params, callback ) { - if ( typeof url !== "string" && _load ) { - return _load.apply( this, arguments ); - } - - var selector, response, type, - self = this, - off = url.indexOf(" "); - - if ( off >= 0 ) { - selector = url.slice( off, url.length ); - url = url.slice( 0, off ); - } - - // If it's a function - if ( jQuery.isFunction( params ) ) { - - // We assume that it's the callback - callback = params; - params = undefined; - - // Otherwise, build a param string - } else if ( params && typeof params === "object" ) { - type = "POST"; - } - - // If we have elements to modify, make the request - if ( self.length > 0 ) { - jQuery.ajax({ - url: url, - - // if "type" variable is undefined, then "GET" method will be used - type: type, - dataType: "html", - data: params - }).done(function( responseText ) { - - // Save response for use in complete callback - response = arguments; - - self.html( selector ? - - // If a selector was specified, locate the right elements in a dummy div - // Exclude scripts to avoid IE 'Permission Denied' errors - jQuery("<div>").append( jQuery.parseHTML( responseText ) ).find( selector ) : - - // Otherwise use the full result - responseText ); - - }).complete( callback && function( jqXHR, status ) { - self.each( callback, response || [ jqXHR.responseText, status, jqXHR ] ); - }); - } - - return this; -}; - -// Attach a bunch of functions for handling common AJAX events -jQuery.each( [ "ajaxStart", "ajaxStop", "ajaxComplete", "ajaxError", "ajaxSuccess", "ajaxSend" ], function( i, type ){ - jQuery.fn[ type ] = function( fn ){ - return this.on( type, fn ); - }; -}); - -jQuery.each( [ "get", "post" ], function( i, method ) { - jQuery[ method ] = function( url, data, callback, type ) { - // shift arguments if data argument was omitted - if ( jQuery.isFunction( data ) ) { - type = type || callback; - callback = data; - data = undefined; - } - - return jQuery.ajax({ - url: url, - type: method, - dataType: type, - data: data, - success: callback - }); - }; -}); - -jQuery.extend({ - - // Counter for holding the number of active queries - active: 0, - - // Last-Modified header cache for next request - lastModified: {}, - etag: {}, - - ajaxSettings: { - url: ajaxLocation, - type: "GET", - isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ), - global: true, - processData: true, - async: true, - contentType: "application/x-www-form-urlencoded; charset=UTF-8", - /* - timeout: 0, - data: null, - dataType: null, - username: null, - password: null, - cache: null, - throws: false, - traditional: false, - headers: {}, - */ - - accepts: { - "*": allTypes, - text: "text/plain", - html: "text/html", - xml: "application/xml, text/xml", - json: "application/json, text/javascript" - }, - - contents: { - xml: /xml/, - html: /html/, - json: /json/ - }, - - responseFields: { - xml: "responseXML", - text: "responseText" - }, - - // Data converters - // Keys separate source (or catchall "*") and destination types with a single space - converters: { - - // Convert anything to text - "* text": window.String, - - // Text to html (true = no transformation) - "text html": true, - - // Evaluate text as a json expression - "text json": jQuery.parseJSON, - - // Parse text as xml - "text xml": jQuery.parseXML - }, - - // For options that shouldn't be deep extended: - // you can add your own custom options here if - // and when you create one that shouldn't be - // deep extended (see ajaxExtend) - flatOptions: { - url: true, - context: true - } - }, - - // Creates a full fledged settings object into target - // with both ajaxSettings and settings fields. - // If target is omitted, writes into ajaxSettings. - ajaxSetup: function( target, settings ) { - return settings ? - - // Building a settings object - ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : - - // Extending ajaxSettings - ajaxExtend( jQuery.ajaxSettings, target ); - }, - - ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), - ajaxTransport: addToPrefiltersOrTransports( transports ), - - // Main method - ajax: function( url, options ) { - - // If url is an object, simulate pre-1.5 signature - if ( typeof url === "object" ) { - options = url; - url = undefined; - } - - // Force options to be an object - options = options || {}; - - var // Cross-domain detection vars - parts, - // Loop variable - i, - // URL without anti-cache param - cacheURL, - // Response headers as string - responseHeadersString, - // timeout handle - timeoutTimer, - - // To know if global events are to be dispatched - fireGlobals, - - transport, - // Response headers - responseHeaders, - // Create the final options object - s = jQuery.ajaxSetup( {}, options ), - // Callbacks context - callbackContext = s.context || s, - // Context for global events is callbackContext if it is a DOM node or jQuery collection - globalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ? - jQuery( callbackContext ) : - jQuery.event, - // Deferreds - deferred = jQuery.Deferred(), - completeDeferred = jQuery.Callbacks("once memory"), - // Status-dependent callbacks - statusCode = s.statusCode || {}, - // Headers (they are sent all at once) - requestHeaders = {}, - requestHeadersNames = {}, - // The jqXHR state - state = 0, - // Default abort message - strAbort = "canceled", - // Fake xhr - jqXHR = { - readyState: 0, - - // Builds headers hashtable if needed - getResponseHeader: function( key ) { - var match; - if ( state === 2 ) { - if ( !responseHeaders ) { - responseHeaders = {}; - while ( (match = rheaders.exec( responseHeadersString )) ) { - responseHeaders[ match[1].toLowerCase() ] = match[ 2 ]; - } - } - match = responseHeaders[ key.toLowerCase() ]; - } - return match == null ? null : match; - }, - - // Raw string - getAllResponseHeaders: function() { - return state === 2 ? responseHeadersString : null; - }, - - // Caches the header - setRequestHeader: function( name, value ) { - var lname = name.toLowerCase(); - if ( !state ) { - name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name; - requestHeaders[ name ] = value; - } - return this; - }, - - // Overrides response content-type header - overrideMimeType: function( type ) { - if ( !state ) { - s.mimeType = type; - } - return this; - }, - - // Status-dependent callbacks - statusCode: function( map ) { - var code; - if ( map ) { - if ( state < 2 ) { - for ( code in map ) { - // Lazy-add the new callback in a way that preserves old ones - statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; - } - } else { - // Execute the appropriate callbacks - jqXHR.always( map[ jqXHR.status ] ); - } - } - return this; - }, - - // Cancel the request - abort: function( statusText ) { - var finalText = statusText || strAbort; - if ( transport ) { - transport.abort( finalText ); - } - done( 0, finalText ); - return this; - } - }; - - // Attach deferreds - deferred.promise( jqXHR ).complete = completeDeferred.add; - jqXHR.success = jqXHR.done; - jqXHR.error = jqXHR.fail; - - // Remove hash character (#7531: and string promotion) - // Add protocol if not provided (#5866: IE7 issue with protocol-less urls) - // Handle falsy url in the settings object (#10093: consistency with old signature) - // We also use the url parameter if available - s.url = ( ( url || s.url || ajaxLocation ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" ); - - // Alias method option to type as per ticket #12004 - s.type = options.method || options.type || s.method || s.type; - - // Extract dataTypes list - s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().match( core_rnotwhite ) || [""]; - - // A cross-domain request is in order when we have a protocol:host:port mismatch - if ( s.crossDomain == null ) { - parts = rurl.exec( s.url.toLowerCase() ); - s.crossDomain = !!( parts && - ( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] || - ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) != - ( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) ) - ); - } - - // Convert data if not already a string - if ( s.data && s.processData && typeof s.data !== "string" ) { - s.data = jQuery.param( s.data, s.traditional ); - } - - // Apply prefilters - inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); - - // If request was aborted inside a prefilter, stop there - if ( state === 2 ) { - return jqXHR; - } - - // We can fire global events as of now if asked to - fireGlobals = s.global; - - // Watch for a new set of requests - if ( fireGlobals && jQuery.active++ === 0 ) { - jQuery.event.trigger("ajaxStart"); - } - - // Uppercase the type - s.type = s.type.toUpperCase(); - - // Determine if request has content - s.hasContent = !rnoContent.test( s.type ); - - // Save the URL in case we're toying with the If-Modified-Since - // and/or If-None-Match header later on - cacheURL = s.url; - - // More options handling for requests with no content - if ( !s.hasContent ) { - - // If data is available, append data to url - if ( s.data ) { - cacheURL = ( s.url += ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + s.data ); - // #9682: remove data so that it's not used in an eventual retry - delete s.data; - } - - // Add anti-cache in url if needed - if ( s.cache === false ) { - s.url = rts.test( cacheURL ) ? - - // If there is already a '_' parameter, set its value - cacheURL.replace( rts, "$1_=" + ajax_nonce++ ) : - - // Otherwise add one to the end - cacheURL + ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ajax_nonce++; - } - } - - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - if ( jQuery.lastModified[ cacheURL ] ) { - jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); - } - if ( jQuery.etag[ cacheURL ] ) { - jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); - } - } - - // Set the correct header, if data is being sent - if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { - jqXHR.setRequestHeader( "Content-Type", s.contentType ); - } - - // Set the Accepts header for the server, depending on the dataType - jqXHR.setRequestHeader( - "Accept", - s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ? - s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : - s.accepts[ "*" ] - ); - - // Check for headers option - for ( i in s.headers ) { - jqXHR.setRequestHeader( i, s.headers[ i ] ); - } - - // Allow custom headers/mimetypes and early abort - if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) { - // Abort if not done already and return - return jqXHR.abort(); - } - - // aborting is no longer a cancellation - strAbort = "abort"; - - // Install callbacks on deferreds - for ( i in { success: 1, error: 1, complete: 1 } ) { - jqXHR[ i ]( s[ i ] ); - } - - // Get transport - transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); - - // If no transport, we auto-abort - if ( !transport ) { - done( -1, "No Transport" ); - } else { - jqXHR.readyState = 1; - - // Send global event - if ( fireGlobals ) { - globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); - } - // Timeout - if ( s.async && s.timeout > 0 ) { - timeoutTimer = setTimeout(function() { - jqXHR.abort("timeout"); - }, s.timeout ); - } - - try { - state = 1; - transport.send( requestHeaders, done ); - } catch ( e ) { - // Propagate exception as error if not done - if ( state < 2 ) { - done( -1, e ); - // Simply rethrow otherwise - } else { - throw e; - } - } - } - - // Callback for when everything is done - function done( status, nativeStatusText, responses, headers ) { - var isSuccess, success, error, response, modified, - statusText = nativeStatusText; - - // Called once - if ( state === 2 ) { - return; - } - - // State is "done" now - state = 2; - - // Clear timeout if it exists - if ( timeoutTimer ) { - clearTimeout( timeoutTimer ); - } - - // Dereference transport for early garbage collection - // (no matter how long the jqXHR object will be used) - transport = undefined; - - // Cache response headers - responseHeadersString = headers || ""; - - // Set readyState - jqXHR.readyState = status > 0 ? 4 : 0; - - // Get response data - if ( responses ) { - response = ajaxHandleResponses( s, jqXHR, responses ); - } - - // If successful, handle type chaining - if ( status >= 200 && status < 300 || status === 304 ) { - - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - modified = jqXHR.getResponseHeader("Last-Modified"); - if ( modified ) { - jQuery.lastModified[ cacheURL ] = modified; - } - modified = jqXHR.getResponseHeader("etag"); - if ( modified ) { - jQuery.etag[ cacheURL ] = modified; - } - } - - // if no content - if ( status === 204 ) { - isSuccess = true; - statusText = "nocontent"; - - // if not modified - } else if ( status === 304 ) { - isSuccess = true; - statusText = "notmodified"; - - // If we have data, let's convert it - } else { - isSuccess = ajaxConvert( s, response ); - statusText = isSuccess.state; - success = isSuccess.data; - error = isSuccess.error; - isSuccess = !error; - } - } else { - // We extract error from statusText - // then normalize statusText and status for non-aborts - error = statusText; - if ( status || !statusText ) { - statusText = "error"; - if ( status < 0 ) { - status = 0; - } - } - } - - // Set data for the fake xhr object - jqXHR.status = status; - jqXHR.statusText = ( nativeStatusText || statusText ) + ""; - - // Success/Error - if ( isSuccess ) { - deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); - } else { - deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); - } - - // Status-dependent callbacks - jqXHR.statusCode( statusCode ); - statusCode = undefined; - - if ( fireGlobals ) { - globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", - [ jqXHR, s, isSuccess ? success : error ] ); - } - - // Complete - completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); - - if ( fireGlobals ) { - globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); - // Handle the global AJAX counter - if ( !( --jQuery.active ) ) { - jQuery.event.trigger("ajaxStop"); - } - } - } - - return jqXHR; - }, - - getScript: function( url, callback ) { - return jQuery.get( url, undefined, callback, "script" ); - }, - - getJSON: function( url, data, callback ) { - return jQuery.get( url, data, callback, "json" ); - } -}); - -/* Handles responses to an ajax request: - * - sets all responseXXX fields accordingly - * - finds the right dataType (mediates between content-type and expected dataType) - * - returns the corresponding response - */ -function ajaxHandleResponses( s, jqXHR, responses ) { - var firstDataType, ct, finalDataType, type, - contents = s.contents, - dataTypes = s.dataTypes, - responseFields = s.responseFields; - - // Fill responseXXX fields - for ( type in responseFields ) { - if ( type in responses ) { - jqXHR[ responseFields[type] ] = responses[ type ]; - } - } - - // Remove auto dataType and get content-type in the process - while( dataTypes[ 0 ] === "*" ) { - dataTypes.shift(); - if ( ct === undefined ) { - ct = s.mimeType || jqXHR.getResponseHeader("Content-Type"); - } - } - - // Check if we're dealing with a known content-type - if ( ct ) { - for ( type in contents ) { - if ( contents[ type ] && contents[ type ].test( ct ) ) { - dataTypes.unshift( type ); - break; - } - } - } - - // Check to see if we have a response for the expected dataType - if ( dataTypes[ 0 ] in responses ) { - finalDataType = dataTypes[ 0 ]; - } else { - // Try convertible dataTypes - for ( type in responses ) { - if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) { - finalDataType = type; - break; - } - if ( !firstDataType ) { - firstDataType = type; - } - } - // Or just use first one - finalDataType = finalDataType || firstDataType; - } - - // If we found a dataType - // We add the dataType to the list if needed - // and return the corresponding response - if ( finalDataType ) { - if ( finalDataType !== dataTypes[ 0 ] ) { - dataTypes.unshift( finalDataType ); - } - return responses[ finalDataType ]; - } -} - -// Chain conversions given the request and the original response -function ajaxConvert( s, response ) { - var conv2, current, conv, tmp, - converters = {}, - i = 0, - // Work with a copy of dataTypes in case we need to modify it for conversion - dataTypes = s.dataTypes.slice(), - prev = dataTypes[ 0 ]; - - // Apply the dataFilter if provided - if ( s.dataFilter ) { - response = s.dataFilter( response, s.dataType ); - } - - // Create converters map with lowercased keys - if ( dataTypes[ 1 ] ) { - for ( conv in s.converters ) { - converters[ conv.toLowerCase() ] = s.converters[ conv ]; - } - } - - // Convert to each sequential dataType, tolerating list modification - for ( ; (current = dataTypes[++i]); ) { - - // There's only work to do if current dataType is non-auto - if ( current !== "*" ) { - - // Convert response if prev dataType is non-auto and differs from current - if ( prev !== "*" && prev !== current ) { - - // Seek a direct converter - conv = converters[ prev + " " + current ] || converters[ "* " + current ]; - - // If none found, seek a pair - if ( !conv ) { - for ( conv2 in converters ) { - - // If conv2 outputs current - tmp = conv2.split(" "); - if ( tmp[ 1 ] === current ) { - - // If prev can be converted to accepted input - conv = converters[ prev + " " + tmp[ 0 ] ] || - converters[ "* " + tmp[ 0 ] ]; - if ( conv ) { - // Condense equivalence converters - if ( conv === true ) { - conv = converters[ conv2 ]; - - // Otherwise, insert the intermediate dataType - } else if ( converters[ conv2 ] !== true ) { - current = tmp[ 0 ]; - dataTypes.splice( i--, 0, current ); - } - - break; - } - } - } - } - - // Apply converter (if not an equivalence) - if ( conv !== true ) { - - // Unless errors are allowed to bubble, catch and return them - if ( conv && s["throws"] ) { - response = conv( response ); - } else { - try { - response = conv( response ); - } catch ( e ) { - return { state: "parsererror", error: conv ? e : "No conversion from " + prev + " to " + current }; - } - } - } - } - - // Update prev for next iteration - prev = current; - } - } - - return { state: "success", data: response }; -} -// Install script dataType -jQuery.ajaxSetup({ - accepts: { - script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript" - }, - contents: { - script: /(?:java|ecma)script/ - }, - converters: { - "text script": function( text ) { - jQuery.globalEval( text ); - return text; - } - } -}); - -// Handle cache's special case and global -jQuery.ajaxPrefilter( "script", function( s ) { - if ( s.cache === undefined ) { - s.cache = false; - } - if ( s.crossDomain ) { - s.type = "GET"; - s.global = false; - } -}); - -// Bind script tag hack transport -jQuery.ajaxTransport( "script", function(s) { - - // This transport only deals with cross domain requests - if ( s.crossDomain ) { - - var script, - head = document.head || jQuery("head")[0] || document.documentElement; - - return { - - send: function( _, callback ) { - - script = document.createElement("script"); - - script.async = true; - - if ( s.scriptCharset ) { - script.charset = s.scriptCharset; - } - - script.src = s.url; - - // Attach handlers for all browsers - script.onload = script.onreadystatechange = function( _, isAbort ) { - - if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) { - - // Handle memory leak in IE - script.onload = script.onreadystatechange = null; - - // Remove the script - if ( script.parentNode ) { - script.parentNode.removeChild( script ); - } - - // Dereference the script - script = null; - - // Callback if not abort - if ( !isAbort ) { - callback( 200, "success" ); - } - } - }; - - // Circumvent IE6 bugs with base elements (#2709 and #4378) by prepending - // Use native DOM manipulation to avoid our domManip AJAX trickery - head.insertBefore( script, head.firstChild ); - }, - - abort: function() { - if ( script ) { - script.onload( undefined, true ); - } - } - }; - } -}); -var oldCallbacks = [], - rjsonp = /(=)\?(?=&|$)|\?\?/; - -// Default jsonp settings -jQuery.ajaxSetup({ - jsonp: "callback", - jsonpCallback: function() { - var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( ajax_nonce++ ) ); - this[ callback ] = true; - return callback; - } -}); - -// Detect, normalize options and install callbacks for jsonp requests -jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) { - - var callbackName, overwritten, responseContainer, - jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ? - "url" : - typeof s.data === "string" && !( s.contentType || "" ).indexOf("application/x-www-form-urlencoded") && rjsonp.test( s.data ) && "data" - ); - - // Handle iff the expected data type is "jsonp" or we have a parameter to set - if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) { - - // Get callback name, remembering preexisting value associated with it - callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ? - s.jsonpCallback() : - s.jsonpCallback; - - // Insert callback into url or form data - if ( jsonProp ) { - s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName ); - } else if ( s.jsonp !== false ) { - s.url += ( ajax_rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName; - } - - // Use data converter to retrieve json after script execution - s.converters["script json"] = function() { - if ( !responseContainer ) { - jQuery.error( callbackName + " was not called" ); - } - return responseContainer[ 0 ]; - }; - - // force json dataType - s.dataTypes[ 0 ] = "json"; - - // Install callback - overwritten = window[ callbackName ]; - window[ callbackName ] = function() { - responseContainer = arguments; - }; - - // Clean-up function (fires after converters) - jqXHR.always(function() { - // Restore preexisting value - window[ callbackName ] = overwritten; - - // Save back as free - if ( s[ callbackName ] ) { - // make sure that re-using the options doesn't screw things around - s.jsonpCallback = originalSettings.jsonpCallback; - - // save the callback name for future use - oldCallbacks.push( callbackName ); - } - - // Call if it was a function and we have a response - if ( responseContainer && jQuery.isFunction( overwritten ) ) { - overwritten( responseContainer[ 0 ] ); - } - - responseContainer = overwritten = undefined; - }); - - // Delegate to script - return "script"; - } -}); -var xhrCallbacks, xhrSupported, - xhrId = 0, - // #5280: Internet Explorer will keep connections alive if we don't abort on unload - xhrOnUnloadAbort = window.ActiveXObject && function() { - // Abort all pending requests - var key; - for ( key in xhrCallbacks ) { - xhrCallbacks[ key ]( undefined, true ); - } - }; - -// Functions to create xhrs -function createStandardXHR() { - try { - return new window.XMLHttpRequest(); - } catch( e ) {} -} - -function createActiveXHR() { - try { - return new window.ActiveXObject("Microsoft.XMLHTTP"); - } catch( e ) {} -} - -// Create the request object -// (This is still attached to ajaxSettings for backward compatibility) -jQuery.ajaxSettings.xhr = window.ActiveXObject ? - /* Microsoft failed to properly - * implement the XMLHttpRequest in IE7 (can't request local files), - * so we use the ActiveXObject when it is available - * Additionally XMLHttpRequest can be disabled in IE7/IE8 so - * we need a fallback. - */ - function() { - return !this.isLocal && createStandardXHR() || createActiveXHR(); - } : - // For all other browsers, use the standard XMLHttpRequest object - createStandardXHR; - -// Determine support properties -xhrSupported = jQuery.ajaxSettings.xhr(); -jQuery.support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); -xhrSupported = jQuery.support.ajax = !!xhrSupported; - -// Create transport if the browser can provide an xhr -if ( xhrSupported ) { - - jQuery.ajaxTransport(function( s ) { - // Cross domain only allowed if supported through XMLHttpRequest - if ( !s.crossDomain || jQuery.support.cors ) { - - var callback; - - return { - send: function( headers, complete ) { - - // Get a new xhr - var handle, i, - xhr = s.xhr(); - - // Open the socket - // Passing null username, generates a login popup on Opera (#2865) - if ( s.username ) { - xhr.open( s.type, s.url, s.async, s.username, s.password ); - } else { - xhr.open( s.type, s.url, s.async ); - } - - // Apply custom fields if provided - if ( s.xhrFields ) { - for ( i in s.xhrFields ) { - xhr[ i ] = s.xhrFields[ i ]; - } - } - - // Override mime type if needed - if ( s.mimeType && xhr.overrideMimeType ) { - xhr.overrideMimeType( s.mimeType ); - } - - // X-Requested-With header - // For cross-domain requests, seeing as conditions for a preflight are - // akin to a jigsaw puzzle, we simply never set it to be sure. - // (it can always be set on a per-request basis or even using ajaxSetup) - // For same-domain requests, won't change header if already provided. - if ( !s.crossDomain && !headers["X-Requested-With"] ) { - headers["X-Requested-With"] = "XMLHttpRequest"; - } - - // Need an extra try/catch for cross domain requests in Firefox 3 - try { - for ( i in headers ) { - xhr.setRequestHeader( i, headers[ i ] ); - } - } catch( err ) {} - - // Do send the request - // This may raise an exception which is actually - // handled in jQuery.ajax (so no try/catch here) - xhr.send( ( s.hasContent && s.data ) || null ); - - // Listener - callback = function( _, isAbort ) { - var status, responseHeaders, statusText, responses; - - // Firefox throws exceptions when accessing properties - // of an xhr when a network error occurred - // http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE) - try { - - // Was never called and is aborted or complete - if ( callback && ( isAbort || xhr.readyState === 4 ) ) { - - // Only called once - callback = undefined; - - // Do not keep as active anymore - if ( handle ) { - xhr.onreadystatechange = jQuery.noop; - if ( xhrOnUnloadAbort ) { - delete xhrCallbacks[ handle ]; - } - } - - // If it's an abort - if ( isAbort ) { - // Abort it manually if needed - if ( xhr.readyState !== 4 ) { - xhr.abort(); - } - } else { - responses = {}; - status = xhr.status; - responseHeaders = xhr.getAllResponseHeaders(); - - // When requesting binary data, IE6-9 will throw an exception - // on any attempt to access responseText (#11426) - if ( typeof xhr.responseText === "string" ) { - responses.text = xhr.responseText; - } - - // Firefox throws an exception when accessing - // statusText for faulty cross-domain requests - try { - statusText = xhr.statusText; - } catch( e ) { - // We normalize with Webkit giving an empty statusText - statusText = ""; - } - - // Filter status for non standard behaviors - - // If the request is local and we have data: assume a success - // (success with no data won't get notified, that's the best we - // can do given current implementations) - if ( !status && s.isLocal && !s.crossDomain ) { - status = responses.text ? 200 : 404; - // IE - #1450: sometimes returns 1223 when it should be 204 - } else if ( status === 1223 ) { - status = 204; - } - } - } - } catch( firefoxAccessException ) { - if ( !isAbort ) { - complete( -1, firefoxAccessException ); - } - } - - // Call complete if needed - if ( responses ) { - complete( status, statusText, responses, responseHeaders ); - } - }; - - if ( !s.async ) { - // if we're in sync mode we fire the callback - callback(); - } else if ( xhr.readyState === 4 ) { - // (IE6 & IE7) if it's in cache and has been - // retrieved directly we need to fire the callback - setTimeout( callback ); - } else { - handle = ++xhrId; - if ( xhrOnUnloadAbort ) { - // Create the active xhrs callbacks list if needed - // and attach the unload handler - if ( !xhrCallbacks ) { - xhrCallbacks = {}; - jQuery( window ).unload( xhrOnUnloadAbort ); - } - // Add to list of active xhrs callbacks - xhrCallbacks[ handle ] = callback; - } - xhr.onreadystatechange = callback; - } - }, - - abort: function() { - if ( callback ) { - callback( undefined, true ); - } - } - }; - } - }); -} -var fxNow, timerId, - rfxtypes = /^(?:toggle|show|hide)$/, - rfxnum = new RegExp( "^(?:([+-])=|)(" + core_pnum + ")([a-z%]*)$", "i" ), - rrun = /queueHooks$/, - animationPrefilters = [ defaultPrefilter ], - tweeners = { - "*": [function( prop, value ) { - var end, unit, - tween = this.createTween( prop, value ), - parts = rfxnum.exec( value ), - target = tween.cur(), - start = +target || 0, - scale = 1, - maxIterations = 20; - - if ( parts ) { - end = +parts[2]; - unit = parts[3] || ( jQuery.cssNumber[ prop ] ? "" : "px" ); - - // We need to compute starting value - if ( unit !== "px" && start ) { - // Iteratively approximate from a nonzero starting point - // Prefer the current property, because this process will be trivial if it uses the same units - // Fallback to end or a simple constant - start = jQuery.css( tween.elem, prop, true ) || end || 1; - - do { - // If previous iteration zeroed out, double until we get *something* - // Use a string for doubling factor so we don't accidentally see scale as unchanged below - scale = scale || ".5"; - - // Adjust and apply - start = start / scale; - jQuery.style( tween.elem, prop, start + unit ); - - // Update scale, tolerating zero or NaN from tween.cur() - // And breaking the loop if scale is unchanged or perfect, or if we've just had enough - } while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations ); - } - - tween.unit = unit; - tween.start = start; - // If a +=/-= token was provided, we're doing a relative animation - tween.end = parts[1] ? start + ( parts[1] + 1 ) * end : end; - } - return tween; - }] - }; - -// Animations created synchronously will run synchronously -function createFxNow() { - setTimeout(function() { - fxNow = undefined; - }); - return ( fxNow = jQuery.now() ); -} - -function createTweens( animation, props ) { - jQuery.each( props, function( prop, value ) { - var collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ), - index = 0, - length = collection.length; - for ( ; index < length; index++ ) { - if ( collection[ index ].call( animation, prop, value ) ) { - - // we're done with this property - return; - } - } - }); -} - -function Animation( elem, properties, options ) { - var result, - stopped, - index = 0, - length = animationPrefilters.length, - deferred = jQuery.Deferred().always( function() { - // don't match elem in the :animated selector - delete tick.elem; - }), - tick = function() { - if ( stopped ) { - return false; - } - var currentTime = fxNow || createFxNow(), - remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), - // archaic crash bug won't allow us to use 1 - ( 0.5 || 0 ) (#12497) - temp = remaining / animation.duration || 0, - percent = 1 - temp, - index = 0, - length = animation.tweens.length; - - for ( ; index < length ; index++ ) { - animation.tweens[ index ].run( percent ); - } - - deferred.notifyWith( elem, [ animation, percent, remaining ]); - - if ( percent < 1 && length ) { - return remaining; - } else { - deferred.resolveWith( elem, [ animation ] ); - return false; - } - }, - animation = deferred.promise({ - elem: elem, - props: jQuery.extend( {}, properties ), - opts: jQuery.extend( true, { specialEasing: {} }, options ), - originalProperties: properties, - originalOptions: options, - startTime: fxNow || createFxNow(), - duration: options.duration, - tweens: [], - createTween: function( prop, end ) { - var tween = jQuery.Tween( elem, animation.opts, prop, end, - animation.opts.specialEasing[ prop ] || animation.opts.easing ); - animation.tweens.push( tween ); - return tween; - }, - stop: function( gotoEnd ) { - var index = 0, - // if we are going to the end, we want to run all the tweens - // otherwise we skip this part - length = gotoEnd ? animation.tweens.length : 0; - if ( stopped ) { - return this; - } - stopped = true; - for ( ; index < length ; index++ ) { - animation.tweens[ index ].run( 1 ); - } - - // resolve when we played the last frame - // otherwise, reject - if ( gotoEnd ) { - deferred.resolveWith( elem, [ animation, gotoEnd ] ); - } else { - deferred.rejectWith( elem, [ animation, gotoEnd ] ); - } - return this; - } - }), - props = animation.props; - - propFilter( props, animation.opts.specialEasing ); - - for ( ; index < length ; index++ ) { - result = animationPrefilters[ index ].call( animation, elem, props, animation.opts ); - if ( result ) { - return result; - } - } - - createTweens( animation, props ); - - if ( jQuery.isFunction( animation.opts.start ) ) { - animation.opts.start.call( elem, animation ); - } - - jQuery.fx.timer( - jQuery.extend( tick, { - elem: elem, - anim: animation, - queue: animation.opts.queue - }) - ); - - // attach callbacks from options - return animation.progress( animation.opts.progress ) - .done( animation.opts.done, animation.opts.complete ) - .fail( animation.opts.fail ) - .always( animation.opts.always ); -} - -function propFilter( props, specialEasing ) { - var value, name, index, easing, hooks; - - // camelCase, specialEasing and expand cssHook pass - for ( index in props ) { - name = jQuery.camelCase( index ); - easing = specialEasing[ name ]; - value = props[ index ]; - if ( jQuery.isArray( value ) ) { - easing = value[ 1 ]; - value = props[ index ] = value[ 0 ]; - } - - if ( index !== name ) { - props[ name ] = value; - delete props[ index ]; - } - - hooks = jQuery.cssHooks[ name ]; - if ( hooks && "expand" in hooks ) { - value = hooks.expand( value ); - delete props[ name ]; - - // not quite $.extend, this wont overwrite keys already present. - // also - reusing 'index' from above because we have the correct "name" - for ( index in value ) { - if ( !( index in props ) ) { - props[ index ] = value[ index ]; - specialEasing[ index ] = easing; - } - } - } else { - specialEasing[ name ] = easing; - } - } -} - -jQuery.Animation = jQuery.extend( Animation, { - - tweener: function( props, callback ) { - if ( jQuery.isFunction( props ) ) { - callback = props; - props = [ "*" ]; - } else { - props = props.split(" "); - } - - var prop, - index = 0, - length = props.length; - - for ( ; index < length ; index++ ) { - prop = props[ index ]; - tweeners[ prop ] = tweeners[ prop ] || []; - tweeners[ prop ].unshift( callback ); - } - }, - - prefilter: function( callback, prepend ) { - if ( prepend ) { - animationPrefilters.unshift( callback ); - } else { - animationPrefilters.push( callback ); - } - } -}); - -function defaultPrefilter( elem, props, opts ) { - /*jshint validthis:true */ - var prop, index, length, - value, dataShow, toggle, - tween, hooks, oldfire, - anim = this, - style = elem.style, - orig = {}, - handled = [], - hidden = elem.nodeType && isHidden( elem ); - - // handle queue: false promises - if ( !opts.queue ) { - hooks = jQuery._queueHooks( elem, "fx" ); - if ( hooks.unqueued == null ) { - hooks.unqueued = 0; - oldfire = hooks.empty.fire; - hooks.empty.fire = function() { - if ( !hooks.unqueued ) { - oldfire(); - } - }; - } - hooks.unqueued++; - - anim.always(function() { - // doing this makes sure that the complete handler will be called - // before this completes - anim.always(function() { - hooks.unqueued--; - if ( !jQuery.queue( elem, "fx" ).length ) { - hooks.empty.fire(); - } - }); - }); - } - - // height/width overflow pass - if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) { - // Make sure that nothing sneaks out - // Record all 3 overflow attributes because IE does not - // change the overflow attribute when overflowX and - // overflowY are set to the same value - opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; - - // Set display property to inline-block for height/width - // animations on inline elements that are having width/height animated - if ( jQuery.css( elem, "display" ) === "inline" && - jQuery.css( elem, "float" ) === "none" ) { - - // inline-level elements accept inline-block; - // block-level elements need to be inline with layout - if ( !jQuery.support.inlineBlockNeedsLayout || css_defaultDisplay( elem.nodeName ) === "inline" ) { - style.display = "inline-block"; - - } else { - style.zoom = 1; - } - } - } - - if ( opts.overflow ) { - style.overflow = "hidden"; - if ( !jQuery.support.shrinkWrapBlocks ) { - anim.always(function() { - style.overflow = opts.overflow[ 0 ]; - style.overflowX = opts.overflow[ 1 ]; - style.overflowY = opts.overflow[ 2 ]; - }); - } - } - - - // show/hide pass - for ( index in props ) { - value = props[ index ]; - if ( rfxtypes.exec( value ) ) { - delete props[ index ]; - toggle = toggle || value === "toggle"; - if ( value === ( hidden ? "hide" : "show" ) ) { - continue; - } - handled.push( index ); - } - } - - length = handled.length; - if ( length ) { - dataShow = jQuery._data( elem, "fxshow" ) || jQuery._data( elem, "fxshow", {} ); - if ( "hidden" in dataShow ) { - hidden = dataShow.hidden; - } - - // store state if its toggle - enables .stop().toggle() to "reverse" - if ( toggle ) { - dataShow.hidden = !hidden; - } - if ( hidden ) { - jQuery( elem ).show(); - } else { - anim.done(function() { - jQuery( elem ).hide(); - }); - } - anim.done(function() { - var prop; - jQuery._removeData( elem, "fxshow" ); - for ( prop in orig ) { - jQuery.style( elem, prop, orig[ prop ] ); - } - }); - for ( index = 0 ; index < length ; index++ ) { - prop = handled[ index ]; - tween = anim.createTween( prop, hidden ? dataShow[ prop ] : 0 ); - orig[ prop ] = dataShow[ prop ] || jQuery.style( elem, prop ); - - if ( !( prop in dataShow ) ) { - dataShow[ prop ] = tween.start; - if ( hidden ) { - tween.end = tween.start; - tween.start = prop === "width" || prop === "height" ? 1 : 0; - } - } - } - } -} - -function Tween( elem, options, prop, end, easing ) { - return new Tween.prototype.init( elem, options, prop, end, easing ); -} -jQuery.Tween = Tween; - -Tween.prototype = { - constructor: Tween, - init: function( elem, options, prop, end, easing, unit ) { - this.elem = elem; - this.prop = prop; - this.easing = easing || "swing"; - this.options = options; - this.start = this.now = this.cur(); - this.end = end; - this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); - }, - cur: function() { - var hooks = Tween.propHooks[ this.prop ]; - - return hooks && hooks.get ? - hooks.get( this ) : - Tween.propHooks._default.get( this ); - }, - run: function( percent ) { - var eased, - hooks = Tween.propHooks[ this.prop ]; - - if ( this.options.duration ) { - this.pos = eased = jQuery.easing[ this.easing ]( - percent, this.options.duration * percent, 0, 1, this.options.duration - ); - } else { - this.pos = eased = percent; - } - this.now = ( this.end - this.start ) * eased + this.start; - - if ( this.options.step ) { - this.options.step.call( this.elem, this.now, this ); - } - - if ( hooks && hooks.set ) { - hooks.set( this ); - } else { - Tween.propHooks._default.set( this ); - } - return this; - } -}; - -Tween.prototype.init.prototype = Tween.prototype; - -Tween.propHooks = { - _default: { - get: function( tween ) { - var result; - - if ( tween.elem[ tween.prop ] != null && - (!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) { - return tween.elem[ tween.prop ]; - } - - // passing an empty string as a 3rd parameter to .css will automatically - // attempt a parseFloat and fallback to a string if the parse fails - // so, simple values such as "10px" are parsed to Float. - // complex values such as "rotate(1rad)" are returned as is. - result = jQuery.css( tween.elem, tween.prop, "" ); - // Empty strings, null, undefined and "auto" are converted to 0. - return !result || result === "auto" ? 0 : result; - }, - set: function( tween ) { - // use step hook for back compat - use cssHook if its there - use .style if its - // available and use plain properties where available - if ( jQuery.fx.step[ tween.prop ] ) { - jQuery.fx.step[ tween.prop ]( tween ); - } else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) { - jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); - } else { - tween.elem[ tween.prop ] = tween.now; - } - } - } -}; - -// Remove in 2.0 - this supports IE8's panic based approach -// to setting things on disconnected nodes - -Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { - set: function( tween ) { - if ( tween.elem.nodeType && tween.elem.parentNode ) { - tween.elem[ tween.prop ] = tween.now; - } - } -}; - -jQuery.each([ "toggle", "show", "hide" ], function( i, name ) { - var cssFn = jQuery.fn[ name ]; - jQuery.fn[ name ] = function( speed, easing, callback ) { - return speed == null || typeof speed === "boolean" ? - cssFn.apply( this, arguments ) : - this.animate( genFx( name, true ), speed, easing, callback ); - }; -}); - -jQuery.fn.extend({ - fadeTo: function( speed, to, easing, callback ) { - - // show any hidden elements after setting opacity to 0 - return this.filter( isHidden ).css( "opacity", 0 ).show() - - // animate to the value specified - .end().animate({ opacity: to }, speed, easing, callback ); - }, - animate: function( prop, speed, easing, callback ) { - var empty = jQuery.isEmptyObject( prop ), - optall = jQuery.speed( speed, easing, callback ), - doAnimation = function() { - // Operate on a copy of prop so per-property easing won't be lost - var anim = Animation( this, jQuery.extend( {}, prop ), optall ); - doAnimation.finish = function() { - anim.stop( true ); - }; - // Empty animations, or finishing resolves immediately - if ( empty || jQuery._data( this, "finish" ) ) { - anim.stop( true ); - } - }; - doAnimation.finish = doAnimation; - - return empty || optall.queue === false ? - this.each( doAnimation ) : - this.queue( optall.queue, doAnimation ); - }, - stop: function( type, clearQueue, gotoEnd ) { - var stopQueue = function( hooks ) { - var stop = hooks.stop; - delete hooks.stop; - stop( gotoEnd ); - }; - - if ( typeof type !== "string" ) { - gotoEnd = clearQueue; - clearQueue = type; - type = undefined; - } - if ( clearQueue && type !== false ) { - this.queue( type || "fx", [] ); - } - - return this.each(function() { - var dequeue = true, - index = type != null && type + "queueHooks", - timers = jQuery.timers, - data = jQuery._data( this ); - - if ( index ) { - if ( data[ index ] && data[ index ].stop ) { - stopQueue( data[ index ] ); - } - } else { - for ( index in data ) { - if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { - stopQueue( data[ index ] ); - } - } - } - - for ( index = timers.length; index--; ) { - if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) { - timers[ index ].anim.stop( gotoEnd ); - dequeue = false; - timers.splice( index, 1 ); - } - } - - // start the next in the queue if the last step wasn't forced - // timers currently will call their complete callbacks, which will dequeue - // but only if they were gotoEnd - if ( dequeue || !gotoEnd ) { - jQuery.dequeue( this, type ); - } - }); - }, - finish: function( type ) { - if ( type !== false ) { - type = type || "fx"; - } - return this.each(function() { - var index, - data = jQuery._data( this ), - queue = data[ type + "queue" ], - hooks = data[ type + "queueHooks" ], - timers = jQuery.timers, - length = queue ? queue.length : 0; - - // enable finishing flag on private data - data.finish = true; - - // empty the queue first - jQuery.queue( this, type, [] ); - - if ( hooks && hooks.cur && hooks.cur.finish ) { - hooks.cur.finish.call( this ); - } - - // look for any active animations, and finish them - for ( index = timers.length; index--; ) { - if ( timers[ index ].elem === this && timers[ index ].queue === type ) { - timers[ index ].anim.stop( true ); - timers.splice( index, 1 ); - } - } - - // look for any animations in the old queue and finish them - for ( index = 0; index < length; index++ ) { - if ( queue[ index ] && queue[ index ].finish ) { - queue[ index ].finish.call( this ); - } - } - - // turn off finishing flag - delete data.finish; - }); - } -}); - -// Generate parameters to create a standard animation -function genFx( type, includeWidth ) { - var which, - attrs = { height: type }, - i = 0; - - // if we include width, step value is 1 to do all cssExpand values, - // if we don't include width, step value is 2 to skip over Left and Right - includeWidth = includeWidth? 1 : 0; - for( ; i < 4 ; i += 2 - includeWidth ) { - which = cssExpand[ i ]; - attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; - } - - if ( includeWidth ) { - attrs.opacity = attrs.width = type; - } - - return attrs; -} - -// Generate shortcuts for custom animations -jQuery.each({ - slideDown: genFx("show"), - slideUp: genFx("hide"), - slideToggle: genFx("toggle"), - fadeIn: { opacity: "show" }, - fadeOut: { opacity: "hide" }, - fadeToggle: { opacity: "toggle" } -}, function( name, props ) { - jQuery.fn[ name ] = function( speed, easing, callback ) { - return this.animate( props, speed, easing, callback ); - }; -}); - -jQuery.speed = function( speed, easing, fn ) { - var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { - complete: fn || !fn && easing || - jQuery.isFunction( speed ) && speed, - duration: speed, - easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing - }; - - opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration : - opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default; - - // normalize opt.queue - true/undefined/null -> "fx" - if ( opt.queue == null || opt.queue === true ) { - opt.queue = "fx"; - } - - // Queueing - opt.old = opt.complete; - - opt.complete = function() { - if ( jQuery.isFunction( opt.old ) ) { - opt.old.call( this ); - } - - if ( opt.queue ) { - jQuery.dequeue( this, opt.queue ); - } - }; - - return opt; -}; - -jQuery.easing = { - linear: function( p ) { - return p; - }, - swing: function( p ) { - return 0.5 - Math.cos( p*Math.PI ) / 2; - } -}; - -jQuery.timers = []; -jQuery.fx = Tween.prototype.init; -jQuery.fx.tick = function() { - var timer, - timers = jQuery.timers, - i = 0; - - fxNow = jQuery.now(); - - for ( ; i < timers.length; i++ ) { - timer = timers[ i ]; - // Checks the timer has not already been removed - if ( !timer() && timers[ i ] === timer ) { - timers.splice( i--, 1 ); - } - } - - if ( !timers.length ) { - jQuery.fx.stop(); - } - fxNow = undefined; -}; - -jQuery.fx.timer = function( timer ) { - if ( timer() && jQuery.timers.push( timer ) ) { - jQuery.fx.start(); - } -}; - -jQuery.fx.interval = 13; - -jQuery.fx.start = function() { - if ( !timerId ) { - timerId = setInterval( jQuery.fx.tick, jQuery.fx.interval ); - } -}; - -jQuery.fx.stop = function() { - clearInterval( timerId ); - timerId = null; -}; - -jQuery.fx.speeds = { - slow: 600, - fast: 200, - // Default speed - _default: 400 -}; - -// Back Compat <1.8 extension point -jQuery.fx.step = {}; - -if ( jQuery.expr && jQuery.expr.filters ) { - jQuery.expr.filters.animated = function( elem ) { - return jQuery.grep(jQuery.timers, function( fn ) { - return elem === fn.elem; - }).length; - }; -} -jQuery.fn.offset = function( options ) { - if ( arguments.length ) { - return options === undefined ? - this : - this.each(function( i ) { - jQuery.offset.setOffset( this, options, i ); - }); - } - - var docElem, win, - box = { top: 0, left: 0 }, - elem = this[ 0 ], - doc = elem && elem.ownerDocument; - - if ( !doc ) { - return; - } - - docElem = doc.documentElement; - - // Make sure it's not a disconnected DOM node - if ( !jQuery.contains( docElem, elem ) ) { - return box; - } - - // If we don't have gBCR, just use 0,0 rather than error - // BlackBerry 5, iOS 3 (original iPhone) - if ( typeof elem.getBoundingClientRect !== core_strundefined ) { - box = elem.getBoundingClientRect(); - } - win = getWindow( doc ); - return { - top: box.top + ( win.pageYOffset || docElem.scrollTop ) - ( docElem.clientTop || 0 ), - left: box.left + ( win.pageXOffset || docElem.scrollLeft ) - ( docElem.clientLeft || 0 ) - }; -}; - -jQuery.offset = { - - setOffset: function( elem, options, i ) { - var position = jQuery.css( elem, "position" ); - - // set position first, in-case top/left are set even on static elem - if ( position === "static" ) { - elem.style.position = "relative"; - } - - var curElem = jQuery( elem ), - curOffset = curElem.offset(), - curCSSTop = jQuery.css( elem, "top" ), - curCSSLeft = jQuery.css( elem, "left" ), - calculatePosition = ( position === "absolute" || position === "fixed" ) && jQuery.inArray("auto", [curCSSTop, curCSSLeft]) > -1, - props = {}, curPosition = {}, curTop, curLeft; - - // need to be able to calculate position if either top or left is auto and position is either absolute or fixed - if ( calculatePosition ) { - curPosition = curElem.position(); - curTop = curPosition.top; - curLeft = curPosition.left; - } else { - curTop = parseFloat( curCSSTop ) || 0; - curLeft = parseFloat( curCSSLeft ) || 0; - } - - if ( jQuery.isFunction( options ) ) { - options = options.call( elem, i, curOffset ); - } - - if ( options.top != null ) { - props.top = ( options.top - curOffset.top ) + curTop; - } - if ( options.left != null ) { - props.left = ( options.left - curOffset.left ) + curLeft; - } - - if ( "using" in options ) { - options.using.call( elem, props ); - } else { - curElem.css( props ); - } - } -}; - - -jQuery.fn.extend({ - - position: function() { - if ( !this[ 0 ] ) { - return; - } - - var offsetParent, offset, - parentOffset = { top: 0, left: 0 }, - elem = this[ 0 ]; - - // fixed elements are offset from window (parentOffset = {top:0, left: 0}, because it is it's only offset parent - if ( jQuery.css( elem, "position" ) === "fixed" ) { - // we assume that getBoundingClientRect is available when computed position is fixed - offset = elem.getBoundingClientRect(); - } else { - // Get *real* offsetParent - offsetParent = this.offsetParent(); - - // Get correct offsets - offset = this.offset(); - if ( !jQuery.nodeName( offsetParent[ 0 ], "html" ) ) { - parentOffset = offsetParent.offset(); - } - - // Add offsetParent borders - parentOffset.top += jQuery.css( offsetParent[ 0 ], "borderTopWidth", true ); - parentOffset.left += jQuery.css( offsetParent[ 0 ], "borderLeftWidth", true ); - } - - // Subtract parent offsets and element margins - // note: when an element has margin: auto the offsetLeft and marginLeft - // are the same in Safari causing offset.left to incorrectly be 0 - return { - top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ), - left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true) - }; - }, - - offsetParent: function() { - return this.map(function() { - var offsetParent = this.offsetParent || document.documentElement; - while ( offsetParent && ( !jQuery.nodeName( offsetParent, "html" ) && jQuery.css( offsetParent, "position") === "static" ) ) { - offsetParent = offsetParent.offsetParent; - } - return offsetParent || document.documentElement; - }); - } -}); - - -// Create scrollLeft and scrollTop methods -jQuery.each( {scrollLeft: "pageXOffset", scrollTop: "pageYOffset"}, function( method, prop ) { - var top = /Y/.test( prop ); - - jQuery.fn[ method ] = function( val ) { - return jQuery.access( this, function( elem, method, val ) { - var win = getWindow( elem ); - - if ( val === undefined ) { - return win ? (prop in win) ? win[ prop ] : - win.document.documentElement[ method ] : - elem[ method ]; - } - - if ( win ) { - win.scrollTo( - !top ? val : jQuery( win ).scrollLeft(), - top ? val : jQuery( win ).scrollTop() - ); - - } else { - elem[ method ] = val; - } - }, method, val, arguments.length, null ); - }; -}); - -function getWindow( elem ) { - return jQuery.isWindow( elem ) ? - elem : - elem.nodeType === 9 ? - elem.defaultView || elem.parentWindow : - false; -} -// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods -jQuery.each( { Height: "height", Width: "width" }, function( name, type ) { - jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name }, function( defaultExtra, funcName ) { - // margin is only for outerHeight, outerWidth - jQuery.fn[ funcName ] = function( margin, value ) { - var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ), - extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" ); - - return jQuery.access( this, function( elem, type, value ) { - var doc; - - if ( jQuery.isWindow( elem ) ) { - // As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there - // isn't a whole lot we can do. See pull request at this URL for discussion: - // https://github.com/jquery/jquery/pull/764 - return elem.document.documentElement[ "client" + name ]; - } - - // Get document width or height - if ( elem.nodeType === 9 ) { - doc = elem.documentElement; - - // Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height], whichever is greatest - // unfortunately, this causes bug #3838 in IE6/8 only, but there is currently no good, small way to fix it. - return Math.max( - elem.body[ "scroll" + name ], doc[ "scroll" + name ], - elem.body[ "offset" + name ], doc[ "offset" + name ], - doc[ "client" + name ] - ); - } - - return value === undefined ? - // Get width or height on the element, requesting but not forcing parseFloat - jQuery.css( elem, type, extra ) : - - // Set width or height on the element - jQuery.style( elem, type, value, extra ); - }, type, chainable ? margin : undefined, chainable, null ); - }; - }); -}); -// Limit scope pollution from any deprecated API -// (function() { - -// })(); -// Expose jQuery to the global object -window.jQuery = window.$ = jQuery; - -// Expose jQuery as an AMD module, but only for AMD loaders that -// understand the issues with loading multiple versions of jQuery -// in a page that all might call define(). The loader will indicate -// they have special allowances for multiple jQuery versions by -// specifying define.amd.jQuery = true. Register as a named module, -// since jQuery can be concatenated with other files that may use define, -// but not use a proper concatenation script that understands anonymous -// AMD modules. A named AMD is safest and most robust way to register. -// Lowercase jquery is used because AMD module names are derived from -// file names, and jQuery is normally delivered in a lowercase file name. -// Do this after creating the global so that if an AMD module wants to call -// noConflict to hide this version of jQuery, it will work. -if ( typeof define === "function" && define.amd && define.amd.jQuery ) { - define( "jquery", [], function () { return jQuery; } ); -} - -})( window ); diff --git a/framework/yii/assets/jquery.min.js b/framework/yii/assets/jquery.min.js deleted file mode 100644 index 006e953..0000000 --- a/framework/yii/assets/jquery.min.js +++ /dev/null @@ -1,5 +0,0 @@ -/*! jQuery v1.9.1 | (c) 2005, 2012 jQuery Foundation, Inc. | jquery.org/license -//@ sourceMappingURL=jquery.min.map -*/(function(e,t){var n,r,i=typeof t,o=e.document,a=e.location,s=e.jQuery,u=e.$,l={},c=[],p="1.9.1",f=c.concat,d=c.push,h=c.slice,g=c.indexOf,m=l.toString,y=l.hasOwnProperty,v=p.trim,b=function(e,t){return new b.fn.init(e,t,r)},x=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,w=/\S+/g,T=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,N=/^(?:(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,k=/^[\],:{}\s]*$/,E=/(?:^|:|,)(?:\s*\[)+/g,S=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,A=/"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,j=/^-ms-/,D=/-([\da-z])/gi,L=function(e,t){return t.toUpperCase()},H=function(e){(o.addEventListener||"load"===e.type||"complete"===o.readyState)&&(q(),b.ready())},q=function(){o.addEventListener?(o.removeEventListener("DOMContentLoaded",H,!1),e.removeEventListener("load",H,!1)):(o.detachEvent("onreadystatechange",H),e.detachEvent("onload",H))};b.fn=b.prototype={jquery:p,constructor:b,init:function(e,n,r){var i,a;if(!e)return this;if("string"==typeof e){if(i="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:N.exec(e),!i||!i[1]&&n)return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e);if(i[1]){if(n=n instanceof b?n[0]:n,b.merge(this,b.parseHTML(i[1],n&&n.nodeType?n.ownerDocument||n:o,!0)),C.test(i[1])&&b.isPlainObject(n))for(i in n)b.isFunction(this[i])?this[i](n[i]):this.attr(i,n[i]);return this}if(a=o.getElementById(i[2]),a&&a.parentNode){if(a.id!==i[2])return r.find(e);this.length=1,this[0]=a}return this.context=o,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):b.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),b.makeArray(e,this))},selector:"",length:0,size:function(){return this.length},toArray:function(){return h.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=b.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return b.each(this,e,t)},ready:function(e){return b.ready.promise().done(e),this},slice:function(){return this.pushStack(h.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(b.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:d,sort:[].sort,splice:[].splice},b.fn.init.prototype=b.fn,b.extend=b.fn.extend=function(){var e,n,r,i,o,a,s=arguments[0]||{},u=1,l=arguments.length,c=!1;for("boolean"==typeof s&&(c=s,s=arguments[1]||{},u=2),"object"==typeof s||b.isFunction(s)||(s={}),l===u&&(s=this,--u);l>u;u++)if(null!=(o=arguments[u]))for(i in o)e=s[i],r=o[i],s!==r&&(c&&r&&(b.isPlainObject(r)||(n=b.isArray(r)))?(n?(n=!1,a=e&&b.isArray(e)?e:[]):a=e&&b.isPlainObject(e)?e:{},s[i]=b.extend(c,a,r)):r!==t&&(s[i]=r));return s},b.extend({noConflict:function(t){return e.$===b&&(e.$=u),t&&e.jQuery===b&&(e.jQuery=s),b},isReady:!1,readyWait:1,holdReady:function(e){e?b.readyWait++:b.ready(!0)},ready:function(e){if(e===!0?!--b.readyWait:!b.isReady){if(!o.body)return setTimeout(b.ready);b.isReady=!0,e!==!0&&--b.readyWait>0||(n.resolveWith(o,[b]),b.fn.trigger&&b(o).trigger("ready").off("ready"))}},isFunction:function(e){return"function"===b.type(e)},isArray:Array.isArray||function(e){return"array"===b.type(e)},isWindow:function(e){return null!=e&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?l[m.call(e)]||"object":typeof e},isPlainObject:function(e){if(!e||"object"!==b.type(e)||e.nodeType||b.isWindow(e))return!1;try{if(e.constructor&&!y.call(e,"constructor")&&!y.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(n){return!1}var r;for(r in e);return r===t||y.call(e,r)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||o;var r=C.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=b.buildFragment([e],t,i),i&&b(i).remove(),b.merge([],r.childNodes))},parseJSON:function(n){return e.JSON&&e.JSON.parse?e.JSON.parse(n):null===n?n:"string"==typeof n&&(n=b.trim(n),n&&k.test(n.replace(S,"@").replace(A,"]").replace(E,"")))?Function("return "+n)():(b.error("Invalid JSON: "+n),t)},parseXML:function(n){var r,i;if(!n||"string"!=typeof n)return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(o){r=t}return r&&r.documentElement&&!r.getElementsByTagName("parsererror").length||b.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&b.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(j,"ms-").replace(D,L)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,a=M(e);if(n){if(a){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(a){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:v&&!v.call("\ufeff\u00a0")?function(e){return null==e?"":v.call(e)}:function(e){return null==e?"":(e+"").replace(T,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(M(Object(e))?b.merge(n,"string"==typeof e?[e]:e):d.call(n,e)),n},inArray:function(e,t,n){var r;if(t){if(g)return g.call(t,e,n);for(r=t.length,n=n?0>n?Math.max(0,r+n):n:0;r>n;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,n){var r=n.length,i=e.length,o=0;if("number"==typeof r)for(;r>o;o++)e[i++]=n[o];else while(n[o]!==t)e[i++]=n[o++];return e.length=i,e},grep:function(e,t,n){var r,i=[],o=0,a=e.length;for(n=!!n;a>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,a=M(e),s=[];if(a)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(s[s.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(s[s.length]=r);return f.apply([],s)},guid:1,proxy:function(e,n){var r,i,o;return"string"==typeof n&&(o=e[n],n=e,e=o),b.isFunction(e)?(r=h.call(arguments,2),i=function(){return e.apply(n||this,r.concat(h.call(arguments)))},i.guid=e.guid=e.guid||b.guid++,i):t},access:function(e,n,r,i,o,a,s){var u=0,l=e.length,c=null==r;if("object"===b.type(r)){o=!0;for(u in r)b.access(e,n,u,r[u],!0,a,s)}else if(i!==t&&(o=!0,b.isFunction(i)||(s=!0),c&&(s?(n.call(e,i),n=null):(c=n,n=function(e,t,n){return c.call(b(e),n)})),n))for(;l>u;u++)n(e[u],r,s?i:i.call(e[u],u,n(e[u],r)));return o?e:c?n.call(e):l?n(e[0],r):a},now:function(){return(new Date).getTime()}}),b.ready.promise=function(t){if(!n)if(n=b.Deferred(),"complete"===o.readyState)setTimeout(b.ready);else if(o.addEventListener)o.addEventListener("DOMContentLoaded",H,!1),e.addEventListener("load",H,!1);else{o.attachEvent("onreadystatechange",H),e.attachEvent("onload",H);var r=!1;try{r=null==e.frameElement&&o.documentElement}catch(i){}r&&r.doScroll&&function a(){if(!b.isReady){try{r.doScroll("left")}catch(e){return setTimeout(a,50)}q(),b.ready()}}()}return n.promise(t)},b.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){l["[object "+t+"]"]=t.toLowerCase()});function M(e){var t=e.length,n=b.type(e);return b.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}r=b(o);var _={};function F(e){var t=_[e]={};return b.each(e.match(w)||[],function(e,n){t[n]=!0}),t}b.Callbacks=function(e){e="string"==typeof e?_[e]||F(e):b.extend({},e);var n,r,i,o,a,s,u=[],l=!e.once&&[],c=function(t){for(r=e.memory&&t,i=!0,a=s||0,s=0,o=u.length,n=!0;u&&o>a;a++)if(u[a].apply(t[0],t[1])===!1&&e.stopOnFalse){r=!1;break}n=!1,u&&(l?l.length&&c(l.shift()):r?u=[]:p.disable())},p={add:function(){if(u){var t=u.length;(function i(t){b.each(t,function(t,n){var r=b.type(n);"function"===r?e.unique&&p.has(n)||u.push(n):n&&n.length&&"string"!==r&&i(n)})})(arguments),n?o=u.length:r&&(s=t,c(r))}return this},remove:function(){return u&&b.each(arguments,function(e,t){var r;while((r=b.inArray(t,u,r))>-1)u.splice(r,1),n&&(o>=r&&o--,a>=r&&a--)}),this},has:function(e){return e?b.inArray(e,u)>-1:!(!u||!u.length)},empty:function(){return u=[],this},disable:function(){return u=l=r=t,this},disabled:function(){return!u},lock:function(){return l=t,r||p.disable(),this},locked:function(){return!l},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],!u||i&&!l||(n?l.push(t):c(t)),this},fire:function(){return p.fireWith(this,arguments),this},fired:function(){return!!i}};return p},b.extend({Deferred:function(e){var t=[["resolve","done",b.Callbacks("once memory"),"resolved"],["reject","fail",b.Callbacks("once memory"),"rejected"],["notify","progress",b.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return b.Deferred(function(n){b.each(t,function(t,o){var a=o[0],s=b.isFunction(e[t])&&e[t];i[o[1]](function(){var e=s&&s.apply(this,arguments);e&&b.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[a+"With"](this===r?n.promise():this,s?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?b.extend(e,r):r}},i={};return r.pipe=r.then,b.each(t,function(e,o){var a=o[2],s=o[3];r[o[1]]=a.add,s&&a.add(function(){n=s},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=a.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=h.call(arguments),r=n.length,i=1!==r||e&&b.isFunction(e.promise)?r:0,o=1===i?e:b.Deferred(),a=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?h.call(arguments):r,n===s?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},s,u,l;if(r>1)for(s=Array(r),u=Array(r),l=Array(r);r>t;t++)n[t]&&b.isFunction(n[t].promise)?n[t].promise().done(a(t,l,n)).fail(o.reject).progress(a(t,u,s)):--i;return i||o.resolveWith(l,n),o.promise()}}),b.support=function(){var t,n,r,a,s,u,l,c,p,f,d=o.createElement("div");if(d.setAttribute("className","t"),d.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",n=d.getElementsByTagName("*"),r=d.getElementsByTagName("a")[0],!n||!r||!n.length)return{};s=o.createElement("select"),l=s.appendChild(o.createElement("option")),a=d.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t={getSetAttribute:"t"!==d.className,leadingWhitespace:3===d.firstChild.nodeType,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/top/.test(r.getAttribute("style")),hrefNormalized:"/a"===r.getAttribute("href"),opacity:/^0.5/.test(r.style.opacity),cssFloat:!!r.style.cssFloat,checkOn:!!a.value,optSelected:l.selected,enctype:!!o.createElement("form").enctype,html5Clone:"<:nav></:nav>"!==o.createElement("nav").cloneNode(!0).outerHTML,boxModel:"CSS1Compat"===o.compatMode,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},a.checked=!0,t.noCloneChecked=a.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!l.disabled;try{delete d.test}catch(h){t.deleteExpando=!1}a=o.createElement("input"),a.setAttribute("value",""),t.input=""===a.getAttribute("value"),a.value="t",a.setAttribute("type","radio"),t.radioValue="t"===a.value,a.setAttribute("checked","t"),a.setAttribute("name","t"),u=o.createDocumentFragment(),u.appendChild(a),t.appendChecked=a.checked,t.checkClone=u.cloneNode(!0).cloneNode(!0).lastChild.checked,d.attachEvent&&(d.attachEvent("onclick",function(){t.noCloneEvent=!1}),d.cloneNode(!0).click());for(f in{submit:!0,change:!0,focusin:!0})d.setAttribute(c="on"+f,"t"),t[f+"Bubbles"]=c in e||d.attributes[c].expando===!1;return d.style.backgroundClip="content-box",d.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===d.style.backgroundClip,b(function(){var n,r,a,s="padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",u=o.getElementsByTagName("body")[0];u&&(n=o.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",u.appendChild(n).appendChild(d),d.innerHTML="<table><tr><td></td><td>t</td></tr></table>",a=d.getElementsByTagName("td"),a[0].style.cssText="padding:0;margin:0;border:0;display:none",p=0===a[0].offsetHeight,a[0].style.display="",a[1].style.display="none",t.reliableHiddenOffsets=p&&0===a[0].offsetHeight,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",t.boxSizing=4===d.offsetWidth,t.doesNotIncludeMarginInBodyOffset=1!==u.offsetTop,e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(d,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(d,null)||{width:"4px"}).width,r=d.appendChild(o.createElement("div")),r.style.cssText=d.style.cssText=s,r.style.marginRight=r.style.width="0",d.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),typeof d.style.zoom!==i&&(d.innerHTML="",d.style.cssText=s+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=3===d.offsetWidth,d.style.display="block",d.innerHTML="<div></div>",d.firstChild.style.width="5px",t.shrinkWrapBlocks=3!==d.offsetWidth,t.inlineBlockNeedsLayout&&(u.style.zoom=1)),u.removeChild(n),n=d=a=r=null)}),n=s=u=l=r=a=null,t}();var O=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,B=/([A-Z])/g;function P(e,n,r,i){if(b.acceptData(e)){var o,a,s=b.expando,u="string"==typeof n,l=e.nodeType,p=l?b.cache:e,f=l?e[s]:e[s]&&s;if(f&&p[f]&&(i||p[f].data)||!u||r!==t)return f||(l?e[s]=f=c.pop()||b.guid++:f=s),p[f]||(p[f]={},l||(p[f].toJSON=b.noop)),("object"==typeof n||"function"==typeof n)&&(i?p[f]=b.extend(p[f],n):p[f].data=b.extend(p[f].data,n)),o=p[f],i||(o.data||(o.data={}),o=o.data),r!==t&&(o[b.camelCase(n)]=r),u?(a=o[n],null==a&&(a=o[b.camelCase(n)])):a=o,a}}function R(e,t,n){if(b.acceptData(e)){var r,i,o,a=e.nodeType,s=a?b.cache:e,u=a?e[b.expando]:b.expando;if(s[u]){if(t&&(o=n?s[u]:s[u].data)){b.isArray(t)?t=t.concat(b.map(t,b.camelCase)):t in o?t=[t]:(t=b.camelCase(t),t=t in o?[t]:t.split(" "));for(r=0,i=t.length;i>r;r++)delete o[t[r]];if(!(n?$:b.isEmptyObject)(o))return}(n||(delete s[u].data,$(s[u])))&&(a?b.cleanData([e],!0):b.support.deleteExpando||s!=s.window?delete s[u]:s[u]=null)}}}b.extend({cache:{},expando:"jQuery"+(p+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(e){return e=e.nodeType?b.cache[e[b.expando]]:e[b.expando],!!e&&!$(e)},data:function(e,t,n){return P(e,t,n)},removeData:function(e,t){return R(e,t)},_data:function(e,t,n){return P(e,t,n,!0)},_removeData:function(e,t){return R(e,t,!0)},acceptData:function(e){if(e.nodeType&&1!==e.nodeType&&9!==e.nodeType)return!1;var t=e.nodeName&&b.noData[e.nodeName.toLowerCase()];return!t||t!==!0&&e.getAttribute("classid")===t}}),b.fn.extend({data:function(e,n){var r,i,o=this[0],a=0,s=null;if(e===t){if(this.length&&(s=b.data(o),1===o.nodeType&&!b._data(o,"parsedAttrs"))){for(r=o.attributes;r.length>a;a++)i=r[a].name,i.indexOf("data-")||(i=b.camelCase(i.slice(5)),W(o,i,s[i]));b._data(o,"parsedAttrs",!0)}return s}return"object"==typeof e?this.each(function(){b.data(this,e)}):b.access(this,function(n){return n===t?o?W(o,e,b.data(o,e)):null:(this.each(function(){b.data(this,e,n)}),t)},null,n,arguments.length>1,null,!0)},removeData:function(e){return this.each(function(){b.removeData(this,e)})}});function W(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(B,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:O.test(r)?b.parseJSON(r):r}catch(o){}b.data(e,n,r)}else r=t}return r}function $(e){var t;for(t in e)if(("data"!==t||!b.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}b.extend({queue:function(e,n,r){var i;return e?(n=(n||"fx")+"queue",i=b._data(e,n),r&&(!i||b.isArray(r)?i=b._data(e,n,b.makeArray(r)):i.push(r)),i||[]):t},dequeue:function(e,t){t=t||"fx";var n=b.queue(e,t),r=n.length,i=n.shift(),o=b._queueHooks(e,t),a=function(){b.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),o.cur=i,i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return b._data(e,n)||b._data(e,n,{empty:b.Callbacks("once memory").add(function(){b._removeData(e,t+"queue"),b._removeData(e,n)})})}}),b.fn.extend({queue:function(e,n){var r=2;return"string"!=typeof e&&(n=e,e="fx",r--),r>arguments.length?b.queue(this[0],e):n===t?this:this.each(function(){var t=b.queue(this,e,n);b._queueHooks(this,e),"fx"===e&&"inprogress"!==t[0]&&b.dequeue(this,e)})},dequeue:function(e){return this.each(function(){b.dequeue(this,e)})},delay:function(e,t){return e=b.fx?b.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,n){var r,i=1,o=b.Deferred(),a=this,s=this.length,u=function(){--i||o.resolveWith(a,[a])};"string"!=typeof e&&(n=e,e=t),e=e||"fx";while(s--)r=b._data(a[s],e+"queueHooks"),r&&r.empty&&(i++,r.empty.add(u));return u(),o.promise(n)}});var I,z,X=/[\t\r\n]/g,U=/\r/g,V=/^(?:input|select|textarea|button|object)$/i,Y=/^(?:a|area)$/i,J=/^(?:checked|selected|autofocus|autoplay|async|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped)$/i,G=/^(?:checked|selected)$/i,Q=b.support.getSetAttribute,K=b.support.input;b.fn.extend({attr:function(e,t){return b.access(this,b.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){b.removeAttr(this,e)})},prop:function(e,t){return b.access(this,b.prop,e,t,arguments.length>1)},removeProp:function(e){return e=b.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,o,a=0,s=this.length,u="string"==typeof e&&e;if(b.isFunction(e))return this.each(function(t){b(this).addClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(X," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=b.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,a=0,s=this.length,u=0===arguments.length||"string"==typeof e&&e;if(b.isFunction(e))return this.each(function(t){b(this).removeClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(X," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?b.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e,r="boolean"==typeof t;return b.isFunction(e)?this.each(function(n){b(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var o,a=0,s=b(this),u=t,l=e.match(w)||[];while(o=l[a++])u=r?u:!s.hasClass(o),s[u?"addClass":"removeClass"](o)}else(n===i||"boolean"===n)&&(this.className&&b._data(this,"__className__",this.className),this.className=this.className||e===!1?"":b._data(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(X," ").indexOf(t)>=0)return!0;return!1},val:function(e){var n,r,i,o=this[0];{if(arguments.length)return i=b.isFunction(e),this.each(function(n){var o,a=b(this);1===this.nodeType&&(o=i?e.call(this,n,a.val()):e,null==o?o="":"number"==typeof o?o+="":b.isArray(o)&&(o=b.map(o,function(e){return null==e?"":e+""})),r=b.valHooks[this.type]||b.valHooks[this.nodeName.toLowerCase()],r&&"set"in r&&r.set(this,o,"value")!==t||(this.value=o))});if(o)return r=b.valHooks[o.type]||b.valHooks[o.nodeName.toLowerCase()],r&&"get"in r&&(n=r.get(o,"value"))!==t?n:(n=o.value,"string"==typeof n?n.replace(U,""):null==n?"":n)}}}),b.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,a=o?null:[],s=o?i+1:r.length,u=0>i?s:o?i:0;for(;s>u;u++)if(n=r[u],!(!n.selected&&u!==i||(b.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&b.nodeName(n.parentNode,"optgroup"))){if(t=b(n).val(),o)return t;a.push(t)}return a},set:function(e,t){var n=b.makeArray(t);return b(e).find("option").each(function(){this.selected=b.inArray(b(this).val(),n)>=0}),n.length||(e.selectedIndex=-1),n}}},attr:function(e,n,r){var o,a,s,u=e.nodeType;if(e&&3!==u&&8!==u&&2!==u)return typeof e.getAttribute===i?b.prop(e,n,r):(a=1!==u||!b.isXMLDoc(e),a&&(n=n.toLowerCase(),o=b.attrHooks[n]||(J.test(n)?z:I)),r===t?o&&a&&"get"in o&&null!==(s=o.get(e,n))?s:(typeof e.getAttribute!==i&&(s=e.getAttribute(n)),null==s?t:s):null!==r?o&&a&&"set"in o&&(s=o.set(e,r,n))!==t?s:(e.setAttribute(n,r+""),r):(b.removeAttr(e,n),t))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(w);if(o&&1===e.nodeType)while(n=o[i++])r=b.propFix[n]||n,J.test(n)?!Q&&G.test(n)?e[b.camelCase("default-"+n)]=e[r]=!1:e[r]=!1:b.attr(e,n,""),e.removeAttribute(Q?n:r)},attrHooks:{type:{set:function(e,t){if(!b.support.radioValue&&"radio"===t&&b.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(e,n,r){var i,o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return a=1!==s||!b.isXMLDoc(e),a&&(n=b.propFix[n]||n,o=b.propHooks[n]),r!==t?o&&"set"in o&&(i=o.set(e,r,n))!==t?i:e[n]=r:o&&"get"in o&&null!==(i=o.get(e,n))?i:e[n]},propHooks:{tabIndex:{get:function(e){var n=e.getAttributeNode("tabindex");return n&&n.specified?parseInt(n.value,10):V.test(e.nodeName)||Y.test(e.nodeName)&&e.href?0:t}}}}),z={get:function(e,n){var r=b.prop(e,n),i="boolean"==typeof r&&e.getAttribute(n),o="boolean"==typeof r?K&&Q?null!=i:G.test(n)?e[b.camelCase("default-"+n)]:!!i:e.getAttributeNode(n);return o&&o.value!==!1?n.toLowerCase():t},set:function(e,t,n){return t===!1?b.removeAttr(e,n):K&&Q||!G.test(n)?e.setAttribute(!Q&&b.propFix[n]||n,n):e[b.camelCase("default-"+n)]=e[n]=!0,n}},K&&Q||(b.attrHooks.value={get:function(e,n){var r=e.getAttributeNode(n);return b.nodeName(e,"input")?e.defaultValue:r&&r.specified?r.value:t},set:function(e,n,r){return b.nodeName(e,"input")?(e.defaultValue=n,t):I&&I.set(e,n,r)}}),Q||(I=b.valHooks.button={get:function(e,n){var r=e.getAttributeNode(n);return r&&("id"===n||"name"===n||"coords"===n?""!==r.value:r.specified)?r.value:t},set:function(e,n,r){var i=e.getAttributeNode(r);return i||e.setAttributeNode(i=e.ownerDocument.createAttribute(r)),i.value=n+="","value"===r||n===e.getAttribute(r)?n:t}},b.attrHooks.contenteditable={get:I.get,set:function(e,t,n){I.set(e,""===t?!1:t,n)}},b.each(["width","height"],function(e,n){b.attrHooks[n]=b.extend(b.attrHooks[n],{set:function(e,r){return""===r?(e.setAttribute(n,"auto"),r):t}})})),b.support.hrefNormalized||(b.each(["href","src","width","height"],function(e,n){b.attrHooks[n]=b.extend(b.attrHooks[n],{get:function(e){var r=e.getAttribute(n,2);return null==r?t:r}})}),b.each(["href","src"],function(e,t){b.propHooks[t]={get:function(e){return e.getAttribute(t,4)}}})),b.support.style||(b.attrHooks.style={get:function(e){return e.style.cssText||t},set:function(e,t){return e.style.cssText=t+""}}),b.support.optSelected||(b.propHooks.selected=b.extend(b.propHooks.selected,{get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null}})),b.support.enctype||(b.propFix.enctype="encoding"),b.support.checkOn||b.each(["radio","checkbox"],function(){b.valHooks[this]={get:function(e){return null===e.getAttribute("value")?"on":e.value}}}),b.each(["radio","checkbox"],function(){b.valHooks[this]=b.extend(b.valHooks[this],{set:function(e,n){return b.isArray(n)?e.checked=b.inArray(b(e).val(),n)>=0:t}})});var Z=/^(?:input|select|textarea)$/i,et=/^key/,tt=/^(?:mouse|contextmenu)|click/,nt=/^(?:focusinfocus|focusoutblur)$/,rt=/^([^.]*)(?:\.(.+)|)$/;function it(){return!0}function ot(){return!1}b.event={global:{},add:function(e,n,r,o,a){var s,u,l,c,p,f,d,h,g,m,y,v=b._data(e);if(v){r.handler&&(c=r,r=c.handler,a=c.selector),r.guid||(r.guid=b.guid++),(u=v.events)||(u=v.events={}),(f=v.handle)||(f=v.handle=function(e){return typeof b===i||e&&b.event.triggered===e.type?t:b.event.dispatch.apply(f.elem,arguments)},f.elem=e),n=(n||"").match(w)||[""],l=n.length;while(l--)s=rt.exec(n[l])||[],g=y=s[1],m=(s[2]||"").split(".").sort(),p=b.event.special[g]||{},g=(a?p.delegateType:p.bindType)||g,p=b.event.special[g]||{},d=b.extend({type:g,origType:y,data:o,handler:r,guid:r.guid,selector:a,needsContext:a&&b.expr.match.needsContext.test(a),namespace:m.join(".")},c),(h=u[g])||(h=u[g]=[],h.delegateCount=0,p.setup&&p.setup.call(e,o,m,f)!==!1||(e.addEventListener?e.addEventListener(g,f,!1):e.attachEvent&&e.attachEvent("on"+g,f))),p.add&&(p.add.call(e,d),d.handler.guid||(d.handler.guid=r.guid)),a?h.splice(h.delegateCount++,0,d):h.push(d),b.event.global[g]=!0;e=null}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,p,f,d,h,g,m=b.hasData(e)&&b._data(e);if(m&&(c=m.events)){t=(t||"").match(w)||[""],l=t.length;while(l--)if(s=rt.exec(t[l])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){p=b.event.special[d]||{},d=(r?p.delegateType:p.bindType)||d,f=c[d]||[],s=s[2]&&RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),u=o=f.length;while(o--)a=f[o],!i&&g!==a.origType||n&&n.guid!==a.guid||s&&!s.test(a.namespace)||r&&r!==a.selector&&("**"!==r||!a.selector)||(f.splice(o,1),a.selector&&f.delegateCount--,p.remove&&p.remove.call(e,a));u&&!f.length&&(p.teardown&&p.teardown.call(e,h,m.handle)!==!1||b.removeEvent(e,d,m.handle),delete c[d])}else for(d in c)b.event.remove(e,d+t[l],n,r,!0);b.isEmptyObject(c)&&(delete m.handle,b._removeData(e,"events"))}},trigger:function(n,r,i,a){var s,u,l,c,p,f,d,h=[i||o],g=y.call(n,"type")?n.type:n,m=y.call(n,"namespace")?n.namespace.split("."):[];if(l=f=i=i||o,3!==i.nodeType&&8!==i.nodeType&&!nt.test(g+b.event.triggered)&&(g.indexOf(".")>=0&&(m=g.split("."),g=m.shift(),m.sort()),u=0>g.indexOf(":")&&"on"+g,n=n[b.expando]?n:new b.Event(g,"object"==typeof n&&n),n.isTrigger=!0,n.namespace=m.join("."),n.namespace_re=n.namespace?RegExp("(^|\\.)"+m.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,n.result=t,n.target||(n.target=i),r=null==r?[n]:b.makeArray(r,[n]),p=b.event.special[g]||{},a||!p.trigger||p.trigger.apply(i,r)!==!1)){if(!a&&!p.noBubble&&!b.isWindow(i)){for(c=p.delegateType||g,nt.test(c+g)||(l=l.parentNode);l;l=l.parentNode)h.push(l),f=l;f===(i.ownerDocument||o)&&h.push(f.defaultView||f.parentWindow||e)}d=0;while((l=h[d++])&&!n.isPropagationStopped())n.type=d>1?c:p.bindType||g,s=(b._data(l,"events")||{})[n.type]&&b._data(l,"handle"),s&&s.apply(l,r),s=u&&l[u],s&&b.acceptData(l)&&s.apply&&s.apply(l,r)===!1&&n.preventDefault();if(n.type=g,!(a||n.isDefaultPrevented()||p._default&&p._default.apply(i.ownerDocument,r)!==!1||"click"===g&&b.nodeName(i,"a")||!b.acceptData(i)||!u||!i[g]||b.isWindow(i))){f=i[u],f&&(i[u]=null),b.event.triggered=g;try{i[g]()}catch(v){}b.event.triggered=t,f&&(i[u]=f)}return n.result}},dispatch:function(e){e=b.event.fix(e);var n,r,i,o,a,s=[],u=h.call(arguments),l=(b._data(this,"events")||{})[e.type]||[],c=b.event.special[e.type]||{};if(u[0]=e,e.delegateTarget=this,!c.preDispatch||c.preDispatch.call(this,e)!==!1){s=b.event.handlers.call(this,e,l),n=0;while((o=s[n++])&&!e.isPropagationStopped()){e.currentTarget=o.elem,a=0;while((i=o.handlers[a++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(i.namespace))&&(e.handleObj=i,e.data=i.data,r=((b.event.special[i.origType]||{}).handle||i.handler).apply(o.elem,u),r!==t&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,e),e.result}},handlers:function(e,n){var r,i,o,a,s=[],u=n.delegateCount,l=e.target;if(u&&l.nodeType&&(!e.button||"click"!==e.type))for(;l!=this;l=l.parentNode||this)if(1===l.nodeType&&(l.disabled!==!0||"click"!==e.type)){for(o=[],a=0;u>a;a++)i=n[a],r=i.selector+" ",o[r]===t&&(o[r]=i.needsContext?b(r,this).index(l)>=0:b.find(r,this,null,[l]).length),o[r]&&o.push(i);o.length&&s.push({elem:l,handlers:o})}return n.length>u&&s.push({elem:this,handlers:n.slice(u)}),s},fix:function(e){if(e[b.expando])return e;var t,n,r,i=e.type,a=e,s=this.fixHooks[i];s||(this.fixHooks[i]=s=tt.test(i)?this.mouseHooks:et.test(i)?this.keyHooks:{}),r=s.props?this.props.concat(s.props):this.props,e=new b.Event(a),t=r.length;while(t--)n=r[t],e[n]=a[n];return e.target||(e.target=a.srcElement||o),3===e.target.nodeType&&(e.target=e.target.parentNode),e.metaKey=!!e.metaKey,s.filter?s.filter(e,a):e},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,n){var r,i,a,s=n.button,u=n.fromElement;return null==e.pageX&&null!=n.clientX&&(i=e.target.ownerDocument||o,a=i.documentElement,r=i.body,e.pageX=n.clientX+(a&&a.scrollLeft||r&&r.scrollLeft||0)-(a&&a.clientLeft||r&&r.clientLeft||0),e.pageY=n.clientY+(a&&a.scrollTop||r&&r.scrollTop||0)-(a&&a.clientTop||r&&r.clientTop||0)),!e.relatedTarget&&u&&(e.relatedTarget=u===e.target?n.toElement:u),e.which||s===t||(e.which=1&s?1:2&s?3:4&s?2:0),e}},special:{load:{noBubble:!0},click:{trigger:function(){return b.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):t}},focus:{trigger:function(){if(this!==o.activeElement&&this.focus)try{return this.focus(),!1}catch(e){}},delegateType:"focusin"},blur:{trigger:function(){return this===o.activeElement&&this.blur?(this.blur(),!1):t},delegateType:"focusout"},beforeunload:{postDispatch:function(e){e.result!==t&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=b.extend(new b.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?b.event.trigger(i,null,t):b.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},b.removeEvent=o.removeEventListener?function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)}:function(e,t,n){var r="on"+t;e.detachEvent&&(typeof e[r]===i&&(e[r]=null),e.detachEvent(r,n))},b.Event=function(e,n){return this instanceof b.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.returnValue===!1||e.getPreventDefault&&e.getPreventDefault()?it:ot):this.type=e,n&&b.extend(this,n),this.timeStamp=e&&e.timeStamp||b.now(),this[b.expando]=!0,t):new b.Event(e,n)},b.Event.prototype={isDefaultPrevented:ot,isPropagationStopped:ot,isImmediatePropagationStopped:ot,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=it,e&&(e.preventDefault?e.preventDefault():e.returnValue=!1)},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=it,e&&(e.stopPropagation&&e.stopPropagation(),e.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=it,this.stopPropagation()}},b.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){b.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj; -return(!i||i!==r&&!b.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),b.support.submitBubbles||(b.event.special.submit={setup:function(){return b.nodeName(this,"form")?!1:(b.event.add(this,"click._submit keypress._submit",function(e){var n=e.target,r=b.nodeName(n,"input")||b.nodeName(n,"button")?n.form:t;r&&!b._data(r,"submitBubbles")&&(b.event.add(r,"submit._submit",function(e){e._submit_bubble=!0}),b._data(r,"submitBubbles",!0))}),t)},postDispatch:function(e){e._submit_bubble&&(delete e._submit_bubble,this.parentNode&&!e.isTrigger&&b.event.simulate("submit",this.parentNode,e,!0))},teardown:function(){return b.nodeName(this,"form")?!1:(b.event.remove(this,"._submit"),t)}}),b.support.changeBubbles||(b.event.special.change={setup:function(){return Z.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(b.event.add(this,"propertychange._change",function(e){"checked"===e.originalEvent.propertyName&&(this._just_changed=!0)}),b.event.add(this,"click._change",function(e){this._just_changed&&!e.isTrigger&&(this._just_changed=!1),b.event.simulate("change",this,e,!0)})),!1):(b.event.add(this,"beforeactivate._change",function(e){var t=e.target;Z.test(t.nodeName)&&!b._data(t,"changeBubbles")&&(b.event.add(t,"change._change",function(e){!this.parentNode||e.isSimulated||e.isTrigger||b.event.simulate("change",this.parentNode,e,!0)}),b._data(t,"changeBubbles",!0))}),t)},handle:function(e){var n=e.target;return this!==n||e.isSimulated||e.isTrigger||"radio"!==n.type&&"checkbox"!==n.type?e.handleObj.handler.apply(this,arguments):t},teardown:function(){return b.event.remove(this,"._change"),!Z.test(this.nodeName)}}),b.support.focusinBubbles||b.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){b.event.simulate(t,e.target,b.event.fix(e),!0)};b.event.special[t]={setup:function(){0===n++&&o.addEventListener(e,r,!0)},teardown:function(){0===--n&&o.removeEventListener(e,r,!0)}}}),b.fn.extend({on:function(e,n,r,i,o){var a,s;if("object"==typeof e){"string"!=typeof n&&(r=r||n,n=t);for(a in e)this.on(a,n,r,e[a],o);return this}if(null==r&&null==i?(i=n,r=n=t):null==i&&("string"==typeof n?(i=r,r=t):(i=r,r=n,n=t)),i===!1)i=ot;else if(!i)return this;return 1===o&&(s=i,i=function(e){return b().off(e),s.apply(this,arguments)},i.guid=s.guid||(s.guid=b.guid++)),this.each(function(){b.event.add(this,e,i,r,n)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,n,r){var i,o;if(e&&e.preventDefault&&e.handleObj)return i=e.handleObj,b(e.delegateTarget).off(i.namespace?i.origType+"."+i.namespace:i.origType,i.selector,i.handler),this;if("object"==typeof e){for(o in e)this.off(o,n,e[o]);return this}return(n===!1||"function"==typeof n)&&(r=n,n=t),r===!1&&(r=ot),this.each(function(){b.event.remove(this,e,r,n)})},bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},trigger:function(e,t){return this.each(function(){b.event.trigger(e,t,this)})},triggerHandler:function(e,n){var r=this[0];return r?b.event.trigger(e,n,r,!0):t}}),function(e,t){var n,r,i,o,a,s,u,l,c,p,f,d,h,g,m,y,v,x="sizzle"+-new Date,w=e.document,T={},N=0,C=0,k=it(),E=it(),S=it(),A=typeof t,j=1<<31,D=[],L=D.pop,H=D.push,q=D.slice,M=D.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},_="[\\x20\\t\\r\\n\\f]",F="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",O=F.replace("w","w#"),B="([*^$|!~]?=)",P="\\["+_+"*("+F+")"+_+"*(?:"+B+_+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+O+")|)|)"+_+"*\\]",R=":("+F+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+P.replace(3,8)+")*)|.*)\\)|)",W=RegExp("^"+_+"+|((?:^|[^\\\\])(?:\\\\.)*)"+_+"+$","g"),$=RegExp("^"+_+"*,"+_+"*"),I=RegExp("^"+_+"*([\\x20\\t\\r\\n\\f>+~])"+_+"*"),z=RegExp(R),X=RegExp("^"+O+"$"),U={ID:RegExp("^#("+F+")"),CLASS:RegExp("^\\.("+F+")"),NAME:RegExp("^\\[name=['\"]?("+F+")['\"]?\\]"),TAG:RegExp("^("+F.replace("w","w*")+")"),ATTR:RegExp("^"+P),PSEUDO:RegExp("^"+R),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+_+"*(even|odd|(([+-]|)(\\d*)n|)"+_+"*(?:([+-]|)"+_+"*(\\d+)|))"+_+"*\\)|)","i"),needsContext:RegExp("^"+_+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+_+"*((?:-\\d)?\\d*)"+_+"*\\)|)(?=[^-]|$)","i")},V=/[\x20\t\r\n\f]*[+~]/,Y=/^[^{]+\{\s*\[native code/,J=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,G=/^(?:input|select|textarea|button)$/i,Q=/^h\d$/i,K=/'|\\/g,Z=/\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,et=/\\([\da-fA-F]{1,6}[\x20\t\r\n\f]?|.)/g,tt=function(e,t){var n="0x"+t-65536;return n!==n?t:0>n?String.fromCharCode(n+65536):String.fromCharCode(55296|n>>10,56320|1023&n)};try{q.call(w.documentElement.childNodes,0)[0].nodeType}catch(nt){q=function(e){var t,n=[];while(t=this[e++])n.push(t);return n}}function rt(e){return Y.test(e+"")}function it(){var e,t=[];return e=function(n,r){return t.push(n+=" ")>i.cacheLength&&delete e[t.shift()],e[n]=r}}function ot(e){return e[x]=!0,e}function at(e){var t=p.createElement("div");try{return e(t)}catch(n){return!1}finally{t=null}}function st(e,t,n,r){var i,o,a,s,u,l,f,g,m,v;if((t?t.ownerDocument||t:w)!==p&&c(t),t=t||p,n=n||[],!e||"string"!=typeof e)return n;if(1!==(s=t.nodeType)&&9!==s)return[];if(!d&&!r){if(i=J.exec(e))if(a=i[1]){if(9===s){if(o=t.getElementById(a),!o||!o.parentNode)return n;if(o.id===a)return n.push(o),n}else if(t.ownerDocument&&(o=t.ownerDocument.getElementById(a))&&y(t,o)&&o.id===a)return n.push(o),n}else{if(i[2])return H.apply(n,q.call(t.getElementsByTagName(e),0)),n;if((a=i[3])&&T.getByClassName&&t.getElementsByClassName)return H.apply(n,q.call(t.getElementsByClassName(a),0)),n}if(T.qsa&&!h.test(e)){if(f=!0,g=x,m=t,v=9===s&&e,1===s&&"object"!==t.nodeName.toLowerCase()){l=ft(e),(f=t.getAttribute("id"))?g=f.replace(K,"\\$&"):t.setAttribute("id",g),g="[id='"+g+"'] ",u=l.length;while(u--)l[u]=g+dt(l[u]);m=V.test(e)&&t.parentNode||t,v=l.join(",")}if(v)try{return H.apply(n,q.call(m.querySelectorAll(v),0)),n}catch(b){}finally{f||t.removeAttribute("id")}}}return wt(e.replace(W,"$1"),t,n,r)}a=st.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},c=st.setDocument=function(e){var n=e?e.ownerDocument||e:w;return n!==p&&9===n.nodeType&&n.documentElement?(p=n,f=n.documentElement,d=a(n),T.tagNameNoComments=at(function(e){return e.appendChild(n.createComment("")),!e.getElementsByTagName("*").length}),T.attributes=at(function(e){e.innerHTML="<select></select>";var t=typeof e.lastChild.getAttribute("multiple");return"boolean"!==t&&"string"!==t}),T.getByClassName=at(function(e){return e.innerHTML="<div class='hidden e'></div><div class='hidden'></div>",e.getElementsByClassName&&e.getElementsByClassName("e").length?(e.lastChild.className="e",2===e.getElementsByClassName("e").length):!1}),T.getByName=at(function(e){e.id=x+0,e.innerHTML="<a name='"+x+"'></a><div name='"+x+"'></div>",f.insertBefore(e,f.firstChild);var t=n.getElementsByName&&n.getElementsByName(x).length===2+n.getElementsByName(x+0).length;return T.getIdNotName=!n.getElementById(x),f.removeChild(e),t}),i.attrHandle=at(function(e){return e.innerHTML="<a href='#'></a>",e.firstChild&&typeof e.firstChild.getAttribute!==A&&"#"===e.firstChild.getAttribute("href")})?{}:{href:function(e){return e.getAttribute("href",2)},type:function(e){return e.getAttribute("type")}},T.getIdNotName?(i.find.ID=function(e,t){if(typeof t.getElementById!==A&&!d){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},i.filter.ID=function(e){var t=e.replace(et,tt);return function(e){return e.getAttribute("id")===t}}):(i.find.ID=function(e,n){if(typeof n.getElementById!==A&&!d){var r=n.getElementById(e);return r?r.id===e||typeof r.getAttributeNode!==A&&r.getAttributeNode("id").value===e?[r]:t:[]}},i.filter.ID=function(e){var t=e.replace(et,tt);return function(e){var n=typeof e.getAttributeNode!==A&&e.getAttributeNode("id");return n&&n.value===t}}),i.find.TAG=T.tagNameNoComments?function(e,n){return typeof n.getElementsByTagName!==A?n.getElementsByTagName(e):t}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},i.find.NAME=T.getByName&&function(e,n){return typeof n.getElementsByName!==A?n.getElementsByName(name):t},i.find.CLASS=T.getByClassName&&function(e,n){return typeof n.getElementsByClassName===A||d?t:n.getElementsByClassName(e)},g=[],h=[":focus"],(T.qsa=rt(n.querySelectorAll))&&(at(function(e){e.innerHTML="<select><option selected=''></option></select>",e.querySelectorAll("[selected]").length||h.push("\\["+_+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),e.querySelectorAll(":checked").length||h.push(":checked")}),at(function(e){e.innerHTML="<input type='hidden' i=''/>",e.querySelectorAll("[i^='']").length&&h.push("[*^$]="+_+"*(?:\"\"|'')"),e.querySelectorAll(":enabled").length||h.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),h.push(",.*:")})),(T.matchesSelector=rt(m=f.matchesSelector||f.mozMatchesSelector||f.webkitMatchesSelector||f.oMatchesSelector||f.msMatchesSelector))&&at(function(e){T.disconnectedMatch=m.call(e,"div"),m.call(e,"[s!='']:x"),g.push("!=",R)}),h=RegExp(h.join("|")),g=RegExp(g.join("|")),y=rt(f.contains)||f.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},v=f.compareDocumentPosition?function(e,t){var r;return e===t?(u=!0,0):(r=t.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(t))?1&r||e.parentNode&&11===e.parentNode.nodeType?e===n||y(w,e)?-1:t===n||y(w,t)?1:0:4&r?-1:1:e.compareDocumentPosition?-1:1}:function(e,t){var r,i=0,o=e.parentNode,a=t.parentNode,s=[e],l=[t];if(e===t)return u=!0,0;if(!o||!a)return e===n?-1:t===n?1:o?-1:a?1:0;if(o===a)return ut(e,t);r=e;while(r=r.parentNode)s.unshift(r);r=t;while(r=r.parentNode)l.unshift(r);while(s[i]===l[i])i++;return i?ut(s[i],l[i]):s[i]===w?-1:l[i]===w?1:0},u=!1,[0,0].sort(v),T.detectDuplicates=u,p):p},st.matches=function(e,t){return st(e,null,null,t)},st.matchesSelector=function(e,t){if((e.ownerDocument||e)!==p&&c(e),t=t.replace(Z,"='$1']"),!(!T.matchesSelector||d||g&&g.test(t)||h.test(t)))try{var n=m.call(e,t);if(n||T.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(r){}return st(t,p,null,[e]).length>0},st.contains=function(e,t){return(e.ownerDocument||e)!==p&&c(e),y(e,t)},st.attr=function(e,t){var n;return(e.ownerDocument||e)!==p&&c(e),d||(t=t.toLowerCase()),(n=i.attrHandle[t])?n(e):d||T.attributes?e.getAttribute(t):((n=e.getAttributeNode(t))||e.getAttribute(t))&&e[t]===!0?t:n&&n.specified?n.value:null},st.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},st.uniqueSort=function(e){var t,n=[],r=1,i=0;if(u=!T.detectDuplicates,e.sort(v),u){for(;t=e[r];r++)t===e[r-1]&&(i=n.push(r));while(i--)e.splice(n[i],1)}return e};function ut(e,t){var n=t&&e,r=n&&(~t.sourceIndex||j)-(~e.sourceIndex||j);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function lt(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function ct(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function pt(e){return ot(function(t){return t=+t,ot(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}o=st.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=o(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=o(t);return n},i=st.selectors={cacheLength:50,createPseudo:ot,match:U,find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(et,tt),e[3]=(e[4]||e[5]||"").replace(et,tt),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||st.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&st.error(e[0]),e},PSEUDO:function(e){var t,n=!e[5]&&e[2];return U.CHILD.test(e[0])?null:(e[4]?e[2]=e[4]:n&&z.test(n)&&(t=ft(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){return"*"===e?function(){return!0}:(e=e.replace(et,tt).toLowerCase(),function(t){return t.nodeName&&t.nodeName.toLowerCase()===e})},CLASS:function(e){var t=k[e+" "];return t||(t=RegExp("(^|"+_+")"+e+"("+_+"|$)"))&&k(e,function(e){return t.test(e.className||typeof e.getAttribute!==A&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=st.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,p,f,d,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!u&&!s;if(m){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===y:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&v){c=m[x]||(m[x]={}),l=c[e]||[],d=l[0]===N&&l[1],f=l[0]===N&&l[2],p=d&&m.childNodes[d];while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if(1===p.nodeType&&++f&&p===t){c[e]=[N,d,f];break}}else if(v&&(l=(t[x]||(t[x]={}))[e])&&l[0]===N)f=l[1];else while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===y:1===p.nodeType)&&++f&&(v&&((p[x]||(p[x]={}))[e]=[N,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=i.pseudos[e]||i.setFilters[e.toLowerCase()]||st.error("unsupported pseudo: "+e);return r[x]?r(t):r.length>1?(n=[e,e,"",t],i.setFilters.hasOwnProperty(e.toLowerCase())?ot(function(e,n){var i,o=r(e,t),a=o.length;while(a--)i=M.call(e,o[a]),e[i]=!(n[i]=o[a])}):function(e){return r(e,0,n)}):r}},pseudos:{not:ot(function(e){var t=[],n=[],r=s(e.replace(W,"$1"));return r[x]?ot(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:ot(function(e){return function(t){return st(e,t).length>0}}),contains:ot(function(e){return function(t){return(t.textContent||t.innerText||o(t)).indexOf(e)>-1}}),lang:ot(function(e){return X.test(e||"")||st.error("unsupported lang: "+e),e=e.replace(et,tt).toLowerCase(),function(t){var n;do if(n=d?t.getAttribute("xml:lang")||t.getAttribute("lang"):t.lang)return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===f},focus:function(e){return e===p.activeElement&&(!p.hasFocus||p.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!i.pseudos.empty(e)},header:function(e){return Q.test(e.nodeName)},input:function(e){return G.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:pt(function(){return[0]}),last:pt(function(e,t){return[t-1]}),eq:pt(function(e,t,n){return[0>n?n+t:n]}),even:pt(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:pt(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:pt(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:pt(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}};for(n in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})i.pseudos[n]=lt(n);for(n in{submit:!0,reset:!0})i.pseudos[n]=ct(n);function ft(e,t){var n,r,o,a,s,u,l,c=E[e+" "];if(c)return t?0:c.slice(0);s=e,u=[],l=i.preFilter;while(s){(!n||(r=$.exec(s)))&&(r&&(s=s.slice(r[0].length)||s),u.push(o=[])),n=!1,(r=I.exec(s))&&(n=r.shift(),o.push({value:n,type:r[0].replace(W," ")}),s=s.slice(n.length));for(a in i.filter)!(r=U[a].exec(s))||l[a]&&!(r=l[a](r))||(n=r.shift(),o.push({value:n,type:a,matches:r}),s=s.slice(n.length));if(!n)break}return t?s.length:s?st.error(e):E(e,u).slice(0)}function dt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function ht(e,t,n){var i=t.dir,o=n&&"parentNode"===i,a=C++;return t.first?function(t,n,r){while(t=t[i])if(1===t.nodeType||o)return e(t,n,r)}:function(t,n,s){var u,l,c,p=N+" "+a;if(s){while(t=t[i])if((1===t.nodeType||o)&&e(t,n,s))return!0}else while(t=t[i])if(1===t.nodeType||o)if(c=t[x]||(t[x]={}),(l=c[i])&&l[0]===p){if((u=l[1])===!0||u===r)return u===!0}else if(l=c[i]=[p],l[1]=e(t,n,s)||r,l[1]===!0)return!0}}function gt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function mt(e,t,n,r,i){var o,a=[],s=0,u=e.length,l=null!=t;for(;u>s;s++)(o=e[s])&&(!n||n(o,r,i))&&(a.push(o),l&&t.push(s));return a}function yt(e,t,n,r,i,o){return r&&!r[x]&&(r=yt(r)),i&&!i[x]&&(i=yt(i,o)),ot(function(o,a,s,u){var l,c,p,f=[],d=[],h=a.length,g=o||xt(t||"*",s.nodeType?[s]:s,[]),m=!e||!o&&t?g:mt(g,f,e,s,u),y=n?i||(o?e:h||r)?[]:a:m;if(n&&n(m,y,s,u),r){l=mt(y,d),r(l,[],s,u),c=l.length;while(c--)(p=l[c])&&(y[d[c]]=!(m[d[c]]=p))}if(o){if(i||e){if(i){l=[],c=y.length;while(c--)(p=y[c])&&l.push(m[c]=p);i(null,y=[],l,u)}c=y.length;while(c--)(p=y[c])&&(l=i?M.call(o,p):f[c])>-1&&(o[l]=!(a[l]=p))}}else y=mt(y===a?y.splice(h,y.length):y),i?i(null,a,y,u):H.apply(a,y)})}function vt(e){var t,n,r,o=e.length,a=i.relative[e[0].type],s=a||i.relative[" "],u=a?1:0,c=ht(function(e){return e===t},s,!0),p=ht(function(e){return M.call(t,e)>-1},s,!0),f=[function(e,n,r){return!a&&(r||n!==l)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;o>u;u++)if(n=i.relative[e[u].type])f=[ht(gt(f),n)];else{if(n=i.filter[e[u].type].apply(null,e[u].matches),n[x]){for(r=++u;o>r;r++)if(i.relative[e[r].type])break;return yt(u>1&>(f),u>1&&dt(e.slice(0,u-1)).replace(W,"$1"),n,r>u&&vt(e.slice(u,r)),o>r&&vt(e=e.slice(r)),o>r&&dt(e))}f.push(n)}return gt(f)}function bt(e,t){var n=0,o=t.length>0,a=e.length>0,s=function(s,u,c,f,d){var h,g,m,y=[],v=0,b="0",x=s&&[],w=null!=d,T=l,C=s||a&&i.find.TAG("*",d&&u.parentNode||u),k=N+=null==T?1:Math.random()||.1;for(w&&(l=u!==p&&u,r=n);null!=(h=C[b]);b++){if(a&&h){g=0;while(m=e[g++])if(m(h,u,c)){f.push(h);break}w&&(N=k,r=++n)}o&&((h=!m&&h)&&v--,s&&x.push(h))}if(v+=b,o&&b!==v){g=0;while(m=t[g++])m(x,y,u,c);if(s){if(v>0)while(b--)x[b]||y[b]||(y[b]=L.call(f));y=mt(y)}H.apply(f,y),w&&!s&&y.length>0&&v+t.length>1&&st.uniqueSort(f)}return w&&(N=k,l=T),x};return o?ot(s):s}s=st.compile=function(e,t){var n,r=[],i=[],o=S[e+" "];if(!o){t||(t=ft(e)),n=t.length;while(n--)o=vt(t[n]),o[x]?r.push(o):i.push(o);o=S(e,bt(i,r))}return o};function xt(e,t,n){var r=0,i=t.length;for(;i>r;r++)st(e,t[r],n);return n}function wt(e,t,n,r){var o,a,u,l,c,p=ft(e);if(!r&&1===p.length){if(a=p[0]=p[0].slice(0),a.length>2&&"ID"===(u=a[0]).type&&9===t.nodeType&&!d&&i.relative[a[1].type]){if(t=i.find.ID(u.matches[0].replace(et,tt),t)[0],!t)return n;e=e.slice(a.shift().value.length)}o=U.needsContext.test(e)?0:a.length;while(o--){if(u=a[o],i.relative[l=u.type])break;if((c=i.find[l])&&(r=c(u.matches[0].replace(et,tt),V.test(a[0].type)&&t.parentNode||t))){if(a.splice(o,1),e=r.length&&dt(a),!e)return H.apply(n,q.call(r,0)),n;break}}}return s(e,p)(r,t,d,n,V.test(e)),n}i.pseudos.nth=i.pseudos.eq;function Tt(){}i.filters=Tt.prototype=i.pseudos,i.setFilters=new Tt,c(),st.attr=b.attr,b.find=st,b.expr=st.selectors,b.expr[":"]=b.expr.pseudos,b.unique=st.uniqueSort,b.text=st.getText,b.isXMLDoc=st.isXML,b.contains=st.contains}(e);var at=/Until$/,st=/^(?:parents|prev(?:Until|All))/,ut=/^.[^:#\[\.,]*$/,lt=b.expr.match.needsContext,ct={children:!0,contents:!0,next:!0,prev:!0};b.fn.extend({find:function(e){var t,n,r,i=this.length;if("string"!=typeof e)return r=this,this.pushStack(b(e).filter(function(){for(t=0;i>t;t++)if(b.contains(r[t],this))return!0}));for(n=[],t=0;i>t;t++)b.find(e,this[t],n);return n=this.pushStack(i>1?b.unique(n):n),n.selector=(this.selector?this.selector+" ":"")+e,n},has:function(e){var t,n=b(e,this),r=n.length;return this.filter(function(){for(t=0;r>t;t++)if(b.contains(this,n[t]))return!0})},not:function(e){return this.pushStack(ft(this,e,!1))},filter:function(e){return this.pushStack(ft(this,e,!0))},is:function(e){return!!e&&("string"==typeof e?lt.test(e)?b(e,this.context).index(this[0])>=0:b.filter(e,this).length>0:this.filter(e).length>0)},closest:function(e,t){var n,r=0,i=this.length,o=[],a=lt.test(e)||"string"!=typeof e?b(e,t||this.context):0;for(;i>r;r++){n=this[r];while(n&&n.ownerDocument&&n!==t&&11!==n.nodeType){if(a?a.index(n)>-1:b.find.matchesSelector(n,e)){o.push(n);break}n=n.parentNode}}return this.pushStack(o.length>1?b.unique(o):o)},index:function(e){return e?"string"==typeof e?b.inArray(this[0],b(e)):b.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?b(e,t):b.makeArray(e&&e.nodeType?[e]:e),r=b.merge(this.get(),n);return this.pushStack(b.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),b.fn.andSelf=b.fn.addBack;function pt(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}b.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return b.dir(e,"parentNode")},parentsUntil:function(e,t,n){return b.dir(e,"parentNode",n)},next:function(e){return pt(e,"nextSibling")},prev:function(e){return pt(e,"previousSibling")},nextAll:function(e){return b.dir(e,"nextSibling")},prevAll:function(e){return b.dir(e,"previousSibling")},nextUntil:function(e,t,n){return b.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return b.dir(e,"previousSibling",n)},siblings:function(e){return b.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return b.sibling(e.firstChild)},contents:function(e){return b.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:b.merge([],e.childNodes)}},function(e,t){b.fn[e]=function(n,r){var i=b.map(this,t,n);return at.test(e)||(r=n),r&&"string"==typeof r&&(i=b.filter(r,i)),i=this.length>1&&!ct[e]?b.unique(i):i,this.length>1&&st.test(e)&&(i=i.reverse()),this.pushStack(i)}}),b.extend({filter:function(e,t,n){return n&&(e=":not("+e+")"),1===t.length?b.find.matchesSelector(t[0],e)?[t[0]]:[]:b.find.matches(e,t)},dir:function(e,n,r){var i=[],o=e[n];while(o&&9!==o.nodeType&&(r===t||1!==o.nodeType||!b(o).is(r)))1===o.nodeType&&i.push(o),o=o[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function ft(e,t,n){if(t=t||0,b.isFunction(t))return b.grep(e,function(e,r){var i=!!t.call(e,r,e);return i===n});if(t.nodeType)return b.grep(e,function(e){return e===t===n});if("string"==typeof t){var r=b.grep(e,function(e){return 1===e.nodeType});if(ut.test(t))return b.filter(t,r,!n);t=b.filter(t,r)}return b.grep(e,function(e){return b.inArray(e,t)>=0===n})}function dt(e){var t=ht.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}var ht="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",gt=/ jQuery\d+="(?:null|\d+)"/g,mt=RegExp("<(?:"+ht+")[\\s/>]","i"),yt=/^\s+/,vt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bt=/<([\w:]+)/,xt=/<tbody/i,wt=/<|&#?\w+;/,Tt=/<(?:script|style|link)/i,Nt=/^(?:checkbox|radio)$/i,Ct=/checked\s*(?:[^=]|=\s*.checked.)/i,kt=/^$|\/(?:java|ecma)script/i,Et=/^true\/(.*)/,St=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,At={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],area:[1,"<map>","</map>"],param:[1,"<object>","</object>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:b.support.htmlSerialize?[0,"",""]:[1,"X<div>","</div>"]},jt=dt(o),Dt=jt.appendChild(o.createElement("div"));At.optgroup=At.option,At.tbody=At.tfoot=At.colgroup=At.caption=At.thead,At.th=At.td,b.fn.extend({text:function(e){return b.access(this,function(e){return e===t?b.text(this):this.empty().append((this[0]&&this[0].ownerDocument||o).createTextNode(e))},null,e,arguments.length)},wrapAll:function(e){if(b.isFunction(e))return this.each(function(t){b(this).wrapAll(e.call(this,t))});if(this[0]){var t=b(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&1===e.firstChild.nodeType)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return b.isFunction(e)?this.each(function(t){b(this).wrapInner(e.call(this,t))}):this.each(function(){var t=b(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=b.isFunction(e);return this.each(function(n){b(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){b.nodeName(this,"body")||b(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(e){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&this.appendChild(e)})},prepend:function(){return this.domManip(arguments,!0,function(e){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&this.insertBefore(e,this.firstChild)})},before:function(){return this.domManip(arguments,!1,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,!1,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=0;for(;null!=(n=this[r]);r++)(!e||b.filter(e,[n]).length>0)&&(t||1!==n.nodeType||b.cleanData(Ot(n)),n.parentNode&&(t&&b.contains(n.ownerDocument,n)&&Mt(Ot(n,"script")),n.parentNode.removeChild(n)));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++){1===e.nodeType&&b.cleanData(Ot(e,!1));while(e.firstChild)e.removeChild(e.firstChild);e.options&&b.nodeName(e,"select")&&(e.options.length=0)}return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return b.clone(this,e,t)})},html:function(e){return b.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return 1===n.nodeType?n.innerHTML.replace(gt,""):t;if(!("string"!=typeof e||Tt.test(e)||!b.support.htmlSerialize&&mt.test(e)||!b.support.leadingWhitespace&&yt.test(e)||At[(bt.exec(e)||["",""])[1].toLowerCase()])){e=e.replace(vt,"<$1></$2>");try{for(;i>r;r++)n=this[r]||{},1===n.nodeType&&(b.cleanData(Ot(n,!1)),n.innerHTML=e);n=0}catch(o){}}n&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(e){var t=b.isFunction(e);return t||"string"==typeof e||(e=b(e).not(this).detach()),this.domManip([e],!0,function(e){var t=this.nextSibling,n=this.parentNode;n&&(b(this).remove(),n.insertBefore(e,t))})},detach:function(e){return this.remove(e,!0)},domManip:function(e,n,r){e=f.apply([],e);var i,o,a,s,u,l,c=0,p=this.length,d=this,h=p-1,g=e[0],m=b.isFunction(g);if(m||!(1>=p||"string"!=typeof g||b.support.checkClone)&&Ct.test(g))return this.each(function(i){var o=d.eq(i);m&&(e[0]=g.call(this,i,n?o.html():t)),o.domManip(e,n,r)});if(p&&(l=b.buildFragment(e,this[0].ownerDocument,!1,this),i=l.firstChild,1===l.childNodes.length&&(l=i),i)){for(n=n&&b.nodeName(i,"tr"),s=b.map(Ot(l,"script"),Ht),a=s.length;p>c;c++)o=l,c!==h&&(o=b.clone(o,!0,!0),a&&b.merge(s,Ot(o,"script"))),r.call(n&&b.nodeName(this[c],"table")?Lt(this[c],"tbody"):this[c],o,c);if(a)for(u=s[s.length-1].ownerDocument,b.map(s,qt),c=0;a>c;c++)o=s[c],kt.test(o.type||"")&&!b._data(o,"globalEval")&&b.contains(u,o)&&(o.src?b.ajax({url:o.src,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0}):b.globalEval((o.text||o.textContent||o.innerHTML||"").replace(St,"")));l=i=null}return this}});function Lt(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function Ht(e){var t=e.getAttributeNode("type");return e.type=(t&&t.specified)+"/"+e.type,e}function qt(e){var t=Et.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function Mt(e,t){var n,r=0;for(;null!=(n=e[r]);r++)b._data(n,"globalEval",!t||b._data(t[r],"globalEval"))}function _t(e,t){if(1===t.nodeType&&b.hasData(e)){var n,r,i,o=b._data(e),a=b._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)b.event.add(t,n,s[n][r])}a.data&&(a.data=b.extend({},a.data))}}function Ft(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!b.support.noCloneEvent&&t[b.expando]){i=b._data(t);for(r in i.events)b.removeEvent(t,r,i.handle);t.removeAttribute(b.expando)}"script"===n&&t.text!==e.text?(Ht(t).text=e.text,qt(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),b.support.html5Clone&&e.innerHTML&&!b.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Nt.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}b.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){b.fn[e]=function(e){var n,r=0,i=[],o=b(e),a=o.length-1;for(;a>=r;r++)n=r===a?this:this.clone(!0),b(o[r])[t](n),d.apply(i,n.get());return this.pushStack(i)}});function Ot(e,n){var r,o,a=0,s=typeof e.getElementsByTagName!==i?e.getElementsByTagName(n||"*"):typeof e.querySelectorAll!==i?e.querySelectorAll(n||"*"):t;if(!s)for(s=[],r=e.childNodes||e;null!=(o=r[a]);a++)!n||b.nodeName(o,n)?s.push(o):b.merge(s,Ot(o,n));return n===t||n&&b.nodeName(e,n)?b.merge([e],s):s}function Bt(e){Nt.test(e.type)&&(e.defaultChecked=e.checked)}b.extend({clone:function(e,t,n){var r,i,o,a,s,u=b.contains(e.ownerDocument,e);if(b.support.html5Clone||b.isXMLDoc(e)||!mt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(Dt.innerHTML=e.outerHTML,Dt.removeChild(o=Dt.firstChild)),!(b.support.noCloneEvent&&b.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||b.isXMLDoc(e)))for(r=Ot(o),s=Ot(e),a=0;null!=(i=s[a]);++a)r[a]&&Ft(i,r[a]);if(t)if(n)for(s=s||Ot(e),r=r||Ot(o),a=0;null!=(i=s[a]);a++)_t(i,r[a]);else _t(e,o);return r=Ot(o,"script"),r.length>0&&Mt(r,!u&&Ot(e,"script")),r=s=i=null,o},buildFragment:function(e,t,n,r){var i,o,a,s,u,l,c,p=e.length,f=dt(t),d=[],h=0;for(;p>h;h++)if(o=e[h],o||0===o)if("object"===b.type(o))b.merge(d,o.nodeType?[o]:o);else if(wt.test(o)){s=s||f.appendChild(t.createElement("div")),u=(bt.exec(o)||["",""])[1].toLowerCase(),c=At[u]||At._default,s.innerHTML=c[1]+o.replace(vt,"<$1></$2>")+c[2],i=c[0];while(i--)s=s.lastChild;if(!b.support.leadingWhitespace&&yt.test(o)&&d.push(t.createTextNode(yt.exec(o)[0])),!b.support.tbody){o="table"!==u||xt.test(o)?"<table>"!==c[1]||xt.test(o)?0:s:s.firstChild,i=o&&o.childNodes.length;while(i--)b.nodeName(l=o.childNodes[i],"tbody")&&!l.childNodes.length&&o.removeChild(l) -}b.merge(d,s.childNodes),s.textContent="";while(s.firstChild)s.removeChild(s.firstChild);s=f.lastChild}else d.push(t.createTextNode(o));s&&f.removeChild(s),b.support.appendChecked||b.grep(Ot(d,"input"),Bt),h=0;while(o=d[h++])if((!r||-1===b.inArray(o,r))&&(a=b.contains(o.ownerDocument,o),s=Ot(f.appendChild(o),"script"),a&&Mt(s),n)){i=0;while(o=s[i++])kt.test(o.type||"")&&n.push(o)}return s=null,f},cleanData:function(e,t){var n,r,o,a,s=0,u=b.expando,l=b.cache,p=b.support.deleteExpando,f=b.event.special;for(;null!=(n=e[s]);s++)if((t||b.acceptData(n))&&(o=n[u],a=o&&l[o])){if(a.events)for(r in a.events)f[r]?b.event.remove(n,r):b.removeEvent(n,r,a.handle);l[o]&&(delete l[o],p?delete n[u]:typeof n.removeAttribute!==i?n.removeAttribute(u):n[u]=null,c.push(o))}}});var Pt,Rt,Wt,$t=/alpha\([^)]*\)/i,It=/opacity\s*=\s*([^)]*)/,zt=/^(top|right|bottom|left)$/,Xt=/^(none|table(?!-c[ea]).+)/,Ut=/^margin/,Vt=RegExp("^("+x+")(.*)$","i"),Yt=RegExp("^("+x+")(?!px)[a-z%]+$","i"),Jt=RegExp("^([+-])=("+x+")","i"),Gt={BODY:"block"},Qt={position:"absolute",visibility:"hidden",display:"block"},Kt={letterSpacing:0,fontWeight:400},Zt=["Top","Right","Bottom","Left"],en=["Webkit","O","Moz","ms"];function tn(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=en.length;while(i--)if(t=en[i]+n,t in e)return t;return r}function nn(e,t){return e=t||e,"none"===b.css(e,"display")||!b.contains(e.ownerDocument,e)}function rn(e,t){var n,r,i,o=[],a=0,s=e.length;for(;s>a;a++)r=e[a],r.style&&(o[a]=b._data(r,"olddisplay"),n=r.style.display,t?(o[a]||"none"!==n||(r.style.display=""),""===r.style.display&&nn(r)&&(o[a]=b._data(r,"olddisplay",un(r.nodeName)))):o[a]||(i=nn(r),(n&&"none"!==n||!i)&&b._data(r,"olddisplay",i?n:b.css(r,"display"))));for(a=0;s>a;a++)r=e[a],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[a]||"":"none"));return e}b.fn.extend({css:function(e,n){return b.access(this,function(e,n,r){var i,o,a={},s=0;if(b.isArray(n)){for(o=Rt(e),i=n.length;i>s;s++)a[n[s]]=b.css(e,n[s],!1,o);return a}return r!==t?b.style(e,n,r):b.css(e,n)},e,n,arguments.length>1)},show:function(){return rn(this,!0)},hide:function(){return rn(this)},toggle:function(e){var t="boolean"==typeof e;return this.each(function(){(t?e:nn(this))?b(this).show():b(this).hide()})}}),b.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Wt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":b.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var o,a,s,u=b.camelCase(n),l=e.style;if(n=b.cssProps[u]||(b.cssProps[u]=tn(l,u)),s=b.cssHooks[n]||b.cssHooks[u],r===t)return s&&"get"in s&&(o=s.get(e,!1,i))!==t?o:l[n];if(a=typeof r,"string"===a&&(o=Jt.exec(r))&&(r=(o[1]+1)*o[2]+parseFloat(b.css(e,n)),a="number"),!(null==r||"number"===a&&isNaN(r)||("number"!==a||b.cssNumber[u]||(r+="px"),b.support.clearCloneStyle||""!==r||0!==n.indexOf("background")||(l[n]="inherit"),s&&"set"in s&&(r=s.set(e,r,i))===t)))try{l[n]=r}catch(c){}}},css:function(e,n,r,i){var o,a,s,u=b.camelCase(n);return n=b.cssProps[u]||(b.cssProps[u]=tn(e.style,u)),s=b.cssHooks[n]||b.cssHooks[u],s&&"get"in s&&(a=s.get(e,!0,r)),a===t&&(a=Wt(e,n,i)),"normal"===a&&n in Kt&&(a=Kt[n]),""===r||r?(o=parseFloat(a),r===!0||b.isNumeric(o)?o||0:a):a},swap:function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=a[o];return i}}),e.getComputedStyle?(Rt=function(t){return e.getComputedStyle(t,null)},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),u=s?s.getPropertyValue(n)||s[n]:t,l=e.style;return s&&(""!==u||b.contains(e.ownerDocument,e)||(u=b.style(e,n)),Yt.test(u)&&Ut.test(n)&&(i=l.width,o=l.minWidth,a=l.maxWidth,l.minWidth=l.maxWidth=l.width=u,u=s.width,l.width=i,l.minWidth=o,l.maxWidth=a)),u}):o.documentElement.currentStyle&&(Rt=function(e){return e.currentStyle},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),u=s?s[n]:t,l=e.style;return null==u&&l&&l[n]&&(u=l[n]),Yt.test(u)&&!zt.test(n)&&(i=l.left,o=e.runtimeStyle,a=o&&o.left,a&&(o.left=e.currentStyle.left),l.left="fontSize"===n?"1em":u,u=l.pixelLeft+"px",l.left=i,a&&(o.left=a)),""===u?"auto":u});function on(e,t,n){var r=Vt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function an(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,a=0;for(;4>o;o+=2)"margin"===n&&(a+=b.css(e,n+Zt[o],!0,i)),r?("content"===n&&(a-=b.css(e,"padding"+Zt[o],!0,i)),"margin"!==n&&(a-=b.css(e,"border"+Zt[o]+"Width",!0,i))):(a+=b.css(e,"padding"+Zt[o],!0,i),"padding"!==n&&(a+=b.css(e,"border"+Zt[o]+"Width",!0,i)));return a}function sn(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=Rt(e),a=b.support.boxSizing&&"border-box"===b.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=Wt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Yt.test(i))return i;r=a&&(b.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+an(e,t,n||(a?"border":"content"),r,o)+"px"}function un(e){var t=o,n=Gt[e];return n||(n=ln(e,t),"none"!==n&&n||(Pt=(Pt||b("<iframe frameborder='0' width='0' height='0'/>").css("cssText","display:block !important")).appendTo(t.documentElement),t=(Pt[0].contentWindow||Pt[0].contentDocument).document,t.write("<!doctype html><html><body>"),t.close(),n=ln(e,t),Pt.detach()),Gt[e]=n),n}function ln(e,t){var n=b(t.createElement(e)).appendTo(t.body),r=b.css(n[0],"display");return n.remove(),r}b.each(["height","width"],function(e,n){b.cssHooks[n]={get:function(e,r,i){return r?0===e.offsetWidth&&Xt.test(b.css(e,"display"))?b.swap(e,Qt,function(){return sn(e,n,i)}):sn(e,n,i):t},set:function(e,t,r){var i=r&&Rt(e);return on(e,t,r?an(e,n,r,b.support.boxSizing&&"border-box"===b.css(e,"boxSizing",!1,i),i):0)}}}),b.support.opacity||(b.cssHooks.opacity={get:function(e,t){return It.test((t&&e.currentStyle?e.currentStyle.filter:e.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":t?"1":""},set:function(e,t){var n=e.style,r=e.currentStyle,i=b.isNumeric(t)?"alpha(opacity="+100*t+")":"",o=r&&r.filter||n.filter||"";n.zoom=1,(t>=1||""===t)&&""===b.trim(o.replace($t,""))&&n.removeAttribute&&(n.removeAttribute("filter"),""===t||r&&!r.filter)||(n.filter=$t.test(o)?o.replace($t,i):o+" "+i)}}),b(function(){b.support.reliableMarginRight||(b.cssHooks.marginRight={get:function(e,n){return n?b.swap(e,{display:"inline-block"},Wt,[e,"marginRight"]):t}}),!b.support.pixelPosition&&b.fn.position&&b.each(["top","left"],function(e,n){b.cssHooks[n]={get:function(e,r){return r?(r=Wt(e,n),Yt.test(r)?b(e).position()[n]+"px":r):t}}})}),b.expr&&b.expr.filters&&(b.expr.filters.hidden=function(e){return 0>=e.offsetWidth&&0>=e.offsetHeight||!b.support.reliableHiddenOffsets&&"none"===(e.style&&e.style.display||b.css(e,"display"))},b.expr.filters.visible=function(e){return!b.expr.filters.hidden(e)}),b.each({margin:"",padding:"",border:"Width"},function(e,t){b.cssHooks[e+t]={expand:function(n){var r=0,i={},o="string"==typeof n?n.split(" "):[n];for(;4>r;r++)i[e+Zt[r]+t]=o[r]||o[r-2]||o[0];return i}},Ut.test(e)||(b.cssHooks[e+t].set=on)});var cn=/%20/g,pn=/\[\]$/,fn=/\r?\n/g,dn=/^(?:submit|button|image|reset|file)$/i,hn=/^(?:input|select|textarea|keygen)/i;b.fn.extend({serialize:function(){return b.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=b.prop(this,"elements");return e?b.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!b(this).is(":disabled")&&hn.test(this.nodeName)&&!dn.test(e)&&(this.checked||!Nt.test(e))}).map(function(e,t){var n=b(this).val();return null==n?null:b.isArray(n)?b.map(n,function(e){return{name:t.name,value:e.replace(fn,"\r\n")}}):{name:t.name,value:n.replace(fn,"\r\n")}}).get()}}),b.param=function(e,n){var r,i=[],o=function(e,t){t=b.isFunction(t)?t():null==t?"":t,i[i.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};if(n===t&&(n=b.ajaxSettings&&b.ajaxSettings.traditional),b.isArray(e)||e.jquery&&!b.isPlainObject(e))b.each(e,function(){o(this.name,this.value)});else for(r in e)gn(r,e[r],n,o);return i.join("&").replace(cn,"+")};function gn(e,t,n,r){var i;if(b.isArray(t))b.each(t,function(t,i){n||pn.test(e)?r(e,i):gn(e+"["+("object"==typeof i?t:"")+"]",i,n,r)});else if(n||"object"!==b.type(t))r(e,t);else for(i in t)gn(e+"["+i+"]",t[i],n,r)}b.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(e,t){b.fn[t]=function(e,n){return arguments.length>0?this.on(t,null,e,n):this.trigger(t)}}),b.fn.hover=function(e,t){return this.mouseenter(e).mouseleave(t||e)};var mn,yn,vn=b.now(),bn=/\?/,xn=/#.*$/,wn=/([?&])_=[^&]*/,Tn=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,Nn=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Cn=/^(?:GET|HEAD)$/,kn=/^\/\//,En=/^([\w.+-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,Sn=b.fn.load,An={},jn={},Dn="*/".concat("*");try{yn=a.href}catch(Ln){yn=o.createElement("a"),yn.href="",yn=yn.href}mn=En.exec(yn.toLowerCase())||[];function Hn(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(w)||[];if(b.isFunction(n))while(r=o[i++])"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function qn(e,n,r,i){var o={},a=e===jn;function s(u){var l;return o[u]=!0,b.each(e[u]||[],function(e,u){var c=u(n,r,i);return"string"!=typeof c||a||o[c]?a?!(l=c):t:(n.dataTypes.unshift(c),s(c),!1)}),l}return s(n.dataTypes[0])||!o["*"]&&s("*")}function Mn(e,n){var r,i,o=b.ajaxSettings.flatOptions||{};for(i in n)n[i]!==t&&((o[i]?e:r||(r={}))[i]=n[i]);return r&&b.extend(!0,e,r),e}b.fn.load=function(e,n,r){if("string"!=typeof e&&Sn)return Sn.apply(this,arguments);var i,o,a,s=this,u=e.indexOf(" ");return u>=0&&(i=e.slice(u,e.length),e=e.slice(0,u)),b.isFunction(n)?(r=n,n=t):n&&"object"==typeof n&&(a="POST"),s.length>0&&b.ajax({url:e,type:a,dataType:"html",data:n}).done(function(e){o=arguments,s.html(i?b("<div>").append(b.parseHTML(e)).find(i):e)}).complete(r&&function(e,t){s.each(r,o||[e.responseText,t,e])}),this},b.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){b.fn[t]=function(e){return this.on(t,e)}}),b.each(["get","post"],function(e,n){b[n]=function(e,r,i,o){return b.isFunction(r)&&(o=o||i,i=r,r=t),b.ajax({url:e,type:n,dataType:o,data:r,success:i})}}),b.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:yn,type:"GET",isLocal:Nn.test(mn[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Dn,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":e.String,"text html":!0,"text json":b.parseJSON,"text xml":b.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?Mn(Mn(e,b.ajaxSettings),t):Mn(b.ajaxSettings,e)},ajaxPrefilter:Hn(An),ajaxTransport:Hn(jn),ajax:function(e,n){"object"==typeof e&&(n=e,e=t),n=n||{};var r,i,o,a,s,u,l,c,p=b.ajaxSetup({},n),f=p.context||p,d=p.context&&(f.nodeType||f.jquery)?b(f):b.event,h=b.Deferred(),g=b.Callbacks("once memory"),m=p.statusCode||{},y={},v={},x=0,T="canceled",N={readyState:0,getResponseHeader:function(e){var t;if(2===x){if(!c){c={};while(t=Tn.exec(a))c[t[1].toLowerCase()]=t[2]}t=c[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return 2===x?a:null},setRequestHeader:function(e,t){var n=e.toLowerCase();return x||(e=v[n]=v[n]||e,y[e]=t),this},overrideMimeType:function(e){return x||(p.mimeType=e),this},statusCode:function(e){var t;if(e)if(2>x)for(t in e)m[t]=[m[t],e[t]];else N.always(e[N.status]);return this},abort:function(e){var t=e||T;return l&&l.abort(t),k(0,t),this}};if(h.promise(N).complete=g.add,N.success=N.done,N.error=N.fail,p.url=((e||p.url||yn)+"").replace(xn,"").replace(kn,mn[1]+"//"),p.type=n.method||n.type||p.method||p.type,p.dataTypes=b.trim(p.dataType||"*").toLowerCase().match(w)||[""],null==p.crossDomain&&(r=En.exec(p.url.toLowerCase()),p.crossDomain=!(!r||r[1]===mn[1]&&r[2]===mn[2]&&(r[3]||("http:"===r[1]?80:443))==(mn[3]||("http:"===mn[1]?80:443)))),p.data&&p.processData&&"string"!=typeof p.data&&(p.data=b.param(p.data,p.traditional)),qn(An,p,n,N),2===x)return N;u=p.global,u&&0===b.active++&&b.event.trigger("ajaxStart"),p.type=p.type.toUpperCase(),p.hasContent=!Cn.test(p.type),o=p.url,p.hasContent||(p.data&&(o=p.url+=(bn.test(o)?"&":"?")+p.data,delete p.data),p.cache===!1&&(p.url=wn.test(o)?o.replace(wn,"$1_="+vn++):o+(bn.test(o)?"&":"?")+"_="+vn++)),p.ifModified&&(b.lastModified[o]&&N.setRequestHeader("If-Modified-Since",b.lastModified[o]),b.etag[o]&&N.setRequestHeader("If-None-Match",b.etag[o])),(p.data&&p.hasContent&&p.contentType!==!1||n.contentType)&&N.setRequestHeader("Content-Type",p.contentType),N.setRequestHeader("Accept",p.dataTypes[0]&&p.accepts[p.dataTypes[0]]?p.accepts[p.dataTypes[0]]+("*"!==p.dataTypes[0]?", "+Dn+"; q=0.01":""):p.accepts["*"]);for(i in p.headers)N.setRequestHeader(i,p.headers[i]);if(p.beforeSend&&(p.beforeSend.call(f,N,p)===!1||2===x))return N.abort();T="abort";for(i in{success:1,error:1,complete:1})N[i](p[i]);if(l=qn(jn,p,n,N)){N.readyState=1,u&&d.trigger("ajaxSend",[N,p]),p.async&&p.timeout>0&&(s=setTimeout(function(){N.abort("timeout")},p.timeout));try{x=1,l.send(y,k)}catch(C){if(!(2>x))throw C;k(-1,C)}}else k(-1,"No Transport");function k(e,n,r,i){var c,y,v,w,T,C=n;2!==x&&(x=2,s&&clearTimeout(s),l=t,a=i||"",N.readyState=e>0?4:0,r&&(w=_n(p,N,r)),e>=200&&300>e||304===e?(p.ifModified&&(T=N.getResponseHeader("Last-Modified"),T&&(b.lastModified[o]=T),T=N.getResponseHeader("etag"),T&&(b.etag[o]=T)),204===e?(c=!0,C="nocontent"):304===e?(c=!0,C="notmodified"):(c=Fn(p,w),C=c.state,y=c.data,v=c.error,c=!v)):(v=C,(e||!C)&&(C="error",0>e&&(e=0))),N.status=e,N.statusText=(n||C)+"",c?h.resolveWith(f,[y,C,N]):h.rejectWith(f,[N,C,v]),N.statusCode(m),m=t,u&&d.trigger(c?"ajaxSuccess":"ajaxError",[N,p,c?y:v]),g.fireWith(f,[N,C]),u&&(d.trigger("ajaxComplete",[N,p]),--b.active||b.event.trigger("ajaxStop")))}return N},getScript:function(e,n){return b.get(e,t,n,"script")},getJSON:function(e,t,n){return b.get(e,t,n,"json")}});function _n(e,n,r){var i,o,a,s,u=e.contents,l=e.dataTypes,c=e.responseFields;for(s in c)s in r&&(n[c[s]]=r[s]);while("*"===l[0])l.shift(),o===t&&(o=e.mimeType||n.getResponseHeader("Content-Type"));if(o)for(s in u)if(u[s]&&u[s].test(o)){l.unshift(s);break}if(l[0]in r)a=l[0];else{for(s in r){if(!l[0]||e.converters[s+" "+l[0]]){a=s;break}i||(i=s)}a=a||i}return a?(a!==l[0]&&l.unshift(a),r[a]):t}function Fn(e,t){var n,r,i,o,a={},s=0,u=e.dataTypes.slice(),l=u[0];if(e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u[1])for(i in e.converters)a[i.toLowerCase()]=e.converters[i];for(;r=u[++s];)if("*"!==r){if("*"!==l&&l!==r){if(i=a[l+" "+r]||a["* "+r],!i)for(n in a)if(o=n.split(" "),o[1]===r&&(i=a[l+" "+o[0]]||a["* "+o[0]])){i===!0?i=a[n]:a[n]!==!0&&(r=o[0],u.splice(s--,0,r));break}if(i!==!0)if(i&&e["throws"])t=i(t);else try{t=i(t)}catch(c){return{state:"parsererror",error:i?c:"No conversion from "+l+" to "+r}}}l=r}return{state:"success",data:t}}b.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(e){return b.globalEval(e),e}}}),b.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),b.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=o.head||b("head")[0]||o.documentElement;return{send:function(t,i){n=o.createElement("script"),n.async=!0,e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,t){(t||!n.readyState||/loaded|complete/.test(n.readyState))&&(n.onload=n.onreadystatechange=null,n.parentNode&&n.parentNode.removeChild(n),n=null,t||i(200,"success"))},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(t,!0)}}}});var On=[],Bn=/(=)\?(?=&|$)|\?\?/;b.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=On.pop()||b.expando+"_"+vn++;return this[e]=!0,e}}),b.ajaxPrefilter("json jsonp",function(n,r,i){var o,a,s,u=n.jsonp!==!1&&(Bn.test(n.url)?"url":"string"==typeof n.data&&!(n.contentType||"").indexOf("application/x-www-form-urlencoded")&&Bn.test(n.data)&&"data");return u||"jsonp"===n.dataTypes[0]?(o=n.jsonpCallback=b.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,u?n[u]=n[u].replace(Bn,"$1"+o):n.jsonp!==!1&&(n.url+=(bn.test(n.url)?"&":"?")+n.jsonp+"="+o),n.converters["script json"]=function(){return s||b.error(o+" was not called"),s[0]},n.dataTypes[0]="json",a=e[o],e[o]=function(){s=arguments},i.always(function(){e[o]=a,n[o]&&(n.jsonpCallback=r.jsonpCallback,On.push(o)),s&&b.isFunction(a)&&a(s[0]),s=a=t}),"script"):t});var Pn,Rn,Wn=0,$n=e.ActiveXObject&&function(){var e;for(e in Pn)Pn[e](t,!0)};function In(){try{return new e.XMLHttpRequest}catch(t){}}function zn(){try{return new e.ActiveXObject("Microsoft.XMLHTTP")}catch(t){}}b.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&In()||zn()}:In,Rn=b.ajaxSettings.xhr(),b.support.cors=!!Rn&&"withCredentials"in Rn,Rn=b.support.ajax=!!Rn,Rn&&b.ajaxTransport(function(n){if(!n.crossDomain||b.support.cors){var r;return{send:function(i,o){var a,s,u=n.xhr();if(n.username?u.open(n.type,n.url,n.async,n.username,n.password):u.open(n.type,n.url,n.async),n.xhrFields)for(s in n.xhrFields)u[s]=n.xhrFields[s];n.mimeType&&u.overrideMimeType&&u.overrideMimeType(n.mimeType),n.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");try{for(s in i)u.setRequestHeader(s,i[s])}catch(l){}u.send(n.hasContent&&n.data||null),r=function(e,i){var s,l,c,p;try{if(r&&(i||4===u.readyState))if(r=t,a&&(u.onreadystatechange=b.noop,$n&&delete Pn[a]),i)4!==u.readyState&&u.abort();else{p={},s=u.status,l=u.getAllResponseHeaders(),"string"==typeof u.responseText&&(p.text=u.responseText);try{c=u.statusText}catch(f){c=""}s||!n.isLocal||n.crossDomain?1223===s&&(s=204):s=p.text?200:404}}catch(d){i||o(-1,d)}p&&o(s,c,p,l)},n.async?4===u.readyState?setTimeout(r):(a=++Wn,$n&&(Pn||(Pn={},b(e).unload($n)),Pn[a]=r),u.onreadystatechange=r):r()},abort:function(){r&&r(t,!0)}}}});var Xn,Un,Vn=/^(?:toggle|show|hide)$/,Yn=RegExp("^(?:([+-])=|)("+x+")([a-z%]*)$","i"),Jn=/queueHooks$/,Gn=[nr],Qn={"*":[function(e,t){var n,r,i=this.createTween(e,t),o=Yn.exec(t),a=i.cur(),s=+a||0,u=1,l=20;if(o){if(n=+o[2],r=o[3]||(b.cssNumber[e]?"":"px"),"px"!==r&&s){s=b.css(i.elem,e,!0)||n||1;do u=u||".5",s/=u,b.style(i.elem,e,s+r);while(u!==(u=i.cur()/a)&&1!==u&&--l)}i.unit=r,i.start=s,i.end=o[1]?s+(o[1]+1)*n:n}return i}]};function Kn(){return setTimeout(function(){Xn=t}),Xn=b.now()}function Zn(e,t){b.each(t,function(t,n){var r=(Qn[t]||[]).concat(Qn["*"]),i=0,o=r.length;for(;o>i;i++)if(r[i].call(e,t,n))return})}function er(e,t,n){var r,i,o=0,a=Gn.length,s=b.Deferred().always(function(){delete u.elem}),u=function(){if(i)return!1;var t=Xn||Kn(),n=Math.max(0,l.startTime+l.duration-t),r=n/l.duration||0,o=1-r,a=0,u=l.tweens.length;for(;u>a;a++)l.tweens[a].run(o);return s.notifyWith(e,[l,o,n]),1>o&&u?n:(s.resolveWith(e,[l]),!1)},l=s.promise({elem:e,props:b.extend({},t),opts:b.extend(!0,{specialEasing:{}},n),originalProperties:t,originalOptions:n,startTime:Xn||Kn(),duration:n.duration,tweens:[],createTween:function(t,n){var r=b.Tween(e,l.opts,t,n,l.opts.specialEasing[t]||l.opts.easing);return l.tweens.push(r),r},stop:function(t){var n=0,r=t?l.tweens.length:0;if(i)return this;for(i=!0;r>n;n++)l.tweens[n].run(1);return t?s.resolveWith(e,[l,t]):s.rejectWith(e,[l,t]),this}}),c=l.props;for(tr(c,l.opts.specialEasing);a>o;o++)if(r=Gn[o].call(l,e,c,l.opts))return r;return Zn(l,c),b.isFunction(l.opts.start)&&l.opts.start.call(e,l),b.fx.timer(b.extend(u,{elem:e,anim:l,queue:l.opts.queue})),l.progress(l.opts.progress).done(l.opts.done,l.opts.complete).fail(l.opts.fail).always(l.opts.always)}function tr(e,t){var n,r,i,o,a;for(i in e)if(r=b.camelCase(i),o=t[r],n=e[i],b.isArray(n)&&(o=n[1],n=e[i]=n[0]),i!==r&&(e[r]=n,delete e[i]),a=b.cssHooks[r],a&&"expand"in a){n=a.expand(n),delete e[r];for(i in n)i in e||(e[i]=n[i],t[i]=o)}else t[r]=o}b.Animation=b.extend(er,{tweener:function(e,t){b.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");var n,r=0,i=e.length;for(;i>r;r++)n=e[r],Qn[n]=Qn[n]||[],Qn[n].unshift(t)},prefilter:function(e,t){t?Gn.unshift(e):Gn.push(e)}});function nr(e,t,n){var r,i,o,a,s,u,l,c,p,f=this,d=e.style,h={},g=[],m=e.nodeType&&nn(e);n.queue||(c=b._queueHooks(e,"fx"),null==c.unqueued&&(c.unqueued=0,p=c.empty.fire,c.empty.fire=function(){c.unqueued||p()}),c.unqueued++,f.always(function(){f.always(function(){c.unqueued--,b.queue(e,"fx").length||c.empty.fire()})})),1===e.nodeType&&("height"in t||"width"in t)&&(n.overflow=[d.overflow,d.overflowX,d.overflowY],"inline"===b.css(e,"display")&&"none"===b.css(e,"float")&&(b.support.inlineBlockNeedsLayout&&"inline"!==un(e.nodeName)?d.zoom=1:d.display="inline-block")),n.overflow&&(d.overflow="hidden",b.support.shrinkWrapBlocks||f.always(function(){d.overflow=n.overflow[0],d.overflowX=n.overflow[1],d.overflowY=n.overflow[2]}));for(i in t)if(a=t[i],Vn.exec(a)){if(delete t[i],u=u||"toggle"===a,a===(m?"hide":"show"))continue;g.push(i)}if(o=g.length){s=b._data(e,"fxshow")||b._data(e,"fxshow",{}),"hidden"in s&&(m=s.hidden),u&&(s.hidden=!m),m?b(e).show():f.done(function(){b(e).hide()}),f.done(function(){var t;b._removeData(e,"fxshow");for(t in h)b.style(e,t,h[t])});for(i=0;o>i;i++)r=g[i],l=f.createTween(r,m?s[r]:0),h[r]=s[r]||b.style(e,r),r in s||(s[r]=l.start,m&&(l.end=l.start,l.start="width"===r||"height"===r?1:0))}}function rr(e,t,n,r,i){return new rr.prototype.init(e,t,n,r,i)}b.Tween=rr,rr.prototype={constructor:rr,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||"swing",this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(b.cssNumber[n]?"":"px")},cur:function(){var e=rr.propHooks[this.prop];return e&&e.get?e.get(this):rr.propHooks._default.get(this)},run:function(e){var t,n=rr.propHooks[this.prop];return this.pos=t=this.options.duration?b.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):rr.propHooks._default.set(this),this}},rr.prototype.init.prototype=rr.prototype,rr.propHooks={_default:{get:function(e){var t;return null==e.elem[e.prop]||e.elem.style&&null!=e.elem.style[e.prop]?(t=b.css(e.elem,e.prop,""),t&&"auto"!==t?t:0):e.elem[e.prop]},set:function(e){b.fx.step[e.prop]?b.fx.step[e.prop](e):e.elem.style&&(null!=e.elem.style[b.cssProps[e.prop]]||b.cssHooks[e.prop])?b.style(e.elem,e.prop,e.now+e.unit):e.elem[e.prop]=e.now}}},rr.propHooks.scrollTop=rr.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},b.each(["toggle","show","hide"],function(e,t){var n=b.fn[t];b.fn[t]=function(e,r,i){return null==e||"boolean"==typeof e?n.apply(this,arguments):this.animate(ir(t,!0),e,r,i)}}),b.fn.extend({fadeTo:function(e,t,n,r){return this.filter(nn).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(e,t,n,r){var i=b.isEmptyObject(e),o=b.speed(t,n,r),a=function(){var t=er(this,b.extend({},e),o);a.finish=function(){t.stop(!0)},(i||b._data(this,"finish"))&&t.stop(!0)};return a.finish=a,i||o.queue===!1?this.each(a):this.queue(o.queue,a)},stop:function(e,n,r){var i=function(e){var t=e.stop;delete e.stop,t(r)};return"string"!=typeof e&&(r=n,n=e,e=t),n&&e!==!1&&this.queue(e||"fx",[]),this.each(function(){var t=!0,n=null!=e&&e+"queueHooks",o=b.timers,a=b._data(this);if(n)a[n]&&a[n].stop&&i(a[n]);else for(n in a)a[n]&&a[n].stop&&Jn.test(n)&&i(a[n]);for(n=o.length;n--;)o[n].elem!==this||null!=e&&o[n].queue!==e||(o[n].anim.stop(r),t=!1,o.splice(n,1));(t||!r)&&b.dequeue(this,e)})},finish:function(e){return e!==!1&&(e=e||"fx"),this.each(function(){var t,n=b._data(this),r=n[e+"queue"],i=n[e+"queueHooks"],o=b.timers,a=r?r.length:0;for(n.finish=!0,b.queue(this,e,[]),i&&i.cur&&i.cur.finish&&i.cur.finish.call(this),t=o.length;t--;)o[t].elem===this&&o[t].queue===e&&(o[t].anim.stop(!0),o.splice(t,1));for(t=0;a>t;t++)r[t]&&r[t].finish&&r[t].finish.call(this);delete n.finish})}});function ir(e,t){var n,r={height:e},i=0;for(t=t?1:0;4>i;i+=2-t)n=Zt[i],r["margin"+n]=r["padding"+n]=e;return t&&(r.opacity=r.width=e),r}b.each({slideDown:ir("show"),slideUp:ir("hide"),slideToggle:ir("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,t){b.fn[e]=function(e,n,r){return this.animate(t,e,n,r)}}),b.speed=function(e,t,n){var r=e&&"object"==typeof e?b.extend({},e):{complete:n||!n&&t||b.isFunction(e)&&e,duration:e,easing:n&&t||t&&!b.isFunction(t)&&t};return r.duration=b.fx.off?0:"number"==typeof r.duration?r.duration:r.duration in b.fx.speeds?b.fx.speeds[r.duration]:b.fx.speeds._default,(null==r.queue||r.queue===!0)&&(r.queue="fx"),r.old=r.complete,r.complete=function(){b.isFunction(r.old)&&r.old.call(this),r.queue&&b.dequeue(this,r.queue)},r},b.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2}},b.timers=[],b.fx=rr.prototype.init,b.fx.tick=function(){var e,n=b.timers,r=0;for(Xn=b.now();n.length>r;r++)e=n[r],e()||n[r]!==e||n.splice(r--,1);n.length||b.fx.stop(),Xn=t},b.fx.timer=function(e){e()&&b.timers.push(e)&&b.fx.start()},b.fx.interval=13,b.fx.start=function(){Un||(Un=setInterval(b.fx.tick,b.fx.interval))},b.fx.stop=function(){clearInterval(Un),Un=null},b.fx.speeds={slow:600,fast:200,_default:400},b.fx.step={},b.expr&&b.expr.filters&&(b.expr.filters.animated=function(e){return b.grep(b.timers,function(t){return e===t.elem}).length}),b.fn.offset=function(e){if(arguments.length)return e===t?this:this.each(function(t){b.offset.setOffset(this,e,t)});var n,r,o={top:0,left:0},a=this[0],s=a&&a.ownerDocument;if(s)return n=s.documentElement,b.contains(n,a)?(typeof a.getBoundingClientRect!==i&&(o=a.getBoundingClientRect()),r=or(s),{top:o.top+(r.pageYOffset||n.scrollTop)-(n.clientTop||0),left:o.left+(r.pageXOffset||n.scrollLeft)-(n.clientLeft||0)}):o},b.offset={setOffset:function(e,t,n){var r=b.css(e,"position");"static"===r&&(e.style.position="relative");var i=b(e),o=i.offset(),a=b.css(e,"top"),s=b.css(e,"left"),u=("absolute"===r||"fixed"===r)&&b.inArray("auto",[a,s])>-1,l={},c={},p,f;u?(c=i.position(),p=c.top,f=c.left):(p=parseFloat(a)||0,f=parseFloat(s)||0),b.isFunction(t)&&(t=t.call(e,n,o)),null!=t.top&&(l.top=t.top-o.top+p),null!=t.left&&(l.left=t.left-o.left+f),"using"in t?t.using.call(e,l):i.css(l)}},b.fn.extend({position:function(){if(this[0]){var e,t,n={top:0,left:0},r=this[0];return"fixed"===b.css(r,"position")?t=r.getBoundingClientRect():(e=this.offsetParent(),t=this.offset(),b.nodeName(e[0],"html")||(n=e.offset()),n.top+=b.css(e[0],"borderTopWidth",!0),n.left+=b.css(e[0],"borderLeftWidth",!0)),{top:t.top-n.top-b.css(r,"marginTop",!0),left:t.left-n.left-b.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||o.documentElement;while(e&&!b.nodeName(e,"html")&&"static"===b.css(e,"position"))e=e.offsetParent;return e||o.documentElement})}}),b.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);b.fn[e]=function(i){return b.access(this,function(e,i,o){var a=or(e);return o===t?a?n in a?a[n]:a.document.documentElement[i]:e[i]:(a?a.scrollTo(r?b(a).scrollLeft():o,r?o:b(a).scrollTop()):e[i]=o,t)},e,i,arguments.length,null)}});function or(e){return b.isWindow(e)?e:9===e.nodeType?e.defaultView||e.parentWindow:!1}b.each({Height:"height",Width:"width"},function(e,n){b.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){b.fn[i]=function(i,o){var a=arguments.length&&(r||"boolean"!=typeof i),s=r||(i===!0||o===!0?"margin":"border");return b.access(this,function(n,r,i){var o;return b.isWindow(n)?n.document.documentElement["client"+e]:9===n.nodeType?(o=n.documentElement,Math.max(n.body["scroll"+e],o["scroll"+e],n.body["offset"+e],o["offset"+e],o["client"+e])):i===t?b.css(n,r,s):b.style(n,r,i,s)},n,a?i:t,a,null)}})}),e.jQuery=e.$=b,"function"==typeof define&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return b})})(window); \ No newline at end of file diff --git a/framework/yii/assets/yii.activeForm.js b/framework/yii/assets/yii.activeForm.js index 17ea8a7..2cb3b90 100644 --- a/framework/yii/assets/yii.activeForm.js +++ b/framework/yii/assets/yii.activeForm.js @@ -345,7 +345,7 @@ var $container = $form.find(attribute.container); var $error = $container.find(attribute.error); if (hasError) { - $error.html(messages[attribute.name][0]); + $error.text(messages[attribute.name][0]); $container.removeClass(data.settings.validatingCssClass + ' ' + data.settings.successCssClass) .addClass(data.settings.errorCssClass); } else { diff --git a/framework/yii/assets/yii.gridView.js b/framework/yii/assets/yii.gridView.js index b07ece2..a452c17 100644 --- a/framework/yii/assets/yii.gridView.js +++ b/framework/yii/assets/yii.gridView.js @@ -22,6 +22,8 @@ }; var defaults = { + filterUrl: undefined, + filterSelector: undefined }; var methods = { @@ -32,6 +34,32 @@ $e.data('yiiGridView', { settings: settings }); + + var enterPressed = false; + $(document).on('change.yiiGridView keydown.yiiGridView', settings.filterSelector, function (event) { + if (event.type === 'keydown') { + if (event.keyCode !== 13) { + return; // only react to enter key + } else { + enterPressed = true; + } + } else { + // prevent processing for both keydown and change events + if (enterPressed) { + enterPressed = false; + return; + } + } + var data = $(settings.filterSelector).serialize(); + var url = settings.filterUrl; + if (url.indexOf('?') >= 0) { + url += '&' + data; + } else { + url += '?' + data; + } + window.location.href = url; + return false; + }); }); }, @@ -74,5 +102,38 @@ return this.data('yiiGridView'); } }; + + var enterPressed = false; + + var filterChanged = function (event) { + if (event.type === 'keydown') { + if (event.keyCode !== 13) { + return; // only react to enter key + } else { + enterPressed = true; + } + } else { + // prevent processing for both keydown and change events + if (enterPressed) { + enterPressed = false; + return; + } + } + var data = $(settings.filterSelector).serialize(); + if (settings.pageVar !== undefined) { + data += '&' + settings.pageVar + '=1'; + } + if (settings.enableHistory && settings.ajaxUpdate !== false && window.History.enabled) { + // Ajaxify this link + var url = $('#' + id).yiiGridView('getUrl'), + params = $.deparam.querystring($.param.querystring(url, data)); + + delete params[settings.ajaxVar]; + window.History.pushState(null, document.title, decodeURIComponent($.param.querystring(url.substr(0, url.indexOf('?')), params))); + } else { + $('#' + id).yiiGridView('update', {data: data}); + } + return false; + }; })(window.jQuery); diff --git a/framework/yii/assets/yii.js b/framework/yii/assets/yii.js index add3a02..b9f2cdd 100644 --- a/framework/yii/assets/yii.js +++ b/framework/yii/assets/yii.js @@ -44,6 +44,11 @@ yii = (function ($) { var pub = { /** + * List of scripts that can be loaded multiple times via AJAX requests. Each script can be represented + * as either an absolute URL or a relative one. + */ + reloadableScripts: [], + /** * The selector for clickable elements that need to support confirmation and form submission. */ clickableSelector: 'a, button, input[type="submit"], input[type="button"], input[type="reset"], input[type="image"]', @@ -161,46 +166,77 @@ yii = (function ($) { }, init: function () { - var $document = $(document); + initCsrfHandler(); + initRedirectHandler(); + initScriptFilter(); + initDataMethods(); + } + }; - // automatically send CSRF token for all AJAX requests - $.ajaxPrefilter(function (options, originalOptions, xhr) { - if (!options.crossDomain && pub.getCsrfVar()) { - xhr.setRequestHeader('X-CSRF-Token', pub.getCsrfToken()); + function initRedirectHandler() { + // handle AJAX redirection + $(document).ajaxComplete(function (event, xhr, settings) { + var url = xhr.getResponseHeader('X-Redirect'); + if (url) { + window.location = url; + } + }); + } + + function initCsrfHandler() { + // automatically send CSRF token for all AJAX requests + $.ajaxPrefilter(function (options, originalOptions, xhr) { + if (!options.crossDomain && pub.getCsrfVar()) { + xhr.setRequestHeader('X-CSRF-Token', pub.getCsrfToken()); + } + }); + } + + function initDataMethods() { + var $document = $(document); + // handle data-confirm and data-method for clickable elements + $document.on('click.yii', pub.clickableSelector, function (event) { + var $this = $(this); + if (pub.allowAction($this)) { + return pub.handleAction($this); + } else { + event.stopImmediatePropagation(); + return false; + } + }); + + // handle data-confirm and data-method for changeable elements + $document.on('change.yii', pub.changeableSelector, function (event) { + var $this = $(this); + if (pub.allowAction($this)) { + return pub.handleAction($this); + } else { + event.stopImmediatePropagation(); + return false; + } + }); + } + + function initScriptFilter() { + var hostInfo = location.protocol + '//' + location.host; + var loadedScripts = $('script[src]').map(function () { + return this.src.charAt(0) === '/' ? hostInfo + this.src : this.src; + }).toArray(); + $.ajaxPrefilter('script', function (options, originalOptions, xhr) { + var url = options.url.charAt(0) === '/' ? hostInfo + options.url : options.url; + if ($.inArray(url, loadedScripts) === -1) { + loadedScripts.push(url); + } else { + var found = $.inArray(url, $.map(pub.reloadableScripts, function (script) { + return script.charAt(0) === '/' ? hostInfo + script : script; + })) !== -1; + if (!found) { + xhr.abort(); } - }); + } + }); + } - // handle AJAX redirection - $document.ajaxComplete(function (event, xhr, settings) { - var url = xhr.getResponseHeader('X-Redirect'); - if (url) { - window.location = url; - } - }); - - // handle data-confirm and data-method for clickable elements - $document.on('click.yii', pub.clickableSelector, function (event) { - var $this = $(this); - if (pub.allowAction($this)) { - return pub.handleAction($this); - } else { - event.stopImmediatePropagation(); - return false; - } - }); - - // handle data-confirm and data-method for changeable elements - $document.on('change.yii', pub.changeableSelector, function (event) { - var $this = $(this); - if (pub.allowAction($this)) { - return pub.handleAction($this); - } else { - event.stopImmediatePropagation(); - return false; - } - }); - } - }; return pub; })(jQuery); diff --git a/framework/yii/assets/yii.validation.js b/framework/yii/assets/yii.validation.js index 015040e..97074ac 100644 --- a/framework/yii/assets/yii.validation.js +++ b/framework/yii/assets/yii.validation.js @@ -16,6 +16,10 @@ yii.validation = (function ($) { || value === '' || trim && $.trim(value) === ''; }; + var addMessage = function (messages, message, value) { + messages.push(message.replace(/\{value\}/g, value)); + }; + return { required: function (value, messages, options) { var valid = false; @@ -28,7 +32,7 @@ yii.validation = (function ($) { } if (!valid) { - messages.push(options.message); + addMessage(messages, options.message, value); } }, @@ -40,7 +44,7 @@ yii.validation = (function ($) { || options.strict && (value === options.trueValue || value === options.falseValue); if (!valid) { - messages.push(options.message); + addMessage(messages, options.message, value); } }, @@ -50,18 +54,18 @@ yii.validation = (function ($) { } if (typeof value !== 'string') { - messages.push(options.message); + addMessage(messages, options.message, value); return; } if (options.min !== undefined && value.length < options.min) { - messages.push(options.tooShort); + addMessage(messages, options.tooShort, value); } if (options.max !== undefined && value.length > options.max) { - messages.push(options.tooLong); + addMessage(messages, options.tooLong, value); } if (options.is !== undefined && value.length != options.is) { - messages.push(options.is); + addMessage(messages, options.is, value); } }, @@ -71,15 +75,15 @@ yii.validation = (function ($) { } if (typeof value === 'string' && !value.match(options.pattern)) { - messages.push(options.message); + addMessage(messages, options.message, value); return; } if (options.min !== undefined && value < options.min) { - messages.push(options.tooSmall); + addMessage(messages, options.tooSmall, value); } if (options.max !== undefined && value > options.max) { - messages.push(options.tooBig); + addMessage(messages, options.tooBig, value); } }, @@ -91,7 +95,7 @@ yii.validation = (function ($) { || options.not && $.inArray(value, options.range) == -1; if (!valid) { - messages.push(options.message); + addMessage(messages, options.message, value); } }, @@ -101,7 +105,7 @@ yii.validation = (function ($) { } if (!options.not && !value.match(options.pattern) || options.not && value.match(options.pattern)) { - messages.push(options.message) + addMessage(messages, options.message, value); } }, @@ -113,17 +117,17 @@ yii.validation = (function ($) { var valid = true; if (options.enableIDN) { - var regexp = /^(.*)@(.*)$/, + var regexp = /^(.*<?)(.*)@(.*)(>?)$/, matches = regexp.exec(value); if (matches === null) { valid = false; } else { - value = punycode.toASCII(matches[1]) + '@' + punycode.toASCII(matches[2]); + value = matches[1] + punycode.toASCII(matches[2]) + '@' + punycode.toASCII(matches[3]) + matches[4]; } } - if (!valid || !(value.match(options.pattern) && (!options.allowName || value.match(options.fullPattern)))) { - messages.push(options.message); + if (!valid || !(value.match(options.pattern) || (options.allowName && value.match(options.fullPattern)))) { + addMessage(messages, options.message, value); } }, @@ -149,7 +153,7 @@ yii.validation = (function ($) { } if (!valid || !value.match(options.pattern)) { - messages.push(options.message); + addMessage(messages, options.message, value); } }, @@ -170,7 +174,7 @@ yii.validation = (function ($) { h += v.charCodeAt(i); } if (h != hash) { - messages.push(options.message); + addMessage(messages, options.message, value); } }, @@ -210,10 +214,13 @@ yii.validation = (function ($) { case '<=': valid = value <= compareValue; break; + default: + valid = false; + break; } if (!valid) { - messages.push(options.message); + addMessage(messages, options.message, value); } } }; diff --git a/framework/yii/base/Action.php b/framework/yii/base/Action.php index 2693003..56b1c45 100644 --- a/framework/yii/base/Action.php +++ b/framework/yii/base/Action.php @@ -25,7 +25,7 @@ use Yii; * public function run($id, $type = 'book') { ... } * ~~~ * - * And the parameters provided for the action are: `array('id' => 1)`. + * And the parameters provided for the action are: `['id' => 1]`. * Then the `run()` method will be invoked as `run(1)` automatically. * * @property string $uniqueId The unique ID of this action among the whole application. This property is @@ -51,7 +51,7 @@ class Action extends Component * @param Controller $controller the controller that owns this action * @param array $config name-value pairs that will be used to initialize the object properties */ - public function __construct($id, $controller, $config = array()) + public function __construct($id, $controller, $config = []) { $this->id = $id; $this->controller = $controller; @@ -84,6 +84,6 @@ class Action extends Component if (Yii::$app->requestedParams === null) { Yii::$app->requestedParams = $args; } - return call_user_func_array(array($this, 'run'), $args); + return call_user_func_array([$this, 'run'], $args); } } diff --git a/framework/yii/base/ActionEvent.php b/framework/yii/base/ActionEvent.php index 9b6c2f0..6e123a0 100644 --- a/framework/yii/base/ActionEvent.php +++ b/framework/yii/base/ActionEvent.php @@ -37,7 +37,7 @@ class ActionEvent extends Event * @param Action $action the action associated with this action event. * @param array $config name-value pairs that will be used to initialize the object properties */ - public function __construct($action, $config = array()) + public function __construct($action, $config = []) { $this->action = $action; parent::__construct($config); diff --git a/framework/yii/base/ActionFilter.php b/framework/yii/base/ActionFilter.php index bc3b60c..648211c 100644 --- a/framework/yii/base/ActionFilter.php +++ b/framework/yii/base/ActionFilter.php @@ -8,6 +8,11 @@ namespace yii\base; /** + * ActionFilter provides a base implementation for action filters that can be added to a controller + * to handle the `beforeAction` event. + * + * Check implementation of [[AccessControl]], [[PageCache]] and [[HttpCache]] as examples on how to use it. + * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ @@ -24,7 +29,7 @@ class ActionFilter extends Behavior * @var array list of action IDs that this filter should not apply to. * @see only */ - public $except = array(); + public $except = []; /** * Declares event handlers for the [[owner]]'s events. @@ -32,10 +37,10 @@ class ActionFilter extends Behavior */ public function events() { - return array( + return [ Controller::EVENT_BEFORE_ACTION => 'beforeFilter', Controller::EVENT_AFTER_ACTION => 'afterFilter', - ); + ]; } /** diff --git a/framework/yii/base/Application.php b/framework/yii/base/Application.php index e8c496c..dc363f1 100644 --- a/framework/yii/base/Application.php +++ b/framework/yii/base/Application.php @@ -15,12 +15,13 @@ use yii\web\HttpException; * Application is the base class for all application classes. * * @property \yii\rbac\Manager $authManager The auth manager for this application. This property is read-only. + * @property string $basePath The root directory of the application. * @property \yii\caching\Cache $cache The cache application component. Null if the component is not enabled. * This property is read-only. * @property \yii\db\Connection $db The database connection. This property is read-only. * @property ErrorHandler $errorHandler The error handler application component. This property is read-only. * @property \yii\base\Formatter $formatter The formatter application component. This property is read-only. - * @property \yii\i18n\I18N $i18N The internationalization component. This property is read-only. + * @property \yii\i18n\I18N $i18n The internationalization component. This property is read-only. * @property \yii\log\Logger $log The log component. This property is read-only. * @property \yii\web\Request|\yii\console\Request $request The request component. This property is read-only. * @property string $runtimePath The directory that stores runtime files. Defaults to the "runtime" @@ -30,7 +31,8 @@ use yii\web\HttpException; * @property \yii\web\UrlManager $urlManager The URL manager for this application. This property is read-only. * @property string $vendorPath The directory that stores vendor files. Defaults to "vendor" directory under * [[basePath]]. - * @property View $view The view object that is used to render various view files. This property is read-only. + * @property View|\yii\web\View $view The view object that is used to render various view files. This property + * is read-only. * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 @@ -54,6 +56,13 @@ abstract class Application extends Module * @event ActionEvent an event raised after executing a controller action. */ const EVENT_AFTER_ACTION = 'afterAction'; + + /** + * @var string the namespace that controller classes are in. If not set, + * it will use the "app\controllers" namespace. + */ + public $controllerNamespace = 'app\\controllers'; + /** * @var string the application name. */ @@ -70,17 +79,13 @@ abstract class Application extends Module * @var string the language that is meant to be used for end users. * @see sourceLanguage */ - public $language = 'en_US'; + public $language = 'en-US'; /** * @var string the language that the application is written in. This mainly refers to * the language that the messages and view files are written in. * @see language */ - public $sourceLanguage = 'en_US'; - /** - * @var array IDs of the components that need to be loaded when the application starts. - */ - public $preload = array(); + public $sourceLanguage = 'en-US'; /** * @var Controller the currently active controller instance */ @@ -109,6 +114,19 @@ abstract class Application extends Module * @var array the parameters supplied to the requested action. */ public $requestedParams; + /** + * @var array list of installed Yii extensions. Each array element represents a single extension + * with the following structure: + * + * ~~~ + * [ + * 'name' => 'extension name', + * 'version' => 'version number', + * 'bootstrap' => 'BootstrapClassName', + * ] + * ~~~ + */ + public $extensions = []; /** * @var string Used to reserve memory for fatal error handler. @@ -121,21 +139,11 @@ abstract class Application extends Module * Note that the configuration must contain both [[id]] and [[basePath]]. * @throws InvalidConfigException if either [[id]] or [[basePath]] configuration is missing. */ - public function __construct($config = array()) + public function __construct($config = []) { Yii::$app = $this; - if (!isset($config['id'])) { - throw new InvalidConfigException('The "id" configuration is required.'); - } - if (isset($config['basePath'])) { - $this->setBasePath($config['basePath']); - unset($config['basePath']); - } else { - throw new InvalidConfigException('The "basePath" configuration is required.'); - } $this->preInit($config); - $this->registerErrorHandlers(); $this->registerCoreComponents(); @@ -145,10 +153,23 @@ abstract class Application extends Module /** * Pre-initializes the application. * This method is called at the beginning of the application constructor. + * It initializes several important application properties. + * If you override this method, please make sure you call the parent implementation. * @param array $config the application configuration + * @throws InvalidConfigException if either [[id]] or [[basePath]] configuration is missing. */ public function preInit(&$config) { + if (!isset($config['id'])) { + throw new InvalidConfigException('The "id" configuration is required.'); + } + if (isset($config['basePath'])) { + $this->setBasePath($config['basePath']); + unset($config['basePath']); + } else { + throw new InvalidConfigException('The "basePath" configuration is required.'); + } + if (isset($config['vendorPath'])) { $this->setVendorPath($config['vendorPath']); unset($config['vendorPath']); @@ -163,6 +184,7 @@ abstract class Application extends Module // set "@runtime" $this->getRuntimePath(); } + if (isset($config['timeZone'])) { $this->setTimeZone($config['timeZone']); unset($config['timeZone']); @@ -172,6 +194,36 @@ abstract class Application extends Module } /** + * @inheritdoc + */ + public function init() + { + parent::init(); + $this->initExtensions($this->extensions); + } + + /** + * Initializes the extensions. + * @param array $extensions the extensions to be initialized. Please refer to [[extensions]] + * for the structure of the extension array. + */ + protected function initExtensions($extensions) + { + foreach ($extensions as $extension) { + if (!empty($extension['alias'])) { + foreach ($extension['alias'] as $name => $path) { + Yii::setAlias($name, $path); + } + } + if (isset($extension['bootstrap'])) { + /** @var Extension $class */ + $class = $extension['bootstrap']; + $class::init(); + } + } + } + + /** * Loads components that are declared in [[preload]]. * @throws InvalidConfigException if a component or module to be preloaded is unknown */ @@ -188,12 +240,12 @@ abstract class Application extends Module { if (YII_ENABLE_ERROR_HANDLER) { ini_set('display_errors', 0); - set_exception_handler(array($this, 'handleException')); - set_error_handler(array($this, 'handleError'), error_reporting()); + set_exception_handler([$this, 'handleException']); + set_error_handler([$this, 'handleError'], error_reporting()); if ($this->memoryReserveSize > 0) { $this->_memoryReserve = str_repeat('x', $this->memoryReserveSize); } - register_shutdown_function(array($this, 'handleFatalError')); + register_shutdown_function([$this, 'handleFatalError']); } } @@ -208,6 +260,19 @@ abstract class Application extends Module } /** + * Sets the root directory of the application and the @app alias. + * This method can only be invoked at the beginning of the constructor. + * @param string $path the root directory of the application. + * @property string the root directory of the application. + * @throws InvalidParamException if the directory does not exist. + */ + public function setBasePath($path) + { + parent::setBasePath($path); + Yii::setAlias('@app', $this->getBasePath()); + } + + /** * Runs the application. * This is the main entrance of an application. * @return integer the exit status (0 means normal, non-zero values mean abnormal) @@ -363,7 +428,7 @@ abstract class Application extends Module /** * Returns the view object. - * @return View the view object that is used to render various view files. + * @return View|\yii\web\View the view object that is used to render various view files. */ public function getView() { @@ -383,7 +448,7 @@ abstract class Application extends Module * Returns the internationalization (i18n) component * @return \yii\i18n\I18N the internationalization component */ - public function getI18N() + public function getI18n() { return $this->getComponent('i18n'); } @@ -403,26 +468,14 @@ abstract class Application extends Module */ public function registerCoreComponents() { - $this->setComponents(array( - 'log' => array( - 'class' => 'yii\log\Logger', - ), - 'errorHandler' => array( - 'class' => 'yii\base\ErrorHandler', - ), - 'formatter' => array( - 'class' => 'yii\base\Formatter', - ), - 'i18n' => array( - 'class' => 'yii\i18n\I18N', - ), - 'urlManager' => array( - 'class' => 'yii\web\UrlManager', - ), - 'view' => array( - 'class' => 'yii\base\View', - ), - )); + $this->setComponents([ + 'log' => ['class' => 'yii\log\Logger'], + 'errorHandler' => ['class' => 'yii\base\ErrorHandler'], + 'formatter' => ['class' => 'yii\base\Formatter'], + 'i18n' => ['class' => 'yii\i18n\I18N'], + 'urlManager' => ['class' => 'yii\web\UrlManager'], + 'view' => ['class' => 'yii\web\View'], + ]); } /** @@ -450,7 +503,11 @@ abstract class Application extends Module $msg .= "\nPrevious exception:\n"; $msg .= (string)$exception; if (YII_DEBUG) { - echo $msg; + if (PHP_SAPI === 'cli') { + echo $msg . "\n"; + } else { + echo '<pre>' . htmlspecialchars($msg, ENT_QUOTES, $this->charset) . '</pre>'; + } } $msg .= "\n\$_SERVER = " . var_export($_SERVER, true); error_log($msg); @@ -560,10 +617,8 @@ abstract class Application extends Module { $category = get_class($exception); if ($exception instanceof HttpException) { - /** @var $exception HttpException */ $category .= '\\' . $exception->statusCode; } elseif ($exception instanceof \ErrorException) { - /** @var $exception \ErrorException */ $category .= '\\' . $exception->getSeverity(); } Yii::error((string)$exception, $category); diff --git a/framework/yii/base/Behavior.php b/framework/yii/base/Behavior.php index abe08bb..1443e06 100644 --- a/framework/yii/base/Behavior.php +++ b/framework/yii/base/Behavior.php @@ -37,25 +37,25 @@ class Behavior extends \yii\base\Object * * The callbacks can be any of the followings: * - * - method in this behavior: `'handleClick'`, equivalent to `array($this, 'handleClick')` - * - object method: `array($object, 'handleClick')` - * - static method: `array('Page', 'handleClick')` + * - method in this behavior: `'handleClick'`, equivalent to `[$this, 'handleClick']` + * - object method: `[$object, 'handleClick']` + * - static method: `['Page', 'handleClick']` * - anonymous function: `function($event) { ... }` * * The following is an example: * * ~~~ - * array( + * [ * 'beforeValidate' => 'myBeforeValidate', * 'afterValidate' => 'myAfterValidate', - * ) + * ] * ~~~ * * @return array events (array keys) and the corresponding event handler methods (array values). */ public function events() { - return array(); + return []; } /** @@ -69,7 +69,7 @@ class Behavior extends \yii\base\Object { $this->owner = $owner; foreach ($this->events() as $event => $handler) { - $owner->on($event, is_string($handler) ? array($this, $handler) : $handler); + $owner->on($event, is_string($handler) ? [$this, $handler] : $handler); } } @@ -83,7 +83,7 @@ class Behavior extends \yii\base\Object { if ($this->owner) { foreach ($this->events() as $event => $handler) { - $this->owner->off($event, is_string($handler) ? array($this, $handler) : $handler); + $this->owner->off($event, is_string($handler) ? [$this, $handler] : $handler); } $this->owner = null; } diff --git a/framework/yii/base/Component.php b/framework/yii/base/Component.php index 2ad2c94..9af22a8 100644 --- a/framework/yii/base/Component.php +++ b/framework/yii/base/Component.php @@ -10,6 +10,8 @@ namespace yii\base; use Yii; /** + * Component is the base class that implements the *property*, *event* and *behavior* features. + * * @include @yii/base/Component.md * * @property Behavior[] $behaviors List of behaviors attached to this component. This property is read-only. @@ -41,7 +43,7 @@ class Component extends Object * @return mixed the property value or the value of a behavior's property * @throws UnknownPropertyException if the property is not defined * @throws InvalidCallException if the property is write-only. - * @see __set + * @see __set() */ public function __get($name) { @@ -80,7 +82,7 @@ class Component extends Object * @param mixed $value the property value * @throws UnknownPropertyException if the property is not defined * @throws InvalidCallException if the property is read-only. - * @see __get + * @see __get() */ public function __set($name, $value) { @@ -195,7 +197,7 @@ class Component extends Object $this->ensureBehaviors(); foreach ($this->_behaviors as $object) { if ($object->hasMethod($name)) { - return call_user_func_array(array($object, $name), $params); + return call_user_func_array([$object, $name], $params); } } @@ -225,8 +227,8 @@ class Component extends Object * @param boolean $checkVars whether to treat member variables as properties * @param boolean $checkBehaviors whether to treat behaviors' properties as properties of this component * @return boolean whether the property is defined - * @see canGetProperty - * @see canSetProperty + * @see canGetProperty() + * @see canSetProperty() */ public function hasProperty($name, $checkVars = true, $checkBehaviors = true) { @@ -246,7 +248,7 @@ class Component extends Object * @param boolean $checkVars whether to treat member variables as properties * @param boolean $checkBehaviors whether to treat behaviors' properties as properties of this component * @return boolean whether the property can be read - * @see canSetProperty + * @see canSetProperty() */ public function canGetProperty($name, $checkVars = true, $checkBehaviors = true) { @@ -276,7 +278,7 @@ class Component extends Object * @param boolean $checkVars whether to treat member variables as properties * @param boolean $checkBehaviors whether to treat behaviors' properties as properties of this component * @return boolean whether the property can be written - * @see canGetProperty + * @see canGetProperty() */ public function canSetProperty($name, $checkVars = true, $checkBehaviors = true) { @@ -329,11 +331,11 @@ class Component extends Object * the behavior class or an array of the following structure: * * ~~~ - * 'behaviorName' => array( + * 'behaviorName' => [ * 'class' => 'BehaviorClass', * 'property1' => 'value1', * 'property2' => 'value2', - * ) + * ] * ~~~ * * Note that a behavior class must extend from [[Behavior]]. Behavior names can be strings @@ -347,7 +349,7 @@ class Component extends Object */ public function behaviors() { - return array(); + return []; } /** @@ -358,23 +360,23 @@ class Component extends Object public function hasEventHandlers($name) { $this->ensureBehaviors(); - return !empty($this->_events[$name]); + return !empty($this->_events[$name]) || Event::hasHandlers($this, $name); } /** * Attaches an event handler to an event. * - * An event handler must be a valid PHP callback. The followings are + * The event handler must be a valid PHP callback. The followings are * some examples: * * ~~~ * function ($event) { ... } // anonymous function - * array($object, 'handleClick') // $object->handleClick() - * array('Page', 'handleClick') // Page::handleClick() - * 'handleClick' // global function handleClick() + * [$object, 'handleClick'] // $object->handleClick() + * ['Page', 'handleClick'] // Page::handleClick() + * 'handleClick' // global function handleClick() * ~~~ * - * An event handler must be defined with the following signature, + * The event handler must be defined with the following signature, * * ~~~ * function ($event) @@ -391,7 +393,7 @@ class Component extends Object public function on($name, $handler, $data = null) { $this->ensureBehaviors(); - $this->_events[$name][] = array($handler, $data); + $this->_events[$name][] = [$handler, $data]; } /** @@ -406,24 +408,25 @@ class Component extends Object public function off($name, $handler = null) { $this->ensureBehaviors(); - if (isset($this->_events[$name])) { - if ($handler === null) { - $this->_events[$name] = array(); - } else { - $removed = false; - foreach ($this->_events[$name] as $i => $event) { - if ($event[0] === $handler) { - unset($this->_events[$name][$i]); - $removed = true; - } - } - if ($removed) { - $this->_events[$name] = array_values($this->_events[$name]); + if (empty($this->_events[$name])) { + return false; + } + if ($handler === null) { + unset($this->_events[$name]); + return true; + } else { + $removed = false; + foreach ($this->_events[$name] as $i => $event) { + if ($event[0] === $handler) { + unset($this->_events[$name][$i]); + $removed = true; } - return $removed; } + if ($removed) { + $this->_events[$name] = array_values($this->_events[$name]); + } + return $removed; } - return false; } /** @@ -433,7 +436,7 @@ class Component extends Object * @param string $name the event name * @param Event $event the event parameter. If not set, a default [[Event]] object will be created. */ - public function trigger($name, $event = null) + public function trigger($name, Event $event = null) { $this->ensureBehaviors(); if (!empty($this->_events[$name])) { @@ -449,11 +452,12 @@ class Component extends Object $event->data = $handler[1]; call_user_func($handler[0], $event); // stop further handling if the event is handled - if ($event instanceof Event && $event->handled) { + if ($event->handled) { return; } } } + Event::trigger($this, $name, $event); } /** @@ -490,7 +494,7 @@ class Component extends Object * - an object configuration array that will be passed to [[Yii::createObject()]] to create the behavior object. * * @return Behavior the behavior object - * @see detachBehavior + * @see detachBehavior() */ public function attachBehavior($name, $behavior) { @@ -503,7 +507,7 @@ class Component extends Object * Each behavior is indexed by its name and should be a [[Behavior]] object, * a string specifying the behavior class, or an configuration array for creating the behavior. * @param array $behaviors list of behaviors to be attached to the component - * @see attachBehavior + * @see attachBehavior() */ public function attachBehaviors($behaviors) { @@ -543,7 +547,7 @@ class Component extends Object $this->detachBehavior($name); } } - $this->_behaviors = array(); + $this->_behaviors = []; } /** @@ -552,7 +556,7 @@ class Component extends Object public function ensureBehaviors() { if ($this->_behaviors === null) { - $this->_behaviors = array(); + $this->_behaviors = []; foreach ($this->behaviors() as $name => $behavior) { $this->attachBehaviorInternal($name, $behavior); } diff --git a/framework/yii/base/Controller.php b/framework/yii/base/Controller.php index 3eebaa0..c8f2d48 100644 --- a/framework/yii/base/Controller.php +++ b/framework/yii/base/Controller.php @@ -25,7 +25,7 @@ use Yii; * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ -class Controller extends Component +class Controller extends Component implements ViewContextInterface { /** * @event ActionEvent an event raised right before executing a controller action. @@ -72,7 +72,7 @@ class Controller extends Component * @param Module $module the module that this controller belongs to. * @param array $config name-value pairs that will be used to initialize the object properties */ - public function __construct($id, $module, $config = array()) + public function __construct($id, $module, $config = []) { $this->id = $id; $this->module = $module; @@ -86,14 +86,14 @@ class Controller extends Component * action class names or action configuration arrays. For example, * * ~~~ - * return array( + * return [ * 'action1' => '@app/components/Action1', - * 'action2' => array( + * 'action2' => [ * 'class' => '@app/components/Action2', * 'property1' => 'value1', * 'property2' => 'value2', - * ), - * ); + * ], + * ]; * ~~~ * * [[\Yii::createObject()]] will be used later to create the requested action @@ -101,7 +101,7 @@ class Controller extends Component */ public function actions() { - return array(); + return []; } /** @@ -111,9 +111,9 @@ class Controller extends Component * @param array $params the parameters (name-value pairs) to be passed to the action. * @return mixed the result of the action * @throws InvalidRouteException if the requested action ID cannot be resolved into an action successfully. - * @see createAction + * @see createAction() */ - public function runAction($id, $params = array()) + public function runAction($id, $params = []) { $action = $this->createAction($id); if ($action !== null) { @@ -149,10 +149,9 @@ class Controller extends Component * @param string $route the route to be handled, e.g., 'view', 'comment/view', '/admin/comment/view'. * @param array $params the parameters to be passed to the action. * @return mixed the result of the action - * @see runAction - * @see forward + * @see runAction() */ - public function run($route, $params = array()) + public function run($route, $params = []) { $pos = strpos($route, '/'); if ($pos === false) { @@ -173,7 +172,7 @@ class Controller extends Component */ public function bindActionParams($action, $params) { - return array(); + return []; } /** @@ -244,7 +243,7 @@ class Controller extends Component */ public function getActionParams() { - return array(); + return []; } /** @@ -303,13 +302,12 @@ class Controller extends Component * @return string the rendering result. * @throws InvalidParamException if the view file or the layout file does not exist. */ - public function render($view, $params = array()) + public function render($view, $params = []) { - $viewFile = $this->findViewFile($view); - $output = $this->getView()->renderFile($viewFile, $params, $this); + $output = $this->getView()->render($view, $params, $this); $layoutFile = $this->findLayoutFile(); if ($layoutFile !== false) { - return $this->getView()->renderFile($layoutFile, array('content' => $output), $this); + return $this->getView()->renderFile($layoutFile, ['content' => $output], $this); } else { return $output; } @@ -323,10 +321,9 @@ class Controller extends Component * @return string the rendering result. * @throws InvalidParamException if the view file does not exist. */ - public function renderPartial($view, $params = array()) + public function renderPartial($view, $params = []) { - $viewFile = $this->findViewFile($view); - return $this->getView()->renderFile($viewFile, $params, $this); + return $this->getView()->render($view, $params, $this); } /** @@ -336,7 +333,7 @@ class Controller extends Component * @return string the rendering result. * @throws InvalidParamException if the view file does not exist. */ - public function renderFile($file, $params = array()) + public function renderFile($file, $params = []) { return $this->getView()->renderFile($file, $params, $this); } @@ -382,22 +379,9 @@ class Controller extends Component * on how to specify this parameter. * @return string the view file path. Note that the file may not exist. */ - protected function findViewFile($view) + public function findViewFile($view) { - if (strncmp($view, '@', 1) === 0) { - // e.g. "@app/views/main" - $file = Yii::getAlias($view); - } elseif (strncmp($view, '//', 2) === 0) { - // e.g. "//layouts/main" - $file = Yii::$app->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/'); - } elseif (strncmp($view, '/', 1) === 0) { - // e.g. "/site/index" - $file = $this->module->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/'); - } else { - $file = $this->getViewPath() . DIRECTORY_SEPARATOR . $view; - } - - return pathinfo($file, PATHINFO_EXTENSION) === '' ? $file . '.php' : $file; + return $this->getViewPath() . DIRECTORY_SEPARATOR . $view; } /** diff --git a/framework/yii/base/ErrorException.php b/framework/yii/base/ErrorException.php index 8e1977a..eed6908 100644 --- a/framework/yii/base/ErrorException.php +++ b/framework/yii/base/ErrorException.php @@ -51,7 +51,7 @@ class ErrorException extends Exception } // XDebug has a different key name - $frame['args'] = array(); + $frame['args'] = []; if (isset($frame['params']) && !isset($frame['args'])) { $frame['args'] = $frame['params']; } @@ -81,7 +81,7 @@ class ErrorException extends Exception */ public static function isFatalError($error) { - return isset($error['type']) && in_array($error['type'], array(E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING)); + return isset($error['type']) && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING]); } /** @@ -89,7 +89,7 @@ class ErrorException extends Exception */ public function getName() { - $names = array( + $names = [ E_ERROR => Yii::t('yii', 'Fatal Error'), E_PARSE => Yii::t('yii', 'Parse Error'), E_CORE_ERROR => Yii::t('yii', 'Core Error'), @@ -103,7 +103,7 @@ class ErrorException extends Exception E_NOTICE => Yii::t('yii', 'Notice'), E_RECOVERABLE_ERROR => Yii::t('yii', 'Recoverable Error'), E_DEPRECATED => Yii::t('yii', 'Deprecated'), - ); + ]; return isset($names[$this->getCode()]) ? $names[$this->getCode()] : Yii::t('yii', 'Error'); } } diff --git a/framework/yii/base/ErrorHandler.php b/framework/yii/base/ErrorHandler.php index 40f5c37..c96ca5e 100644 --- a/framework/yii/base/ErrorHandler.php +++ b/framework/yii/base/ErrorHandler.php @@ -16,6 +16,9 @@ use yii\web\HttpException; * ErrorHandler displays these errors using appropriate views based on the * nature of the errors and the mode the application runs at. * + * ErrorHandler is configured as an application component in [[yii\base\Application]] by default. + * You can access that instance via `Yii::$app->errorHandler`. + * * @author Qiang Xue <qiang.xue@gmail.com> * @author Timur Ruziev <resurtm@gmail.com> * @since 2.0 @@ -112,19 +115,19 @@ class ErrorHandler extends Component ini_set('display_errors', 1); } $file = $useErrorView ? $this->errorView : $this->exceptionView; - $response->data = $this->renderFile($file, array( + $response->data = $this->renderFile($file, [ 'exception' => $exception, - )); + ]); } } elseif ($exception instanceof Arrayable) { $response->data = $exception; } else { - $response->data = array( + $response->data = [ 'type' => get_class($exception), 'name' => 'Exception', 'message' => $exception->getMessage(), 'code' => $exception->getCode(), - ); + ]; } if ($exception instanceof HttpException) { @@ -175,10 +178,9 @@ class ErrorHandler extends Component $html = rtrim($html, '\\'); } elseif (strpos($code, '()') !== false) { // method/function call - $self = $this; - $html = preg_replace_callback('/^(.*)\(\)$/', function ($matches) use ($self) { - return '<a href="http://yiiframework.com/doc/api/2.0/' . $self->htmlEncode($matches[1]) . '" target="_blank">' . - $self->htmlEncode($matches[1]) . '</a>()'; + $html = preg_replace_callback('/^(.*)\(\)$/', function ($matches) { + return '<a href="http://yiiframework.com/doc/api/2.0/' . $this->htmlEncode($matches[1]) . '" target="_blank">' . + $this->htmlEncode($matches[1]) . '</a>()'; }, $code); } return $html; @@ -213,9 +215,7 @@ class ErrorHandler extends Component public function renderPreviousExceptions($exception) { if (($previous = $exception->getPrevious()) !== null) { - return $this->renderFile($this->previousExceptionView, array( - 'exception' => $previous, - )); + return $this->renderFile($this->previousExceptionView, ['exception' => $previous]); } else { return ''; } @@ -232,7 +232,7 @@ class ErrorHandler extends Component */ public function renderCallStackItem($file, $line, $class, $method, $index) { - $lines = array(); + $lines = []; $begin = $end = 0; if ($file !== null && $line !== null) { $line--; // adjust line number from one-based to zero-based @@ -246,7 +246,7 @@ class ErrorHandler extends Component $end = $line + $half < $lineCount ? $line + $half : $lineCount - 1; } - return $this->renderFile($this->callStackItemView, array( + return $this->renderFile($this->callStackItemView, [ 'file' => $file, 'line' => $line, 'class' => $class, @@ -255,7 +255,7 @@ class ErrorHandler extends Component 'lines' => $lines, 'begin' => $begin, 'end' => $end, - )); + ]); } /** @@ -265,7 +265,7 @@ class ErrorHandler extends Component public function renderRequest() { $request = ''; - foreach (array('_GET', '_POST', '_SERVER', '_FILES', '_COOKIE', '_SESSION', '_ENV') as $name) { + foreach (['_GET', '_POST', '_SERVER', '_FILES', '_COOKIE', '_SESSION', '_ENV'] as $name) { if (!empty($GLOBALS[$name])) { $request .= '$' . $name . ' = ' . var_export($GLOBALS[$name], true) . ";\n\n"; } @@ -301,14 +301,14 @@ class ErrorHandler extends Component */ public function createServerInformationLink() { - static $serverUrls = array( - 'http://httpd.apache.org/' => array('apache'), - 'http://nginx.org/' => array('nginx'), - 'http://lighttpd.net/' => array('lighttpd'), - 'http://gwan.com/' => array('g-wan', 'gwan'), - 'http://iis.net/' => array('iis', 'services'), - 'http://php.net/manual/en/features.commandline.webserver.php' => array('development'), - ); + static $serverUrls = [ + 'http://httpd.apache.org/' => ['apache'], + 'http://nginx.org/' => ['nginx'], + 'http://lighttpd.net/' => ['lighttpd'], + 'http://gwan.com/' => ['g-wan', 'gwan'], + 'http://iis.net/' => ['iis', 'services'], + 'http://php.net/manual/en/features.commandline.webserver.php' => ['development'], + ]; if (isset($_SERVER['SERVER_SOFTWARE'])) { foreach ($serverUrls as $url => $keywords) { foreach ($keywords as $keyword) { diff --git a/framework/yii/base/Event.php b/framework/yii/base/Event.php index 5d40736..a678c89 100644 --- a/framework/yii/base/Event.php +++ b/framework/yii/base/Event.php @@ -45,4 +45,136 @@ class Event extends Object * Note that this varies according to which event handler is currently executing. */ public $data; + + private static $_events = []; + + /** + * Attaches an event handler to a class-level event. + * + * When a class-level event is triggered, event handlers attached + * to that class and all parent classes will be invoked. + * + * For example, the following code attaches an event handler to `ActiveRecord`'s + * `afterInsert` event: + * + * ~~~ + * Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function ($event) { + * Yii::trace(get_class($event->sender) . ' is inserted.'); + * }); + * ~~~ + * + * The handler will be invoked for EVERY successful ActiveRecord insertion. + * + * For more details about how to declare an event handler, please refer to [[Component::on()]]. + * + * @param string $class the fully qualified class name to which the event handler needs to attach + * @param string $name the event name + * @param callback $handler the event handler + * @param mixed $data the data to be passed to the event handler when the event is triggered. + * When the event handler is invoked, this data can be accessed via [[Event::data]]. + * @see off() + */ + public static function on($class, $name, $handler, $data = null) + { + self::$_events[$name][ltrim($class, '\\')][] = [$handler, $data]; + } + + /** + * Detaches an event handler from a class-level event. + * + * This method is the opposite of [[on()]]. + * + * @param string $class the fully qualified class name from which the event handler needs to be detached + * @param string $name the event name + * @param callback $handler the event handler to be removed. + * If it is null, all handlers attached to the named event will be removed. + * @return boolean if a handler is found and detached + * @see on() + */ + public static function off($class, $name, $handler = null) + { + $class = ltrim($class, '\\'); + if (empty(self::$_events[$name][$class])) { + return false; + } + if ($handler === null) { + unset(self::$_events[$name][$class]); + return true; + } else { + $removed = false; + foreach (self::$_events[$name][$class] as $i => $event) { + if ($event[0] === $handler) { + unset(self::$_events[$name][$class][$i]); + $removed = true; + } + } + if ($removed) { + self::$_events[$name][$class] = array_values(self::$_events[$name][$class]); + } + return $removed; + } + } + + /** + * Returns a value indicating whether there is any handler attached to the specified class-level event. + * Note that this method will also check all parent classes to see if there is any handler attached + * to the named event. + * @param string|object $class the object or the fully qualified class name specifying the class-level event + * @param string $name the event name + * @return boolean whether there is any handler attached to the event. + */ + public static function hasHandlers($class, $name) + { + if (empty(self::$_events[$name])) { + return false; + } + if (is_object($class)) { + $class = get_class($class); + } else { + $class = ltrim($class, '\\'); + } + do { + if (!empty(self::$_events[$name][$class])) { + return true; + } + } while (($class = get_parent_class($class)) !== false); + return false; + } + + /** + * Triggers a class-level event. + * This method will cause invocation of event handlers that are attached to the named event + * for the specified class and all its parent classes. + * @param string|object $class the object or the fully qualified class name specifying the class-level event + * @param string $name the event name + * @param Event $event the event parameter. If not set, a default [[Event]] object will be created. + */ + public static function trigger($class, $name, $event = null) + { + if (empty(self::$_events[$name])) { + return; + } + if ($event === null) { + $event = new self; + } + $event->handled = false; + $event->name = $name; + + if (is_object($class)) { + $class = get_class($class); + } else { + $class = ltrim($class, '\\'); + } + do { + if (!empty(self::$_events[$name][$class])) { + foreach (self::$_events[$name][$class] as $handler) { + $event->data = $handler[1]; + call_user_func($handler[0], $event); + if ($event instanceof Event && $event->handled) { + return; + } + } + } + } while (($class = get_parent_class($class)) !== false); + } } diff --git a/framework/yii/base/Exception.php b/framework/yii/base/Exception.php index 4f66e53..7e01bd4 100644 --- a/framework/yii/base/Exception.php +++ b/framework/yii/base/Exception.php @@ -39,21 +39,12 @@ class Exception extends \Exception implements Arrayable */ protected function toArrayRecursive($exception) { - if ($exception instanceof self) { - $array = array( - 'type' => get_class($this), - 'name' => $this->getName(), - 'message' => $this->getMessage(), - 'code' => $this->getCode(), - ); - } else { - $array = array( - 'type' => get_class($exception), - 'name' => 'Exception', - 'message' => $exception->getMessage(), - 'code' => $exception->getCode(), - ); - } + $array = [ + 'type' => get_class($exception), + 'name' => $exception instanceof self ? $exception->getName() : 'Exception', + 'message' => $exception->getMessage(), + 'code' => $exception->getCode(), + ]; if (($prev = $exception->getPrevious()) !== null) { $array['previous'] = $this->toArrayRecursive($prev); } diff --git a/framework/yii/test/TestCase.php b/framework/yii/base/Extension.php similarity index 54% rename from framework/yii/test/TestCase.php rename to framework/yii/base/Extension.php index 2e9b480..c25a043 100644 --- a/framework/yii/test/TestCase.php +++ b/framework/yii/base/Extension.php @@ -1,25 +1,29 @@ <?php /** - * TestCase class. - * * @link http://www.yiiframework.com/ * @copyright Copyright (c) 2008 Yii Software LLC * @license http://www.yiiframework.com/license/ */ -namespace yii\test; - -require_once('PHPUnit/Runner/Version.php'); -spl_autoload_unregister(array('Yii', 'autoload')); -require_once('PHPUnit/Autoload.php'); -spl_autoload_register(array('Yii', 'autoload')); // put yii's autoloader at the end +namespace yii\base; /** - * TestCase is the base class for all test case classes. + * Extension is the base class that may be extended by individual extensions. + * + * Extension serves as the bootstrap class for extensions. When an extension + * is installed via composer, the [[init()]] method of its Extension class (if any) + * will be invoked during the application initialization stage. * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ -abstract class TestCase extends \PHPUnit_Framework_TestCase +class Extension { + /** + * Initializes the extension. + * This method is invoked at the end of [[Application::init()]]. + */ + public static function init() + { + } } diff --git a/framework/yii/base/Formatter.php b/framework/yii/base/Formatter.php index 84b4b8d..33a6c16 100644 --- a/framework/yii/base/Formatter.php +++ b/framework/yii/base/Formatter.php @@ -19,6 +19,9 @@ use yii\helpers\Html; * The behavior of some of them may be configured via the properties of Formatter. For example, * by configuring [[dateFormat]], one may control how [[asDate()]] formats the value into a date string. * + * Formatter is configured as an application component in [[yii\base\Application]] by default. + * You can access that instance via `Yii::$app->formatter`. + * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ @@ -42,7 +45,7 @@ class Formatter extends Component public $nullDisplay; /** * @var array the text to be displayed when formatting a boolean value. The first element corresponds - * to the text display for false, the second element for true. Defaults to `array('No', 'Yes')`. + * to the text display for false, the second element for true. Defaults to `['No', 'Yes']`. */ public $booleanFormat; /** @@ -63,7 +66,7 @@ class Formatter extends Component public function init() { if (empty($this->booleanFormat)) { - $this->booleanFormat = array(Yii::t('yii', 'No'), Yii::t('yii', 'Yes')); + $this->booleanFormat = [Yii::t('yii', 'No'), Yii::t('yii', 'Yes')]; } if ($this->nullDisplay === null) { $this->nullDisplay = Yii::t('yii', '(not set)'); @@ -79,7 +82,7 @@ class Formatter extends Component * @param string|array $format the format of the value, e.g., "html", "text". To specify additional * parameters of the formatting method, you may use an array. The first element of the array * specifies the format name, while the rest of the elements will be used as the parameters to the formatting - * method. For example, a format of `array('date', 'Y-m-d')` will cause the invocation of `asDate($value, 'Y-m-d')`. + * method. For example, a format of `['date', 'Y-m-d']` will cause the invocation of `asDate($value, 'Y-m-d')`. * @return string the formatting result * @throws InvalidParamException if the type is not supported by this class. */ @@ -94,11 +97,11 @@ class Formatter extends Component $params = $format; $format = $f; } else { - $params = array($value); + $params = [$value]; } $method = 'as' . $format; if (method_exists($this, $method)) { - return call_user_func_array(array($this, $method), $params); + return call_user_func_array([$this, $method], $params); } else { throw new InvalidParamException("Unknown type: $format"); } @@ -187,7 +190,7 @@ class Formatter extends Component if ($value === null) { return $this->nullDisplay; } - return Html::mailto($value); + return Html::mailto(Html::encode($value), $value); } /** diff --git a/framework/yii/base/InlineAction.php b/framework/yii/base/InlineAction.php index a669563..412b357 100644 --- a/framework/yii/base/InlineAction.php +++ b/framework/yii/base/InlineAction.php @@ -31,7 +31,7 @@ class InlineAction extends Action * @param string $actionMethod the controller method that this inline action is associated with * @param array $config name-value pairs that will be used to initialize the object properties */ - public function __construct($id, $controller, $actionMethod, $config = array()) + public function __construct($id, $controller, $actionMethod, $config = []) { $this->actionMethod = $actionMethod; parent::__construct($id, $controller, $config); @@ -50,6 +50,6 @@ class InlineAction extends Action if (Yii::$app->requestedParams === null) { Yii::$app->requestedParams = $args; } - return call_user_func_array(array($this->controller, $this->actionMethod), $args); + return call_user_func_array([$this->controller, $this->actionMethod], $args); } } diff --git a/framework/yii/base/Model.php b/framework/yii/base/Model.php index 93b5a6b..b366a9f 100644 --- a/framework/yii/base/Model.php +++ b/framework/yii/base/Model.php @@ -46,7 +46,8 @@ use yii\validators\Validator; * @property ArrayIterator $iterator An iterator for traversing the items in the list. This property is * read-only. * @property string $scenario The scenario that this model is in. Defaults to [[DEFAULT_SCENARIO]]. - * @property ArrayObject $validators All the validators declared in the model. This property is read-only. + * @property ArrayObject|\yii\validators\Validator[] $validators All the validators declared in the model. + * This property is read-only. * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 @@ -90,20 +91,20 @@ class Model extends Component implements IteratorAggregate, ArrayAccess * Each rule is an array with the following structure: * * ~~~ - * array( - * 'attribute list', + * [ + * ['attribute1', 'attribute2'], * 'validator type', - * 'on' => 'scenario name', + * 'on' => ['scenario1', 'scenario2'], * ...other parameters... - * ) + * ] * ~~~ * * where * - * - attribute list: required, specifies the attributes (separated by commas) to be validated; + * - attribute list: required, specifies the attributes array to be validated, for single attribute you can pass string; * - validator type: required, specifies the validator to be used. It can be the name of a model * class method, the name of a built-in validator, or a validator class name (or its path alias). - * - on: optional, specifies the [[scenario|scenarios]] (separated by commas) when the validation + * - on: optional, specifies the [[scenario|scenarios]] array when the validation * rule can be applied. If this option is not set, the rule will apply to all scenarios. * - additional name-value pairs can be specified to initialize the corresponding validator properties. * Please refer to individual validator class API for possible properties. @@ -117,7 +118,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess * ~~~ * * In the above `$attribute` refers to currently validated attribute name while `$params` contains an array of - * validator configuration options such as `max` in case of `length` validator. Currently validate attribute value + * validator configuration options such as `max` in case of `string` validator. Currently validate attribute value * can be accessed as `$this->[$attribute]`. * * Yii also provides a set of [[Validator::builtInValidators|built-in validators]]. @@ -126,29 +127,29 @@ class Model extends Component implements IteratorAggregate, ArrayAccess * Below are some examples: * * ~~~ - * array( + * [ * // built-in "required" validator - * array('username', 'required'), - * // built-in "length" validator customized with "min" and "max" properties - * array('username', 'length', 'min' => 3, 'max' => 12), + * [['username', 'password'], 'required'], + * // built-in "string" validator customized with "min" and "max" properties + * ['username', 'string', 'min' => 3, 'max' => 12], * // built-in "compare" validator that is used in "register" scenario only - * array('password', 'compare', 'compareAttribute' => 'password2', 'on' => 'register'), + * ['password', 'compare', 'compareAttribute' => 'password2', 'on' => 'register'], * // an inline validator defined via the "authenticate()" method in the model class - * array('password', 'authenticate', 'on' => 'login'), + * ['password', 'authenticate', 'on' => 'login'], * // a validator of class "DateRangeValidator" - * array('dateRange', 'DateRangeValidator'), - * ); + * ['dateRange', 'DateRangeValidator'], + * ]; * ~~~ * * Note, in order to inherit rules defined in the parent class, a child class needs to * merge the parent rules with child rules using functions such as `array_merge()`. * * @return array validation rules - * @see scenarios + * @see scenarios() */ public function rules() { - return array(); + return []; } /** @@ -157,11 +158,11 @@ class Model extends Component implements IteratorAggregate, ArrayAccess * The returned array should be in the following format: * * ~~~ - * array( - * 'scenario1' => array('attribute11', 'attribute12', ...), - * 'scenario2' => array('attribute21', 'attribute22', ...), + * [ + * 'scenario1' => ['attribute11', 'attribute12', ...], + * 'scenario2' => ['attribute21', 'attribute22', ...], * ... - * ) + * ] * ~~~ * * By default, an active attribute that is considered safe and can be massively assigned. @@ -177,8 +178,8 @@ class Model extends Component implements IteratorAggregate, ArrayAccess */ public function scenarios() { - $scenarios = array(); - $defaults = array(); + $scenarios = []; + $defaults = []; /** @var $validator Validator */ foreach ($this->getValidators() as $validator) { if (empty($validator->on)) { @@ -231,7 +232,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess public function attributes() { $class = new ReflectionClass($this); - $names = array(); + $names = []; foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) { $name = $property->getName(); if (!$property->isStatic()) { @@ -255,11 +256,11 @@ class Model extends Component implements IteratorAggregate, ArrayAccess * merge the parent labels with child labels using functions such as `array_merge()`. * * @return array attribute labels (name => label) - * @see generateAttributeLabel + * @see generateAttributeLabel() */ public function attributeLabels() { - return array(); + return []; } /** @@ -349,7 +350,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess * $model->validators[] = $newValidator; * ~~~ * - * @return ArrayObject all the validators declared in the model. + * @return ArrayObject|\yii\validators\Validator[] all the validators declared in the model. */ public function getValidators() { @@ -367,9 +368,8 @@ class Model extends Component implements IteratorAggregate, ArrayAccess */ public function getActiveValidators($attribute = null) { - $validators = array(); + $validators = []; $scenario = $this->getScenario(); - /** @var $validator Validator */ foreach ($this->getValidators() as $validator) { if ($validator->isActive($scenario) && ($attribute === null || in_array($attribute, $validator->attributes, true))) { $validators[] = $validator; @@ -391,7 +391,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess if ($rule instanceof Validator) { $validators->append($rule); } elseif (is_array($rule) && isset($rule[0], $rule[1])) { // attributes, validator type - $validator = Validator::createValidator($rule[1], $this, $rule[0], array_slice($rule, 2)); + $validator = Validator::createValidator($rule[1], $this, (array) $rule[0], array_slice($rule, 2)); $validators->append($validator); } else { throw new InvalidConfigException('Invalid validation rule: a rule must specify both attribute names and validator type.'); @@ -422,6 +422,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess * Returns a value indicating whether the attribute is safe for massive assignments. * @param string $attribute attribute name * @return boolean whether the attribute is safe for massive assignments + * @see safeAttributes() */ public function isAttributeSafe($attribute) { @@ -429,11 +430,22 @@ class Model extends Component implements IteratorAggregate, ArrayAccess } /** + * Returns a value indicating whether the attribute is active in the current scenario. + * @param string $attribute attribute name + * @return boolean whether the attribute is active in the current scenario + * @see activeAttributes() + */ + public function isAttributeActive($attribute) + { + return in_array($attribute, $this->activeAttributes(), true); + } + + /** * Returns the text label for the specified attribute. * @param string $attribute the attribute name * @return string the attribute label - * @see generateAttributeLabel - * @see attributeLabels + * @see generateAttributeLabel() + * @see attributeLabels() */ public function getAttributeLabel($attribute) { @@ -460,41 +472,41 @@ class Model extends Component implements IteratorAggregate, ArrayAccess * Note that when returning errors for all attributes, the result is a two-dimensional array, like the following: * * ~~~ - * array( - * 'username' => array( + * [ + * 'username' => [ * 'Username is required.', * 'Username must contain only word characters.', - * ), - * 'email' => array( + * ], + * 'email' => [ * 'Email address is invalid.', - * ) - * ) + * ] + * ] * ~~~ * - * @see getFirstErrors - * @see getFirstError + * @see getFirstErrors() + * @see getFirstError() */ public function getErrors($attribute = null) { if ($attribute === null) { - return $this->_errors === null ? array() : $this->_errors; + return $this->_errors === null ? [] : $this->_errors; } else { - return isset($this->_errors[$attribute]) ? $this->_errors[$attribute] : array(); + return isset($this->_errors[$attribute]) ? $this->_errors[$attribute] : []; } } /** * Returns the first error of every attribute in the model. * @return array the first errors. An empty array will be returned if there is no error. - * @see getErrors - * @see getFirstError + * @see getErrors() + * @see getFirstError() */ public function getFirstErrors() { if (empty($this->_errors)) { - return array(); + return []; } else { - $errors = array(); + $errors = []; foreach ($this->_errors as $attributeErrors) { if (isset($attributeErrors[0])) { $errors[] = $attributeErrors[0]; @@ -508,8 +520,8 @@ class Model extends Component implements IteratorAggregate, ArrayAccess * Returns the first error of the specified attribute. * @param string $attribute attribute name. * @return string the error message. Null is returned if no error. - * @see getErrors - * @see getFirstErrors + * @see getErrors() + * @see getFirstErrors() */ public function getFirstError($attribute) { @@ -533,7 +545,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess public function clearErrors($attribute = null) { if ($attribute === null) { - $this->_errors = array(); + $this->_errors = []; } else { unset($this->_errors[$attribute]); } @@ -560,9 +572,9 @@ class Model extends Component implements IteratorAggregate, ArrayAccess * @param array $except list of attributes whose value should NOT be returned. * @return array attribute values (name => value). */ - public function getAttributes($names = null, $except = array()) + public function getAttributes($names = null, $except = []) { - $values = array(); + $values = []; if ($names === null) { $names = $this->attributes(); } @@ -645,9 +657,9 @@ class Model extends Component implements IteratorAggregate, ArrayAccess $scenario = $this->getScenario(); $scenarios = $this->scenarios(); if (!isset($scenarios[$scenario])) { - return array(); + return []; } - $attributes = array(); + $attributes = []; foreach ($scenarios[$scenario] as $attribute) { if ($attribute[0] !== '!') { $attributes[] = $attribute; @@ -665,7 +677,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess $scenario = $this->getScenario(); $scenarios = $this->scenarios(); if (!isset($scenarios[$scenario])) { - return array(); + return []; } $attributes = $scenarios[$scenario]; foreach ($attributes as $i => $attribute) { @@ -736,16 +748,21 @@ class Model extends Component implements IteratorAggregate, ArrayAccess /** * Validates multiple models. + * This method will validate every model. The models being validated may + * be of the same or different types. * @param array $models the models to be validated + * @param array $attributes list of attributes that should be validated. + * If this parameter is empty, it means any attribute listed in the applicable + * validation rules should be validated. * @return boolean whether all models are valid. False will be returned if one * or multiple models have validation error. */ - public static function validateMultiple($models) + public static function validateMultiple($models, $attributes = null) { $valid = true; /** @var Model $model */ foreach ($models as $model) { - $valid = $model->validate() && $valid; + $valid = $model->validate($attributes) && $valid; } return $valid; } diff --git a/framework/yii/base/Module.php b/framework/yii/base/Module.php index 7df87a5..1e5302c 100644 --- a/framework/yii/base/Module.php +++ b/framework/yii/base/Module.php @@ -39,11 +39,11 @@ abstract class Module extends Component /** * @var array custom module parameters (name => value). */ - public $params = array(); + public $params = []; /** - * @var array the IDs of the components or modules that should be preloaded when this module is created. + * @var array the IDs of the components or modules that should be preloaded right after initialization. */ - public $preload = array(); + public $preload = []; /** * @var string an ID that uniquely identifies this module among other modules which have the same [[module|parent]]. */ @@ -68,22 +68,21 @@ abstract class Module extends Component * in the array are used to initialize the corresponding controller properties. For example, * * ~~~ - * array( + * [ * 'account' => '@app/controllers/UserController', - * 'article' => array( + * 'article' => [ * 'class' => '@app/controllers/PostController', * 'pageTitle' => 'something new', - * ), - * ) + * ], + * ] * ~~~ */ - public $controllerMap = array(); + public $controllerMap = []; /** * @var string the namespace that controller classes are in. If not set, * it will use the "controllers" sub-namespace under the namespace of this module. * For example, if the namespace of this module is "foo\bar", then the default * controller namespace would be "foo\bar\controllers". - * If the module is an application, it will default to "app\controllers". */ public $controllerNamespace; /** @@ -113,11 +112,11 @@ abstract class Module extends Component /** * @var array child modules of this module */ - private $_modules = array(); + private $_modules = []; /** * @var array components registered under this module */ - private $_components = array(); + private $_components = []; /** * Constructor. @@ -125,7 +124,7 @@ abstract class Module extends Component * @param Module $parent the parent module (if any) * @param array $config name-value pairs that will be used to initialize the object properties */ - public function __construct($id, $parent = null, $config = array()) + public function __construct($id, $parent = null, $config = []) { $this->id = $id; $this->module = $parent; @@ -167,22 +166,20 @@ abstract class Module extends Component /** * Initializes the module. * This method is called after the module is created and initialized with property values - * given in configuration. The default implement will create a path alias using the module [[id]] + * given in configuration. The default implementation will create a path alias using the module [[id]] * and then call [[preloadComponents()]] to load components that are declared in [[preload]]. + * + * If you override this method, please make sure you call the parent implementation. */ public function init() { - $this->preloadComponents(); if ($this->controllerNamespace === null) { - if ($this instanceof Application) { - $this->controllerNamespace = 'app\\controllers'; - } else { - $class = get_class($this); - if (($pos = strrpos($class, '\\')) !== false) { - $this->controllerNamespace = substr($class, 0, $pos) . '\\controllers'; - } + $class = get_class($this); + if (($pos = strrpos($class, '\\')) !== false) { + $this->controllerNamespace = substr($class, 0, $pos) . '\\controllers'; } } + $this->preloadComponents(); } /** @@ -221,9 +218,6 @@ abstract class Module extends Component $p = realpath($path); if ($p !== false && is_dir($p)) { $this->_basePath = $p; - if ($this instanceof Application) { - Yii::setAlias('@app', $p); - } } else { throw new InvalidParamException("The directory does not exist: $path"); } @@ -247,7 +241,7 @@ abstract class Module extends Component * Sets the directory that contains the controller classes. * @param string $path the directory that contains the controller classes. * This can be either a directory name or a path alias. - * @throws Exception if the directory is invalid + * @throws InvalidParamException if the directory is invalid */ public function setControllerPath($path) { @@ -270,7 +264,7 @@ abstract class Module extends Component /** * Sets the directory that contains the view files. * @param string $path the root directory of view files. - * @throws Exception if the directory is invalid + * @throws InvalidParamException if the directory is invalid */ public function setViewPath($path) { @@ -293,7 +287,7 @@ abstract class Module extends Component /** * Sets the directory that contains the layout files. * @param string $path the root directory of layout files. - * @throws Exception if the directory is invalid + * @throws InvalidParamException if the directory is invalid */ public function setLayoutPath($path) { @@ -312,10 +306,10 @@ abstract class Module extends Component * For example, * * ~~~ - * array( + * [ * '@models' => '@app/models', // an existing alias * '@backend' => __DIR__ . '/../backend', // a directory - * ) + * ] * ~~~ */ public function setAliases($aliases) @@ -326,25 +320,40 @@ abstract class Module extends Component } /** - * Checks whether the named module exists. - * @param string $id module ID + * Checks whether the child module of the specified ID exists. + * This method supports checking the existence of both child and grand child modules. + * @param string $id module ID. For grand child modules, use ID path relative to this module (e.g. `admin/content`). * @return boolean whether the named module exists. Both loaded and unloaded modules * are considered. */ public function hasModule($id) { - return isset($this->_modules[$id]); + if (($pos = strpos($id, '/')) !== false) { + // sub-module + $module = $this->getModule(substr($id, 0, $pos)); + return $module === null ? false : $module->hasModule(substr($id, $pos + 1)); + } else { + return isset($this->_modules[$id]); + } } /** - * Retrieves the named module. - * @param string $id module ID (case-sensitive). + * Retrieves the child module of the specified ID. + * This method supports retrieving both child modules and grand child modules. + * @param string $id module ID (case-sensitive). To retrieve grand child modules, + * use ID path relative to this module (e.g. `admin/content`). * @param boolean $load whether to load the module if it is not yet loaded. * @return Module|null the module instance, null if the module does not exist. * @see hasModule() */ public function getModule($id, $load = true) { + if (($pos = strpos($id, '/')) !== false) { + // sub-module + $module = $this->getModule(substr($id, 0, $pos)); + return $module === null ? null : $module->getModule(substr($id, $pos + 1), $load); + } + if (isset($this->_modules[$id])) { if ($this->_modules[$id] instanceof Module) { return $this->_modules[$id]; @@ -386,7 +395,7 @@ abstract class Module extends Component public function getModules($loadedOnly = false) { if ($loadedOnly) { - $modules = array(); + $modules = []; foreach ($this->_modules as $module) { if ($module instanceof Module) { $modules[] = $module; @@ -411,15 +420,13 @@ abstract class Module extends Component * The following is an example for registering two sub-modules: * * ~~~ - * array( - * 'comment' => array( + * [ + * 'comment' => [ * 'class' => 'app\modules\comment\CommentModule', * 'db' => 'db', - * ), - * 'booking' => array( - * 'class' => 'app\modules\booking\BookingModule', - * ), - * ) + * ], + * 'booking' => ['class' => 'app\modules\booking\BookingModule'], + * ] * ~~~ * * @param array $modules modules (id => module configuration or instances) @@ -491,7 +498,7 @@ abstract class Module extends Component public function getComponents($loadedOnly = false) { if ($loadedOnly) { - $components = array(); + $components = []; foreach ($this->_components as $component) { if ($component instanceof Component) { $components[] = $component; @@ -516,16 +523,16 @@ abstract class Module extends Component * The following is an example for setting two components: * * ~~~ - * array( - * 'db' => array( + * [ + * 'db' => [ * 'class' => 'yii\db\Connection', * 'dsn' => 'sqlite:path/to/file.db', - * ), - * 'cache' => array( + * ], + * 'cache' => [ * 'class' => 'yii\caching\DbCache', * 'db' => 'db', - * ), - * ) + * ], + * ] * ~~~ * * @param array $components components (id => component configuration or instance) @@ -567,11 +574,11 @@ abstract class Module extends Component * @return mixed the result of the action. * @throws InvalidRouteException if the requested route cannot be resolved into an action successfully */ - public function runAction($route, $params = array()) + public function runAction($route, $params = []) { $parts = $this->createController($route); if (is_array($parts)) { - /** @var $controller Controller */ + /** @var Controller $controller */ list($controller, $actionID) = $parts; $oldController = Yii::$app->controller; Yii::$app->controller = $controller; @@ -631,7 +638,7 @@ abstract class Module extends Component } } - return isset($controller) ? array($controller, $route) : false; + return isset($controller) ? [$controller, $route] : false; } /** diff --git a/framework/yii/base/Object.php b/framework/yii/base/Object.php index 55754de..06fca50 100644 --- a/framework/yii/base/Object.php +++ b/framework/yii/base/Object.php @@ -10,7 +10,10 @@ namespace yii\base; use Yii; /** + * Object is the base class that implements the *property* feature. + * * @include @yii/base/Object.md + * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ @@ -38,7 +41,7 @@ class Object implements Arrayable * * @param array $config name-value pairs that will be used to initialize the object properties */ - public function __construct($config = array()) + public function __construct($config = []) { if (!empty($config)) { Yii::configure($this, $config); @@ -63,7 +66,8 @@ class Object implements Arrayable * @param string $name the property name * @return mixed the property value * @throws UnknownPropertyException if the property is not defined - * @see __set + * @throws InvalidCallException if the property is write-only + * @see __set() */ public function __get($name) { @@ -85,8 +89,8 @@ class Object implements Arrayable * @param string $name the property name or the event name * @param mixed $value the property value * @throws UnknownPropertyException if the property is not defined - * @throws InvalidCallException if the property is read-only. - * @see __get + * @throws InvalidCallException if the property is read-only + * @see __get() */ public function __set($name, $value) { @@ -167,8 +171,8 @@ class Object implements Arrayable * @param string $name the property name * @param boolean $checkVars whether to treat member variables as properties * @return boolean whether the property is defined - * @see canGetProperty - * @see canSetProperty + * @see canGetProperty() + * @see canSetProperty() */ public function hasProperty($name, $checkVars = true) { @@ -186,7 +190,7 @@ class Object implements Arrayable * @param string $name the property name * @param boolean $checkVars whether to treat member variables as properties * @return boolean whether the property can be read - * @see canSetProperty + * @see canSetProperty() */ public function canGetProperty($name, $checkVars = true) { @@ -204,7 +208,7 @@ class Object implements Arrayable * @param string $name the property name * @param boolean $checkVars whether to treat member variables as properties * @return boolean whether the property can be written - * @see canGetProperty + * @see canGetProperty() */ public function canSetProperty($name, $checkVars = true) { diff --git a/framework/yii/base/Request.php b/framework/yii/base/Request.php index 0d660d6..cd3ffdc 100644 --- a/framework/yii/base/Request.php +++ b/framework/yii/base/Request.php @@ -8,6 +8,7 @@ namespace yii\base; /** + * Request represents a request that is handled by an [[Application]]. * * @property boolean $isConsoleRequest The value indicating whether the current request is made via console. * @property string $scriptFile Entry script file path (processed w/ realpath()). diff --git a/framework/yii/base/Response.php b/framework/yii/base/Response.php index 467de9e..1403b69 100644 --- a/framework/yii/base/Response.php +++ b/framework/yii/base/Response.php @@ -8,6 +8,8 @@ namespace yii\base; /** + * Response represents the response of an [[Application]] to a [[Request]]. + * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ diff --git a/framework/yii/base/Theme.php b/framework/yii/base/Theme.php index 658ada1..1d8771f 100644 --- a/framework/yii/base/Theme.php +++ b/framework/yii/base/Theme.php @@ -8,33 +8,53 @@ namespace yii\base; use Yii; -use yii\base\InvalidConfigException; use yii\helpers\FileHelper; /** * Theme represents an application theme. * - * A theme is directory consisting of view and layout files which are meant to replace their - * non-themed counterparts. + * When [[View]] renders a view file, it will check the [[Application::theme|active theme]] + * to see if there is a themed version of the view file exists. If so, the themed version will be rendered instead. * - * Theme uses [[pathMap]] to achieve the file replacement. A view or layout file will be replaced - * with its themed version if part of its path matches one of the keys in [[pathMap]]. - * Then the matched part will be replaced with the corresponding array value. + * A theme is a directory consisting of view files which are meant to replace their non-themed counterparts. * - * For example, if [[pathMap]] is `array('/web/views' => '/web/themes/basic')`, + * Theme uses [[pathMap]] to achieve the view file replacement: + * + * 1. It first looks for a key in [[pathMap]] that is a substring of the given view file path; + * 2. If such a key exists, the corresponding value will be used to replace the corresponding part + * in the view file path; + * 3. It will then check if the updated view file exists or not. If so, that file will be used + * to replace the original view file. + * 4. If Step 2 or 3 fails, the original view file will be used. + * + * For example, if [[pathMap]] is `['/web/views' => '/web/themes/basic']`, * then the themed version for a view file `/web/views/site/index.php` will be * `/web/themes/basic/site/index.php`. * + * It is possible to map a single path to multiple paths. For example, + * + * ~~~ + * 'pathMap' => [ + * '/web/views' => [ + * '/web/themes/christmas', + * '/web/themes/basic', + * ], + * ] + * ~~~ + * + * In this case, the themed version could be either `/web/themes/christmas/site/index.php` or + * `/web/themes/basic/site/index.php`. The former has precedence over the latter if both files exist. + * * To use a theme, you should configure the [[View::theme|theme]] property of the "view" application * component like the following: * * ~~~ - * 'view' => array( - * 'theme' => array( + * 'view' => [ + * 'theme' => [ * 'basePath' => '@webroot/themes/basic', * 'baseUrl' => '@web/themes/basic', - * ), - * ), + * ], + * ], * ~~~ * * The above configuration specifies a theme located under the "themes/basic" directory of the Web folder @@ -76,16 +96,18 @@ class Theme extends Component if (empty($this->pathMap)) { if ($this->basePath !== null) { $this->basePath = Yii::getAlias($this->basePath); - $this->pathMap = array(Yii::$app->getBasePath() => $this->basePath); + $this->pathMap = [Yii::$app->getBasePath() => [$this->basePath]]; } else { throw new InvalidConfigException('The "basePath" property must be set.'); } } - $paths = array(); - foreach ($this->pathMap as $from => $to) { + $paths = []; + foreach ($this->pathMap as $from => $tos) { $from = FileHelper::normalizePath(Yii::getAlias($from)); - $to = FileHelper::normalizePath(Yii::getAlias($to)); - $paths[$from . DIRECTORY_SEPARATOR] = $to . DIRECTORY_SEPARATOR; + foreach ((array)$tos as $to) { + $to = FileHelper::normalizePath(Yii::getAlias($to)); + $paths[$from . DIRECTORY_SEPARATOR][] = $to . DIRECTORY_SEPARATOR; + } } $this->pathMap = $paths; if ($this->baseUrl === null) { @@ -104,12 +126,14 @@ class Theme extends Component public function applyTo($path) { $path = FileHelper::normalizePath($path); - foreach ($this->pathMap as $from => $to) { + foreach ($this->pathMap as $from => $tos) { if (strpos($path, $from) === 0) { $n = strlen($from); - $file = $to . substr($path, $n); - if (is_file($file)) { - return $file; + foreach ($tos as $to) { + $file = $to . substr($path, $n); + if (is_file($file)) { + return $file; + } } } } diff --git a/framework/yii/base/View.php b/framework/yii/base/View.php index df0b2b2..3eee4ef 100644 --- a/framework/yii/base/View.php +++ b/framework/yii/base/View.php @@ -9,9 +9,6 @@ namespace yii\base; use Yii; use yii\helpers\FileHelper; -use yii\helpers\Html; -use yii\web\JqueryAsset; -use yii\web\AssetBundle; use yii\widgets\Block; use yii\widgets\ContentDecorator; use yii\widgets\FragmentCache; @@ -21,9 +18,6 @@ use yii\widgets\FragmentCache; * * View provides a set of methods (e.g. [[render()]]) for rendering purpose. * - * @property \yii\web\AssetManager $assetManager The asset manager. Defaults to the "assetManager" application - * component. - * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ @@ -38,14 +32,6 @@ class View extends Component */ const EVENT_END_PAGE = 'endPage'; /** - * @event Event an event that is triggered by [[beginBody()]]. - */ - const EVENT_BEGIN_BODY = 'beginBody'; - /** - * @event Event an event that is triggered by [[endBody()]]. - */ - const EVENT_END_BODY = 'endBody'; - /** * @event ViewEvent an event that is triggered by [[renderFile()]] right before it renders a view file. */ const EVENT_BEFORE_RENDER = 'beforeRender'; @@ -55,62 +41,23 @@ class View extends Component const EVENT_AFTER_RENDER = 'afterRender'; /** - * The location of registered JavaScript code block or files. - * This means the location is in the head section. - */ - const POS_HEAD = 1; - /** - * The location of registered JavaScript code block or files. - * This means the location is at the beginning of the body section. - */ - const POS_BEGIN = 2; - /** - * The location of registered JavaScript code block or files. - * This means the location is at the end of the body section. - */ - const POS_END = 3; - /** - * The location of registered JavaScript code block. - * This means the JavaScript code block will be enclosed within `jQuery(document).ready()`. - */ - const POS_READY = 4; - /** - * This is internally used as the placeholder for receiving the content registered for the head section. - */ - const PH_HEAD = '<![CDATA[YII-BLOCK-HEAD]]>'; - /** - * This is internally used as the placeholder for receiving the content registered for the beginning of the body section. - */ - const PH_BODY_BEGIN = '<![CDATA[YII-BLOCK-BODY-BEGIN]]>'; - /** - * This is internally used as the placeholder for receiving the content registered for the end of the body section. - */ - const PH_BODY_END = '<![CDATA[YII-BLOCK-BODY-END]]>'; - - - /** - * @var object the context under which the [[renderFile()]] method is being invoked. - * This can be a controller, a widget, or any other object. + * @var ViewContextInterface the context under which the [[renderFile()]] method is being invoked. */ public $context; /** * @var mixed custom parameters that are shared among view templates. */ - public $params; + public $params = []; /** * @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, the following configuration enables both Smarty and Twig view renderers: * * ~~~ - * array( - * 'tpl' => array( - * 'class' => 'yii\smarty\ViewRenderer', - * ), - * 'twig' => array( - * 'class' => 'yii\twig\ViewRenderer', - * ), - * ) + * [ + * 'tpl' => ['class' => 'yii\smarty\ViewRenderer'], + * 'twig' => ['class' => 'yii\twig\ViewRenderer'], + * ] * ~~~ * * If no renderer is available for the given view file, the view file will be treated as a normal PHP @@ -134,53 +81,13 @@ class View extends Component * is used internally to implement the content caching feature. Do not modify it directly. * @internal */ - public $cacheStack = array(); + public $cacheStack = []; /** * @var array a list of placeholders for embedding dynamic contents. This property * is used internally to implement the content caching feature. Do not modify it directly. * @internal */ - public $dynamicPlaceholders = array(); - /** - * @var array list of the registered asset bundles. The keys are the bundle names, and the values - * are booleans indicating whether the bundles have been registered. - * @see registerAssetBundle - */ - public $assetBundles; - /** - * @var string the page title - */ - public $title; - /** - * @var array the registered meta tags. - * @see registerMetaTag - */ - public $metaTags; - /** - * @var array the registered link tags. - * @see registerLinkTag - */ - public $linkTags; - /** - * @var array the registered CSS code blocks. - * @see registerCss - */ - public $css; - /** - * @var array the registered CSS files. - * @see registerCssFile - */ - public $cssFiles; - /** - * @var array the registered JS code blocks - * @see registerJs - */ - public $js; - /** - * @var array the registered JS files. - * @see registerJsFile - */ - public $jsFiles; + public $dynamicPlaceholders = []; /** @@ -200,29 +107,67 @@ class View extends Component /** * Renders a view. * - * This method delegates the call to the [[context]] object: + * The view to be rendered can be specified in one of the following formats: * - * - If [[context]] is a controller, the [[Controller::renderPartial()]] method will be called; - * - If [[context]] is a widget, the [[Widget::render()]] method will be called; - * - Otherwise, an InvalidCallException exception will be thrown. + * - path alias (e.g. "@app/views/site/index"); + * - absolute path within application (e.g. "//site/index"): the view name starts with double slashes. + * The actual view file will be looked for under the [[Application::viewPath|view path]] of the application. + * - absolute path within current module (e.g. "/site/index"): the view name starts with a single slash. + * The actual view file will be looked for under the [[Module::viewPath|view path]] of [[module]]. + * - resolving any other format will be performed via [[ViewContext::findViewFile()]]. * * @param string $view the view name. Please refer to [[Controller::findViewFile()]] * and [[Widget::findViewFile()]] on how to specify this parameter. * @param array $params the parameters (name-value pairs) that will be extracted and made available in the view file. + * @param object $context the context that the view should use for rendering the view. If null, + * existing [[context]] will be used. * @return string the rendering result - * @throws InvalidCallException if [[context]] is neither a controller nor a widget. * @throws InvalidParamException if the view cannot be resolved or the view file does not exist. - * @see renderFile + * @see renderFile() */ - public function render($view, $params = array()) + public function render($view, $params = [], $context = null) { - if ($this->context instanceof Controller) { - return $this->context->renderPartial($view, $params); - } elseif ($this->context instanceof Widget) { - return $this->context->render($view, $params); + $viewFile = $this->findViewFile($view, $context); + return $this->renderFile($viewFile, $params, $context); + } + + /** + * Finds the view file based on the given view name. + * @param string $view the view name or the path alias of the view file. Please refer to [[render()]] + * on how to specify this parameter. + * @param object $context the context that the view should be used to search the view file. If null, + * existing [[context]] will be used. + * @return string the view file path. Note that the file may not exist. + * @throws InvalidCallException if [[context]] is required and invalid. + */ + protected function findViewFile($view, $context = null) + { + if (strncmp($view, '@', 1) === 0) { + // e.g. "@app/views/main" + $file = Yii::getAlias($view); + } elseif (strncmp($view, '//', 2) === 0) { + // e.g. "//layouts/main" + $file = Yii::$app->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/'); + } elseif (strncmp($view, '/', 1) === 0) { + // e.g. "/site/index" + if (Yii::$app->controller !== null) { + $file = Yii::$app->controller->module->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/'); + } else { + throw new InvalidCallException("Unable to locate view file for view '$view': no active controller."); + } } else { - throw new InvalidCallException('View::render() is not supported for the current context.'); + // context required + if ($context === null) { + $context = $this->context; + } + if ($context instanceof ViewContextInterface) { + $file = $context->findViewFile($view); + } else { + throw new InvalidCallException("Unable to locate view file for view '$view': no active view context."); + } } + + return pathinfo($file, PATHINFO_EXTENSION) === '' ? $file . '.php' : $file; } /** @@ -244,7 +189,7 @@ class View extends Component * @return string the rendering result * @throws InvalidParamException if the view file does not exist */ - public function renderFile($viewFile, $params = array(), $context = null) + public function renderFile($viewFile, $params = [], $context = null) { $viewFile = Yii::getAlias($viewFile); if ($this->theme !== null) { @@ -263,16 +208,16 @@ class View extends Component $output = ''; if ($this->beforeRender($viewFile)) { + Yii::trace("Rendering view file: $viewFile", __METHOD__); $ext = pathinfo($viewFile, PATHINFO_EXTENSION); if (isset($this->renderers[$ext])) { - if (is_array($this->renderers[$ext])) { + if (is_array($this->renderers[$ext]) || is_string($this->renderers[$ext])) { $this->renderers[$ext] = Yii::createObject($this->renderers[$ext]); } /** @var ViewRenderer $renderer */ $renderer = $this->renderers[$ext]; $output = $renderer->render($this, $viewFile, $params); } else { - Yii::trace("Rendering view file: $viewFile", __METHOD__); $output = $this->renderPhpFile($viewFile, $params); } $this->afterRender($viewFile, $output); @@ -328,7 +273,7 @@ class View extends Component * @param array $_params_ the parameters (name-value pairs) that will be extracted and made available in the view file. * @return string the rendering result */ - public function renderPhpFile($_file_, $_params_ = array()) + public function renderPhpFile($_file_, $_params_ = []) { ob_start(); ob_implicit_flush(false); @@ -393,11 +338,11 @@ class View extends Component */ public function beginBlock($id, $renderInPlace = false) { - return Block::begin(array( + return Block::begin([ 'id' => $id, 'renderInPlace' => $renderInPlace, 'view' => $this, - )); + ]); } /** @@ -411,10 +356,10 @@ class View extends Component /** * Begins the rendering of content that is to be decorated by the specified view. * This method can be used to implement nested layout. For example, a layout can be embedded - * in another layout file specified as '@app/view/layouts/base.php' like the following: + * in another layout file specified as '@app/views/layouts/base.php' like the following: * * ~~~ - * <?php $this->beginContent('@app/view/layouts/base.php'); ?> + * <?php $this->beginContent('@app/views/layouts/base.php'); ?> * ...layout content here... * <?php $this->endContent(); ?> * ~~~ @@ -425,13 +370,13 @@ class View extends Component * @return ContentDecorator the ContentDecorator widget instance * @see ContentDecorator */ - public function beginContent($viewFile, $params = array()) + public function beginContent($viewFile, $params = []) { - return ContentDecorator::begin(array( + return ContentDecorator::begin([ 'viewFile' => $viewFile, 'params' => $params, 'view' => $this, - )); + ]); } /** @@ -461,11 +406,11 @@ class View extends Component * @return boolean whether you should generate the content for caching. * False if the cached version is available. */ - public function beginCache($id, $properties = array()) + public function beginCache($id, $properties = []) { $properties['id'] = $id; $properties['view'] = $this; - /** @var $cache FragmentCache */ + /** @var FragmentCache $cache */ $cache = FragmentCache::begin($properties); if ($cache->getCachedContent() !== false) { $this->endCache(); @@ -483,29 +428,8 @@ class View extends Component FragmentCache::end(); } - - private $_assetManager; - - /** - * Registers the asset manager being used by this view object. - * @return \yii\web\AssetManager the asset manager. Defaults to the "assetManager" application component. - */ - public function getAssetManager() - { - return $this->_assetManager ?: Yii::$app->getAssetManager(); - } - - /** - * Sets the asset manager. - * @param \yii\web\AssetManager $value the asset manager - */ - public function setAssetManager($value) - { - $this->_assetManager = $value; - } - /** - * Marks the beginning of an HTML page. + * Marks the beginning of a page. */ public function beginPage() { @@ -516,258 +440,11 @@ class View extends Component } /** - * Marks the ending of an HTML page. + * Marks the ending of a page. */ public function endPage() { $this->trigger(self::EVENT_END_PAGE); - - $content = ob_get_clean(); - echo strtr($content, array( - self::PH_HEAD => $this->renderHeadHtml(), - self::PH_BODY_BEGIN => $this->renderBodyBeginHtml(), - self::PH_BODY_END => $this->renderBodyEndHtml(), - )); - - unset( - $this->assetBundles, - $this->metaTags, - $this->linkTags, - $this->css, - $this->cssFiles, - $this->js, - $this->jsFiles - ); - } - - /** - * Marks the beginning of an HTML body section. - */ - public function beginBody() - { - echo self::PH_BODY_BEGIN; - $this->trigger(self::EVENT_BEGIN_BODY); - } - - /** - * Marks the ending of an HTML body section. - */ - public function endBody() - { - $this->trigger(self::EVENT_END_BODY); - echo self::PH_BODY_END; - } - - /** - * Marks the position of an HTML head section. - */ - public function head() - { - echo self::PH_HEAD; - } - - /** - * Registers the named asset bundle. - * All dependent asset bundles will be registered. - * @param string $name the name of the asset bundle. - * @return AssetBundle the registered asset bundle instance - * @throws InvalidConfigException if the asset bundle does not exist or a circular dependency is detected - */ - public function registerAssetBundle($name) - { - if (!isset($this->assetBundles[$name])) { - $am = $this->getAssetManager(); - $bundle = $am->getBundle($name); - $this->assetBundles[$name] = false; - $bundle->registerAssets($this); - $this->assetBundles[$name] = $bundle; - } elseif ($this->assetBundles[$name] === false) { - throw new InvalidConfigException("A circular dependency is detected for bundle '$name'."); - } - return $this->assetBundles[$name]; - } - - /** - * Registers a meta tag. - * @param array $options the HTML attributes for the meta tag. - * @param string $key the key that identifies the meta tag. If two meta tags are registered - * with the same key, the latter will overwrite the former. If this is null, the new meta tag - * will be appended to the existing ones. - */ - public function registerMetaTag($options, $key = null) - { - if ($key === null) { - $this->metaTags[] = Html::tag('meta', '', $options); - } else { - $this->metaTags[$key] = Html::tag('meta', '', $options); - } - } - - /** - * Registers a link tag. - * @param array $options the HTML attributes for the link tag. - * @param string $key the key that identifies the link tag. If two link tags are registered - * with the same key, the latter will overwrite the former. If this is null, the new link tag - * will be appended to the existing ones. - */ - public function registerLinkTag($options, $key = null) - { - if ($key === null) { - $this->linkTags[] = Html::tag('link', '', $options); - } else { - $this->linkTags[$key] = Html::tag('link', '', $options); - } - } - - /** - * Registers a CSS code block. - * @param string $css the CSS code block to be registered - * @param array $options the HTML attributes for the style tag. - * @param string $key the key that identifies the CSS code block. If null, it will use - * $css as the key. If two CSS code blocks are registered with the same key, the latter - * will overwrite the former. - */ - public function registerCss($css, $options = array(), $key = null) - { - $key = $key ?: md5($css); - $this->css[$key] = Html::style($css, $options); - } - - /** - * Registers a CSS file. - * @param string $url the CSS file to be registered. - * @param array $options the HTML attributes for the link tag. - * @param string $key the key that identifies the CSS script file. If null, it will use - * $url as the key. If two CSS files are registered with the same key, the latter - * will overwrite the former. - */ - public function registerCssFile($url, $options = array(), $key = null) - { - $key = $key ?: $url; - $this->cssFiles[$key] = Html::cssFile($url, $options); - } - - /** - * Registers a JS code block. - * @param string $js the JS code block to be registered - * @param integer $position the position at which the JS script tag should be inserted - * in a page. The possible values are: - * - * - [[POS_HEAD]]: in the head section - * - [[POS_BEGIN]]: at the beginning of the body section - * - [[POS_END]]: at the end of the body section - * - [[POS_READY]]: enclosed within jQuery(document).ready(). This is the default value. - * Note that by using this position, the method will automatically register the jQuery js file. - * - * @param string $key the key that identifies the JS code block. If null, it will use - * $js as the key. If two JS code blocks are registered with the same key, the latter - * will overwrite the former. - */ - public function registerJs($js, $position = self::POS_READY, $key = null) - { - $key = $key ?: md5($js); - $this->js[$position][$key] = $js; - if ($position === self::POS_READY) { - JqueryAsset::register($this); - } - } - - /** - * Registers a JS file. - * Please note that when this file depends on other JS files to be registered before, - * for example jQuery, you should use [[registerAssetBundle]] instead. - * @param string $url the JS file to be registered. - * @param array $options the HTML attributes for the script tag. A special option - * named "position" is supported which specifies where the JS script tag should be inserted - * in a page. The possible values of "position" are: - * - * - [[POS_HEAD]]: in the head section - * - [[POS_BEGIN]]: at the beginning of the body section - * - [[POS_END]]: at the end of the body section. This is the default value. - * - * @param string $key the key that identifies the JS script file. If null, it will use - * $url as the key. If two JS files are registered with the same key, the latter - * will overwrite the former. - */ - public function registerJsFile($url, $options = array(), $key = null) - { - $position = isset($options['position']) ? $options['position'] : self::POS_END; - unset($options['position']); - $key = $key ?: $url; - $this->jsFiles[$position][$key] = Html::jsFile($url, $options); - } - - /** - * Renders the content to be inserted in the head section. - * The content is rendered using the registered meta tags, link tags, CSS/JS code blocks and files. - * @return string the rendered content - */ - protected function renderHeadHtml() - { - $lines = array(); - if (!empty($this->metaTags)) { - $lines[] = implode("\n", $this->metaTags); - } - - $request = Yii::$app->getRequest(); - if ($request instanceof \yii\web\Request && $request->enableCsrfValidation) { - $lines[] = Html::tag('meta', '', array('name' => 'csrf-var', 'content' => $request->csrfVar)); - $lines[] = Html::tag('meta', '', array('name' => 'csrf-token', 'content' => $request->getCsrfToken())); - } - - if (!empty($this->linkTags)) { - $lines[] = implode("\n", $this->linkTags); - } - if (!empty($this->cssFiles)) { - $lines[] = implode("\n", $this->cssFiles); - } - if (!empty($this->css)) { - $lines[] = implode("\n", $this->css); - } - if (!empty($this->jsFiles[self::POS_HEAD])) { - $lines[] = implode("\n", $this->jsFiles[self::POS_HEAD]); - } - if (!empty($this->js[self::POS_HEAD])) { - $lines[] = Html::script(implode("\n", $this->js[self::POS_HEAD]), array('type' => 'text/javascript')); - } - return empty($lines) ? '' : implode("\n", $lines) . "\n"; - } - - /** - * Renders the content to be inserted at the beginning of the body section. - * The content is rendered using the registered JS code blocks and files. - * @return string the rendered content - */ - protected function renderBodyBeginHtml() - { - $lines = array(); - if (!empty($this->jsFiles[self::POS_BEGIN])) { - $lines[] = implode("\n", $this->jsFiles[self::POS_BEGIN]); - } - if (!empty($this->js[self::POS_BEGIN])) { - $lines[] = Html::script(implode("\n", $this->js[self::POS_BEGIN]), array('type' => 'text/javascript')); - } - return empty($lines) ? '' : implode("\n", $lines) . "\n"; - } - - /** - * Renders the content to be inserted at the end of the body section. - * The content is rendered using the registered JS code blocks and files. - * @return string the rendered content - */ - protected function renderBodyEndHtml() - { - $lines = array(); - if (!empty($this->jsFiles[self::POS_END])) { - $lines[] = implode("\n", $this->jsFiles[self::POS_END]); - } - if (!empty($this->js[self::POS_END])) { - $lines[] = Html::script(implode("\n", $this->js[self::POS_END]), array('type' => 'text/javascript')); - } - if (!empty($this->js[self::POS_READY])) { - $js = "jQuery(document).ready(function(){\n" . implode("\n", $this->js[self::POS_READY]) . "\n});"; - $lines[] = Html::script($js, array('type' => 'text/javascript')); - } - return empty($lines) ? '' : implode("\n", $lines) . "\n"; + ob_end_flush(); } } diff --git a/framework/yii/test/WebTestCase.php b/framework/yii/base/ViewContextInterface.php similarity index 52% rename from framework/yii/test/WebTestCase.php rename to framework/yii/base/ViewContextInterface.php index 39162c9..94f6751 100644 --- a/framework/yii/test/WebTestCase.php +++ b/framework/yii/base/ViewContextInterface.php @@ -1,25 +1,26 @@ <?php /** - * WebTestCase class. - * * @link http://www.yiiframework.com/ * @copyright Copyright (c) 2008 Yii Software LLC * @license http://www.yiiframework.com/license/ */ -namespace yii\test; - -require_once('PHPUnit/Runner/Version.php'); -spl_autoload_unregister(array('Yii','autoload')); -require_once('PHPUnit/Autoload.php'); -spl_autoload_register(array('Yii','autoload')); // put yii's autoloader at the end +namespace yii\base; /** - * WebTestCase is the base class for all test case classes. + * ViewContextInterface is the interface that should implemented by classes who want to support relative view names. + * + * The method [[findViewFile()]] should be implemented to convert a relative view name into a file path. * - * @author Qiang Xue <qiang.xue@gmail.com> + * @author Paul Klimov <klimov.paul@gmail.com> * @since 2.0 */ -abstract class WebTestCase extends \PHPUnit_Extensions_SeleniumTestCase +interface ViewContextInterface { + /** + * Finds the view file corresponding to the specified relative view name. + * @param string $view a relative view name. The name does NOT start with a slash. + * @return string the view file path. Note that the file may not exist. + */ + public function findViewFile($view); } diff --git a/framework/yii/base/ViewEvent.php b/framework/yii/base/ViewEvent.php index f1ee7b9..d02e180 100644 --- a/framework/yii/base/ViewEvent.php +++ b/framework/yii/base/ViewEvent.php @@ -8,6 +8,8 @@ namespace yii\base; /** + * ViewEvent represents events triggered by the [[View]] component. + * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ @@ -36,7 +38,7 @@ class ViewEvent extends Event * @param string $viewFile the view file path that is being rendered by [[View::renderFile()]]. * @param array $config name-value pairs that will be used to initialize the object properties */ - public function __construct($viewFile, $config = array()) + public function __construct($viewFile, $config = []) { $this->viewFile = $viewFile; parent::__construct($config); diff --git a/framework/yii/base/Widget.php b/framework/yii/base/Widget.php index 943d007..a0f7795 100644 --- a/framework/yii/base/Widget.php +++ b/framework/yii/base/Widget.php @@ -8,6 +8,7 @@ namespace yii\base; use Yii; +use ReflectionClass; /** * Widget is the base class for widgets. @@ -20,19 +21,19 @@ use Yii; * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ -class Widget extends Component +class Widget extends Component implements ViewContextInterface { /** * @var integer a counter used to generate [[id]] for widgets. * @internal */ - public static $_counter = 0; + public static $counter = 0; /** * @var Widget[] the widgets that are currently being rendered (not ended). This property * is maintained by [[begin()]] and [[end()]] methods. * @internal */ - public static $_stack = array(); + public static $stack = []; /** @@ -40,27 +41,27 @@ class Widget extends Component * This method creates an instance of the calling class. It will apply the configuration * to the created instance. A matching [[end()]] call should be called later. * @param array $config name-value pairs that will be used to initialize the object properties - * @return Widget the newly created widget instance + * @return static the newly created widget instance */ - public static function begin($config = array()) + public static function begin($config = []) { $config['class'] = get_called_class(); /** @var Widget $widget */ $widget = Yii::createObject($config); - self::$_stack[] = $widget; + self::$stack[] = $widget; return $widget; } /** * Ends a widget. * Note that the rendering result of the widget is directly echoed out. - * @return Widget the widget instance that is ended. + * @return static the widget instance that is ended. * @throws InvalidCallException if [[begin()]] and [[end()]] calls are not properly nested */ public static function end() { - if (!empty(self::$_stack)) { - $widget = array_pop(self::$_stack); + if (!empty(self::$stack)) { + $widget = array_pop(self::$stack); if (get_class($widget) === get_called_class()) { $widget->run(); return $widget; @@ -78,7 +79,7 @@ class Widget extends Component * @param array $config name-value pairs that will be used to initialize the object properties * @return string the rendering result of the widget. */ - public static function widget($config = array()) + public static function widget($config = []) { ob_start(); ob_implicit_flush(false); @@ -99,7 +100,7 @@ class Widget extends Component public function getId($autoGenerate = true) { if ($autoGenerate && $this->_id === null) { - $this->_id = 'w' . self::$_counter++; + $this->_id = 'w' . self::$counter++; } return $this->_id; } @@ -165,10 +166,9 @@ class Widget extends Component * @return string the rendering result. * @throws InvalidParamException if the view file does not exist. */ - public function render($view, $params = array()) + public function render($view, $params = []) { - $viewFile = $this->findViewFile($view); - return $this->getView()->renderFile($viewFile, $params, $this); + return $this->getView()->render($view, $params, $this); } /** @@ -178,7 +178,7 @@ class Widget extends Component * @return string the rendering result. * @throws InvalidParamException if the view file does not exist. */ - public function renderFile($file, $params = array()) + public function renderFile($file, $params = []) { return $this->getView()->renderFile($file, $params, $this); } @@ -190,32 +190,18 @@ class Widget extends Component */ public function getViewPath() { - $className = get_class($this); - $class = new \ReflectionClass($className); + $class = new ReflectionClass($this); return dirname($class->getFileName()) . DIRECTORY_SEPARATOR . 'views'; } /** * Finds the view file based on the given view name. - * @param string $view the view name or the path alias of the view file. Please refer to [[render()]] - * on how to specify this parameter. + * File will be searched under [[viewPath]] directory. + * @param string $view the view name. * @return string the view file path. Note that the file may not exist. */ - protected function findViewFile($view) + public function findViewFile($view) { - if (strncmp($view, '@', 1) === 0) { - // e.g. "@app/views/main" - $file = Yii::getAlias($view); - } elseif (strncmp($view, '//', 2) === 0) { - // e.g. "//layouts/main" - $file = Yii::$app->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/'); - } elseif (strncmp($view, '/', 1) === 0 && Yii::$app->controller !== null) { - // e.g. "/site/index" - $file = Yii::$app->controller->module->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/'); - } else { - $file = $this->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/'); - } - - return pathinfo($file, PATHINFO_EXTENSION) === '' ? $file . '.php' : $file; + return $this->getViewPath() . DIRECTORY_SEPARATOR . $view; } } diff --git a/framework/yii/behaviors/AutoTimestamp.php b/framework/yii/behaviors/AutoTimestamp.php index 63a3ef0..0e0844a 100644 --- a/framework/yii/behaviors/AutoTimestamp.php +++ b/framework/yii/behaviors/AutoTimestamp.php @@ -21,11 +21,9 @@ use yii\db\ActiveRecord; * ~~~ * public function behaviors() * { - * return array( - * 'timestamp' => array( - * 'class' => 'yii\behaviors\AutoTimestamp', - * ), - * ); + * return [ + * 'timestamp' => ['class' => 'yii\behaviors\AutoTimestamp'], + * ]; * } * ~~~ * @@ -46,10 +44,10 @@ class AutoTimestamp extends Behavior * The default setting is to update the `create_time` attribute upon AR insertion, * and update the `update_time` attribute upon AR updating. */ - public $attributes = array( + public $attributes = [ ActiveRecord::EVENT_BEFORE_INSERT => 'create_time', ActiveRecord::EVENT_BEFORE_UPDATE => 'update_time', - ); + ]; /** * @var \Closure|Expression The expression that will be used for generating the timestamp. * This can be either an anonymous function that returns the timestamp value, @@ -78,7 +76,7 @@ class AutoTimestamp extends Behavior */ public function updateTimestamp($event) { - $attributes = isset($this->attributes[$event->name]) ? (array)$this->attributes[$event->name] : array(); + $attributes = isset($this->attributes[$event->name]) ? (array)$this->attributes[$event->name] : []; if (!empty($attributes)) { $timestamp = $this->evaluateTimestamp(); foreach ($attributes as $attribute) { diff --git a/framework/yii/bootstrap/assets/css/bootstrap-theme.css b/framework/yii/bootstrap/assets/css/bootstrap-theme.css deleted file mode 100644 index ad11735..0000000 --- a/framework/yii/bootstrap/assets/css/bootstrap-theme.css +++ /dev/null @@ -1,384 +0,0 @@ -.btn-default, -.btn-primary, -.btn-success, -.btn-info, -.btn-warning, -.btn-danger { - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2); - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075); -} - -.btn-default:active, -.btn-primary:active, -.btn-success:active, -.btn-info:active, -.btn-warning:active, -.btn-danger:active, -.btn-default.active, -.btn-primary.active, -.btn-success.active, -.btn-info.active, -.btn-warning.active, -.btn-danger.active { - -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); - box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); -} - -.btn:active, -.btn.active { - background-image: none; -} - -.btn-default { - text-shadow: 0 1px 0 #fff; - background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ffffff), to(#e6e6e6)); - background-image: -webkit-linear-gradient(top, #ffffff, 0%, #e6e6e6, 100%); - background-image: -moz-linear-gradient(top, #ffffff 0%, #e6e6e6 100%); - background-image: linear-gradient(to bottom, #ffffff 0%, #e6e6e6 100%); - background-repeat: repeat-x; - border-color: #e0e0e0; - border-color: #ccc; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0); -} - -.btn-default:active, -.btn-default.active { - background-color: #e6e6e6; - border-color: #e0e0e0; -} - -.btn-primary { - background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3071a9)); - background-image: -webkit-linear-gradient(top, #428bca, 0%, #3071a9, 100%); - background-image: -moz-linear-gradient(top, #428bca 0%, #3071a9 100%); - background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%); - background-repeat: repeat-x; - border-color: #2d6ca2; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0); -} - -.btn-primary:active, -.btn-primary.active { - background-color: #3071a9; - border-color: #2d6ca2; -} - -.btn-success { - background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5cb85c), to(#449d44)); - background-image: -webkit-linear-gradient(top, #5cb85c, 0%, #449d44, 100%); - background-image: -moz-linear-gradient(top, #5cb85c 0%, #449d44 100%); - background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); - background-repeat: repeat-x; - border-color: #419641; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); -} - -.btn-success:active, -.btn-success.active { - background-color: #449d44; - border-color: #419641; -} - -.btn-warning { - background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f0ad4e), to(#ec971f)); - background-image: -webkit-linear-gradient(top, #f0ad4e, 0%, #ec971f, 100%); - background-image: -moz-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); - background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); - background-repeat: repeat-x; - border-color: #eb9316; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); -} - -.btn-warning:active, -.btn-warning.active { - background-color: #ec971f; - border-color: #eb9316; -} - -.btn-danger { - background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9534f), to(#c9302c)); - background-image: -webkit-linear-gradient(top, #d9534f, 0%, #c9302c, 100%); - background-image: -moz-linear-gradient(top, #d9534f 0%, #c9302c 100%); - background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); - background-repeat: repeat-x; - border-color: #c12e2a; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); -} - -.btn-danger:active, -.btn-danger.active { - background-color: #c9302c; - border-color: #c12e2a; -} - -.btn-info { - background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5bc0de), to(#31b0d5)); - background-image: -webkit-linear-gradient(top, #5bc0de, 0%, #31b0d5, 100%); - background-image: -moz-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); - background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); - background-repeat: repeat-x; - border-color: #2aabd2; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); -} - -.btn-info:active, -.btn-info.active { - background-color: #31b0d5; - border-color: #2aabd2; -} - -.thumbnail, -.img-thumbnail { - -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); -} - -.dropdown-menu > li > a:hover, -.dropdown-menu > li > a:focus, -.dropdown-menu > .active > a, -.dropdown-menu > .active > a:hover, -.dropdown-menu > .active > a:focus { - background-color: #357ebd; - background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#357ebd)); - background-image: -webkit-linear-gradient(top, #428bca, 0%, #357ebd, 100%); - background-image: -moz-linear-gradient(top, #428bca 0%, #357ebd 100%); - background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0); -} - -.navbar { - background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ffffff), to(#f8f8f8)); - background-image: -webkit-linear-gradient(top, #ffffff, 0%, #f8f8f8, 100%); - background-image: -moz-linear-gradient(top, #ffffff 0%, #f8f8f8 100%); - background-image: linear-gradient(to bottom, #ffffff 0%, #f8f8f8 100%); - background-repeat: repeat-x; - border-radius: 4px; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0); - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075); -} - -.navbar .navbar-nav > .active > a { - background-color: #f8f8f8; -} - -.navbar-brand, -.navbar-nav > li > a { - text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25); -} - -.navbar-inverse { - background-image: -webkit-gradient(linear, left 0%, left 100%, from(#3c3c3c), to(#222222)); - background-image: -webkit-linear-gradient(top, #3c3c3c, 0%, #222222, 100%); - background-image: -moz-linear-gradient(top, #3c3c3c 0%, #222222 100%); - background-image: linear-gradient(to bottom, #3c3c3c 0%, #222222 100%); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0); -} - -.navbar-inverse .navbar-nav > .active > a { - background-color: #222222; -} - -.navbar-inverse .navbar-brand, -.navbar-inverse .navbar-nav > li > a { - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); -} - -.navbar-static-top, -.navbar-fixed-top, -.navbar-fixed-bottom { - border-radius: 0; -} - -.alert { - text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2); - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05); -} - -.alert-success { - background-image: -webkit-gradient(linear, left 0%, left 100%, from(#dff0d8), to(#c8e5bc)); - background-image: -webkit-linear-gradient(top, #dff0d8, 0%, #c8e5bc, 100%); - background-image: -moz-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); - background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%); - background-repeat: repeat-x; - border-color: #b2dba1; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0); -} - -.alert-info { - background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9edf7), to(#b9def0)); - background-image: -webkit-linear-gradient(top, #d9edf7, 0%, #b9def0, 100%); - background-image: -moz-linear-gradient(top, #d9edf7 0%, #b9def0 100%); - background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%); - background-repeat: repeat-x; - border-color: #9acfea; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0); -} - -.alert-warning { - background-image: -webkit-gradient(linear, left 0%, left 100%, from(#fcf8e3), to(#f8efc0)); - background-image: -webkit-linear-gradient(top, #fcf8e3, 0%, #f8efc0, 100%); - background-image: -moz-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); - background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%); - background-repeat: repeat-x; - border-color: #f5e79e; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0); -} - -.alert-danger { - background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f2dede), to(#e7c3c3)); - background-image: -webkit-linear-gradient(top, #f2dede, 0%, #e7c3c3, 100%); - background-image: -moz-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); - background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%); - background-repeat: repeat-x; - border-color: #dca7a7; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0); -} - -.progress { - background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ebebeb), to(#f5f5f5)); - background-image: -webkit-linear-gradient(top, #ebebeb, 0%, #f5f5f5, 100%); - background-image: -moz-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); - background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0); -} - -.progress-bar { - background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3071a9)); - background-image: -webkit-linear-gradient(top, #428bca, 0%, #3071a9, 100%); - background-image: -moz-linear-gradient(top, #428bca 0%, #3071a9 100%); - background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0); -} - -.progress-bar-success { - background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5cb85c), to(#449d44)); - background-image: -webkit-linear-gradient(top, #5cb85c, 0%, #449d44, 100%); - background-image: -moz-linear-gradient(top, #5cb85c 0%, #449d44 100%); - background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); -} - -.progress-bar-info { - background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5bc0de), to(#31b0d5)); - background-image: -webkit-linear-gradient(top, #5bc0de, 0%, #31b0d5, 100%); - background-image: -moz-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); - background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); -} - -.progress-bar-warning { - background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f0ad4e), to(#ec971f)); - background-image: -webkit-linear-gradient(top, #f0ad4e, 0%, #ec971f, 100%); - background-image: -moz-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); - background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); -} - -.progress-bar-danger { - background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9534f), to(#c9302c)); - background-image: -webkit-linear-gradient(top, #d9534f, 0%, #c9302c, 100%); - background-image: -moz-linear-gradient(top, #d9534f 0%, #c9302c 100%); - background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); -} - -.list-group { - border-radius: 4px; - -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); -} - -.list-group-item.active, -.list-group-item.active:hover, -.list-group-item.active:focus { - text-shadow: 0 -1px 0 #3071a9; - background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3278b3)); - background-image: -webkit-linear-gradient(top, #428bca, 0%, #3278b3, 100%); - background-image: -moz-linear-gradient(top, #428bca 0%, #3278b3 100%); - background-image: linear-gradient(to bottom, #428bca 0%, #3278b3 100%); - background-repeat: repeat-x; - border-color: #3278b3; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0); -} - -.panel { - -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); -} - -.panel-default > .panel-heading { - background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f5f5f5), to(#e8e8e8)); - background-image: -webkit-linear-gradient(top, #f5f5f5, 0%, #e8e8e8, 100%); - background-image: -moz-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); - background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); -} - -.panel-primary > .panel-heading { - background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#357ebd)); - background-image: -webkit-linear-gradient(top, #428bca, 0%, #357ebd, 100%); - background-image: -moz-linear-gradient(top, #428bca 0%, #357ebd 100%); - background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0); -} - -.panel-success > .panel-heading { - background-image: -webkit-gradient(linear, left 0%, left 100%, from(#dff0d8), to(#d0e9c6)); - background-image: -webkit-linear-gradient(top, #dff0d8, 0%, #d0e9c6, 100%); - background-image: -moz-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); - background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0); -} - -.panel-info > .panel-heading { - background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9edf7), to(#c4e3f3)); - background-image: -webkit-linear-gradient(top, #d9edf7, 0%, #c4e3f3, 100%); - background-image: -moz-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); - background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0); -} - -.panel-warning > .panel-heading { - background-image: -webkit-gradient(linear, left 0%, left 100%, from(#fcf8e3), to(#faf2cc)); - background-image: -webkit-linear-gradient(top, #fcf8e3, 0%, #faf2cc, 100%); - background-image: -moz-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); - background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0); -} - -.panel-danger > .panel-heading { - background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f2dede), to(#ebcccc)); - background-image: -webkit-linear-gradient(top, #f2dede, 0%, #ebcccc, 100%); - background-image: -moz-linear-gradient(top, #f2dede 0%, #ebcccc 100%); - background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0); -} - -.well { - background-image: -webkit-gradient(linear, left 0%, left 100%, from(#e8e8e8), to(#f5f5f5)); - background-image: -webkit-linear-gradient(top, #e8e8e8, 0%, #f5f5f5, 100%); - background-image: -moz-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); - background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%); - background-repeat: repeat-x; - border-color: #dcdcdc; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0); - -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1); - box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1); -} \ No newline at end of file diff --git a/framework/yii/bootstrap/assets/css/bootstrap.css b/framework/yii/bootstrap/assets/css/bootstrap.css deleted file mode 100644 index bbda4ee..0000000 --- a/framework/yii/bootstrap/assets/css/bootstrap.css +++ /dev/null @@ -1,6805 +0,0 @@ -/*! - * Bootstrap v3.0.0 - * - * Copyright 2013 Twitter, Inc - * Licensed under the Apache License v2.0 - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Designed and built with all the love in the world by @mdo and @fat. - */ - -/*! normalize.css v2.1.0 | MIT License | git.io/normalize */ - -article, -aside, -details, -figcaption, -figure, -footer, -header, -hgroup, -main, -nav, -section, -summary { - display: block; -} - -audio, -canvas, -video { - display: inline-block; -} - -audio:not([controls]) { - display: none; - height: 0; -} - -[hidden] { - display: none; -} - -html { - font-family: sans-serif; - -webkit-text-size-adjust: 100%; - -ms-text-size-adjust: 100%; -} - -body { - margin: 0; -} - -a:focus { - outline: thin dotted; -} - -a:active, -a:hover { - outline: 0; -} - -h1 { - margin: 0.67em 0; - font-size: 2em; -} - -abbr[title] { - border-bottom: 1px dotted; -} - -b, -strong { - font-weight: bold; -} - -dfn { - font-style: italic; -} - -hr { - height: 0; - -moz-box-sizing: content-box; - box-sizing: content-box; -} - -mark { - color: #000; - background: #ff0; -} - -code, -kbd, -pre, -samp { - font-family: monospace, serif; - font-size: 1em; -} - -pre { - white-space: pre-wrap; -} - -q { - quotes: "\201C" "\201D" "\2018" "\2019"; -} - -small { - font-size: 80%; -} - -sub, -sup { - position: relative; - font-size: 75%; - line-height: 0; - vertical-align: baseline; -} - -sup { - top: -0.5em; -} - -sub { - bottom: -0.25em; -} - -img { - border: 0; -} - -svg:not(:root) { - overflow: hidden; -} - -figure { - margin: 0; -} - -fieldset { - padding: 0.35em 0.625em 0.75em; - margin: 0 2px; - border: 1px solid #c0c0c0; -} - -legend { - padding: 0; - border: 0; -} - -button, -input, -select, -textarea { - margin: 0; - font-family: inherit; - font-size: 100%; -} - -button, -input { - line-height: normal; -} - -button, -select { - text-transform: none; -} - -button, -html input[type="button"], -input[type="reset"], -input[type="submit"] { - cursor: pointer; - -webkit-appearance: button; -} - -button[disabled], -html input[disabled] { - cursor: default; -} - -input[type="checkbox"], -input[type="radio"] { - padding: 0; - box-sizing: border-box; -} - -input[type="search"] { - -webkit-box-sizing: content-box; - -moz-box-sizing: content-box; - box-sizing: content-box; - -webkit-appearance: textfield; -} - -input[type="search"]::-webkit-search-cancel-button, -input[type="search"]::-webkit-search-decoration { - -webkit-appearance: none; -} - -button::-moz-focus-inner, -input::-moz-focus-inner { - padding: 0; - border: 0; -} - -textarea { - overflow: auto; - vertical-align: top; -} - -table { - border-collapse: collapse; - border-spacing: 0; -} - -@media print { - * { - color: #000 !important; - text-shadow: none !important; - background: transparent !important; - box-shadow: none !important; - } - a, - a:visited { - text-decoration: underline; - } - a[href]:after { - content: " (" attr(href) ")"; - } - abbr[title]:after { - content: " (" attr(title) ")"; - } - .ir a:after, - a[href^="javascript:"]:after, - a[href^="#"]:after { - content: ""; - } - pre, - blockquote { - border: 1px solid #999; - page-break-inside: avoid; - } - thead { - display: table-header-group; - } - tr, - img { - page-break-inside: avoid; - } - img { - max-width: 100% !important; - } - @page { - margin: 2cm .5cm; - } - p, - h2, - h3 { - orphans: 3; - widows: 3; - } - h2, - h3 { - page-break-after: avoid; - } - .navbar { - display: none; - } - .table td, - .table th { - background-color: #fff !important; - } - .btn > .caret, - .dropup > .btn > .caret { - border-top-color: #000 !important; - } - .label { - border: 1px solid #000; - } - .table { - border-collapse: collapse !important; - } - .table-bordered th, - .table-bordered td { - border: 1px solid #ddd !important; - } -} - -*, -*:before, -*:after { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -html { - font-size: 62.5%; - -webkit-tap-highlight-color: rgba(0, 0, 0, 0); -} - -body { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 14px; - line-height: 1.428571429; - color: #333333; - background-color: #ffffff; -} - -input, -button, -select, -textarea { - font-family: inherit; - font-size: inherit; - line-height: inherit; -} - -button, -input, -select[multiple], -textarea { - background-image: none; -} - -a { - color: #428bca; - text-decoration: none; -} - -a:hover, -a:focus { - color: #2a6496; - text-decoration: underline; -} - -a:focus { - outline: thin dotted #333; - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px; -} - -img { - vertical-align: middle; -} - -.img-responsive { - display: block; - height: auto; - max-width: 100%; -} - -.img-rounded { - border-radius: 6px; -} - -.img-thumbnail { - display: inline-block; - height: auto; - max-width: 100%; - padding: 4px; - line-height: 1.428571429; - background-color: #ffffff; - border: 1px solid #dddddd; - border-radius: 4px; - -webkit-transition: all 0.2s ease-in-out; - transition: all 0.2s ease-in-out; -} - -.img-circle { - border-radius: 50%; -} - -hr { - margin-top: 20px; - margin-bottom: 20px; - border: 0; - border-top: 1px solid #eeeeee; -} - -.sr-only { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0 0 0 0); - border: 0; -} - -p { - margin: 0 0 10px; -} - -.lead { - margin-bottom: 20px; - font-size: 16.099999999999998px; - font-weight: 200; - line-height: 1.4; -} - -@media (min-width: 768px) { - .lead { - font-size: 21px; - } -} - -small { - font-size: 85%; -} - -cite { - font-style: normal; -} - -.text-muted { - color: #999999; -} - -.text-primary { - color: #428bca; -} - -.text-warning { - color: #c09853; -} - -.text-danger { - color: #b94a48; -} - -.text-success { - color: #468847; -} - -.text-info { - color: #3a87ad; -} - -.text-left { - text-align: left; -} - -.text-right { - text-align: right; -} - -.text-center { - text-align: center; -} - -h1, -h2, -h3, -h4, -h5, -h6, -.h1, -.h2, -.h3, -.h4, -.h5, -.h6 { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-weight: 500; - line-height: 1.1; -} - -h1 small, -h2 small, -h3 small, -h4 small, -h5 small, -h6 small, -.h1 small, -.h2 small, -.h3 small, -.h4 small, -.h5 small, -.h6 small { - font-weight: normal; - line-height: 1; - color: #999999; -} - -h1, -h2, -h3 { - margin-top: 20px; - margin-bottom: 10px; -} - -h4, -h5, -h6 { - margin-top: 10px; - margin-bottom: 10px; -} - -h1, -.h1 { - font-size: 36px; -} - -h2, -.h2 { - font-size: 30px; -} - -h3, -.h3 { - font-size: 24px; -} - -h4, -.h4 { - font-size: 18px; -} - -h5, -.h5 { - font-size: 14px; -} - -h6, -.h6 { - font-size: 12px; -} - -h1 small, -.h1 small { - font-size: 24px; -} - -h2 small, -.h2 small { - font-size: 18px; -} - -h3 small, -.h3 small, -h4 small, -.h4 small { - font-size: 14px; -} - -.page-header { - padding-bottom: 9px; - margin: 40px 0 20px; - border-bottom: 1px solid #eeeeee; -} - -ul, -ol { - margin-top: 0; - margin-bottom: 10px; -} - -ul ul, -ol ul, -ul ol, -ol ol { - margin-bottom: 0; -} - -.list-unstyled { - padding-left: 0; - list-style: none; -} - -.list-inline { - padding-left: 0; - list-style: none; -} - -.list-inline > li { - display: inline-block; - padding-right: 5px; - padding-left: 5px; -} - -dl { - margin-bottom: 20px; -} - -dt, -dd { - line-height: 1.428571429; -} - -dt { - font-weight: bold; -} - -dd { - margin-left: 0; -} - -@media (min-width: 768px) { - .dl-horizontal dt { - float: left; - width: 160px; - overflow: hidden; - clear: left; - text-align: right; - text-overflow: ellipsis; - white-space: nowrap; - } - .dl-horizontal dd { - margin-left: 180px; - } - .dl-horizontal dd:before, - .dl-horizontal dd:after { - display: table; - content: " "; - } - .dl-horizontal dd:after { - clear: both; - } - .dl-horizontal dd:before, - .dl-horizontal dd:after { - display: table; - content: " "; - } - .dl-horizontal dd:after { - clear: both; - } -} - -abbr[title], -abbr[data-original-title] { - cursor: help; - border-bottom: 1px dotted #999999; -} - -abbr.initialism { - font-size: 90%; - text-transform: uppercase; -} - -blockquote { - padding: 10px 20px; - margin: 0 0 20px; - border-left: 5px solid #eeeeee; -} - -blockquote p { - font-size: 17.5px; - font-weight: 300; - line-height: 1.25; -} - -blockquote p:last-child { - margin-bottom: 0; -} - -blockquote small { - display: block; - line-height: 1.428571429; - color: #999999; -} - -blockquote small:before { - content: '\2014 \00A0'; -} - -blockquote.pull-right { - padding-right: 15px; - padding-left: 0; - border-right: 5px solid #eeeeee; - border-left: 0; -} - -blockquote.pull-right p, -blockquote.pull-right small { - text-align: right; -} - -blockquote.pull-right small:before { - content: ''; -} - -blockquote.pull-right small:after { - content: '\00A0 \2014'; -} - -q:before, -q:after, -blockquote:before, -blockquote:after { - content: ""; -} - -address { - display: block; - margin-bottom: 20px; - font-style: normal; - line-height: 1.428571429; -} - -code, -pre { - font-family: Monaco, Menlo, Consolas, "Courier New", monospace; -} - -code { - padding: 2px 4px; - font-size: 90%; - color: #c7254e; - white-space: nowrap; - background-color: #f9f2f4; - border-radius: 4px; -} - -pre { - display: block; - padding: 9.5px; - margin: 0 0 10px; - font-size: 13px; - line-height: 1.428571429; - color: #333333; - word-break: break-all; - word-wrap: break-word; - background-color: #f5f5f5; - border: 1px solid #cccccc; - border-radius: 4px; -} - -pre.prettyprint { - margin-bottom: 20px; -} - -pre code { - padding: 0; - font-size: inherit; - color: inherit; - white-space: pre-wrap; - background-color: transparent; - border: 0; -} - -.pre-scrollable { - max-height: 340px; - overflow-y: scroll; -} - -.container { - padding-right: 15px; - padding-left: 15px; - margin-right: auto; - margin-left: auto; -} - -.container:before, -.container:after { - display: table; - content: " "; -} - -.container:after { - clear: both; -} - -.container:before, -.container:after { - display: table; - content: " "; -} - -.container:after { - clear: both; -} - -.row { - margin-right: -15px; - margin-left: -15px; -} - -.row:before, -.row:after { - display: table; - content: " "; -} - -.row:after { - clear: both; -} - -.row:before, -.row:after { - display: table; - content: " "; -} - -.row:after { - clear: both; -} - -.col-xs-1, -.col-xs-2, -.col-xs-3, -.col-xs-4, -.col-xs-5, -.col-xs-6, -.col-xs-7, -.col-xs-8, -.col-xs-9, -.col-xs-10, -.col-xs-11, -.col-xs-12, -.col-sm-1, -.col-sm-2, -.col-sm-3, -.col-sm-4, -.col-sm-5, -.col-sm-6, -.col-sm-7, -.col-sm-8, -.col-sm-9, -.col-sm-10, -.col-sm-11, -.col-sm-12, -.col-md-1, -.col-md-2, -.col-md-3, -.col-md-4, -.col-md-5, -.col-md-6, -.col-md-7, -.col-md-8, -.col-md-9, -.col-md-10, -.col-md-11, -.col-md-12, -.col-lg-1, -.col-lg-2, -.col-lg-3, -.col-lg-4, -.col-lg-5, -.col-lg-6, -.col-lg-7, -.col-lg-8, -.col-lg-9, -.col-lg-10, -.col-lg-11, -.col-lg-12 { - position: relative; - min-height: 1px; - padding-right: 15px; - padding-left: 15px; -} - -.col-xs-1, -.col-xs-2, -.col-xs-3, -.col-xs-4, -.col-xs-5, -.col-xs-6, -.col-xs-7, -.col-xs-8, -.col-xs-9, -.col-xs-10, -.col-xs-11 { - float: left; -} - -.col-xs-1 { - width: 8.333333333333332%; -} - -.col-xs-2 { - width: 16.666666666666664%; -} - -.col-xs-3 { - width: 25%; -} - -.col-xs-4 { - width: 33.33333333333333%; -} - -.col-xs-5 { - width: 41.66666666666667%; -} - -.col-xs-6 { - width: 50%; -} - -.col-xs-7 { - width: 58.333333333333336%; -} - -.col-xs-8 { - width: 66.66666666666666%; -} - -.col-xs-9 { - width: 75%; -} - -.col-xs-10 { - width: 83.33333333333334%; -} - -.col-xs-11 { - width: 91.66666666666666%; -} - -.col-xs-12 { - width: 100%; -} - -@media (min-width: 768px) { - .container { - max-width: 750px; - } - .col-sm-1, - .col-sm-2, - .col-sm-3, - .col-sm-4, - .col-sm-5, - .col-sm-6, - .col-sm-7, - .col-sm-8, - .col-sm-9, - .col-sm-10, - .col-sm-11 { - float: left; - } - .col-sm-1 { - width: 8.333333333333332%; - } - .col-sm-2 { - width: 16.666666666666664%; - } - .col-sm-3 { - width: 25%; - } - .col-sm-4 { - width: 33.33333333333333%; - } - .col-sm-5 { - width: 41.66666666666667%; - } - .col-sm-6 { - width: 50%; - } - .col-sm-7 { - width: 58.333333333333336%; - } - .col-sm-8 { - width: 66.66666666666666%; - } - .col-sm-9 { - width: 75%; - } - .col-sm-10 { - width: 83.33333333333334%; - } - .col-sm-11 { - width: 91.66666666666666%; - } - .col-sm-12 { - width: 100%; - } - .col-sm-push-1 { - left: 8.333333333333332%; - } - .col-sm-push-2 { - left: 16.666666666666664%; - } - .col-sm-push-3 { - left: 25%; - } - .col-sm-push-4 { - left: 33.33333333333333%; - } - .col-sm-push-5 { - left: 41.66666666666667%; - } - .col-sm-push-6 { - left: 50%; - } - .col-sm-push-7 { - left: 58.333333333333336%; - } - .col-sm-push-8 { - left: 66.66666666666666%; - } - .col-sm-push-9 { - left: 75%; - } - .col-sm-push-10 { - left: 83.33333333333334%; - } - .col-sm-push-11 { - left: 91.66666666666666%; - } - .col-sm-pull-1 { - right: 8.333333333333332%; - } - .col-sm-pull-2 { - right: 16.666666666666664%; - } - .col-sm-pull-3 { - right: 25%; - } - .col-sm-pull-4 { - right: 33.33333333333333%; - } - .col-sm-pull-5 { - right: 41.66666666666667%; - } - .col-sm-pull-6 { - right: 50%; - } - .col-sm-pull-7 { - right: 58.333333333333336%; - } - .col-sm-pull-8 { - right: 66.66666666666666%; - } - .col-sm-pull-9 { - right: 75%; - } - .col-sm-pull-10 { - right: 83.33333333333334%; - } - .col-sm-pull-11 { - right: 91.66666666666666%; - } - .col-sm-offset-1 { - margin-left: 8.333333333333332%; - } - .col-sm-offset-2 { - margin-left: 16.666666666666664%; - } - .col-sm-offset-3 { - margin-left: 25%; - } - .col-sm-offset-4 { - margin-left: 33.33333333333333%; - } - .col-sm-offset-5 { - margin-left: 41.66666666666667%; - } - .col-sm-offset-6 { - margin-left: 50%; - } - .col-sm-offset-7 { - margin-left: 58.333333333333336%; - } - .col-sm-offset-8 { - margin-left: 66.66666666666666%; - } - .col-sm-offset-9 { - margin-left: 75%; - } - .col-sm-offset-10 { - margin-left: 83.33333333333334%; - } - .col-sm-offset-11 { - margin-left: 91.66666666666666%; - } -} - -@media (min-width: 992px) { - .container { - max-width: 970px; - } - .col-md-1, - .col-md-2, - .col-md-3, - .col-md-4, - .col-md-5, - .col-md-6, - .col-md-7, - .col-md-8, - .col-md-9, - .col-md-10, - .col-md-11 { - float: left; - } - .col-md-1 { - width: 8.333333333333332%; - } - .col-md-2 { - width: 16.666666666666664%; - } - .col-md-3 { - width: 25%; - } - .col-md-4 { - width: 33.33333333333333%; - } - .col-md-5 { - width: 41.66666666666667%; - } - .col-md-6 { - width: 50%; - } - .col-md-7 { - width: 58.333333333333336%; - } - .col-md-8 { - width: 66.66666666666666%; - } - .col-md-9 { - width: 75%; - } - .col-md-10 { - width: 83.33333333333334%; - } - .col-md-11 { - width: 91.66666666666666%; - } - .col-md-12 { - width: 100%; - } - .col-md-push-0 { - left: auto; - } - .col-md-push-1 { - left: 8.333333333333332%; - } - .col-md-push-2 { - left: 16.666666666666664%; - } - .col-md-push-3 { - left: 25%; - } - .col-md-push-4 { - left: 33.33333333333333%; - } - .col-md-push-5 { - left: 41.66666666666667%; - } - .col-md-push-6 { - left: 50%; - } - .col-md-push-7 { - left: 58.333333333333336%; - } - .col-md-push-8 { - left: 66.66666666666666%; - } - .col-md-push-9 { - left: 75%; - } - .col-md-push-10 { - left: 83.33333333333334%; - } - .col-md-push-11 { - left: 91.66666666666666%; - } - .col-md-pull-0 { - right: auto; - } - .col-md-pull-1 { - right: 8.333333333333332%; - } - .col-md-pull-2 { - right: 16.666666666666664%; - } - .col-md-pull-3 { - right: 25%; - } - .col-md-pull-4 { - right: 33.33333333333333%; - } - .col-md-pull-5 { - right: 41.66666666666667%; - } - .col-md-pull-6 { - right: 50%; - } - .col-md-pull-7 { - right: 58.333333333333336%; - } - .col-md-pull-8 { - right: 66.66666666666666%; - } - .col-md-pull-9 { - right: 75%; - } - .col-md-pull-10 { - right: 83.33333333333334%; - } - .col-md-pull-11 { - right: 91.66666666666666%; - } - .col-md-offset-0 { - margin-left: 0; - } - .col-md-offset-1 { - margin-left: 8.333333333333332%; - } - .col-md-offset-2 { - margin-left: 16.666666666666664%; - } - .col-md-offset-3 { - margin-left: 25%; - } - .col-md-offset-4 { - margin-left: 33.33333333333333%; - } - .col-md-offset-5 { - margin-left: 41.66666666666667%; - } - .col-md-offset-6 { - margin-left: 50%; - } - .col-md-offset-7 { - margin-left: 58.333333333333336%; - } - .col-md-offset-8 { - margin-left: 66.66666666666666%; - } - .col-md-offset-9 { - margin-left: 75%; - } - .col-md-offset-10 { - margin-left: 83.33333333333334%; - } - .col-md-offset-11 { - margin-left: 91.66666666666666%; - } -} - -@media (min-width: 1200px) { - .container { - max-width: 1170px; - } - .col-lg-1, - .col-lg-2, - .col-lg-3, - .col-lg-4, - .col-lg-5, - .col-lg-6, - .col-lg-7, - .col-lg-8, - .col-lg-9, - .col-lg-10, - .col-lg-11 { - float: left; - } - .col-lg-1 { - width: 8.333333333333332%; - } - .col-lg-2 { - width: 16.666666666666664%; - } - .col-lg-3 { - width: 25%; - } - .col-lg-4 { - width: 33.33333333333333%; - } - .col-lg-5 { - width: 41.66666666666667%; - } - .col-lg-6 { - width: 50%; - } - .col-lg-7 { - width: 58.333333333333336%; - } - .col-lg-8 { - width: 66.66666666666666%; - } - .col-lg-9 { - width: 75%; - } - .col-lg-10 { - width: 83.33333333333334%; - } - .col-lg-11 { - width: 91.66666666666666%; - } - .col-lg-12 { - width: 100%; - } - .col-lg-push-0 { - left: auto; - } - .col-lg-push-1 { - left: 8.333333333333332%; - } - .col-lg-push-2 { - left: 16.666666666666664%; - } - .col-lg-push-3 { - left: 25%; - } - .col-lg-push-4 { - left: 33.33333333333333%; - } - .col-lg-push-5 { - left: 41.66666666666667%; - } - .col-lg-push-6 { - left: 50%; - } - .col-lg-push-7 { - left: 58.333333333333336%; - } - .col-lg-push-8 { - left: 66.66666666666666%; - } - .col-lg-push-9 { - left: 75%; - } - .col-lg-push-10 { - left: 83.33333333333334%; - } - .col-lg-push-11 { - left: 91.66666666666666%; - } - .col-lg-pull-0 { - right: auto; - } - .col-lg-pull-1 { - right: 8.333333333333332%; - } - .col-lg-pull-2 { - right: 16.666666666666664%; - } - .col-lg-pull-3 { - right: 25%; - } - .col-lg-pull-4 { - right: 33.33333333333333%; - } - .col-lg-pull-5 { - right: 41.66666666666667%; - } - .col-lg-pull-6 { - right: 50%; - } - .col-lg-pull-7 { - right: 58.333333333333336%; - } - .col-lg-pull-8 { - right: 66.66666666666666%; - } - .col-lg-pull-9 { - right: 75%; - } - .col-lg-pull-10 { - right: 83.33333333333334%; - } - .col-lg-pull-11 { - right: 91.66666666666666%; - } - .col-lg-offset-0 { - margin-left: 0; - } - .col-lg-offset-1 { - margin-left: 8.333333333333332%; - } - .col-lg-offset-2 { - margin-left: 16.666666666666664%; - } - .col-lg-offset-3 { - margin-left: 25%; - } - .col-lg-offset-4 { - margin-left: 33.33333333333333%; - } - .col-lg-offset-5 { - margin-left: 41.66666666666667%; - } - .col-lg-offset-6 { - margin-left: 50%; - } - .col-lg-offset-7 { - margin-left: 58.333333333333336%; - } - .col-lg-offset-8 { - margin-left: 66.66666666666666%; - } - .col-lg-offset-9 { - margin-left: 75%; - } - .col-lg-offset-10 { - margin-left: 83.33333333333334%; - } - .col-lg-offset-11 { - margin-left: 91.66666666666666%; - } -} - -table { - max-width: 100%; - background-color: transparent; -} - -th { - text-align: left; -} - -.table { - width: 100%; - margin-bottom: 20px; -} - -.table thead > tr > th, -.table tbody > tr > th, -.table tfoot > tr > th, -.table thead > tr > td, -.table tbody > tr > td, -.table tfoot > tr > td { - padding: 8px; - line-height: 1.428571429; - vertical-align: top; - border-top: 1px solid #dddddd; -} - -.table thead > tr > th { - vertical-align: bottom; - border-bottom: 2px solid #dddddd; -} - -.table caption + thead tr:first-child th, -.table colgroup + thead tr:first-child th, -.table thead:first-child tr:first-child th, -.table caption + thead tr:first-child td, -.table colgroup + thead tr:first-child td, -.table thead:first-child tr:first-child td { - border-top: 0; -} - -.table tbody + tbody { - border-top: 2px solid #dddddd; -} - -.table .table { - background-color: #ffffff; -} - -.table-condensed thead > tr > th, -.table-condensed tbody > tr > th, -.table-condensed tfoot > tr > th, -.table-condensed thead > tr > td, -.table-condensed tbody > tr > td, -.table-condensed tfoot > tr > td { - padding: 5px; -} - -.table-bordered { - border: 1px solid #dddddd; -} - -.table-bordered > thead > tr > th, -.table-bordered > tbody > tr > th, -.table-bordered > tfoot > tr > th, -.table-bordered > thead > tr > td, -.table-bordered > tbody > tr > td, -.table-bordered > tfoot > tr > td { - border: 1px solid #dddddd; -} - -.table-bordered > thead > tr > th, -.table-bordered > thead > tr > td { - border-bottom-width: 2px; -} - -.table-striped > tbody > tr:nth-child(odd) > td, -.table-striped > tbody > tr:nth-child(odd) > th { - background-color: #f9f9f9; -} - -.table-hover > tbody > tr:hover > td, -.table-hover > tbody > tr:hover > th { - background-color: #f5f5f5; -} - -table col[class*="col-"] { - display: table-column; - float: none; -} - -table td[class*="col-"], -table th[class*="col-"] { - display: table-cell; - float: none; -} - -.table > thead > tr > td.active, -.table > tbody > tr > td.active, -.table > tfoot > tr > td.active, -.table > thead > tr > th.active, -.table > tbody > tr > th.active, -.table > tfoot > tr > th.active, -.table > thead > tr.active > td, -.table > tbody > tr.active > td, -.table > tfoot > tr.active > td, -.table > thead > tr.active > th, -.table > tbody > tr.active > th, -.table > tfoot > tr.active > th { - background-color: #f5f5f5; -} - -.table > thead > tr > td.success, -.table > tbody > tr > td.success, -.table > tfoot > tr > td.success, -.table > thead > tr > th.success, -.table > tbody > tr > th.success, -.table > tfoot > tr > th.success, -.table > thead > tr.success > td, -.table > tbody > tr.success > td, -.table > tfoot > tr.success > td, -.table > thead > tr.success > th, -.table > tbody > tr.success > th, -.table > tfoot > tr.success > th { - background-color: #dff0d8; - border-color: #d6e9c6; -} - -.table-hover > tbody > tr > td.success:hover, -.table-hover > tbody > tr > th.success:hover, -.table-hover > tbody > tr.success:hover > td { - background-color: #d0e9c6; - border-color: #c9e2b3; -} - -.table > thead > tr > td.danger, -.table > tbody > tr > td.danger, -.table > tfoot > tr > td.danger, -.table > thead > tr > th.danger, -.table > tbody > tr > th.danger, -.table > tfoot > tr > th.danger, -.table > thead > tr.danger > td, -.table > tbody > tr.danger > td, -.table > tfoot > tr.danger > td, -.table > thead > tr.danger > th, -.table > tbody > tr.danger > th, -.table > tfoot > tr.danger > th { - background-color: #f2dede; - border-color: #eed3d7; -} - -.table-hover > tbody > tr > td.danger:hover, -.table-hover > tbody > tr > th.danger:hover, -.table-hover > tbody > tr.danger:hover > td { - background-color: #ebcccc; - border-color: #e6c1c7; -} - -.table > thead > tr > td.warning, -.table > tbody > tr > td.warning, -.table > tfoot > tr > td.warning, -.table > thead > tr > th.warning, -.table > tbody > tr > th.warning, -.table > tfoot > tr > th.warning, -.table > thead > tr.warning > td, -.table > tbody > tr.warning > td, -.table > tfoot > tr.warning > td, -.table > thead > tr.warning > th, -.table > tbody > tr.warning > th, -.table > tfoot > tr.warning > th { - background-color: #fcf8e3; - border-color: #fbeed5; -} - -.table-hover > tbody > tr > td.warning:hover, -.table-hover > tbody > tr > th.warning:hover, -.table-hover > tbody > tr.warning:hover > td { - background-color: #faf2cc; - border-color: #f8e5be; -} - -@media (max-width: 768px) { - .table-responsive { - width: 100%; - margin-bottom: 15px; - overflow-x: scroll; - overflow-y: hidden; - border: 1px solid #dddddd; - } - .table-responsive > .table { - margin-bottom: 0; - background-color: #fff; - } - .table-responsive > .table > thead > tr > th, - .table-responsive > .table > tbody > tr > th, - .table-responsive > .table > tfoot > tr > th, - .table-responsive > .table > thead > tr > td, - .table-responsive > .table > tbody > tr > td, - .table-responsive > .table > tfoot > tr > td { - white-space: nowrap; - } - .table-responsive > .table-bordered { - border: 0; - } - .table-responsive > .table-bordered > thead > tr > th:first-child, - .table-responsive > .table-bordered > tbody > tr > th:first-child, - .table-responsive > .table-bordered > tfoot > tr > th:first-child, - .table-responsive > .table-bordered > thead > tr > td:first-child, - .table-responsive > .table-bordered > tbody > tr > td:first-child, - .table-responsive > .table-bordered > tfoot > tr > td:first-child { - border-left: 0; - } - .table-responsive > .table-bordered > thead > tr > th:last-child, - .table-responsive > .table-bordered > tbody > tr > th:last-child, - .table-responsive > .table-bordered > tfoot > tr > th:last-child, - .table-responsive > .table-bordered > thead > tr > td:last-child, - .table-responsive > .table-bordered > tbody > tr > td:last-child, - .table-responsive > .table-bordered > tfoot > tr > td:last-child { - border-right: 0; - } - .table-responsive > .table-bordered > thead > tr:last-child > th, - .table-responsive > .table-bordered > tbody > tr:last-child > th, - .table-responsive > .table-bordered > tfoot > tr:last-child > th, - .table-responsive > .table-bordered > thead > tr:last-child > td, - .table-responsive > .table-bordered > tbody > tr:last-child > td, - .table-responsive > .table-bordered > tfoot > tr:last-child > td { - border-bottom: 0; - } -} - -fieldset { - padding: 0; - margin: 0; - border: 0; -} - -legend { - display: block; - width: 100%; - padding: 0; - margin-bottom: 20px; - font-size: 21px; - line-height: inherit; - color: #333333; - border: 0; - border-bottom: 1px solid #e5e5e5; -} - -label { - display: inline-block; - margin-bottom: 5px; - font-weight: bold; -} - -input[type="search"] { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -input[type="radio"], -input[type="checkbox"] { - margin: 4px 0 0; - margin-top: 1px \9; - /* IE8-9 */ - - line-height: normal; -} - -input[type="file"] { - display: block; -} - -select[multiple], -select[size] { - height: auto; -} - -select optgroup { - font-family: inherit; - font-size: inherit; - font-style: inherit; -} - -input[type="file"]:focus, -input[type="radio"]:focus, -input[type="checkbox"]:focus { - outline: thin dotted #333; - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px; -} - -input[type="number"]::-webkit-outer-spin-button, -input[type="number"]::-webkit-inner-spin-button { - height: auto; -} - -.form-control:-moz-placeholder { - color: #999999; -} - -.form-control::-moz-placeholder { - color: #999999; -} - -.form-control:-ms-input-placeholder { - color: #999999; -} - -.form-control::-webkit-input-placeholder { - color: #999999; -} - -.form-control { - display: block; - width: 100%; - height: 34px; - padding: 6px 12px; - font-size: 14px; - line-height: 1.428571429; - color: #555555; - vertical-align: middle; - background-color: #ffffff; - border: 1px solid #cccccc; - border-radius: 4px; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - -webkit-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; - transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; -} - -.form-control:focus { - border-color: #66afe9; - outline: 0; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6); -} - -.form-control[disabled], -.form-control[readonly], -fieldset[disabled] .form-control { - cursor: not-allowed; - background-color: #eeeeee; -} - -textarea.form-control { - height: auto; -} - -.form-group { - margin-bottom: 15px; -} - -.radio, -.checkbox { - display: block; - min-height: 20px; - padding-left: 20px; - margin-top: 10px; - margin-bottom: 10px; - vertical-align: middle; -} - -.radio label, -.checkbox label { - display: inline; - margin-bottom: 0; - font-weight: normal; - cursor: pointer; -} - -.radio input[type="radio"], -.radio-inline input[type="radio"], -.checkbox input[type="checkbox"], -.checkbox-inline input[type="checkbox"] { - float: left; - margin-left: -20px; -} - -.radio + .radio, -.checkbox + .checkbox { - margin-top: -5px; -} - -.radio-inline, -.checkbox-inline { - display: inline-block; - padding-left: 20px; - margin-bottom: 0; - font-weight: normal; - vertical-align: middle; - cursor: pointer; -} - -.radio-inline + .radio-inline, -.checkbox-inline + .checkbox-inline { - margin-top: 0; - margin-left: 10px; -} - -input[type="radio"][disabled], -input[type="checkbox"][disabled], -.radio[disabled], -.radio-inline[disabled], -.checkbox[disabled], -.checkbox-inline[disabled], -fieldset[disabled] input[type="radio"], -fieldset[disabled] input[type="checkbox"], -fieldset[disabled] .radio, -fieldset[disabled] .radio-inline, -fieldset[disabled] .checkbox, -fieldset[disabled] .checkbox-inline { - cursor: not-allowed; -} - -.input-sm { - height: 30px; - padding: 5px 10px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px; -} - -select.input-sm { - height: 30px; - line-height: 30px; -} - -textarea.input-sm { - height: auto; -} - -.input-lg { - height: 45px; - padding: 10px 16px; - font-size: 18px; - line-height: 1.33; - border-radius: 6px; -} - -select.input-lg { - height: 45px; - line-height: 45px; -} - -textarea.input-lg { - height: auto; -} - -.has-warning .help-block, -.has-warning .control-label { - color: #c09853; -} - -.has-warning .form-control { - border-color: #c09853; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); -} - -.has-warning .form-control:focus { - border-color: #a47e3c; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e; -} - -.has-warning .input-group-addon { - color: #c09853; - background-color: #fcf8e3; - border-color: #c09853; -} - -.has-error .help-block, -.has-error .control-label { - color: #b94a48; -} - -.has-error .form-control { - border-color: #b94a48; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); -} - -.has-error .form-control:focus { - border-color: #953b39; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392; -} - -.has-error .input-group-addon { - color: #b94a48; - background-color: #f2dede; - border-color: #b94a48; -} - -.has-success .help-block, -.has-success .control-label { - color: #468847; -} - -.has-success .form-control { - border-color: #468847; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); -} - -.has-success .form-control:focus { - border-color: #356635; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b; -} - -.has-success .input-group-addon { - color: #468847; - background-color: #dff0d8; - border-color: #468847; -} - -.form-control-static { - padding-top: 7px; - margin-bottom: 0; -} - -.help-block { - display: block; - margin-top: 5px; - margin-bottom: 10px; - color: #737373; -} - -@media (min-width: 768px) { - .form-inline .form-group { - display: inline-block; - margin-bottom: 0; - vertical-align: middle; - } - .form-inline .form-control { - display: inline-block; - } - .form-inline .radio, - .form-inline .checkbox { - display: inline-block; - padding-left: 0; - margin-top: 0; - margin-bottom: 0; - } - .form-inline .radio input[type="radio"], - .form-inline .checkbox input[type="checkbox"] { - float: none; - margin-left: 0; - } -} - -.form-horizontal .control-label, -.form-horizontal .radio, -.form-horizontal .checkbox, -.form-horizontal .radio-inline, -.form-horizontal .checkbox-inline { - padding-top: 7px; - margin-top: 0; - margin-bottom: 0; -} - -.form-horizontal .form-group { - margin-right: -15px; - margin-left: -15px; -} - -.form-horizontal .form-group:before, -.form-horizontal .form-group:after { - display: table; - content: " "; -} - -.form-horizontal .form-group:after { - clear: both; -} - -.form-horizontal .form-group:before, -.form-horizontal .form-group:after { - display: table; - content: " "; -} - -.form-horizontal .form-group:after { - clear: both; -} - -@media (min-width: 768px) { - .form-horizontal .control-label { - text-align: right; - } -} - -.btn { - display: inline-block; - padding: 6px 12px; - margin-bottom: 0; - font-size: 14px; - font-weight: normal; - line-height: 1.428571429; - text-align: center; - white-space: nowrap; - vertical-align: middle; - cursor: pointer; - border: 1px solid transparent; - border-radius: 4px; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - -o-user-select: none; - user-select: none; -} - -.btn:focus { - outline: thin dotted #333; - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px; -} - -.btn:hover, -.btn:focus { - color: #333333; - text-decoration: none; -} - -.btn:active, -.btn.active { - background-image: none; - outline: 0; - -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); - box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); -} - -.btn.disabled, -.btn[disabled], -fieldset[disabled] .btn { - pointer-events: none; - cursor: not-allowed; - opacity: 0.65; - filter: alpha(opacity=65); - -webkit-box-shadow: none; - box-shadow: none; -} - -.btn-default { - color: #333333; - background-color: #ffffff; - border-color: #cccccc; -} - -.btn-default:hover, -.btn-default:focus, -.btn-default:active, -.btn-default.active, -.open .dropdown-toggle.btn-default { - color: #333333; - background-color: #ebebeb; - border-color: #adadad; -} - -.btn-default:active, -.btn-default.active, -.open .dropdown-toggle.btn-default { - background-image: none; -} - -.btn-default.disabled, -.btn-default[disabled], -fieldset[disabled] .btn-default, -.btn-default.disabled:hover, -.btn-default[disabled]:hover, -fieldset[disabled] .btn-default:hover, -.btn-default.disabled:focus, -.btn-default[disabled]:focus, -fieldset[disabled] .btn-default:focus, -.btn-default.disabled:active, -.btn-default[disabled]:active, -fieldset[disabled] .btn-default:active, -.btn-default.disabled.active, -.btn-default[disabled].active, -fieldset[disabled] .btn-default.active { - background-color: #ffffff; - border-color: #cccccc; -} - -.btn-primary { - color: #ffffff; - background-color: #428bca; - border-color: #357ebd; -} - -.btn-primary:hover, -.btn-primary:focus, -.btn-primary:active, -.btn-primary.active, -.open .dropdown-toggle.btn-primary { - color: #ffffff; - background-color: #3276b1; - border-color: #285e8e; -} - -.btn-primary:active, -.btn-primary.active, -.open .dropdown-toggle.btn-primary { - background-image: none; -} - -.btn-primary.disabled, -.btn-primary[disabled], -fieldset[disabled] .btn-primary, -.btn-primary.disabled:hover, -.btn-primary[disabled]:hover, -fieldset[disabled] .btn-primary:hover, -.btn-primary.disabled:focus, -.btn-primary[disabled]:focus, -fieldset[disabled] .btn-primary:focus, -.btn-primary.disabled:active, -.btn-primary[disabled]:active, -fieldset[disabled] .btn-primary:active, -.btn-primary.disabled.active, -.btn-primary[disabled].active, -fieldset[disabled] .btn-primary.active { - background-color: #428bca; - border-color: #357ebd; -} - -.btn-warning { - color: #ffffff; - background-color: #f0ad4e; - border-color: #eea236; -} - -.btn-warning:hover, -.btn-warning:focus, -.btn-warning:active, -.btn-warning.active, -.open .dropdown-toggle.btn-warning { - color: #ffffff; - background-color: #ed9c28; - border-color: #d58512; -} - -.btn-warning:active, -.btn-warning.active, -.open .dropdown-toggle.btn-warning { - background-image: none; -} - -.btn-warning.disabled, -.btn-warning[disabled], -fieldset[disabled] .btn-warning, -.btn-warning.disabled:hover, -.btn-warning[disabled]:hover, -fieldset[disabled] .btn-warning:hover, -.btn-warning.disabled:focus, -.btn-warning[disabled]:focus, -fieldset[disabled] .btn-warning:focus, -.btn-warning.disabled:active, -.btn-warning[disabled]:active, -fieldset[disabled] .btn-warning:active, -.btn-warning.disabled.active, -.btn-warning[disabled].active, -fieldset[disabled] .btn-warning.active { - background-color: #f0ad4e; - border-color: #eea236; -} - -.btn-danger { - color: #ffffff; - background-color: #d9534f; - border-color: #d43f3a; -} - -.btn-danger:hover, -.btn-danger:focus, -.btn-danger:active, -.btn-danger.active, -.open .dropdown-toggle.btn-danger { - color: #ffffff; - background-color: #d2322d; - border-color: #ac2925; -} - -.btn-danger:active, -.btn-danger.active, -.open .dropdown-toggle.btn-danger { - background-image: none; -} - -.btn-danger.disabled, -.btn-danger[disabled], -fieldset[disabled] .btn-danger, -.btn-danger.disabled:hover, -.btn-danger[disabled]:hover, -fieldset[disabled] .btn-danger:hover, -.btn-danger.disabled:focus, -.btn-danger[disabled]:focus, -fieldset[disabled] .btn-danger:focus, -.btn-danger.disabled:active, -.btn-danger[disabled]:active, -fieldset[disabled] .btn-danger:active, -.btn-danger.disabled.active, -.btn-danger[disabled].active, -fieldset[disabled] .btn-danger.active { - background-color: #d9534f; - border-color: #d43f3a; -} - -.btn-success { - color: #ffffff; - background-color: #5cb85c; - border-color: #4cae4c; -} - -.btn-success:hover, -.btn-success:focus, -.btn-success:active, -.btn-success.active, -.open .dropdown-toggle.btn-success { - color: #ffffff; - background-color: #47a447; - border-color: #398439; -} - -.btn-success:active, -.btn-success.active, -.open .dropdown-toggle.btn-success { - background-image: none; -} - -.btn-success.disabled, -.btn-success[disabled], -fieldset[disabled] .btn-success, -.btn-success.disabled:hover, -.btn-success[disabled]:hover, -fieldset[disabled] .btn-success:hover, -.btn-success.disabled:focus, -.btn-success[disabled]:focus, -fieldset[disabled] .btn-success:focus, -.btn-success.disabled:active, -.btn-success[disabled]:active, -fieldset[disabled] .btn-success:active, -.btn-success.disabled.active, -.btn-success[disabled].active, -fieldset[disabled] .btn-success.active { - background-color: #5cb85c; - border-color: #4cae4c; -} - -.btn-info { - color: #ffffff; - background-color: #5bc0de; - border-color: #46b8da; -} - -.btn-info:hover, -.btn-info:focus, -.btn-info:active, -.btn-info.active, -.open .dropdown-toggle.btn-info { - color: #ffffff; - background-color: #39b3d7; - border-color: #269abc; -} - -.btn-info:active, -.btn-info.active, -.open .dropdown-toggle.btn-info { - background-image: none; -} - -.btn-info.disabled, -.btn-info[disabled], -fieldset[disabled] .btn-info, -.btn-info.disabled:hover, -.btn-info[disabled]:hover, -fieldset[disabled] .btn-info:hover, -.btn-info.disabled:focus, -.btn-info[disabled]:focus, -fieldset[disabled] .btn-info:focus, -.btn-info.disabled:active, -.btn-info[disabled]:active, -fieldset[disabled] .btn-info:active, -.btn-info.disabled.active, -.btn-info[disabled].active, -fieldset[disabled] .btn-info.active { - background-color: #5bc0de; - border-color: #46b8da; -} - -.btn-link { - font-weight: normal; - color: #428bca; - cursor: pointer; - border-radius: 0; -} - -.btn-link, -.btn-link:active, -.btn-link[disabled], -fieldset[disabled] .btn-link { - background-color: transparent; - -webkit-box-shadow: none; - box-shadow: none; -} - -.btn-link, -.btn-link:hover, -.btn-link:focus, -.btn-link:active { - border-color: transparent; -} - -.btn-link:hover, -.btn-link:focus { - color: #2a6496; - text-decoration: underline; - background-color: transparent; -} - -.btn-link[disabled]:hover, -fieldset[disabled] .btn-link:hover, -.btn-link[disabled]:focus, -fieldset[disabled] .btn-link:focus { - color: #999999; - text-decoration: none; -} - -.btn-lg { - padding: 10px 16px; - font-size: 18px; - line-height: 1.33; - border-radius: 6px; -} - -.btn-sm, -.btn-xs { - padding: 5px 10px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px; -} - -.btn-xs { - padding: 1px 5px; -} - -.btn-block { - display: block; - width: 100%; - padding-right: 0; - padding-left: 0; -} - -.btn-block + .btn-block { - margin-top: 5px; -} - -input[type="submit"].btn-block, -input[type="reset"].btn-block, -input[type="button"].btn-block { - width: 100%; -} - -.fade { - opacity: 0; - -webkit-transition: opacity 0.15s linear; - transition: opacity 0.15s linear; -} - -.fade.in { - opacity: 1; -} - -.collapse { - display: none; -} - -.collapse.in { - display: block; -} - -.collapsing { - position: relative; - height: 0; - overflow: hidden; - -webkit-transition: height 0.35s ease; - transition: height 0.35s ease; -} - -@font-face { - font-family: 'Glyphicons Halflings'; - src: url('../fonts/glyphicons-halflings-regular.eot'); - src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons-halflingsregular') format('svg'); -} - -.glyphicon { - position: relative; - top: 1px; - display: inline-block; - font-family: 'Glyphicons Halflings'; - -webkit-font-smoothing: antialiased; - font-style: normal; - font-weight: normal; - line-height: 1; -} - -.glyphicon-asterisk:before { - content: "\2a"; -} - -.glyphicon-plus:before { - content: "\2b"; -} - -.glyphicon-euro:before { - content: "\20ac"; -} - -.glyphicon-minus:before { - content: "\2212"; -} - -.glyphicon-cloud:before { - content: "\2601"; -} - -.glyphicon-envelope:before { - content: "\2709"; -} - -.glyphicon-pencil:before { - content: "\270f"; -} - -.glyphicon-glass:before { - content: "\e001"; -} - -.glyphicon-music:before { - content: "\e002"; -} - -.glyphicon-search:before { - content: "\e003"; -} - -.glyphicon-heart:before { - content: "\e005"; -} - -.glyphicon-star:before { - content: "\e006"; -} - -.glyphicon-star-empty:before { - content: "\e007"; -} - -.glyphicon-user:before { - content: "\e008"; -} - -.glyphicon-film:before { - content: "\e009"; -} - -.glyphicon-th-large:before { - content: "\e010"; -} - -.glyphicon-th:before { - content: "\e011"; -} - -.glyphicon-th-list:before { - content: "\e012"; -} - -.glyphicon-ok:before { - content: "\e013"; -} - -.glyphicon-remove:before { - content: "\e014"; -} - -.glyphicon-zoom-in:before { - content: "\e015"; -} - -.glyphicon-zoom-out:before { - content: "\e016"; -} - -.glyphicon-off:before { - content: "\e017"; -} - -.glyphicon-signal:before { - content: "\e018"; -} - -.glyphicon-cog:before { - content: "\e019"; -} - -.glyphicon-trash:before { - content: "\e020"; -} - -.glyphicon-home:before { - content: "\e021"; -} - -.glyphicon-file:before { - content: "\e022"; -} - -.glyphicon-time:before { - content: "\e023"; -} - -.glyphicon-road:before { - content: "\e024"; -} - -.glyphicon-download-alt:before { - content: "\e025"; -} - -.glyphicon-download:before { - content: "\e026"; -} - -.glyphicon-upload:before { - content: "\e027"; -} - -.glyphicon-inbox:before { - content: "\e028"; -} - -.glyphicon-play-circle:before { - content: "\e029"; -} - -.glyphicon-repeat:before { - content: "\e030"; -} - -.glyphicon-refresh:before { - content: "\e031"; -} - -.glyphicon-list-alt:before { - content: "\e032"; -} - -.glyphicon-flag:before { - content: "\e034"; -} - -.glyphicon-headphones:before { - content: "\e035"; -} - -.glyphicon-volume-off:before { - content: "\e036"; -} - -.glyphicon-volume-down:before { - content: "\e037"; -} - -.glyphicon-volume-up:before { - content: "\e038"; -} - -.glyphicon-qrcode:before { - content: "\e039"; -} - -.glyphicon-barcode:before { - content: "\e040"; -} - -.glyphicon-tag:before { - content: "\e041"; -} - -.glyphicon-tags:before { - content: "\e042"; -} - -.glyphicon-book:before { - content: "\e043"; -} - -.glyphicon-print:before { - content: "\e045"; -} - -.glyphicon-font:before { - content: "\e047"; -} - -.glyphicon-bold:before { - content: "\e048"; -} - -.glyphicon-italic:before { - content: "\e049"; -} - -.glyphicon-text-height:before { - content: "\e050"; -} - -.glyphicon-text-width:before { - content: "\e051"; -} - -.glyphicon-align-left:before { - content: "\e052"; -} - -.glyphicon-align-center:before { - content: "\e053"; -} - -.glyphicon-align-right:before { - content: "\e054"; -} - -.glyphicon-align-justify:before { - content: "\e055"; -} - -.glyphicon-list:before { - content: "\e056"; -} - -.glyphicon-indent-left:before { - content: "\e057"; -} - -.glyphicon-indent-right:before { - content: "\e058"; -} - -.glyphicon-facetime-video:before { - content: "\e059"; -} - -.glyphicon-picture:before { - content: "\e060"; -} - -.glyphicon-map-marker:before { - content: "\e062"; -} - -.glyphicon-adjust:before { - content: "\e063"; -} - -.glyphicon-tint:before { - content: "\e064"; -} - -.glyphicon-edit:before { - content: "\e065"; -} - -.glyphicon-share:before { - content: "\e066"; -} - -.glyphicon-check:before { - content: "\e067"; -} - -.glyphicon-move:before { - content: "\e068"; -} - -.glyphicon-step-backward:before { - content: "\e069"; -} - -.glyphicon-fast-backward:before { - content: "\e070"; -} - -.glyphicon-backward:before { - content: "\e071"; -} - -.glyphicon-play:before { - content: "\e072"; -} - -.glyphicon-pause:before { - content: "\e073"; -} - -.glyphicon-stop:before { - content: "\e074"; -} - -.glyphicon-forward:before { - content: "\e075"; -} - -.glyphicon-fast-forward:before { - content: "\e076"; -} - -.glyphicon-step-forward:before { - content: "\e077"; -} - -.glyphicon-eject:before { - content: "\e078"; -} - -.glyphicon-chevron-left:before { - content: "\e079"; -} - -.glyphicon-chevron-right:before { - content: "\e080"; -} - -.glyphicon-plus-sign:before { - content: "\e081"; -} - -.glyphicon-minus-sign:before { - content: "\e082"; -} - -.glyphicon-remove-sign:before { - content: "\e083"; -} - -.glyphicon-ok-sign:before { - content: "\e084"; -} - -.glyphicon-question-sign:before { - content: "\e085"; -} - -.glyphicon-info-sign:before { - content: "\e086"; -} - -.glyphicon-screenshot:before { - content: "\e087"; -} - -.glyphicon-remove-circle:before { - content: "\e088"; -} - -.glyphicon-ok-circle:before { - content: "\e089"; -} - -.glyphicon-ban-circle:before { - content: "\e090"; -} - -.glyphicon-arrow-left:before { - content: "\e091"; -} - -.glyphicon-arrow-right:before { - content: "\e092"; -} - -.glyphicon-arrow-up:before { - content: "\e093"; -} - -.glyphicon-arrow-down:before { - content: "\e094"; -} - -.glyphicon-share-alt:before { - content: "\e095"; -} - -.glyphicon-resize-full:before { - content: "\e096"; -} - -.glyphicon-resize-small:before { - content: "\e097"; -} - -.glyphicon-exclamation-sign:before { - content: "\e101"; -} - -.glyphicon-gift:before { - content: "\e102"; -} - -.glyphicon-leaf:before { - content: "\e103"; -} - -.glyphicon-eye-open:before { - content: "\e105"; -} - -.glyphicon-eye-close:before { - content: "\e106"; -} - -.glyphicon-warning-sign:before { - content: "\e107"; -} - -.glyphicon-plane:before { - content: "\e108"; -} - -.glyphicon-random:before { - content: "\e110"; -} - -.glyphicon-comment:before { - content: "\e111"; -} - -.glyphicon-magnet:before { - content: "\e112"; -} - -.glyphicon-chevron-up:before { - content: "\e113"; -} - -.glyphicon-chevron-down:before { - content: "\e114"; -} - -.glyphicon-retweet:before { - content: "\e115"; -} - -.glyphicon-shopping-cart:before { - content: "\e116"; -} - -.glyphicon-folder-close:before { - content: "\e117"; -} - -.glyphicon-folder-open:before { - content: "\e118"; -} - -.glyphicon-resize-vertical:before { - content: "\e119"; -} - -.glyphicon-resize-horizontal:before { - content: "\e120"; -} - -.glyphicon-hdd:before { - content: "\e121"; -} - -.glyphicon-bullhorn:before { - content: "\e122"; -} - -.glyphicon-certificate:before { - content: "\e124"; -} - -.glyphicon-thumbs-up:before { - content: "\e125"; -} - -.glyphicon-thumbs-down:before { - content: "\e126"; -} - -.glyphicon-hand-right:before { - content: "\e127"; -} - -.glyphicon-hand-left:before { - content: "\e128"; -} - -.glyphicon-hand-up:before { - content: "\e129"; -} - -.glyphicon-hand-down:before { - content: "\e130"; -} - -.glyphicon-circle-arrow-right:before { - content: "\e131"; -} - -.glyphicon-circle-arrow-left:before { - content: "\e132"; -} - -.glyphicon-circle-arrow-up:before { - content: "\e133"; -} - -.glyphicon-circle-arrow-down:before { - content: "\e134"; -} - -.glyphicon-globe:before { - content: "\e135"; -} - -.glyphicon-tasks:before { - content: "\e137"; -} - -.glyphicon-filter:before { - content: "\e138"; -} - -.glyphicon-fullscreen:before { - content: "\e140"; -} - -.glyphicon-dashboard:before { - content: "\e141"; -} - -.glyphicon-heart-empty:before { - content: "\e143"; -} - -.glyphicon-link:before { - content: "\e144"; -} - -.glyphicon-phone:before { - content: "\e145"; -} - -.glyphicon-usd:before { - content: "\e148"; -} - -.glyphicon-gbp:before { - content: "\e149"; -} - -.glyphicon-sort:before { - content: "\e150"; -} - -.glyphicon-sort-by-alphabet:before { - content: "\e151"; -} - -.glyphicon-sort-by-alphabet-alt:before { - content: "\e152"; -} - -.glyphicon-sort-by-order:before { - content: "\e153"; -} - -.glyphicon-sort-by-order-alt:before { - content: "\e154"; -} - -.glyphicon-sort-by-attributes:before { - content: "\e155"; -} - -.glyphicon-sort-by-attributes-alt:before { - content: "\e156"; -} - -.glyphicon-unchecked:before { - content: "\e157"; -} - -.glyphicon-expand:before { - content: "\e158"; -} - -.glyphicon-collapse-down:before { - content: "\e159"; -} - -.glyphicon-collapse-up:before { - content: "\e160"; -} - -.glyphicon-log-in:before { - content: "\e161"; -} - -.glyphicon-flash:before { - content: "\e162"; -} - -.glyphicon-log-out:before { - content: "\e163"; -} - -.glyphicon-new-window:before { - content: "\e164"; -} - -.glyphicon-record:before { - content: "\e165"; -} - -.glyphicon-save:before { - content: "\e166"; -} - -.glyphicon-open:before { - content: "\e167"; -} - -.glyphicon-saved:before { - content: "\e168"; -} - -.glyphicon-import:before { - content: "\e169"; -} - -.glyphicon-export:before { - content: "\e170"; -} - -.glyphicon-send:before { - content: "\e171"; -} - -.glyphicon-floppy-disk:before { - content: "\e172"; -} - -.glyphicon-floppy-saved:before { - content: "\e173"; -} - -.glyphicon-floppy-remove:before { - content: "\e174"; -} - -.glyphicon-floppy-save:before { - content: "\e175"; -} - -.glyphicon-floppy-open:before { - content: "\e176"; -} - -.glyphicon-credit-card:before { - content: "\e177"; -} - -.glyphicon-transfer:before { - content: "\e178"; -} - -.glyphicon-cutlery:before { - content: "\e179"; -} - -.glyphicon-header:before { - content: "\e180"; -} - -.glyphicon-compressed:before { - content: "\e181"; -} - -.glyphicon-earphone:before { - content: "\e182"; -} - -.glyphicon-phone-alt:before { - content: "\e183"; -} - -.glyphicon-tower:before { - content: "\e184"; -} - -.glyphicon-stats:before { - content: "\e185"; -} - -.glyphicon-sd-video:before { - content: "\e186"; -} - -.glyphicon-hd-video:before { - content: "\e187"; -} - -.glyphicon-subtitles:before { - content: "\e188"; -} - -.glyphicon-sound-stereo:before { - content: "\e189"; -} - -.glyphicon-sound-dolby:before { - content: "\e190"; -} - -.glyphicon-sound-5-1:before { - content: "\e191"; -} - -.glyphicon-sound-6-1:before { - content: "\e192"; -} - -.glyphicon-sound-7-1:before { - content: "\e193"; -} - -.glyphicon-copyright-mark:before { - content: "\e194"; -} - -.glyphicon-registration-mark:before { - content: "\e195"; -} - -.glyphicon-cloud-download:before { - content: "\e197"; -} - -.glyphicon-cloud-upload:before { - content: "\e198"; -} - -.glyphicon-tree-conifer:before { - content: "\e199"; -} - -.glyphicon-tree-deciduous:before { - content: "\e200"; -} - -.glyphicon-briefcase:before { - content: "\1f4bc"; -} - -.glyphicon-calendar:before { - content: "\1f4c5"; -} - -.glyphicon-pushpin:before { - content: "\1f4cc"; -} - -.glyphicon-paperclip:before { - content: "\1f4ce"; -} - -.glyphicon-camera:before { - content: "\1f4f7"; -} - -.glyphicon-lock:before { - content: "\1f512"; -} - -.glyphicon-bell:before { - content: "\1f514"; -} - -.glyphicon-bookmark:before { - content: "\1f516"; -} - -.glyphicon-fire:before { - content: "\1f525"; -} - -.glyphicon-wrench:before { - content: "\1f527"; -} - -.caret { - display: inline-block; - width: 0; - height: 0; - margin-left: 2px; - vertical-align: middle; - border-top: 4px solid #000000; - border-right: 4px solid transparent; - border-bottom: 0 dotted; - border-left: 4px solid transparent; - content: ""; -} - -.dropdown { - position: relative; -} - -.dropdown-toggle:focus { - outline: 0; -} - -.dropdown-menu { - position: absolute; - top: 100%; - left: 0; - z-index: 1000; - display: none; - float: left; - min-width: 160px; - padding: 5px 0; - margin: 2px 0 0; - font-size: 14px; - list-style: none; - background-color: #ffffff; - border: 1px solid #cccccc; - border: 1px solid rgba(0, 0, 0, 0.15); - border-radius: 4px; - -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); - box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); - background-clip: padding-box; -} - -.dropdown-menu.pull-right { - right: 0; - left: auto; -} - -.dropdown-menu .divider { - height: 1px; - margin: 9px 0; - overflow: hidden; - background-color: #e5e5e5; -} - -.dropdown-menu > li > a { - display: block; - padding: 3px 20px; - clear: both; - font-weight: normal; - line-height: 1.428571429; - color: #333333; - white-space: nowrap; -} - -.dropdown-menu > li > a:hover, -.dropdown-menu > li > a:focus { - color: #ffffff; - text-decoration: none; - background-color: #428bca; -} - -.dropdown-menu > .active > a, -.dropdown-menu > .active > a:hover, -.dropdown-menu > .active > a:focus { - color: #ffffff; - text-decoration: none; - background-color: #428bca; - outline: 0; -} - -.dropdown-menu > .disabled > a, -.dropdown-menu > .disabled > a:hover, -.dropdown-menu > .disabled > a:focus { - color: #999999; -} - -.dropdown-menu > .disabled > a:hover, -.dropdown-menu > .disabled > a:focus { - text-decoration: none; - cursor: not-allowed; - background-color: transparent; - background-image: none; - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); -} - -.open > .dropdown-menu { - display: block; -} - -.open > a { - outline: 0; -} - -.dropdown-header { - display: block; - padding: 3px 20px; - font-size: 12px; - line-height: 1.428571429; - color: #999999; -} - -.dropdown-backdrop { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 990; -} - -.pull-right > .dropdown-menu { - right: 0; - left: auto; -} - -.dropup .caret, -.navbar-fixed-bottom .dropdown .caret { - border-top: 0 dotted; - border-bottom: 4px solid #000000; - content: ""; -} - -.dropup .dropdown-menu, -.navbar-fixed-bottom .dropdown .dropdown-menu { - top: auto; - bottom: 100%; - margin-bottom: 1px; -} - -@media (min-width: 768px) { - .navbar-right .dropdown-menu { - right: 0; - left: auto; - } -} - -.btn-default .caret { - border-top-color: #333333; -} - -.btn-primary .caret, -.btn-success .caret, -.btn-warning .caret, -.btn-danger .caret, -.btn-info .caret { - border-top-color: #fff; -} - -.dropup .btn-default .caret { - border-bottom-color: #333333; -} - -.dropup .btn-primary .caret, -.dropup .btn-success .caret, -.dropup .btn-warning .caret, -.dropup .btn-danger .caret, -.dropup .btn-info .caret { - border-bottom-color: #fff; -} - -.btn-group, -.btn-group-vertical { - position: relative; - display: inline-block; - vertical-align: middle; -} - -.btn-group > .btn, -.btn-group-vertical > .btn { - position: relative; - float: left; -} - -.btn-group > .btn:hover, -.btn-group-vertical > .btn:hover, -.btn-group > .btn:focus, -.btn-group-vertical > .btn:focus, -.btn-group > .btn:active, -.btn-group-vertical > .btn:active, -.btn-group > .btn.active, -.btn-group-vertical > .btn.active { - z-index: 2; -} - -.btn-group > .btn:focus, -.btn-group-vertical > .btn:focus { - outline: none; -} - -.btn-group .btn + .btn, -.btn-group .btn + .btn-group, -.btn-group .btn-group + .btn, -.btn-group .btn-group + .btn-group { - margin-left: -1px; -} - -.btn-toolbar:before, -.btn-toolbar:after { - display: table; - content: " "; -} - -.btn-toolbar:after { - clear: both; -} - -.btn-toolbar:before, -.btn-toolbar:after { - display: table; - content: " "; -} - -.btn-toolbar:after { - clear: both; -} - -.btn-toolbar .btn-group { - float: left; -} - -.btn-toolbar > .btn + .btn, -.btn-toolbar > .btn-group + .btn, -.btn-toolbar > .btn + .btn-group, -.btn-toolbar > .btn-group + .btn-group { - margin-left: 5px; -} - -.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { - border-radius: 0; -} - -.btn-group > .btn:first-child { - margin-left: 0; -} - -.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} - -.btn-group > .btn:last-child:not(:first-child), -.btn-group > .dropdown-toggle:not(:first-child) { - border-bottom-left-radius: 0; - border-top-left-radius: 0; -} - -.btn-group > .btn-group { - float: left; -} - -.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { - border-radius: 0; -} - -.btn-group > .btn-group:first-child > .btn:last-child, -.btn-group > .btn-group:first-child > .dropdown-toggle { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} - -.btn-group > .btn-group:last-child > .btn:first-child { - border-bottom-left-radius: 0; - border-top-left-radius: 0; -} - -.btn-group .dropdown-toggle:active, -.btn-group.open .dropdown-toggle { - outline: 0; -} - -.btn-group-xs > .btn { - padding: 5px 10px; - padding: 1px 5px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px; -} - -.btn-group-sm > .btn { - padding: 5px 10px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px; -} - -.btn-group-lg > .btn { - padding: 10px 16px; - font-size: 18px; - line-height: 1.33; - border-radius: 6px; -} - -.btn-group > .btn + .dropdown-toggle { - padding-right: 8px; - padding-left: 8px; -} - -.btn-group > .btn-lg + .dropdown-toggle { - padding-right: 12px; - padding-left: 12px; -} - -.btn-group.open .dropdown-toggle { - -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); - box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); -} - -.btn .caret { - margin-left: 0; -} - -.btn-lg .caret { - border-width: 5px 5px 0; - border-bottom-width: 0; -} - -.dropup .btn-lg .caret { - border-width: 0 5px 5px; -} - -.btn-group-vertical > .btn, -.btn-group-vertical > .btn-group { - display: block; - float: none; - width: 100%; - max-width: 100%; -} - -.btn-group-vertical > .btn-group:before, -.btn-group-vertical > .btn-group:after { - display: table; - content: " "; -} - -.btn-group-vertical > .btn-group:after { - clear: both; -} - -.btn-group-vertical > .btn-group:before, -.btn-group-vertical > .btn-group:after { - display: table; - content: " "; -} - -.btn-group-vertical > .btn-group:after { - clear: both; -} - -.btn-group-vertical > .btn-group > .btn { - float: none; -} - -.btn-group-vertical > .btn + .btn, -.btn-group-vertical > .btn + .btn-group, -.btn-group-vertical > .btn-group + .btn, -.btn-group-vertical > .btn-group + .btn-group { - margin-top: -1px; - margin-left: 0; -} - -.btn-group-vertical > .btn:not(:first-child):not(:last-child) { - border-radius: 0; -} - -.btn-group-vertical > .btn:first-child:not(:last-child) { - border-top-right-radius: 4px; - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; -} - -.btn-group-vertical > .btn:last-child:not(:first-child) { - border-top-right-radius: 0; - border-bottom-left-radius: 4px; - border-top-left-radius: 0; -} - -.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { - border-radius: 0; -} - -.btn-group-vertical > .btn-group:first-child > .btn:last-child, -.btn-group-vertical > .btn-group:first-child > .dropdown-toggle { - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; -} - -.btn-group-vertical > .btn-group:last-child > .btn:first-child { - border-top-right-radius: 0; - border-top-left-radius: 0; -} - -.btn-group-justified { - display: table; - width: 100%; - border-collapse: separate; - table-layout: fixed; -} - -.btn-group-justified .btn { - display: table-cell; - float: none; - width: 1%; -} - -[data-toggle="buttons"] > .btn > input[type="radio"], -[data-toggle="buttons"] > .btn > input[type="checkbox"] { - display: none; -} - -.input-group { - position: relative; - display: table; - border-collapse: separate; -} - -.input-group.col { - float: none; - padding-right: 0; - padding-left: 0; -} - -.input-group .form-control { - width: 100%; - margin-bottom: 0; -} - -.input-group-lg > .form-control, -.input-group-lg > .input-group-addon, -.input-group-lg > .input-group-btn > .btn { - height: 45px; - padding: 10px 16px; - font-size: 18px; - line-height: 1.33; - border-radius: 6px; -} - -select.input-group-lg > .form-control, -select.input-group-lg > .input-group-addon, -select.input-group-lg > .input-group-btn > .btn { - height: 45px; - line-height: 45px; -} - -textarea.input-group-lg > .form-control, -textarea.input-group-lg > .input-group-addon, -textarea.input-group-lg > .input-group-btn > .btn { - height: auto; -} - -.input-group-sm > .form-control, -.input-group-sm > .input-group-addon, -.input-group-sm > .input-group-btn > .btn { - height: 30px; - padding: 5px 10px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px; -} - -select.input-group-sm > .form-control, -select.input-group-sm > .input-group-addon, -select.input-group-sm > .input-group-btn > .btn { - height: 30px; - line-height: 30px; -} - -textarea.input-group-sm > .form-control, -textarea.input-group-sm > .input-group-addon, -textarea.input-group-sm > .input-group-btn > .btn { - height: auto; -} - -.input-group-addon, -.input-group-btn, -.input-group .form-control { - display: table-cell; -} - -.input-group-addon:not(:first-child):not(:last-child), -.input-group-btn:not(:first-child):not(:last-child), -.input-group .form-control:not(:first-child):not(:last-child) { - border-radius: 0; -} - -.input-group-addon, -.input-group-btn { - width: 1%; - white-space: nowrap; - vertical-align: middle; -} - -.input-group-addon { - padding: 6px 12px; - font-size: 14px; - font-weight: normal; - line-height: 1; - text-align: center; - background-color: #eeeeee; - border: 1px solid #cccccc; - border-radius: 4px; -} - -.input-group-addon.input-sm { - padding: 5px 10px; - font-size: 12px; - border-radius: 3px; -} - -.input-group-addon.input-lg { - padding: 10px 16px; - font-size: 18px; - border-radius: 6px; -} - -.input-group-addon input[type="radio"], -.input-group-addon input[type="checkbox"] { - margin-top: 0; -} - -.input-group .form-control:first-child, -.input-group-addon:first-child, -.input-group-btn:first-child > .btn, -.input-group-btn:first-child > .dropdown-toggle, -.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle) { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} - -.input-group-addon:first-child { - border-right: 0; -} - -.input-group .form-control:last-child, -.input-group-addon:last-child, -.input-group-btn:last-child > .btn, -.input-group-btn:last-child > .dropdown-toggle, -.input-group-btn:first-child > .btn:not(:first-child) { - border-bottom-left-radius: 0; - border-top-left-radius: 0; -} - -.input-group-addon:last-child { - border-left: 0; -} - -.input-group-btn { - position: relative; - white-space: nowrap; -} - -.input-group-btn > .btn { - position: relative; -} - -.input-group-btn > .btn + .btn { - margin-left: -4px; -} - -.input-group-btn > .btn:hover, -.input-group-btn > .btn:active { - z-index: 2; -} - -.nav { - padding-left: 0; - margin-bottom: 0; - list-style: none; -} - -.nav:before, -.nav:after { - display: table; - content: " "; -} - -.nav:after { - clear: both; -} - -.nav:before, -.nav:after { - display: table; - content: " "; -} - -.nav:after { - clear: both; -} - -.nav > li { - position: relative; - display: block; -} - -.nav > li > a { - position: relative; - display: block; - padding: 10px 15px; -} - -.nav > li > a:hover, -.nav > li > a:focus { - text-decoration: none; - background-color: #eeeeee; -} - -.nav > li.disabled > a { - color: #999999; -} - -.nav > li.disabled > a:hover, -.nav > li.disabled > a:focus { - color: #999999; - text-decoration: none; - cursor: not-allowed; - background-color: transparent; -} - -.nav .open > a, -.nav .open > a:hover, -.nav .open > a:focus { - background-color: #eeeeee; - border-color: #428bca; -} - -.nav .nav-divider { - height: 1px; - margin: 9px 0; - overflow: hidden; - background-color: #e5e5e5; -} - -.nav > li > a > img { - max-width: none; -} - -.nav-tabs { - border-bottom: 1px solid #dddddd; -} - -.nav-tabs > li { - float: left; - margin-bottom: -1px; -} - -.nav-tabs > li > a { - margin-right: 2px; - line-height: 1.428571429; - border: 1px solid transparent; - border-radius: 4px 4px 0 0; -} - -.nav-tabs > li > a:hover { - border-color: #eeeeee #eeeeee #dddddd; -} - -.nav-tabs > li.active > a, -.nav-tabs > li.active > a:hover, -.nav-tabs > li.active > a:focus { - color: #555555; - cursor: default; - background-color: #ffffff; - border: 1px solid #dddddd; - border-bottom-color: transparent; -} - -.nav-tabs.nav-justified { - width: 100%; - border-bottom: 0; -} - -.nav-tabs.nav-justified > li { - float: none; -} - -.nav-tabs.nav-justified > li > a { - text-align: center; -} - -@media (min-width: 768px) { - .nav-tabs.nav-justified > li { - display: table-cell; - width: 1%; - } -} - -.nav-tabs.nav-justified > li > a { - margin-right: 0; - border-bottom: 1px solid #dddddd; -} - -.nav-tabs.nav-justified > .active > a { - border-bottom-color: #ffffff; -} - -.nav-pills > li { - float: left; -} - -.nav-pills > li > a { - border-radius: 5px; -} - -.nav-pills > li + li { - margin-left: 2px; -} - -.nav-pills > li.active > a, -.nav-pills > li.active > a:hover, -.nav-pills > li.active > a:focus { - color: #ffffff; - background-color: #428bca; -} - -.nav-stacked > li { - float: none; -} - -.nav-stacked > li + li { - margin-top: 2px; - margin-left: 0; -} - -.nav-justified { - width: 100%; -} - -.nav-justified > li { - float: none; -} - -.nav-justified > li > a { - text-align: center; -} - -@media (min-width: 768px) { - .nav-justified > li { - display: table-cell; - width: 1%; - } -} - -.nav-tabs-justified { - border-bottom: 0; -} - -.nav-tabs-justified > li > a { - margin-right: 0; - border-bottom: 1px solid #dddddd; -} - -.nav-tabs-justified > .active > a { - border-bottom-color: #ffffff; -} - -.tabbable:before, -.tabbable:after { - display: table; - content: " "; -} - -.tabbable:after { - clear: both; -} - -.tabbable:before, -.tabbable:after { - display: table; - content: " "; -} - -.tabbable:after { - clear: both; -} - -.tab-content > .tab-pane, -.pill-content > .pill-pane { - display: none; -} - -.tab-content > .active, -.pill-content > .active { - display: block; -} - -.nav .caret { - border-top-color: #428bca; - border-bottom-color: #428bca; -} - -.nav a:hover .caret { - border-top-color: #2a6496; - border-bottom-color: #2a6496; -} - -.nav-tabs .dropdown-menu { - margin-top: -1px; - border-top-right-radius: 0; - border-top-left-radius: 0; -} - -.navbar { - position: relative; - z-index: 1000; - min-height: 50px; - margin-bottom: 20px; - border: 1px solid transparent; -} - -.navbar:before, -.navbar:after { - display: table; - content: " "; -} - -.navbar:after { - clear: both; -} - -.navbar:before, -.navbar:after { - display: table; - content: " "; -} - -.navbar:after { - clear: both; -} - -@media (min-width: 768px) { - .navbar { - border-radius: 4px; - } -} - -.navbar-header:before, -.navbar-header:after { - display: table; - content: " "; -} - -.navbar-header:after { - clear: both; -} - -.navbar-header:before, -.navbar-header:after { - display: table; - content: " "; -} - -.navbar-header:after { - clear: both; -} - -@media (min-width: 768px) { - .navbar-header { - float: left; - } -} - -.navbar-collapse { - max-height: 340px; - padding-right: 15px; - padding-left: 15px; - overflow-x: visible; - border-top: 1px solid transparent; - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1); - -webkit-overflow-scrolling: touch; -} - -.navbar-collapse:before, -.navbar-collapse:after { - display: table; - content: " "; -} - -.navbar-collapse:after { - clear: both; -} - -.navbar-collapse:before, -.navbar-collapse:after { - display: table; - content: " "; -} - -.navbar-collapse:after { - clear: both; -} - -.navbar-collapse.in { - overflow-y: auto; -} - -@media (min-width: 768px) { - .navbar-collapse { - width: auto; - border-top: 0; - box-shadow: none; - } - .navbar-collapse.collapse { - display: block !important; - height: auto !important; - padding-bottom: 0; - overflow: visible !important; - } - .navbar-collapse.in { - overflow-y: visible; - } - .navbar-collapse .navbar-nav.navbar-left:first-child { - margin-left: -15px; - } - .navbar-collapse .navbar-nav.navbar-right:last-child { - margin-right: -15px; - } - .navbar-collapse .navbar-text:last-child { - margin-right: 0; - } -} - -.container > .navbar-header, -.container > .navbar-collapse { - margin-right: -15px; - margin-left: -15px; -} - -@media (min-width: 768px) { - .container > .navbar-header, - .container > .navbar-collapse { - margin-right: 0; - margin-left: 0; - } -} - -.navbar-static-top { - border-width: 0 0 1px; -} - -@media (min-width: 768px) { - .navbar-static-top { - border-radius: 0; - } -} - -.navbar-fixed-top, -.navbar-fixed-bottom { - position: fixed; - right: 0; - left: 0; - border-width: 0 0 1px; -} - -@media (min-width: 768px) { - .navbar-fixed-top, - .navbar-fixed-bottom { - border-radius: 0; - } -} - -.navbar-fixed-top { - top: 0; - z-index: 1030; -} - -.navbar-fixed-bottom { - bottom: 0; - margin-bottom: 0; -} - -.navbar-brand { - float: left; - padding: 15px 15px; - font-size: 18px; - line-height: 20px; -} - -.navbar-brand:hover, -.navbar-brand:focus { - text-decoration: none; -} - -@media (min-width: 768px) { - .navbar > .container .navbar-brand { - margin-left: -15px; - } -} - -.navbar-toggle { - position: relative; - float: right; - padding: 9px 10px; - margin-top: 8px; - margin-right: 15px; - margin-bottom: 8px; - background-color: transparent; - border: 1px solid transparent; - border-radius: 4px; -} - -.navbar-toggle .icon-bar { - display: block; - width: 22px; - height: 2px; - border-radius: 1px; -} - -.navbar-toggle .icon-bar + .icon-bar { - margin-top: 4px; -} - -@media (min-width: 768px) { - .navbar-toggle { - display: none; - } -} - -.navbar-nav { - margin: 7.5px -15px; -} - -.navbar-nav > li > a { - padding-top: 10px; - padding-bottom: 10px; - line-height: 20px; -} - -@media (max-width: 767px) { - .navbar-nav .open .dropdown-menu { - position: static; - float: none; - width: auto; - margin-top: 0; - background-color: transparent; - border: 0; - box-shadow: none; - } - .navbar-nav .open .dropdown-menu > li > a, - .navbar-nav .open .dropdown-menu .dropdown-header { - padding: 5px 15px 5px 25px; - } - .navbar-nav .open .dropdown-menu > li > a { - line-height: 20px; - } - .navbar-nav .open .dropdown-menu > li > a:hover, - .navbar-nav .open .dropdown-menu > li > a:focus { - background-image: none; - } -} - -@media (min-width: 768px) { - .navbar-nav { - float: left; - margin: 0; - } - .navbar-nav > li { - float: left; - } - .navbar-nav > li > a { - padding-top: 15px; - padding-bottom: 15px; - } -} - -@media (min-width: 768px) { - .navbar-left { - float: left !important; - } - .navbar-right { - float: right !important; - } -} - -.navbar-form { - padding: 10px 15px; - margin-top: 8px; - margin-right: -15px; - margin-bottom: 8px; - margin-left: -15px; - border-top: 1px solid transparent; - border-bottom: 1px solid transparent; - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); -} - -@media (min-width: 768px) { - .navbar-form .form-group { - display: inline-block; - margin-bottom: 0; - vertical-align: middle; - } - .navbar-form .form-control { - display: inline-block; - } - .navbar-form .radio, - .navbar-form .checkbox { - display: inline-block; - padding-left: 0; - margin-top: 0; - margin-bottom: 0; - } - .navbar-form .radio input[type="radio"], - .navbar-form .checkbox input[type="checkbox"] { - float: none; - margin-left: 0; - } -} - -@media (max-width: 767px) { - .navbar-form .form-group { - margin-bottom: 5px; - } -} - -@media (min-width: 768px) { - .navbar-form { - width: auto; - padding-top: 0; - padding-bottom: 0; - margin-right: 0; - margin-left: 0; - border: 0; - -webkit-box-shadow: none; - box-shadow: none; - } -} - -.navbar-nav > li > .dropdown-menu { - margin-top: 0; - border-top-right-radius: 0; - border-top-left-radius: 0; -} - -.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu { - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; -} - -.navbar-nav.pull-right > li > .dropdown-menu, -.navbar-nav > li > .dropdown-menu.pull-right { - right: 0; - left: auto; -} - -.navbar-btn { - margin-top: 8px; - margin-bottom: 8px; -} - -.navbar-text { - float: left; - margin-top: 15px; - margin-bottom: 15px; -} - -@media (min-width: 768px) { - .navbar-text { - margin-right: 15px; - margin-left: 15px; - } -} - -.navbar-default { - background-color: #f8f8f8; - border-color: #e7e7e7; -} - -.navbar-default .navbar-brand { - color: #777777; -} - -.navbar-default .navbar-brand:hover, -.navbar-default .navbar-brand:focus { - color: #5e5e5e; - background-color: transparent; -} - -.navbar-default .navbar-text { - color: #777777; -} - -.navbar-default .navbar-nav > li > a { - color: #777777; -} - -.navbar-default .navbar-nav > li > a:hover, -.navbar-default .navbar-nav > li > a:focus { - color: #333333; - background-color: transparent; -} - -.navbar-default .navbar-nav > .active > a, -.navbar-default .navbar-nav > .active > a:hover, -.navbar-default .navbar-nav > .active > a:focus { - color: #555555; - background-color: #e7e7e7; -} - -.navbar-default .navbar-nav > .disabled > a, -.navbar-default .navbar-nav > .disabled > a:hover, -.navbar-default .navbar-nav > .disabled > a:focus { - color: #cccccc; - background-color: transparent; -} - -.navbar-default .navbar-toggle { - border-color: #dddddd; -} - -.navbar-default .navbar-toggle:hover, -.navbar-default .navbar-toggle:focus { - background-color: #dddddd; -} - -.navbar-default .navbar-toggle .icon-bar { - background-color: #cccccc; -} - -.navbar-default .navbar-collapse, -.navbar-default .navbar-form { - border-color: #e6e6e6; -} - -.navbar-default .navbar-nav > .dropdown > a:hover .caret, -.navbar-default .navbar-nav > .dropdown > a:focus .caret { - border-top-color: #333333; - border-bottom-color: #333333; -} - -.navbar-default .navbar-nav > .open > a, -.navbar-default .navbar-nav > .open > a:hover, -.navbar-default .navbar-nav > .open > a:focus { - color: #555555; - background-color: #e7e7e7; -} - -.navbar-default .navbar-nav > .open > a .caret, -.navbar-default .navbar-nav > .open > a:hover .caret, -.navbar-default .navbar-nav > .open > a:focus .caret { - border-top-color: #555555; - border-bottom-color: #555555; -} - -.navbar-default .navbar-nav > .dropdown > a .caret { - border-top-color: #777777; - border-bottom-color: #777777; -} - -@media (max-width: 767px) { - .navbar-default .navbar-nav .open .dropdown-menu > li > a { - color: #777777; - } - .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover, - .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus { - color: #333333; - background-color: transparent; - } - .navbar-default .navbar-nav .open .dropdown-menu > .active > a, - .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover, - .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus { - color: #555555; - background-color: #e7e7e7; - } - .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a, - .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover, - .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus { - color: #cccccc; - background-color: transparent; - } -} - -.navbar-default .navbar-link { - color: #777777; -} - -.navbar-default .navbar-link:hover { - color: #333333; -} - -.navbar-inverse { - background-color: #222222; - border-color: #080808; -} - -.navbar-inverse .navbar-brand { - color: #999999; -} - -.navbar-inverse .navbar-brand:hover, -.navbar-inverse .navbar-brand:focus { - color: #ffffff; - background-color: transparent; -} - -.navbar-inverse .navbar-text { - color: #999999; -} - -.navbar-inverse .navbar-nav > li > a { - color: #999999; -} - -.navbar-inverse .navbar-nav > li > a:hover, -.navbar-inverse .navbar-nav > li > a:focus { - color: #ffffff; - background-color: transparent; -} - -.navbar-inverse .navbar-nav > .active > a, -.navbar-inverse .navbar-nav > .active > a:hover, -.navbar-inverse .navbar-nav > .active > a:focus { - color: #ffffff; - background-color: #080808; -} - -.navbar-inverse .navbar-nav > .disabled > a, -.navbar-inverse .navbar-nav > .disabled > a:hover, -.navbar-inverse .navbar-nav > .disabled > a:focus { - color: #444444; - background-color: transparent; -} - -.navbar-inverse .navbar-toggle { - border-color: #333333; -} - -.navbar-inverse .navbar-toggle:hover, -.navbar-inverse .navbar-toggle:focus { - background-color: #333333; -} - -.navbar-inverse .navbar-toggle .icon-bar { - background-color: #ffffff; -} - -.navbar-inverse .navbar-collapse, -.navbar-inverse .navbar-form { - border-color: #101010; -} - -.navbar-inverse .navbar-nav > .open > a, -.navbar-inverse .navbar-nav > .open > a:hover, -.navbar-inverse .navbar-nav > .open > a:focus { - color: #ffffff; - background-color: #080808; -} - -.navbar-inverse .navbar-nav > .dropdown > a:hover .caret { - border-top-color: #ffffff; - border-bottom-color: #ffffff; -} - -.navbar-inverse .navbar-nav > .dropdown > a .caret { - border-top-color: #999999; - border-bottom-color: #999999; -} - -.navbar-inverse .navbar-nav > .open > a .caret, -.navbar-inverse .navbar-nav > .open > a:hover .caret, -.navbar-inverse .navbar-nav > .open > a:focus .caret { - border-top-color: #ffffff; - border-bottom-color: #ffffff; -} - -@media (max-width: 767px) { - .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header { - border-color: #080808; - } - .navbar-inverse .navbar-nav .open .dropdown-menu > li > a { - color: #999999; - } - .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover, - .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus { - color: #ffffff; - background-color: transparent; - } - .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a, - .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover, - .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus { - color: #ffffff; - background-color: #080808; - } - .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a, - .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover, - .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus { - color: #444444; - background-color: transparent; - } -} - -.navbar-inverse .navbar-link { - color: #999999; -} - -.navbar-inverse .navbar-link:hover { - color: #ffffff; -} - -.breadcrumb { - padding: 8px 15px; - margin-bottom: 20px; - list-style: none; - background-color: #f5f5f5; - border-radius: 4px; -} - -.breadcrumb > li { - display: inline-block; -} - -.breadcrumb > li + li:before { - padding: 0 5px; - color: #cccccc; - content: "/\00a0"; -} - -.breadcrumb > .active { - color: #999999; -} - -.pagination { - display: inline-block; - padding-left: 0; - margin: 20px 0; - border-radius: 4px; -} - -.pagination > li { - display: inline; -} - -.pagination > li > a, -.pagination > li > span { - position: relative; - float: left; - padding: 6px 12px; - margin-left: -1px; - line-height: 1.428571429; - text-decoration: none; - background-color: #ffffff; - border: 1px solid #dddddd; -} - -.pagination > li:first-child > a, -.pagination > li:first-child > span { - margin-left: 0; - border-bottom-left-radius: 4px; - border-top-left-radius: 4px; -} - -.pagination > li:last-child > a, -.pagination > li:last-child > span { - border-top-right-radius: 4px; - border-bottom-right-radius: 4px; -} - -.pagination > li > a:hover, -.pagination > li > span:hover, -.pagination > li > a:focus, -.pagination > li > span:focus { - background-color: #eeeeee; -} - -.pagination > .active > a, -.pagination > .active > span, -.pagination > .active > a:hover, -.pagination > .active > span:hover, -.pagination > .active > a:focus, -.pagination > .active > span:focus { - z-index: 2; - color: #ffffff; - cursor: default; - background-color: #428bca; - border-color: #428bca; -} - -.pagination > .disabled > span, -.pagination > .disabled > a, -.pagination > .disabled > a:hover, -.pagination > .disabled > a:focus { - color: #999999; - cursor: not-allowed; - background-color: #ffffff; - border-color: #dddddd; -} - -.pagination-lg > li > a, -.pagination-lg > li > span { - padding: 10px 16px; - font-size: 18px; -} - -.pagination-lg > li:first-child > a, -.pagination-lg > li:first-child > span { - border-bottom-left-radius: 6px; - border-top-left-radius: 6px; -} - -.pagination-lg > li:last-child > a, -.pagination-lg > li:last-child > span { - border-top-right-radius: 6px; - border-bottom-right-radius: 6px; -} - -.pagination-sm > li > a, -.pagination-sm > li > span { - padding: 5px 10px; - font-size: 12px; -} - -.pagination-sm > li:first-child > a, -.pagination-sm > li:first-child > span { - border-bottom-left-radius: 3px; - border-top-left-radius: 3px; -} - -.pagination-sm > li:last-child > a, -.pagination-sm > li:last-child > span { - border-top-right-radius: 3px; - border-bottom-right-radius: 3px; -} - -.pager { - padding-left: 0; - margin: 20px 0; - text-align: center; - list-style: none; -} - -.pager:before, -.pager:after { - display: table; - content: " "; -} - -.pager:after { - clear: both; -} - -.pager:before, -.pager:after { - display: table; - content: " "; -} - -.pager:after { - clear: both; -} - -.pager li { - display: inline; -} - -.pager li > a, -.pager li > span { - display: inline-block; - padding: 5px 14px; - background-color: #ffffff; - border: 1px solid #dddddd; - border-radius: 15px; -} - -.pager li > a:hover, -.pager li > a:focus { - text-decoration: none; - background-color: #eeeeee; -} - -.pager .next > a, -.pager .next > span { - float: right; -} - -.pager .previous > a, -.pager .previous > span { - float: left; -} - -.pager .disabled > a, -.pager .disabled > a:hover, -.pager .disabled > a:focus, -.pager .disabled > span { - color: #999999; - cursor: not-allowed; - background-color: #ffffff; -} - -.label { - display: inline; - padding: .2em .6em .3em; - font-size: 75%; - font-weight: bold; - line-height: 1; - color: #ffffff; - text-align: center; - white-space: nowrap; - vertical-align: baseline; - border-radius: .25em; -} - -.label[href]:hover, -.label[href]:focus { - color: #ffffff; - text-decoration: none; - cursor: pointer; -} - -.label:empty { - display: none; -} - -.label-default { - background-color: #999999; -} - -.label-default[href]:hover, -.label-default[href]:focus { - background-color: #808080; -} - -.label-primary { - background-color: #428bca; -} - -.label-primary[href]:hover, -.label-primary[href]:focus { - background-color: #3071a9; -} - -.label-success { - background-color: #5cb85c; -} - -.label-success[href]:hover, -.label-success[href]:focus { - background-color: #449d44; -} - -.label-info { - background-color: #5bc0de; -} - -.label-info[href]:hover, -.label-info[href]:focus { - background-color: #31b0d5; -} - -.label-warning { - background-color: #f0ad4e; -} - -.label-warning[href]:hover, -.label-warning[href]:focus { - background-color: #ec971f; -} - -.label-danger { - background-color: #d9534f; -} - -.label-danger[href]:hover, -.label-danger[href]:focus { - background-color: #c9302c; -} - -.badge { - display: inline-block; - min-width: 10px; - padding: 3px 7px; - font-size: 12px; - font-weight: bold; - line-height: 1; - color: #ffffff; - text-align: center; - white-space: nowrap; - vertical-align: baseline; - background-color: #999999; - border-radius: 10px; -} - -.badge:empty { - display: none; -} - -a.badge:hover, -a.badge:focus { - color: #ffffff; - text-decoration: none; - cursor: pointer; -} - -.btn .badge { - position: relative; - top: -1px; -} - -a.list-group-item.active > .badge, -.nav-pills > .active > a > .badge { - color: #428bca; - background-color: #ffffff; -} - -.nav-pills > li > a > .badge { - margin-left: 3px; -} - -.jumbotron { - padding: 30px; - margin-bottom: 30px; - font-size: 21px; - font-weight: 200; - line-height: 2.1428571435; - color: inherit; - background-color: #eeeeee; -} - -.jumbotron h1 { - line-height: 1; - color: inherit; -} - -.jumbotron p { - line-height: 1.4; -} - -.container .jumbotron { - border-radius: 6px; -} - -@media screen and (min-width: 768px) { - .jumbotron { - padding-top: 48px; - padding-bottom: 48px; - } - .container .jumbotron { - padding-right: 60px; - padding-left: 60px; - } - .jumbotron h1 { - font-size: 63px; - } -} - -.thumbnail { - display: inline-block; - display: block; - height: auto; - max-width: 100%; - padding: 4px; - line-height: 1.428571429; - background-color: #ffffff; - border: 1px solid #dddddd; - border-radius: 4px; - -webkit-transition: all 0.2s ease-in-out; - transition: all 0.2s ease-in-out; -} - -.thumbnail > img { - display: block; - height: auto; - max-width: 100%; -} - -a.thumbnail:hover, -a.thumbnail:focus { - border-color: #428bca; -} - -.thumbnail > img { - margin-right: auto; - margin-left: auto; -} - -.thumbnail .caption { - padding: 9px; - color: #333333; -} - -.alert { - padding: 15px; - margin-bottom: 20px; - border: 1px solid transparent; - border-radius: 4px; -} - -.alert h4 { - margin-top: 0; - color: inherit; -} - -.alert .alert-link { - font-weight: bold; -} - -.alert > p, -.alert > ul { - margin-bottom: 0; -} - -.alert > p + p { - margin-top: 5px; -} - -.alert-dismissable { - padding-right: 35px; -} - -.alert-dismissable .close { - position: relative; - top: -2px; - right: -21px; - color: inherit; -} - -.alert-success { - color: #468847; - background-color: #dff0d8; - border-color: #d6e9c6; -} - -.alert-success hr { - border-top-color: #c9e2b3; -} - -.alert-success .alert-link { - color: #356635; -} - -.alert-info { - color: #3a87ad; - background-color: #d9edf7; - border-color: #bce8f1; -} - -.alert-info hr { - border-top-color: #a6e1ec; -} - -.alert-info .alert-link { - color: #2d6987; -} - -.alert-warning { - color: #c09853; - background-color: #fcf8e3; - border-color: #fbeed5; -} - -.alert-warning hr { - border-top-color: #f8e5be; -} - -.alert-warning .alert-link { - color: #a47e3c; -} - -.alert-danger { - color: #b94a48; - background-color: #f2dede; - border-color: #eed3d7; -} - -.alert-danger hr { - border-top-color: #e6c1c7; -} - -.alert-danger .alert-link { - color: #953b39; -} - -@-webkit-keyframes progress-bar-stripes { - from { - background-position: 40px 0; - } - to { - background-position: 0 0; - } -} - -@-moz-keyframes progress-bar-stripes { - from { - background-position: 40px 0; - } - to { - background-position: 0 0; - } -} - -@-o-keyframes progress-bar-stripes { - from { - background-position: 0 0; - } - to { - background-position: 40px 0; - } -} - -@keyframes progress-bar-stripes { - from { - background-position: 40px 0; - } - to { - background-position: 0 0; - } -} - -.progress { - height: 20px; - margin-bottom: 20px; - overflow: hidden; - background-color: #f5f5f5; - border-radius: 4px; - -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); - box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); -} - -.progress-bar { - float: left; - width: 0; - height: 100%; - font-size: 12px; - color: #ffffff; - text-align: center; - background-color: #428bca; - -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); - box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); - -webkit-transition: width 0.6s ease; - transition: width 0.6s ease; -} - -.progress-striped .progress-bar { - background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-size: 40px 40px; -} - -.progress.active .progress-bar { - -webkit-animation: progress-bar-stripes 2s linear infinite; - -moz-animation: progress-bar-stripes 2s linear infinite; - -ms-animation: progress-bar-stripes 2s linear infinite; - -o-animation: progress-bar-stripes 2s linear infinite; - animation: progress-bar-stripes 2s linear infinite; -} - -.progress-bar-success { - background-color: #5cb85c; -} - -.progress-striped .progress-bar-success { - background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); -} - -.progress-bar-info { - background-color: #5bc0de; -} - -.progress-striped .progress-bar-info { - background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); -} - -.progress-bar-warning { - background-color: #f0ad4e; -} - -.progress-striped .progress-bar-warning { - background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); -} - -.progress-bar-danger { - background-color: #d9534f; -} - -.progress-striped .progress-bar-danger { - background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); -} - -.media, -.media-body { - overflow: hidden; - zoom: 1; -} - -.media, -.media .media { - margin-top: 15px; -} - -.media:first-child { - margin-top: 0; -} - -.media-object { - display: block; -} - -.media-heading { - margin: 0 0 5px; -} - -.media > .pull-left { - margin-right: 10px; -} - -.media > .pull-right { - margin-left: 10px; -} - -.media-list { - padding-left: 0; - list-style: none; -} - -.list-group { - padding-left: 0; - margin-bottom: 20px; -} - -.list-group-item { - position: relative; - display: block; - padding: 10px 15px; - margin-bottom: -1px; - background-color: #ffffff; - border: 1px solid #dddddd; -} - -.list-group-item:first-child { - border-top-right-radius: 4px; - border-top-left-radius: 4px; -} - -.list-group-item:last-child { - margin-bottom: 0; - border-bottom-right-radius: 4px; - border-bottom-left-radius: 4px; -} - -.list-group-item > .badge { - float: right; -} - -.list-group-item > .badge + .badge { - margin-right: 5px; -} - -a.list-group-item { - color: #555555; -} - -a.list-group-item .list-group-item-heading { - color: #333333; -} - -a.list-group-item:hover, -a.list-group-item:focus { - text-decoration: none; - background-color: #f5f5f5; -} - -.list-group-item.active, -.list-group-item.active:hover, -.list-group-item.active:focus { - z-index: 2; - color: #ffffff; - background-color: #428bca; - border-color: #428bca; -} - -.list-group-item.active .list-group-item-heading, -.list-group-item.active:hover .list-group-item-heading, -.list-group-item.active:focus .list-group-item-heading { - color: inherit; -} - -.list-group-item.active .list-group-item-text, -.list-group-item.active:hover .list-group-item-text, -.list-group-item.active:focus .list-group-item-text { - color: #e1edf7; -} - -.list-group-item-heading { - margin-top: 0; - margin-bottom: 5px; -} - -.list-group-item-text { - margin-bottom: 0; - line-height: 1.3; -} - -.panel { - margin-bottom: 20px; - background-color: #ffffff; - border: 1px solid transparent; - border-radius: 4px; - -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); - box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); -} - -.panel-body { - padding: 15px; -} - -.panel-body:before, -.panel-body:after { - display: table; - content: " "; -} - -.panel-body:after { - clear: both; -} - -.panel-body:before, -.panel-body:after { - display: table; - content: " "; -} - -.panel-body:after { - clear: both; -} - -.panel > .list-group { - margin-bottom: 0; -} - -.panel > .list-group .list-group-item { - border-width: 1px 0; -} - -.panel > .list-group .list-group-item:first-child { - border-top-right-radius: 0; - border-top-left-radius: 0; -} - -.panel > .list-group .list-group-item:last-child { - border-bottom: 0; -} - -.panel-heading + .list-group .list-group-item:first-child { - border-top-width: 0; -} - -.panel > .table { - margin-bottom: 0; -} - -.panel > .panel-body + .table { - border-top: 1px solid #dddddd; -} - -.panel-heading { - padding: 10px 15px; - border-bottom: 1px solid transparent; - border-top-right-radius: 3px; - border-top-left-radius: 3px; -} - -.panel-title { - margin-top: 0; - margin-bottom: 0; - font-size: 16px; -} - -.panel-title > a { - color: inherit; -} - -.panel-footer { - padding: 10px 15px; - background-color: #f5f5f5; - border-top: 1px solid #dddddd; - border-bottom-right-radius: 3px; - border-bottom-left-radius: 3px; -} - -.panel-group .panel { - margin-bottom: 0; - overflow: hidden; - border-radius: 4px; -} - -.panel-group .panel + .panel { - margin-top: 5px; -} - -.panel-group .panel-heading { - border-bottom: 0; -} - -.panel-group .panel-heading + .panel-collapse .panel-body { - border-top: 1px solid #dddddd; -} - -.panel-group .panel-footer { - border-top: 0; -} - -.panel-group .panel-footer + .panel-collapse .panel-body { - border-bottom: 1px solid #dddddd; -} - -.panel-default { - border-color: #dddddd; -} - -.panel-default > .panel-heading { - color: #333333; - background-color: #f5f5f5; - border-color: #dddddd; -} - -.panel-default > .panel-heading + .panel-collapse .panel-body { - border-top-color: #dddddd; -} - -.panel-default > .panel-footer + .panel-collapse .panel-body { - border-bottom-color: #dddddd; -} - -.panel-primary { - border-color: #428bca; -} - -.panel-primary > .panel-heading { - color: #ffffff; - background-color: #428bca; - border-color: #428bca; -} - -.panel-primary > .panel-heading + .panel-collapse .panel-body { - border-top-color: #428bca; -} - -.panel-primary > .panel-footer + .panel-collapse .panel-body { - border-bottom-color: #428bca; -} - -.panel-success { - border-color: #d6e9c6; -} - -.panel-success > .panel-heading { - color: #468847; - background-color: #dff0d8; - border-color: #d6e9c6; -} - -.panel-success > .panel-heading + .panel-collapse .panel-body { - border-top-color: #d6e9c6; -} - -.panel-success > .panel-footer + .panel-collapse .panel-body { - border-bottom-color: #d6e9c6; -} - -.panel-warning { - border-color: #fbeed5; -} - -.panel-warning > .panel-heading { - color: #c09853; - background-color: #fcf8e3; - border-color: #fbeed5; -} - -.panel-warning > .panel-heading + .panel-collapse .panel-body { - border-top-color: #fbeed5; -} - -.panel-warning > .panel-footer + .panel-collapse .panel-body { - border-bottom-color: #fbeed5; -} - -.panel-danger { - border-color: #eed3d7; -} - -.panel-danger > .panel-heading { - color: #b94a48; - background-color: #f2dede; - border-color: #eed3d7; -} - -.panel-danger > .panel-heading + .panel-collapse .panel-body { - border-top-color: #eed3d7; -} - -.panel-danger > .panel-footer + .panel-collapse .panel-body { - border-bottom-color: #eed3d7; -} - -.panel-info { - border-color: #bce8f1; -} - -.panel-info > .panel-heading { - color: #3a87ad; - background-color: #d9edf7; - border-color: #bce8f1; -} - -.panel-info > .panel-heading + .panel-collapse .panel-body { - border-top-color: #bce8f1; -} - -.panel-info > .panel-footer + .panel-collapse .panel-body { - border-bottom-color: #bce8f1; -} - -.well { - min-height: 20px; - padding: 19px; - margin-bottom: 20px; - background-color: #f5f5f5; - border: 1px solid #e3e3e3; - border-radius: 4px; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); -} - -.well blockquote { - border-color: #ddd; - border-color: rgba(0, 0, 0, 0.15); -} - -.well-lg { - padding: 24px; - border-radius: 6px; -} - -.well-sm { - padding: 9px; - border-radius: 3px; -} - -.close { - float: right; - font-size: 21px; - font-weight: bold; - line-height: 1; - color: #000000; - text-shadow: 0 1px 0 #ffffff; - opacity: 0.2; - filter: alpha(opacity=20); -} - -.close:hover, -.close:focus { - color: #000000; - text-decoration: none; - cursor: pointer; - opacity: 0.5; - filter: alpha(opacity=50); -} - -button.close { - padding: 0; - cursor: pointer; - background: transparent; - border: 0; - -webkit-appearance: none; -} - -.modal-open { - overflow: hidden; -} - -body.modal-open, -.modal-open .navbar-fixed-top, -.modal-open .navbar-fixed-bottom { - margin-right: 15px; -} - -.modal { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1040; - display: none; - overflow: auto; - overflow-y: scroll; -} - -.modal.fade .modal-dialog { - -webkit-transform: translate(0, -25%); - -ms-transform: translate(0, -25%); - transform: translate(0, -25%); - -webkit-transition: -webkit-transform 0.3s ease-out; - -moz-transition: -moz-transform 0.3s ease-out; - -o-transition: -o-transform 0.3s ease-out; - transition: transform 0.3s ease-out; -} - -.modal.in .modal-dialog { - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - transform: translate(0, 0); -} - -.modal-dialog { - z-index: 1050; - width: auto; - padding: 10px; - margin-right: auto; - margin-left: auto; -} - -.modal-content { - position: relative; - background-color: #ffffff; - border: 1px solid #999999; - border: 1px solid rgba(0, 0, 0, 0.2); - border-radius: 6px; - outline: none; - -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5); - box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5); - background-clip: padding-box; -} - -.modal-backdrop { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1030; - background-color: #000000; -} - -.modal-backdrop.fade { - opacity: 0; - filter: alpha(opacity=0); -} - -.modal-backdrop.in { - opacity: 0.5; - filter: alpha(opacity=50); -} - -.modal-header { - min-height: 16.428571429px; - padding: 15px; - border-bottom: 1px solid #e5e5e5; -} - -.modal-header .close { - margin-top: -2px; -} - -.modal-title { - margin: 0; - line-height: 1.428571429; -} - -.modal-body { - position: relative; - padding: 20px; -} - -.modal-footer { - padding: 19px 20px 20px; - margin-top: 15px; - text-align: right; - border-top: 1px solid #e5e5e5; -} - -.modal-footer:before, -.modal-footer:after { - display: table; - content: " "; -} - -.modal-footer:after { - clear: both; -} - -.modal-footer:before, -.modal-footer:after { - display: table; - content: " "; -} - -.modal-footer:after { - clear: both; -} - -.modal-footer .btn + .btn { - margin-bottom: 0; - margin-left: 5px; -} - -.modal-footer .btn-group .btn + .btn { - margin-left: -1px; -} - -.modal-footer .btn-block + .btn-block { - margin-left: 0; -} - -@media screen and (min-width: 768px) { - .modal-dialog { - right: auto; - left: 50%; - width: 600px; - padding-top: 30px; - padding-bottom: 30px; - } - .modal-content { - -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); - box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); - } -} - -.tooltip { - position: absolute; - z-index: 1030; - display: block; - font-size: 12px; - line-height: 1.4; - opacity: 0; - filter: alpha(opacity=0); - visibility: visible; -} - -.tooltip.in { - opacity: 0.9; - filter: alpha(opacity=90); -} - -.tooltip.top { - padding: 5px 0; - margin-top: -3px; -} - -.tooltip.right { - padding: 0 5px; - margin-left: 3px; -} - -.tooltip.bottom { - padding: 5px 0; - margin-top: 3px; -} - -.tooltip.left { - padding: 0 5px; - margin-left: -3px; -} - -.tooltip-inner { - max-width: 200px; - padding: 3px 8px; - color: #ffffff; - text-align: center; - text-decoration: none; - background-color: #000000; - border-radius: 4px; -} - -.tooltip-arrow { - position: absolute; - width: 0; - height: 0; - border-color: transparent; - border-style: solid; -} - -.tooltip.top .tooltip-arrow { - bottom: 0; - left: 50%; - margin-left: -5px; - border-top-color: #000000; - border-width: 5px 5px 0; -} - -.tooltip.top-left .tooltip-arrow { - bottom: 0; - left: 5px; - border-top-color: #000000; - border-width: 5px 5px 0; -} - -.tooltip.top-right .tooltip-arrow { - right: 5px; - bottom: 0; - border-top-color: #000000; - border-width: 5px 5px 0; -} - -.tooltip.right .tooltip-arrow { - top: 50%; - left: 0; - margin-top: -5px; - border-right-color: #000000; - border-width: 5px 5px 5px 0; -} - -.tooltip.left .tooltip-arrow { - top: 50%; - right: 0; - margin-top: -5px; - border-left-color: #000000; - border-width: 5px 0 5px 5px; -} - -.tooltip.bottom .tooltip-arrow { - top: 0; - left: 50%; - margin-left: -5px; - border-bottom-color: #000000; - border-width: 0 5px 5px; -} - -.tooltip.bottom-left .tooltip-arrow { - top: 0; - left: 5px; - border-bottom-color: #000000; - border-width: 0 5px 5px; -} - -.tooltip.bottom-right .tooltip-arrow { - top: 0; - right: 5px; - border-bottom-color: #000000; - border-width: 0 5px 5px; -} - -.popover { - position: absolute; - top: 0; - left: 0; - z-index: 1010; - display: none; - max-width: 276px; - padding: 1px; - text-align: left; - white-space: normal; - background-color: #ffffff; - border: 1px solid #cccccc; - border: 1px solid rgba(0, 0, 0, 0.2); - border-radius: 6px; - -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); - box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); - background-clip: padding-box; -} - -.popover.top { - margin-top: -10px; -} - -.popover.right { - margin-left: 10px; -} - -.popover.bottom { - margin-top: 10px; -} - -.popover.left { - margin-left: -10px; -} - -.popover-title { - padding: 8px 14px; - margin: 0; - font-size: 14px; - font-weight: normal; - line-height: 18px; - background-color: #f7f7f7; - border-bottom: 1px solid #ebebeb; - border-radius: 5px 5px 0 0; -} - -.popover-content { - padding: 9px 14px; -} - -.popover .arrow, -.popover .arrow:after { - position: absolute; - display: block; - width: 0; - height: 0; - border-color: transparent; - border-style: solid; -} - -.popover .arrow { - border-width: 11px; -} - -.popover .arrow:after { - border-width: 10px; - content: ""; -} - -.popover.top .arrow { - bottom: -11px; - left: 50%; - margin-left: -11px; - border-top-color: #999999; - border-top-color: rgba(0, 0, 0, 0.25); - border-bottom-width: 0; -} - -.popover.top .arrow:after { - bottom: 1px; - margin-left: -10px; - border-top-color: #ffffff; - border-bottom-width: 0; - content: " "; -} - -.popover.right .arrow { - top: 50%; - left: -11px; - margin-top: -11px; - border-right-color: #999999; - border-right-color: rgba(0, 0, 0, 0.25); - border-left-width: 0; -} - -.popover.right .arrow:after { - bottom: -10px; - left: 1px; - border-right-color: #ffffff; - border-left-width: 0; - content: " "; -} - -.popover.bottom .arrow { - top: -11px; - left: 50%; - margin-left: -11px; - border-bottom-color: #999999; - border-bottom-color: rgba(0, 0, 0, 0.25); - border-top-width: 0; -} - -.popover.bottom .arrow:after { - top: 1px; - margin-left: -10px; - border-bottom-color: #ffffff; - border-top-width: 0; - content: " "; -} - -.popover.left .arrow { - top: 50%; - right: -11px; - margin-top: -11px; - border-left-color: #999999; - border-left-color: rgba(0, 0, 0, 0.25); - border-right-width: 0; -} - -.popover.left .arrow:after { - right: 1px; - bottom: -10px; - border-left-color: #ffffff; - border-right-width: 0; - content: " "; -} - -.carousel { - position: relative; -} - -.carousel-inner { - position: relative; - width: 100%; - overflow: hidden; -} - -.carousel-inner > .item { - position: relative; - display: none; - -webkit-transition: 0.6s ease-in-out left; - transition: 0.6s ease-in-out left; -} - -.carousel-inner > .item > img, -.carousel-inner > .item > a > img { - display: block; - height: auto; - max-width: 100%; - line-height: 1; -} - -.carousel-inner > .active, -.carousel-inner > .next, -.carousel-inner > .prev { - display: block; -} - -.carousel-inner > .active { - left: 0; -} - -.carousel-inner > .next, -.carousel-inner > .prev { - position: absolute; - top: 0; - width: 100%; -} - -.carousel-inner > .next { - left: 100%; -} - -.carousel-inner > .prev { - left: -100%; -} - -.carousel-inner > .next.left, -.carousel-inner > .prev.right { - left: 0; -} - -.carousel-inner > .active.left { - left: -100%; -} - -.carousel-inner > .active.right { - left: 100%; -} - -.carousel-control { - position: absolute; - top: 0; - bottom: 0; - left: 0; - width: 15%; - font-size: 20px; - color: #ffffff; - text-align: center; - text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); - opacity: 0.5; - filter: alpha(opacity=50); -} - -.carousel-control.left { - background-image: -webkit-gradient(linear, 0 top, 100% top, from(rgba(0, 0, 0, 0.5)), to(rgba(0, 0, 0, 0.0001))); - background-image: -webkit-linear-gradient(left, color-stop(rgba(0, 0, 0, 0.5) 0), color-stop(rgba(0, 0, 0, 0.0001) 100%)); - background-image: -moz-linear-gradient(left, rgba(0, 0, 0, 0.5) 0, rgba(0, 0, 0, 0.0001) 100%); - background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0, rgba(0, 0, 0, 0.0001) 100%); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1); -} - -.carousel-control.right { - right: 0; - left: auto; - background-image: -webkit-gradient(linear, 0 top, 100% top, from(rgba(0, 0, 0, 0.0001)), to(rgba(0, 0, 0, 0.5))); - background-image: -webkit-linear-gradient(left, color-stop(rgba(0, 0, 0, 0.0001) 0), color-stop(rgba(0, 0, 0, 0.5) 100%)); - background-image: -moz-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0, rgba(0, 0, 0, 0.5) 100%); - background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0, rgba(0, 0, 0, 0.5) 100%); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1); -} - -.carousel-control:hover, -.carousel-control:focus { - color: #ffffff; - text-decoration: none; - opacity: 0.9; - filter: alpha(opacity=90); -} - -.carousel-control .icon-prev, -.carousel-control .icon-next, -.carousel-control .glyphicon-chevron-left, -.carousel-control .glyphicon-chevron-right { - position: absolute; - top: 50%; - left: 50%; - z-index: 5; - display: inline-block; -} - -.carousel-control .icon-prev, -.carousel-control .icon-next { - width: 20px; - height: 20px; - margin-top: -10px; - margin-left: -10px; - font-family: serif; -} - -.carousel-control .icon-prev:before { - content: '\2039'; -} - -.carousel-control .icon-next:before { - content: '\203a'; -} - -.carousel-indicators { - position: absolute; - bottom: 10px; - left: 50%; - z-index: 15; - width: 60%; - padding-left: 0; - margin-left: -30%; - text-align: center; - list-style: none; -} - -.carousel-indicators li { - display: inline-block; - width: 10px; - height: 10px; - margin: 1px; - text-indent: -999px; - cursor: pointer; - border: 1px solid #ffffff; - border-radius: 10px; -} - -.carousel-indicators .active { - width: 12px; - height: 12px; - margin: 0; - background-color: #ffffff; -} - -.carousel-caption { - position: absolute; - right: 15%; - bottom: 20px; - left: 15%; - z-index: 10; - padding-top: 20px; - padding-bottom: 20px; - color: #ffffff; - text-align: center; - text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); -} - -.carousel-caption .btn { - text-shadow: none; -} - -@media screen and (min-width: 768px) { - .carousel-control .icon-prev, - .carousel-control .icon-next { - width: 30px; - height: 30px; - margin-top: -15px; - margin-left: -15px; - font-size: 30px; - } - .carousel-caption { - right: 20%; - left: 20%; - padding-bottom: 30px; - } - .carousel-indicators { - bottom: 20px; - } -} - -.clearfix:before, -.clearfix:after { - display: table; - content: " "; -} - -.clearfix:after { - clear: both; -} - -.pull-right { - float: right !important; -} - -.pull-left { - float: left !important; -} - -.hide { - display: none !important; -} - -.show { - display: block !important; -} - -.invisible { - visibility: hidden; -} - -.text-hide { - font: 0/0 a; - color: transparent; - text-shadow: none; - background-color: transparent; - border: 0; -} - -.affix { - position: fixed; -} - -@-ms-viewport { - width: device-width; -} - -@media screen and (max-width: 400px) { - @-ms-viewport { - width: 320px; - } -} - -.hidden { - display: none !important; - visibility: hidden !important; -} - -.visible-xs { - display: none !important; -} - -tr.visible-xs { - display: none !important; -} - -th.visible-xs, -td.visible-xs { - display: none !important; -} - -@media (max-width: 767px) { - .visible-xs { - display: block !important; - } - tr.visible-xs { - display: table-row !important; - } - th.visible-xs, - td.visible-xs { - display: table-cell !important; - } -} - -@media (min-width: 768px) and (max-width: 991px) { - .visible-xs.visible-sm { - display: block !important; - } - tr.visible-xs.visible-sm { - display: table-row !important; - } - th.visible-xs.visible-sm, - td.visible-xs.visible-sm { - display: table-cell !important; - } -} - -@media (min-width: 992px) and (max-width: 1199px) { - .visible-xs.visible-md { - display: block !important; - } - tr.visible-xs.visible-md { - display: table-row !important; - } - th.visible-xs.visible-md, - td.visible-xs.visible-md { - display: table-cell !important; - } -} - -@media (min-width: 1200px) { - .visible-xs.visible-lg { - display: block !important; - } - tr.visible-xs.visible-lg { - display: table-row !important; - } - th.visible-xs.visible-lg, - td.visible-xs.visible-lg { - display: table-cell !important; - } -} - -.visible-sm { - display: none !important; -} - -tr.visible-sm { - display: none !important; -} - -th.visible-sm, -td.visible-sm { - display: none !important; -} - -@media (max-width: 767px) { - .visible-sm.visible-xs { - display: block !important; - } - tr.visible-sm.visible-xs { - display: table-row !important; - } - th.visible-sm.visible-xs, - td.visible-sm.visible-xs { - display: table-cell !important; - } -} - -@media (min-width: 768px) and (max-width: 991px) { - .visible-sm { - display: block !important; - } - tr.visible-sm { - display: table-row !important; - } - th.visible-sm, - td.visible-sm { - display: table-cell !important; - } -} - -@media (min-width: 992px) and (max-width: 1199px) { - .visible-sm.visible-md { - display: block !important; - } - tr.visible-sm.visible-md { - display: table-row !important; - } - th.visible-sm.visible-md, - td.visible-sm.visible-md { - display: table-cell !important; - } -} - -@media (min-width: 1200px) { - .visible-sm.visible-lg { - display: block !important; - } - tr.visible-sm.visible-lg { - display: table-row !important; - } - th.visible-sm.visible-lg, - td.visible-sm.visible-lg { - display: table-cell !important; - } -} - -.visible-md { - display: none !important; -} - -tr.visible-md { - display: none !important; -} - -th.visible-md, -td.visible-md { - display: none !important; -} - -@media (max-width: 767px) { - .visible-md.visible-xs { - display: block !important; - } - tr.visible-md.visible-xs { - display: table-row !important; - } - th.visible-md.visible-xs, - td.visible-md.visible-xs { - display: table-cell !important; - } -} - -@media (min-width: 768px) and (max-width: 991px) { - .visible-md.visible-sm { - display: block !important; - } - tr.visible-md.visible-sm { - display: table-row !important; - } - th.visible-md.visible-sm, - td.visible-md.visible-sm { - display: table-cell !important; - } -} - -@media (min-width: 992px) and (max-width: 1199px) { - .visible-md { - display: block !important; - } - tr.visible-md { - display: table-row !important; - } - th.visible-md, - td.visible-md { - display: table-cell !important; - } -} - -@media (min-width: 1200px) { - .visible-md.visible-lg { - display: block !important; - } - tr.visible-md.visible-lg { - display: table-row !important; - } - th.visible-md.visible-lg, - td.visible-md.visible-lg { - display: table-cell !important; - } -} - -.visible-lg { - display: none !important; -} - -tr.visible-lg { - display: none !important; -} - -th.visible-lg, -td.visible-lg { - display: none !important; -} - -@media (max-width: 767px) { - .visible-lg.visible-xs { - display: block !important; - } - tr.visible-lg.visible-xs { - display: table-row !important; - } - th.visible-lg.visible-xs, - td.visible-lg.visible-xs { - display: table-cell !important; - } -} - -@media (min-width: 768px) and (max-width: 991px) { - .visible-lg.visible-sm { - display: block !important; - } - tr.visible-lg.visible-sm { - display: table-row !important; - } - th.visible-lg.visible-sm, - td.visible-lg.visible-sm { - display: table-cell !important; - } -} - -@media (min-width: 992px) and (max-width: 1199px) { - .visible-lg.visible-md { - display: block !important; - } - tr.visible-lg.visible-md { - display: table-row !important; - } - th.visible-lg.visible-md, - td.visible-lg.visible-md { - display: table-cell !important; - } -} - -@media (min-width: 1200px) { - .visible-lg { - display: block !important; - } - tr.visible-lg { - display: table-row !important; - } - th.visible-lg, - td.visible-lg { - display: table-cell !important; - } -} - -.hidden-xs { - display: block !important; -} - -tr.hidden-xs { - display: table-row !important; -} - -th.hidden-xs, -td.hidden-xs { - display: table-cell !important; -} - -@media (max-width: 767px) { - .hidden-xs { - display: none !important; - } - tr.hidden-xs { - display: none !important; - } - th.hidden-xs, - td.hidden-xs { - display: none !important; - } -} - -@media (min-width: 768px) and (max-width: 991px) { - .hidden-xs.hidden-sm { - display: none !important; - } - tr.hidden-xs.hidden-sm { - display: none !important; - } - th.hidden-xs.hidden-sm, - td.hidden-xs.hidden-sm { - display: none !important; - } -} - -@media (min-width: 992px) and (max-width: 1199px) { - .hidden-xs.hidden-md { - display: none !important; - } - tr.hidden-xs.hidden-md { - display: none !important; - } - th.hidden-xs.hidden-md, - td.hidden-xs.hidden-md { - display: none !important; - } -} - -@media (min-width: 1200px) { - .hidden-xs.hidden-lg { - display: none !important; - } - tr.hidden-xs.hidden-lg { - display: none !important; - } - th.hidden-xs.hidden-lg, - td.hidden-xs.hidden-lg { - display: none !important; - } -} - -.hidden-sm { - display: block !important; -} - -tr.hidden-sm { - display: table-row !important; -} - -th.hidden-sm, -td.hidden-sm { - display: table-cell !important; -} - -@media (max-width: 767px) { - .hidden-sm.hidden-xs { - display: none !important; - } - tr.hidden-sm.hidden-xs { - display: none !important; - } - th.hidden-sm.hidden-xs, - td.hidden-sm.hidden-xs { - display: none !important; - } -} - -@media (min-width: 768px) and (max-width: 991px) { - .hidden-sm { - display: none !important; - } - tr.hidden-sm { - display: none !important; - } - th.hidden-sm, - td.hidden-sm { - display: none !important; - } -} - -@media (min-width: 992px) and (max-width: 1199px) { - .hidden-sm.hidden-md { - display: none !important; - } - tr.hidden-sm.hidden-md { - display: none !important; - } - th.hidden-sm.hidden-md, - td.hidden-sm.hidden-md { - display: none !important; - } -} - -@media (min-width: 1200px) { - .hidden-sm.hidden-lg { - display: none !important; - } - tr.hidden-sm.hidden-lg { - display: none !important; - } - th.hidden-sm.hidden-lg, - td.hidden-sm.hidden-lg { - display: none !important; - } -} - -.hidden-md { - display: block !important; -} - -tr.hidden-md { - display: table-row !important; -} - -th.hidden-md, -td.hidden-md { - display: table-cell !important; -} - -@media (max-width: 767px) { - .hidden-md.hidden-xs { - display: none !important; - } - tr.hidden-md.hidden-xs { - display: none !important; - } - th.hidden-md.hidden-xs, - td.hidden-md.hidden-xs { - display: none !important; - } -} - -@media (min-width: 768px) and (max-width: 991px) { - .hidden-md.hidden-sm { - display: none !important; - } - tr.hidden-md.hidden-sm { - display: none !important; - } - th.hidden-md.hidden-sm, - td.hidden-md.hidden-sm { - display: none !important; - } -} - -@media (min-width: 992px) and (max-width: 1199px) { - .hidden-md { - display: none !important; - } - tr.hidden-md { - display: none !important; - } - th.hidden-md, - td.hidden-md { - display: none !important; - } -} - -@media (min-width: 1200px) { - .hidden-md.hidden-lg { - display: none !important; - } - tr.hidden-md.hidden-lg { - display: none !important; - } - th.hidden-md.hidden-lg, - td.hidden-md.hidden-lg { - display: none !important; - } -} - -.hidden-lg { - display: block !important; -} - -tr.hidden-lg { - display: table-row !important; -} - -th.hidden-lg, -td.hidden-lg { - display: table-cell !important; -} - -@media (max-width: 767px) { - .hidden-lg.hidden-xs { - display: none !important; - } - tr.hidden-lg.hidden-xs { - display: none !important; - } - th.hidden-lg.hidden-xs, - td.hidden-lg.hidden-xs { - display: none !important; - } -} - -@media (min-width: 768px) and (max-width: 991px) { - .hidden-lg.hidden-sm { - display: none !important; - } - tr.hidden-lg.hidden-sm { - display: none !important; - } - th.hidden-lg.hidden-sm, - td.hidden-lg.hidden-sm { - display: none !important; - } -} - -@media (min-width: 992px) and (max-width: 1199px) { - .hidden-lg.hidden-md { - display: none !important; - } - tr.hidden-lg.hidden-md { - display: none !important; - } - th.hidden-lg.hidden-md, - td.hidden-lg.hidden-md { - display: none !important; - } -} - -@media (min-width: 1200px) { - .hidden-lg { - display: none !important; - } - tr.hidden-lg { - display: none !important; - } - th.hidden-lg, - td.hidden-lg { - display: none !important; - } -} - -.visible-print { - display: none !important; -} - -tr.visible-print { - display: none !important; -} - -th.visible-print, -td.visible-print { - display: none !important; -} - -@media print { - .visible-print { - display: block !important; - } - tr.visible-print { - display: table-row !important; - } - th.visible-print, - td.visible-print { - display: table-cell !important; - } - .hidden-print { - display: none !important; - } - tr.hidden-print { - display: none !important; - } - th.hidden-print, - td.hidden-print { - display: none !important; - } -} \ No newline at end of file diff --git a/framework/yii/bootstrap/assets/fonts/glyphicons-halflings-regular.eot b/framework/yii/bootstrap/assets/fonts/glyphicons-halflings-regular.eot deleted file mode 100644 index 87eaa43..0000000 Binary files a/framework/yii/bootstrap/assets/fonts/glyphicons-halflings-regular.eot and /dev/null differ diff --git a/framework/yii/bootstrap/assets/fonts/glyphicons-halflings-regular.svg b/framework/yii/bootstrap/assets/fonts/glyphicons-halflings-regular.svg deleted file mode 100644 index 5fee068..0000000 --- a/framework/yii/bootstrap/assets/fonts/glyphicons-halflings-regular.svg +++ /dev/null @@ -1,228 +0,0 @@ -<?xml version="1.0" standalone="no"?> -<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" > -<svg xmlns="http://www.w3.org/2000/svg"> -<metadata></metadata> -<defs> -<font id="glyphicons_halflingsregular" horiz-adv-x="1200" > -<font-face units-per-em="1200" ascent="960" descent="-240" /> -<missing-glyph horiz-adv-x="500" /> -<glyph /> -<glyph /> -<glyph unicode=" " /> -<glyph unicode="*" d="M1100 500h-259l183 -183l-141 -141l-183 183v-259h-200v259l-183 -183l-141 141l183 183h-259v200h259l-183 183l141 141l183 -183v259h200v-259l183 183l141 -141l-183 -183h259v-200z" /> -<glyph unicode="+" d="M1100 400h-400v-400h-300v400h-400v300h400v400h300v-400h400v-300z" /> -<glyph unicode=" " /> -<glyph unicode=" " horiz-adv-x="652" /> -<glyph unicode=" " horiz-adv-x="1304" /> -<glyph unicode=" " horiz-adv-x="652" /> -<glyph unicode=" " horiz-adv-x="1304" /> -<glyph unicode=" " horiz-adv-x="434" /> -<glyph unicode=" " horiz-adv-x="326" /> -<glyph unicode=" " horiz-adv-x="217" /> -<glyph unicode=" " horiz-adv-x="217" /> -<glyph unicode=" " horiz-adv-x="163" /> -<glyph unicode=" " horiz-adv-x="260" /> -<glyph unicode=" " horiz-adv-x="72" /> -<glyph unicode=" " horiz-adv-x="260" /> -<glyph unicode=" " horiz-adv-x="326" /> -<glyph unicode="€" d="M800 500h-300q9 -74 33 -132t52.5 -91t62 -54.5t59 -29t46.5 -7.5q29 0 66 13t75 37t63.5 67.5t25.5 96.5h174q-31 -172 -128 -278q-107 -117 -274 -117q-205 0 -324 158q-36 46 -69 131.5t-45 205.5h-217l100 100h113q0 47 5 100h-218l100 100h135q37 167 112 257 q117 141 297 141q242 0 354 -189q60 -103 66 -209h-181q0 55 -25.5 99t-63.5 68t-75 36.5t-67 12.5q-24 0 -52.5 -10t-62.5 -32t-65.5 -67t-50.5 -107h379l-100 -100h-300q-6 -46 -6 -100h406z" /> -<glyph unicode="−" d="M1100 700h-900v-300h900v300z" /> -<glyph unicode="☁" d="M178 300h750q120 0 205 86t85 208q0 120 -85 206.5t-205 86.5q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5q0 -80 56.5 -137t135.5 -57z" /> -<glyph unicode="✉" d="M1200 1100h-1200l600 -603zM300 600l-300 -300v600zM1200 900v-600l-300 300zM800 500l400 -400h-1200l400 400l200 -200z" /> -<glyph unicode="✏" d="M1101 889l99 92q13 13 13 32.5t-13 33.5l-153 153q-15 13 -33 13t-33 -13l-94 -97zM401 189l614 614l-214 214l-614 -614zM-13 -13l333 112l-223 223z" /> -<glyph unicode="" horiz-adv-x="500" d="M0 0z" /> -<glyph unicode="" d="M700 100h300v-100h-800v100h300v550l-500 550h1200l-500 -550v-550z" /> -<glyph unicode="" d="M1000 934v-521q-64 16 -138 -7q-79 -26 -122.5 -83t-25.5 -111q17 -55 85.5 -75.5t147.5 4.5q70 23 111.5 63.5t41.5 95.5v881q0 10 -7 15.5t-17 2.5l-752 -193q-10 -3 -17 -12.5t-7 -19.5v-689q-64 17 -138 -7q-79 -25 -122.5 -82t-25.5 -112t86 -75.5t147 5.5 q65 21 109 69t44 90v606z" /> -<glyph unicode="" d="M913 432l300 -300q7 -8 7 -18t-7 -18l-109 -109q-8 -7 -18 -7t-18 7l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342t142 342t342 142t342 -142t142 -342q0 -142 -78 -261zM176 693q0 -136 97 -233t234 -97t233.5 96.5t96.5 233.5t-96.5 233.5t-233.5 96.5 t-234 -97t-97 -233z" /> -<glyph unicode="" d="M649 949q48 69 109.5 105t121.5 38t118.5 -20.5t102.5 -64t71 -100.5t27 -123q0 -57 -33.5 -117.5t-94 -124.5t-126.5 -127.5t-150 -152.5t-146 -174q-62 85 -145.5 174t-149.5 152.5t-126.5 127.5t-94 124.5t-33.5 117.5q0 64 28 123t73 100.5t104.5 64t119 20.5 t120 -38.5t104.5 -104.5z" /> -<glyph unicode="" d="M791 522l145 -449l-384 275l-382 -275l146 447l-388 280h479l146 400h2l146 -400h472zM168 71l2 1z" /> -<glyph unicode="" d="M791 522l145 -449l-384 275l-382 -275l146 447l-388 280h479l146 400h2l146 -400h472zM747 331l-74 229l193 140h-235l-77 211l-78 -211h-239l196 -142l-73 -226l192 140zM168 71l2 1z" /> -<glyph unicode="" d="M1200 143v-143h-1200v143l400 257v100q-37 0 -68.5 74.5t-31.5 125.5v200q0 124 88 212t212 88t212 -88t88 -212v-200q0 -51 -31.5 -125.5t-68.5 -74.5v-100z" /> -<glyph unicode="" d="M1200 1100v-1100h-1200v1100h1200zM200 1000h-100v-100h100v100zM900 1000h-600v-400h600v400zM1100 1000h-100v-100h100v100zM200 800h-100v-100h100v100zM1100 800h-100v-100h100v100zM200 600h-100v-100h100v100zM1100 600h-100v-100h100v100zM900 500h-600v-400h600 v400zM200 400h-100v-100h100v100zM1100 400h-100v-100h100v100zM200 200h-100v-100h100v100zM1100 200h-100v-100h100v100z" /> -<glyph unicode="" d="M500 1050v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5zM1100 1050v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5h400 q21 0 35.5 -14.5t14.5 -35.5zM500 450v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5zM1100 450v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400 q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5z" /> -<glyph unicode="" d="M300 1050v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5zM700 1050v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h200 q21 0 35.5 -14.5t14.5 -35.5zM1100 1050v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5zM300 650v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200 q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5zM700 650v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5zM1100 650v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200 q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5zM300 250v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5zM700 250v-200 q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5zM1100 250v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5 t14.5 -35.5z" /> -<glyph unicode="" d="M300 1050v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5zM1200 1050v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h700 q21 0 35.5 -14.5t14.5 -35.5zM300 450v200q0 21 -14.5 35.5t-35.5 14.5h-200q-21 0 -35.5 -14.5t-14.5 -35.5v-200q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5zM1200 650v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5v200 q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5zM300 250v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5zM1200 250v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700 q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5z" /> -<glyph unicode="" d="M448 34l818 820l-212 212l-607 -607l-206 207l-212 -212z" /> -<glyph unicode="" d="M882 106l-282 282l-282 -282l-212 212l282 282l-282 282l212 212l282 -282l282 282l212 -212l-282 -282l282 -282z" /> -<glyph unicode="" d="M913 432l300 -300q7 -8 7 -18t-7 -18l-109 -109q-8 -7 -18 -7t-18 7l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342t142 342t342 142t342 -142t142 -342q0 -142 -78 -261zM507 363q137 0 233.5 96.5t96.5 233.5t-96.5 233.5t-233.5 96.5t-234 -97t-97 -233 t97 -233t234 -97zM600 800h100v-200h-100v-100h-200v100h-100v200h100v100h200v-100z" /> -<glyph unicode="" d="M913 432l300 -299q7 -7 7 -18t-7 -18l-109 -109q-8 -8 -18 -8t-18 8l-300 299q-120 -77 -261 -77q-200 0 -342 142t-142 342t142 342t342 142t342 -142t142 -342q0 -141 -78 -262zM176 694q0 -136 97 -233t234 -97t233.5 97t96.5 233t-96.5 233t-233.5 97t-234 -97 t-97 -233zM300 801v-200h400v200h-400z" /> -<glyph unicode="" d="M700 750v400q0 21 -14.5 35.5t-35.5 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-400q0 -21 14.5 -35.5t35.5 -14.5h100q21 0 35.5 14.5t14.5 35.5zM800 975v166q167 -62 272 -210t105 -331q0 -118 -45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123 t-123 184t-45.5 224.5q0 183 105 331t272 210v-166q-103 -55 -165 -155t-62 -220q0 -177 125 -302t302 -125t302 125t125 302q0 120 -62 220t-165 155z" /> -<glyph unicode="" d="M1200 1h-200v1200h200v-1200zM900 1h-200v800h200v-800zM600 1h-200v500h200v-500zM300 301h-200v-300h200v300z" /> -<glyph unicode="" d="M488 183l38 -151q40 -5 74 -5q27 0 74 5l38 151l6 2q46 13 93 39l5 3l134 -81q56 44 104 105l-80 134l3 5q24 44 39 93l1 6l152 38q5 40 5 74q0 28 -5 73l-152 38l-1 6q-16 51 -39 93l-3 5l80 134q-44 58 -104 105l-134 -81l-5 3q-45 25 -93 39l-6 1l-38 152q-40 5 -74 5 q-27 0 -74 -5l-38 -152l-5 -1q-50 -14 -94 -39l-5 -3l-133 81q-59 -47 -105 -105l80 -134l-3 -5q-25 -47 -38 -93l-2 -6l-151 -38q-6 -48 -6 -73q0 -33 6 -74l151 -38l2 -6q14 -49 38 -93l3 -5l-80 -134q45 -59 105 -105l133 81l5 -3q45 -26 94 -39zM600 815q89 0 152 -63 t63 -151q0 -89 -63 -152t-152 -63t-152 63t-63 152q0 88 63 151t152 63z" /> -<glyph unicode="" d="M900 1100h275q10 0 17.5 -7.5t7.5 -17.5v-50q0 -11 -7 -18t-18 -7h-1050q-11 0 -18 7t-7 18v50q0 10 7.5 17.5t17.5 7.5h275v100q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5v-100zM800 1100v100h-300v-100h300zM200 900h900v-800q0 -41 -29.5 -71 t-70.5 -30h-700q-41 0 -70.5 30t-29.5 71v800zM300 100h100v700h-100v-700zM500 100h100v700h-100v-700zM700 100h100v700h-100v-700zM900 100h100v700h-100v-700z" /> -<glyph unicode="" d="M1301 601h-200v-600h-300v400h-300v-400h-300v600h-200l656 644z" /> -<glyph unicode="" d="M600 700h400v-675q0 -11 -7 -18t-18 -7h-850q-11 0 -18 7t-7 18v1150q0 11 7 18t18 7h475v-500zM1000 800h-300v300z" /> -<glyph unicode="" d="M600 1196q162 0 299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299t80 299t217 217t299 80zM600 1014q-171 0 -292.5 -121.5t-121.5 -292.5t121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5zM600 600h200 v-100h-300v400h100v-300z" /> -<glyph unicode="" d="M721 400h-242l-40 -400h-539l431 1200h209l-21 -300h162l-20 300h208l431 -1200h-538zM712 500l-27 300h-170l-27 -300h224z" /> -<glyph unicode="" d="M1100 400v-400h-1100v400h490l-290 300h200v500h300v-500h200l-290 -300h490zM988 300h-175v-100h175v100z" /> -<glyph unicode="" d="M600 1199q122 0 233 -47.5t191 -127.5t127.5 -191t47.5 -233t-47.5 -233t-127.5 -191t-191 -127.5t-233 -47.5t-233 47.5t-191 127.5t-127.5 191t-47.5 233t47.5 233t127.5 191t191 127.5t233 47.5zM600 1012q-170 0 -291 -121t-121 -291t121 -291t291 -121t291 121 t121 291t-121 291t-291 121zM700 600h150l-250 -300l-250 300h150v300h200v-300z" /> -<glyph unicode="" d="M600 1196q162 0 299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299t80 299t217 217t299 80zM600 1014q-171 0 -292.5 -121.5t-121.5 -292.5t121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5zM850 600h-150 v-300h-200v300h-150l250 300z" /> -<glyph unicode="" d="M0 500l200 700h800q199 -700 200 -700v-475q0 -11 -7 -18t-18 -7h-1150q-11 0 -18 7t-7 18v475zM903 1000h-606l-97 -500h200l50 -200h300l50 200h200z" /> -<glyph unicode="" d="M600 1196q162 0 299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299t80 299t217 217t299 80zM600 1014q-171 0 -292.5 -121.5t-121.5 -292.5q0 -172 121.5 -293t292.5 -121t292.5 121t121.5 293q0 171 -121.5 292.5t-292.5 121.5zM797 598 l-297 -201v401z" /> -<glyph unicode="" d="M1177 600h-150q0 -177 -125 -302t-302 -125t-302 125t-125 302t125 302t302 125q136 0 246 -81l-146 -146h400v400l-145 -145q-157 122 -355 122q-118 0 -224.5 -45.5t-184 -123t-123 -184t-45.5 -224.5t45.5 -224.5t123 -184t184 -123t224.5 -45.5t224.5 45.5t184 123 t123 184t45.5 224.5z" /> -<glyph unicode="" d="M700 800l147 147q-112 80 -247 80q-177 0 -302 -125t-125 -302h-150q0 118 45.5 224.5t123 184t184 123t224.5 45.5q198 0 355 -122l145 145v-400h-400zM500 400l-147 -147q112 -80 247 -80q177 0 302 125t125 302h150q0 -118 -45.5 -224.5t-123 -184t-184 -123 t-224.5 -45.5q-198 0 -355 122l-145 -145v400h400z" /> -<glyph unicode="" d="M100 1200v-1200h1100v1200h-1100zM1100 100h-900v900h900v-900zM400 800h-100v100h100v-100zM1000 800h-500v100h500v-100zM400 600h-100v100h100v-100zM1000 600h-500v100h500v-100zM400 400h-100v100h100v-100zM1000 400h-500v100h500v-100zM400 200h-100v100h100v-100 zM1000 300h-500v-100h500v100z" /> -<glyph unicode="" d="M200 0h-100v1100h100v-1100zM1100 600v500q-40 -81 -101.5 -115.5t-127.5 -29.5t-138 25t-139.5 40t-125.5 25t-103 -29.5t-65 -115.5v-500q60 60 127.5 84t127.5 17.5t122 -23t119 -30t110 -11t103 42t91 120.5z" /> -<glyph unicode="" d="M1200 275v300q0 116 -49.5 227t-131 192.5t-192.5 131t-227 49.5t-227 -49.5t-192.5 -131t-131 -192.5t-49.5 -227v-300q0 -11 7 -18t18 -7h50q11 0 18 7t7 18v300q0 127 70.5 231.5t184.5 161.5t245 57t245 -57t184.5 -161.5t70.5 -231.5v-300q0 -11 7 -18t18 -7h50 q11 0 18 7t7 18zM400 480v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14v460q0 8 6 14t14 6h160q8 0 14 -6t6 -14zM1000 480v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14v460q0 8 6 14t14 6h160q8 0 14 -6t6 -14z" /> -<glyph unicode="" d="M0 800v-400h300l300 -200v800l-300 -200h-300zM971 600l141 -141l-71 -71l-141 141l-141 -141l-71 71l141 141l-141 141l71 71l141 -141l141 141l71 -71z" /> -<glyph unicode="" d="M0 800v-400h300l300 -200v800l-300 -200h-300zM700 857l69 53q111 -135 111 -310q0 -169 -106 -302l-67 54q86 110 86 248q0 146 -93 257z" /> -<glyph unicode="" d="M974 186l6 8q142 178 142 405q0 230 -144 408l-6 8l-83 -64l7 -8q123 -151 123 -344q0 -189 -119 -339l-7 -8zM300 801l300 200v-800l-300 200h-300v400h300zM702 858l69 53q111 -135 111 -310q0 -170 -106 -303l-67 55q86 110 86 248q0 145 -93 257z" /> -<glyph unicode="" d="M100 700h400v100h100v100h-100v300h-500v-600h100v100zM1200 700v500h-600v-200h100v-300h200v-300h300v200h-200v100h200zM100 1100h300v-300h-300v300zM800 800v300h300v-300h-300zM200 900h100v100h-100v-100zM900 1000h100v-100h-100v100zM300 600h-100v-100h-200 v-500h500v500h-200v100zM900 200v-100h-200v100h-100v100h100v200h-200v100h300v-300h200v-100h-100zM400 400v-300h-300v300h300zM300 200h-100v100h100v-100zM1100 300h100v-100h-100v100zM600 100h100v-100h-100v100zM1200 100v-100h-300v100h300z" /> -<glyph unicode="" d="M100 1200h-100v-1000h100v1000zM300 200h-100v1000h100v-1000zM700 200h-200v1000h200v-1000zM900 200h-100v1000h100v-1000zM1200 1200v-1000h-200v1000h200zM400 100v-100h-300v100h300zM500 91h100v-91h-100v91zM700 91h100v-91h-100v91zM1100 91v-91h-200v91h200z " /> -<glyph unicode="" d="M1200 500l-500 -500l-699 700v475q0 10 7.5 17.5t17.5 7.5h474zM320 882q29 29 29 71t-29 71q-30 30 -71.5 30t-71.5 -30q-29 -29 -29 -71t29 -71q30 -30 71.5 -30t71.5 30z" /> -<glyph unicode="" d="M1201 500l-500 -500l-699 700v475q0 11 7 18t18 7h474zM1501 500l-500 -500l-50 50l450 450l-700 700h100zM320 882q30 29 30 71t-30 71q-29 30 -71 30t-71 -30q-30 -29 -30 -71t30 -71q29 -30 71 -30t71 30z" /> -<glyph unicode="" d="M1200 1200v-1000l-100 -100v1000h-750l-100 -100h750v-1000h-900v1025l175 175h925z" /> -<glyph unicode="" d="M947 829l-94 346q-2 11 -10 18t-18 7h-450q-10 0 -18 -7t-10 -18l-94 -346l40 -124h592zM1200 800v-700h-200v200h-800v-200h-200v700h200l100 -200h600l100 200h200zM881 176l38 -152q2 -10 -3.5 -17t-15.5 -7h-600q-10 0 -15.5 7t-3.5 17l38 152q2 10 11.5 17t19.5 7 h500q10 0 19.5 -7t11.5 -17z" /> -<glyph unicode="" d="M1200 0v66q-34 1 -74 43q-18 19 -33 42t-21 37l-6 13l-385 998h-93l-399 -1006q-24 -48 -52 -75q-12 -12 -33 -25t-36 -20l-15 -7v-66h365v66q-41 0 -72 11t-49 38t1 71l92 234h391l82 -222q16 -45 -5.5 -88.5t-74.5 -43.5v-66h417zM416 521l178 457l46 -140l116 -317 h-340z" /> -<glyph unicode="" d="M100 1199h471q120 0 213 -88t93 -228q0 -55 -11.5 -101.5t-28 -74t-33.5 -47.5t-28 -28l-12 -7q8 -3 21.5 -9t48 -31.5t60.5 -58t47.5 -91.5t21.5 -129q0 -84 -59 -156.5t-142 -111t-162 -38.5h-500v89q41 7 70.5 32.5t29.5 65.5v827q0 28 -1 39.5t-5.5 26t-15.5 21 t-29 14t-49 14.5v70zM400 1079v-379h139q76 0 130 61.5t54 138.5q0 82 -84 130.5t-239 48.5zM400 200h161q89 0 153 48.5t64 132.5q0 90 -62.5 154.5t-156.5 64.5h-159v-400z" /> -<glyph unicode="" d="M877 1200l2 -57q-33 -8 -62 -25.5t-46 -37t-29.5 -38t-17.5 -30.5l-5 -12l-128 -825q-10 -52 14 -82t95 -36v-57h-500v57q77 7 134.5 40.5t65.5 80.5l173 849q10 56 -10 74t-91 37q-6 1 -10.5 2.5t-9.5 2.5v57h425z" /> -<glyph unicode="" d="M1150 1200h150v-300h-50q0 29 -8 48.5t-18.5 30t-33.5 15t-39.5 5.5t-50.5 1h-200v-850l100 -50v-100h-400v100l100 50v850h-200q-34 0 -50.5 -1t-40 -5.5t-33.5 -15t-18.5 -30t-8.5 -48.5h-49v300h150h700zM100 1000v-800h75l-125 -167l-125 167h75v800h-75l125 167 l125 -167h-75z" /> -<glyph unicode="" d="M950 1201h150v-300h-50q0 29 -8 48.5t-18 30t-33.5 15t-40 5.5t-50.5 1h-200v-650l100 -50v-100h-400v100l100 50v650h-200q-34 0 -50.5 -1t-39.5 -5.5t-33.5 -15t-18.5 -30t-8 -48.5h-50v300h150h700zM200 101h800v75l167 -125l-167 -125v75h-800v-75l-167 125l167 125 v-75z" /> -<glyph unicode="" d="M700 950v100q0 21 -14.5 35.5t-35.5 14.5h-600q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35t35.5 -15h600q21 0 35.5 15t14.5 35zM1100 650v100q0 21 -14.5 35.5t-35.5 14.5h-1000q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35t35.5 -15h1000 q21 0 35.5 15t14.5 35zM900 350v100q0 21 -14.5 35.5t-35.5 14.5h-800q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35t35.5 -15h800q21 0 35.5 15t14.5 35zM1200 50v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35 t35.5 -15h1100q21 0 35.5 15t14.5 35z" /> -<glyph unicode="" d="M1000 950v100q0 21 -14.5 35.5t-35.5 14.5h-700q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35t35.5 -15h700q21 0 35.5 15t14.5 35zM1200 650v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35t35.5 -15h1100 q21 0 35.5 15t14.5 35zM1000 350v100q0 21 -14.5 35.5t-35.5 14.5h-700q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35t35.5 -15h700q21 0 35.5 15t14.5 35zM1200 50v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35 t35.5 -15h1100q21 0 35.5 15t14.5 35z" /> -<glyph unicode="" d="M500 950v100q0 21 14.5 35.5t35.5 14.5h600q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-600q-21 0 -35.5 15t-14.5 35zM100 650v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1000q-21 0 -35.5 15 t-14.5 35zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100 q-21 0 -35.5 15t-14.5 35z" /> -<glyph unicode="" d="M0 950v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM0 650v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15 t-14.5 35zM0 350v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100 q-21 0 -35.5 15t-14.5 35z" /> -<glyph unicode="" d="M0 950v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35zM300 950v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15 t-14.5 35zM0 650v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35zM300 650v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800 q-21 0 -35.5 15t-14.5 35zM0 350v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15 h-800q-21 0 -35.5 15t-14.5 35zM0 50v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35zM300 50v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15 h-800q-21 0 -35.5 15t-14.5 35z" /> -<glyph unicode="" d="M400 1100h-100v-1100h100v1100zM700 950v100q0 21 -15 35.5t-35 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35t35.5 -15h100q20 0 35 15t15 35zM1100 650v100q0 21 -15 35.5t-35 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35t35.5 -15 h500q20 0 35 15t15 35zM100 425v75h-201v100h201v75l166 -125zM900 350v100q0 21 -15 35.5t-35 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35t35.5 -15h300q20 0 35 15t15 35zM1200 50v100q0 21 -15 35.5t-35 14.5h-600q-21 0 -35.5 -14.5t-14.5 -35.5 v-100q0 -20 14.5 -35t35.5 -15h600q20 0 35 15t15 35z" /> -<glyph unicode="" d="M201 950v100q0 21 -15 35.5t-35 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35t35.5 -15h100q20 0 35 15t15 35zM801 1100h100v-1100h-100v1100zM601 650v100q0 21 -15 35.5t-35 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35t35.5 -15 h500q20 0 35 15t15 35zM1101 425v75h200v100h-200v75l-167 -125zM401 350v100q0 21 -15 35.5t-35 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -20 14.5 -35t35.5 -15h300q20 0 35 15t15 35zM701 50v100q0 21 -15 35.5t-35 14.5h-600q-21 0 -35.5 -14.5t-14.5 -35.5 v-100q0 -20 14.5 -35t35.5 -15h600q20 0 35 15t15 35z" /> -<glyph unicode="" d="M900 925v-650q0 -31 -22 -53t-53 -22h-750q-31 0 -53 22t-22 53v650q0 31 22 53t53 22h750q31 0 53 -22t22 -53zM1200 300l-300 300l300 300v-600z" /> -<glyph unicode="" d="M1200 1056v-1012q0 -18 -12.5 -31t-31.5 -13h-1112q-18 0 -31 13t-13 31v1012q0 18 13 31t31 13h1112q19 0 31.5 -13t12.5 -31zM1100 1000h-1000v-737l247 182l298 -131l-74 156l293 318l236 -288v500zM476 750q0 -56 -39 -95t-95 -39t-95 39t-39 95t39 95t95 39t95 -39 t39 -95z" /> -<glyph unicode="" d="M600 1213q123 0 227 -63t164.5 -169.5t60.5 -229.5t-73 -272q-73 -114 -166.5 -237t-150.5 -189l-57 -66q-10 9 -27 26t-66.5 70.5t-96 109t-104 135.5t-100.5 155q-63 139 -63 262q0 124 60.5 231.5t165 172t226.5 64.5zM599 514q107 0 182.5 75.5t75.5 182.5t-75.5 182 t-182.5 75t-182 -75.5t-75 -181.5q0 -107 75.5 -182.5t181.5 -75.5z" /> -<glyph unicode="" d="M600 1199q122 0 233 -47.5t191 -127.5t127.5 -191t47.5 -233t-47.5 -233t-127.5 -191t-191 -127.5t-233 -47.5t-233 47.5t-191 127.5t-127.5 191t-47.5 233t47.5 233t127.5 191t191 127.5t233 47.5zM600 173v854q-176 0 -301.5 -125t-125.5 -302t125.5 -302t301.5 -125z " /> -<glyph unicode="" d="M554 1295q21 -71 57.5 -142.5t76 -130.5t83 -118.5t82 -117t70 -116t50 -125.5t18.5 -136q0 -89 -39 -165.5t-102 -126.5t-140 -79.5t-156 -33.5q-114 6 -211.5 53t-161.5 138.5t-64 210.5q0 94 34 186t88.5 172.5t112 159t115 177t87.5 194.5zM455 296q-7 6 -18 17 t-34 48t-33 77q-15 73 -14 143.5t10 122.5l9 51q-92 -110 -119.5 -185t-12.5 -156q14 -82 59.5 -136t136.5 -80z" /> -<glyph unicode="" d="M1108 902l113 113l-21 85l-92 28l-113 -113zM1100 625v-225q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5v300q0 165 117.5 282.5t282.5 117.5q366 -6 397 -14l-186 -186h-311q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5 t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v125zM436 341l161 50l412 412l-114 113l-405 -405z" /> -<glyph unicode="" d="M1100 453v-53q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5v300q0 165 117.5 282.5t282.5 117.5h261l2 -80q-133 -32 -218 -120h-145q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5z M813 431l360 324l-359 318v-216q-7 0 -19 -1t-48 -8t-69.5 -18.5t-76.5 -37t-76.5 -59t-62 -88t-39.5 -121.5q30 38 81.5 64t103 35.5t99 14t77.5 3.5l29 -1v-209z" /> -<glyph unicode="" d="M1100 569v-169q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5v300q0 165 117.5 282.5t282.5 117.5h300q60 0 127 -23l-178 -177h-349q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v69z M625 348l566 567l-136 137l-430 -431l-147 147l-136 -136z" /> -<glyph unicode="" d="M900 303v198h-200v-200h195l-295 -300l-300 300h200v200h-200v-198l-300 300l300 296v-198h200v200h-200l300 300l295 -300h-195v-200h200v198l300 -296z" /> -<glyph unicode="" d="M900 0l-500 488v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v1000q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-437l500 487v-1100z" /> -<glyph unicode="" d="M1200 0l-500 488v-488l-500 488v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v1000q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-437l500 487v-487l500 487v-1100z" /> -<glyph unicode="" d="M1200 0l-500 488v-488l-564 550l564 550v-487l500 487v-1100z" /> -<glyph unicode="" d="M1100 550l-900 550v-1100z" /> -<glyph unicode="" d="M500 150v800q0 21 -14.5 35.5t-35.5 14.5h-200q-21 0 -35.5 -14.5t-14.5 -35.5v-800q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5zM900 150v800q0 21 -14.5 35.5t-35.5 14.5h-200q-21 0 -35.5 -14.5t-14.5 -35.5v-800q0 -21 14.5 -35.5t35.5 -14.5h200 q21 0 35.5 14.5t14.5 35.5z" /> -<glyph unicode="" d="M1100 150v800q0 21 -14.5 35.5t-35.5 14.5h-800q-21 0 -35.5 -14.5t-14.5 -35.5v-800q0 -20 14.5 -35t35.5 -15h800q21 0 35.5 15t14.5 35z" /> -<glyph unicode="" d="M500 0v488l-500 -488v1100l500 -487v487l564 -550z" /> -<glyph unicode="" d="M1050 1100h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438l-500 -488v488l-500 -488v1100l500 -487v487l500 -487v437q0 21 14.5 35.5t35.5 14.5z" /> -<glyph unicode="" d="M850 1100h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438l-500 -488v1100l500 -487v437q0 21 14.5 35.5t35.5 14.5z" /> -<glyph unicode="" d="M650 1064l-550 -564h1100zM1200 350v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5z" /> -<glyph unicode="" d="M777 7l240 240l-353 353l353 353l-240 240l-592 -594z" /> -<glyph unicode="" d="M513 -46l-241 240l353 353l-353 353l241 240l572 -571l21 -22l-1 -1v-1z" /> -<glyph unicode="" d="M600 1197q162 0 299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5t80 299.5t217.5 217.5t299.5 80zM500 900v-200h-200v-200h200v-200h200v200h200v200h-200v200h-200z" /> -<glyph unicode="" d="M600 1197q162 0 299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5t80 299.5t217.5 217.5t299.5 80zM300 700v-200h600v200h-600z" /> -<glyph unicode="" d="M600 1197q162 0 299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5t80 299.5t217.5 217.5t299.5 80zM247 741l141 -141l-142 -141l213 -213l141 142l141 -142l213 213l-142 141l142 141l-213 212l-141 -141 l-141 142z" /> -<glyph unicode="" d="M600 1197q162 0 299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5t80 299.5t217.5 217.5t299.5 80zM546 623l-102 102l-174 -174l276 -277l411 411l-175 174z" /> -<glyph unicode="" d="M600 1197q162 0 299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5t80 299.5t217.5 217.5t299.5 80zM500 500h200q5 3 14 8t31.5 25.5t39.5 45.5t31 69t14 94q0 51 -17.5 89t-42 58t-58.5 32t-58.5 15t-51.5 3 q-105 0 -172 -56t-67 -183h144q4 0 11.5 -1t11 -1t6.5 3t3 9t1 11t3.5 8.5t3.5 6t5.5 4t6.5 2.5t9 1.5t9 0.5h11.5h12.5q19 0 30 -10t11 -26q0 -22 -4 -28t-27 -22q-5 -1 -12.5 -3t-27 -13.5t-34 -27t-26.5 -46t-11 -68.5zM500 400v-100h200v100h-200z" /> -<glyph unicode="" d="M600 1197q162 0 299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5t80 299.5t217.5 217.5t299.5 80zM500 900v-100h200v100h-200zM400 700v-100h100v-200h-100v-100h400v100h-100v300h-300z" /> -<glyph unicode="" d="M1200 700v-200h-203q-25 -102 -116.5 -186t-180.5 -117v-197h-200v197q-140 27 -208 102.5t-98 200.5h-194v200h194q15 60 36 104.5t55.5 86t88 69t126.5 40.5v200h200v-200q54 -20 113 -60t112.5 -105.5t71.5 -134.5h203zM700 500v-206q149 48 201 206h-201v200h200 q-25 74 -76 127.5t-124 76.5v-204h-200v203q-75 -24 -130 -77.5t-79 -125.5h209v-200h-210q24 -73 79.5 -127.5t130.5 -78.5v206h200z" /> -<glyph unicode="" d="M600 1196q162 0 299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299t80 299t217 217t299 80zM600 1014q-171 0 -292.5 -121.5t-121.5 -292.5t121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5zM844 735 l-135 -135l135 -135l-109 -109l-135 135l-135 -135l-109 109l135 135l-135 135l109 109l135 -135l135 135z" /> -<glyph unicode="" d="M600 1196q162 0 299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299t80 299t217 217t299 80zM600 1014q-171 0 -292.5 -121.5t-121.5 -292.5t121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5zM896 654 l-346 -345l-228 228l141 141l87 -87l204 205z" /> -<glyph unicode="" d="M600 1196q162 0 299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299t80 299t217 217t299 80zM248 385l568 567q-100 62 -216 62q-171 0 -292.5 -121.5t-121.5 -292.5q0 -115 62 -215zM955 809l-564 -564q97 -59 209 -59q171 0 292.5 121.5 t121.5 292.5q0 112 -59 209z" /> -<glyph unicode="" d="M1200 400h-600v-301l-600 448l600 453v-300h600v-300z" /> -<glyph unicode="" d="M600 400h-600v300h600v300l600 -453l-600 -448v301z" /> -<glyph unicode="" d="M1098 600h-298v-600h-300v600h-296l450 600z" /> -<glyph unicode="" d="M998 600l-449 -600l-445 600h296v600h300v-600h298z" /> -<glyph unicode="" d="M600 199v301q-95 -2 -183 -20t-170 -52t-147 -92.5t-100 -135.5q6 132 41 238.5t103.5 193t184 138t271.5 59.5v271l600 -453z" /> -<glyph unicode="" d="M1200 1200h-400l129 -129l-294 -294l142 -142l294 294l129 -129v400zM565 423l-294 -294l129 -129h-400v400l129 -129l294 294z" /> -<glyph unicode="" d="M871 730l129 -130h-400v400l129 -129l295 295l142 -141zM200 600h400v-400l-129 130l-295 -295l-142 141l295 295z" /> -<glyph unicode="" d="M600 1177q118 0 224.5 -45.5t184 -123t123 -184t45.5 -224.5t-45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5t45.5 224.5t123 184t184 123t224.5 45.5zM686 549l58 302q4 20 -8 34.5t-33 14.5h-207q-20 0 -32 -14.5t-8 -34.5 l58 -302q4 -20 21.5 -34.5t37.5 -14.5h54q20 0 37.5 14.5t21.5 34.5zM700 400h-200v-100h200v100z" /> -<glyph unicode="" d="M1200 900h-111v6t-1 15t-3 18l-34 172q-11 39 -41.5 63t-69.5 24q-32 0 -61 -17l-239 -144q-22 -13 -40 -35q-19 24 -40 36l-238 144q-33 18 -62 18q-39 0 -69.5 -23t-40.5 -61l-35 -177q-2 -8 -3 -18t-1 -15v-6h-111v-100h100v-200h400v300h200v-300h400v200h100v100z M731 900l202 197q5 -12 12 -32.5t23 -64t25 -72t7 -28.5h-269zM481 900h-281q-3 0 14 48t35 96l18 47zM100 0h400v400h-400v-400zM700 400h400v-400h-400v400z" /> -<glyph unicode="" d="M0 121l216 193q-9 53 -13 83t-5.5 94t9 113t38.5 114t74 124q47 60 99.5 102.5t103 68t127.5 48t145.5 37.5t184.5 43.5t220 58.5q0 -189 -22 -343t-59 -258t-89 -181.5t-108.5 -120t-122 -68t-125.5 -30t-121.5 -1.5t-107.5 12.5t-87.5 17t-56.5 7.5l-99 -55l-201 -202 v143zM692 611q70 38 118.5 69.5t102 79t99 111.5t86.5 148q22 50 24 60t-6 19q-7 5 -17 5t-26.5 -14.5t-33.5 -39.5q-35 -51 -113.5 -108.5t-139.5 -89.5l-61 -32q-369 -197 -458 -401q-48 -111 -28.5 -117.5t86.5 76.5q55 66 367 234z" /> -<glyph unicode="" d="M1261 600l-26 -40q-6 -10 -20 -30t-49 -63.5t-74.5 -85.5t-97 -90t-116.5 -83.5t-132.5 -59t-145.5 -23.5t-145.5 23.5t-132.5 59t-116.5 83.5t-97 90t-74.5 85.5t-49 63.5t-20 30l-26 40l26 40q6 10 20 30t49 63.5t74.5 85.5t97 90t116.5 83.5t132.5 59t145.5 23.5 t145.5 -23.5t132.5 -59t116.5 -83.5t97 -90t74.5 -85.5t49 -63.5t20 -30zM600 240q64 0 123.5 20t100.5 45.5t85.5 71.5t66.5 75.5t58 81.5t47 66q-1 1 -28.5 37.5t-42 55t-43.5 53t-57.5 63.5t-58.5 54q49 -74 49 -163q0 -124 -88 -212t-212 -88t-212 88t-88 212 q0 85 46 158q-102 -87 -226 -258q7 -10 40.5 -58t56 -78.5t68 -77.5t87.5 -75t103 -49.5t125 -21.5zM484 762l-107 -106q49 -124 154 -191l105 105q-37 24 -75 72t-57 84z" /> -<glyph unicode="" d="M906 1200l-314 -1200h-148l37 143q-82 21 -165 71.5t-140 102t-109.5 112t-72 88.5t-29.5 43l-26 40l26 40q6 10 20 30t49 63.5t74.5 85.5t97 90t116.5 83.5t132.5 59t145.5 23.5q61 0 121 -17l37 142h148zM1261 600l-26 -40q-7 -12 -25.5 -38t-63.5 -79.5t-95.5 -102.5 t-124 -100t-146.5 -79l38 145q22 15 44.5 34t46 44t40.5 44t41 50.5t33.5 43.5t33 44t24.5 34q-97 127 -140 175l39 146q67 -54 131.5 -125.5t87.5 -103.5t36 -52zM513 264l37 141q-107 18 -178.5 101.5t-71.5 193.5q0 85 46 158q-102 -87 -226 -258q210 -282 393 -336z M484 762l-107 -106q49 -124 154 -191l47 47l23 87q-30 28 -59 69t-44 68z" /> -<glyph unicode="" d="M-47 0h1294q37 0 50.5 35.5t-7.5 67.5l-642 1056q-20 33 -48 36t-48 -29l-642 -1066q-21 -32 -7.5 -66t50.5 -34zM700 200v100h-200v-100h-345l445 723l445 -723h-345zM700 700h-200v-100l100 -300l100 300v100z" /> -<glyph unicode="" d="M800 711l363 -325q15 -14 26 -38.5t11 -44.5v-41q0 -20 -12 -26.5t-29 5.5l-359 249v-263q100 -91 100 -113v-64q0 -21 -13 -29t-32 1l-94 78h-222l-94 -78q-19 -9 -32 -1t-13 29v64q0 22 100 113v263l-359 -249q-17 -12 -29 -5.5t-12 26.5v41q0 20 11 44.5t26 38.5 l363 325v339q0 62 44 106t106 44t106 -44t44 -106v-339z" /> -<glyph unicode="" d="M941 800l-600 -600h-341v200h259l600 600h241v198l300 -295l-300 -300v197h-159zM381 678l141 142l-181 180h-341v-200h259zM1100 598l300 -295l-300 -300v197h-241l-181 181l141 142l122 -123h159v198z" /> -<glyph unicode="" d="M100 1100h1000q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-596l-304 -300v300h-100q-41 0 -70.5 29.5t-29.5 70.5v600q0 41 29.5 70.5t70.5 29.5z" /> -<glyph unicode="" d="M400 900h-300v300h300v-300zM1100 900h-300v300h300v-300zM1100 800v-200q0 -42 -3 -83t-15 -104t-31.5 -116t-58 -109.5t-89 -96.5t-129 -65.5t-174.5 -25.5t-174.5 25.5t-129 65.5t-89 96.5t-58 109.5t-31.5 116t-15 104t-3 83v200h300v-250q0 -113 6 -145 q17 -92 102 -117q39 -11 92 -11q37 0 66.5 5.5t50 15.5t36 24t24 31.5t14 37.5t7 42t2.5 45t0 47v25v250h300z" /> -<glyph unicode="" d="M902 184l226 227l-578 579l-580 -579l227 -227l352 353z" /> -<glyph unicode="" d="M650 218l578 579l-226 227l-353 -353l-352 353l-227 -227z" /> -<glyph unicode="" d="M1198 400v600h-796l215 -200h381v-400h-198l299 -283l299 283h-200zM-198 700l299 283l300 -283h-203v-400h385l215 -200h-800v600h-196z" /> -<glyph unicode="" d="M1050 1200h94q20 0 35 -14.5t15 -35.5t-15 -35.5t-35 -14.5h-54l-201 -961q-2 -4 -6 -10.5t-19 -17.5t-33 -11h-31v-50q0 -20 -14.5 -35t-35.5 -15t-35.5 15t-14.5 35v50h-300v-50q0 -20 -14.5 -35t-35.5 -15t-35.5 15t-14.5 35v50h-50q-21 0 -35.5 15t-14.5 35 q0 21 14.5 35.5t35.5 14.5h535l48 200h-633q-32 0 -54.5 21t-27.5 43l-100 475q-5 24 10 42q14 19 39 19h896l38 162q5 17 18.5 27.5t30.5 10.5z" /> -<glyph unicode="" d="M1200 1000v-100h-1200v100h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500zM0 800h1200v-800h-1200v800z" /> -<glyph unicode="" d="M201 800l-200 -400v600h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500v-200h-1000zM1501 700l-300 -700h-1200l300 700h1200z" /> -<glyph unicode="" d="M302 300h198v600h-198l298 300l298 -300h-198v-600h198l-298 -300z" /> -<glyph unicode="" d="M900 303v197h-600v-197l-300 297l300 298v-198h600v198l300 -298z" /> -<glyph unicode="" d="M31 400l172 739q5 22 23 41.5t38 19.5h672q19 0 37.5 -22.5t23.5 -45.5l172 -732h-1138zM100 300h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5v100q0 41 29.5 70.5t70.5 29.5zM900 200h-100v-100h100v100z M1100 200h-100v-100h100v100z" /> -<glyph unicode="" d="M1100 200v850q0 21 14.5 35.5t35.5 14.5q20 0 35 -14.5t15 -35.5v-850q0 -20 -15 -35t-35 -15q-21 0 -35.5 15t-14.5 35zM325 800l675 250v-850l-675 200h-38l47 -276q2 -12 -3 -17.5t-11 -6t-21 -0.5h-8h-83q-20 0 -34.5 14t-18.5 35q-56 337 -56 351v250v5 q0 13 0.5 18.5t2.5 13t8 10.5t15 3h200zM-101 600v50q0 24 25 49t50 38l25 13v-250l-11 5.5t-24 14t-30 21.5t-24 27.5t-11 31.5z" /> -<glyph unicode="" d="M445 1180l-45 -233l-224 78l78 -225l-233 -44l179 -156l-179 -155l233 -45l-78 -224l224 78l45 -233l155 179l155 -179l45 233l224 -78l-78 224l234 45l-180 155l180 156l-234 44l78 225l-224 -78l-45 233l-155 -180z" /> -<glyph unicode="" d="M700 1200h-50q-27 0 -51 -20t-38 -48l-96 -198l-145 -196q-20 -26 -20 -63v-400q0 -75 100 -75h61q123 -100 139 -100h250q46 0 83 57l238 344q29 31 29 74v100q0 44 -30.5 84.5t-69.5 40.5h-328q28 118 28 125v150q0 44 -30.5 84.5t-69.5 40.5zM700 925l-50 -225h450 v-125l-250 -375h-214l-136 100h-100v375l150 212l100 213h50v-175zM0 800v-600h200v600h-200z" /> -<glyph unicode="" d="M700 0h-50q-27 0 -51 20t-38 48l-96 198l-145 196q-20 26 -20 63v400q0 75 100 75h61q123 100 139 100h250q46 0 83 -57l238 -344q29 -31 29 -74v-100q0 -44 -30.5 -84.5t-69.5 -40.5h-328q28 -118 28 -125v-150q0 -44 -30.5 -84.5t-69.5 -40.5zM200 400h-200v600h200 v-600zM700 275l-50 225h450v125l-250 375h-214l-136 -100h-100v-375l150 -212l100 -213h50v175z" /> -<glyph unicode="" d="M364 873l362 230q14 6 25 6q17 0 29 -12l109 -112q14 -14 14 -34q0 -18 -11 -32l-85 -121h302q85 0 138.5 -38t53.5 -110t-54.5 -111t-138.5 -39h-107l-130 -339q-7 -22 -20.5 -41.5t-28.5 -19.5h-341q-7 0 -90 81t-83 94v525q0 17 14 35.5t28 28.5zM408 792v-503 l100 -89h293l131 339q6 21 19.5 41t28.5 20h203q16 0 25 15t9 36q0 20 -9 34.5t-25 14.5h-457h-6.5h-7.5t-6.5 0.5t-6 1t-5 1.5t-5.5 2.5t-4 4t-4 5.5q-5 12 -5 20q0 14 10 27l147 183l-86 83zM208 200h-200v600h200v-600z" /> -<glyph unicode="" d="M475 1104l365 -230q7 -4 16.5 -10.5t26 -26t16.5 -36.5v-526q0 -13 -85.5 -93.5t-93.5 -80.5h-342q-15 0 -28.5 20t-19.5 41l-131 339h-106q-84 0 -139 39t-55 111t54 110t139 37h302l-85 121q-11 16 -11 32q0 21 14 34l109 113q13 12 29 12q11 0 25 -6zM370 946 l145 -184q10 -11 10 -26q0 -11 -5 -20q-1 -3 -3.5 -5.5l-4 -4t-5 -2.5t-5.5 -1.5t-6.5 -1t-6.5 -0.5h-7.5h-6.5h-476v-100h222q15 0 28.5 -20.5t19.5 -40.5l131 -339h293l106 89v502l-342 237zM1199 201h-200v600h200v-600z" /> -<glyph unicode="" d="M1100 473v342q0 15 -20 28.5t-41 19.5l-339 131v106q0 84 -39 139t-111 55t-110 -53.5t-38 -138.5v-302l-121 84q-15 12 -33.5 11.5t-32.5 -13.5l-112 -110q-22 -22 -6 -53l230 -363q4 -6 10.5 -15.5t26 -25t36.5 -15.5h525q13 0 94 83t81 90zM911 400h-503l-236 339 l83 86l183 -146q22 -18 47 -5q3 1 5.5 3.5l4 4t2.5 5t1.5 5.5t1 6.5t0.5 6v7.5v7v456q0 22 25 31t50 -0.5t25 -30.5v-202q0 -16 20 -29.5t41 -19.5l339 -130v-294zM1000 200v-200h-600v200h600z" /> -<glyph unicode="" d="M305 1104v200h600v-200h-600zM605 310l339 131q20 6 40.5 19.5t20.5 28.5v342q0 7 -81 90t-94 83h-525q-17 0 -35.5 -14t-28.5 -28l-10 -15l-230 -362q-15 -31 7 -53l112 -110q13 -13 32 -13.5t34 10.5l121 85l-1 -302q0 -84 38.5 -138t110.5 -54t111 55t39 139v106z M905 804v-294l-340 -130q-20 -6 -40 -20t-20 -29v-202q0 -22 -25 -31t-50 0t-25 31v456v14.5t-1.5 11.5t-5 12t-9.5 7q-24 13 -46 -5l-184 -146l-83 86l237 339h503z" /> -<glyph unicode="" d="M603 1195q162 0 299.5 -80t217.5 -218t80 -300t-80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5zM598 701h-298v-201h300l-2 -194l402 294l-402 298v-197z" /> -<glyph unicode="" d="M597 1195q122 0 232.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-218 -217.5t-300 -80t-299.5 80t-217.5 217.5t-80 299.5q0 122 47.5 232.5t127.5 190.5t190.5 127.5t231.5 47.5zM200 600l400 -294v194h302v201h-300v197z" /> -<glyph unicode="" d="M603 1195q121 0 231.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5zM300 600h200v-300h200v300h200l-300 400z" /> -<glyph unicode="" d="M603 1195q121 0 231.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5zM500 900v-300h-200l300 -400l300 400h-200v300h-200z" /> -<glyph unicode="" d="M603 1195q121 0 231.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5zM627 1101q-15 -12 -36.5 -21t-34.5 -12t-44 -8t-39 -6 q-15 -3 -45.5 0.5t-45.5 -2.5q-21 -7 -52 -26.5t-34 -34.5q-3 -11 6.5 -22.5t8.5 -18.5q-3 -34 -27.5 -90.5t-29.5 -79.5q-8 -33 5.5 -92.5t7.5 -87.5q0 -9 17 -44t16 -60q12 0 23 -5.5t23 -15t20 -13.5q24 -12 108 -42q22 -8 53 -31.5t59.5 -38.5t57.5 -11q8 -18 -15 -55 t-20 -57q42 -71 87 -80q0 -6 -3 -15.5t-3.5 -14.5t4.5 -17q102 -2 221 112q30 29 47 47t34.5 49t20.5 62q-14 9 -37 9.5t-36 7.5q-14 7 -49 15t-52 19q-9 0 -39.5 -0.5t-46.5 -1.5t-39 -6.5t-39 -16.5q-50 -35 -66 -12q-4 2 -3.5 25.5t0.5 25.5q-6 13 -26.5 17t-24.5 7 q2 22 -2 41t-16.5 28t-38.5 -20q-23 -25 -42 4q-19 28 -8 58q6 16 22 22q6 -1 26 -1.5t33.5 -4t19.5 -13.5q12 -19 32 -37.5t34 -27.5l14 -8q0 3 9.5 39.5t5.5 57.5q-4 23 14.5 44.5t22.5 31.5q5 14 10 35t8.5 31t15.5 22.5t34 21.5q-6 18 10 37q8 0 23.5 -1.5t24.5 -1.5 t20.5 4.5t20.5 15.5q-10 23 -30.5 42.5t-38 30t-49 26.5t-43.5 23q11 41 1 44q31 -13 58.5 -14.5t39.5 3.5l11 4q6 36 -17 53.5t-64 28.5t-56 23q-19 -3 -37 0zM613 994q0 -18 8 -42.5t16.5 -44t9.5 -23.5q-9 2 -31 5t-36 5t-32 8t-30 14q3 12 16 30t16 25q10 -10 18.5 -10 t14 6t14.5 14.5t16 12.5z" /> -<glyph unicode="" horiz-adv-x="1220" d="M100 1196h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5v100q0 41 29.5 70.5t70.5 29.5zM1100 1096h-200v-100h200v100zM100 796h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000 q-41 0 -70.5 29.5t-29.5 70.5v100q0 41 29.5 70.5t70.5 29.5zM1100 696h-500v-100h500v100zM100 396h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5v100q0 41 29.5 70.5t70.5 29.5zM1100 296h-300v-100h300v100z " /> -<glyph unicode="" d="M1100 1200v-100h-1000v100h1000zM150 1000h900l-350 -500v-300l-200 -200v500z" /> -<glyph unicode="" d="M329 729l142 142l-200 200l129 129h-400v-400l129 129zM1200 1200v-400l-129 129l-200 -200l-142 142l200 200l-129 129h400zM271 129l129 -129h-400v400l129 -129l200 200l142 -142zM1071 271l129 129v-400h-400l129 129l-200 200l142 142z" /> -<glyph unicode="" d="M596 1192q162 0 299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299t80 299t217 217t299 80zM596 1010q-171 0 -292.5 -121.5t-121.5 -292.5q0 -172 121.5 -293t292.5 -121t292.5 121t121.5 293q0 171 -121.5 292.5t-292.5 121.5zM455 905 q22 0 38 -16t16 -39t-16 -39t-38 -16q-23 0 -39 16.5t-16 38.5t16 38.5t39 16.5zM708 821l1 1q-9 14 -9 28q0 22 16 38.5t39 16.5q22 0 38 -16t16 -39t-16 -39t-38 -16q-14 0 -29 10l-55 -145q17 -22 17 -51q0 -36 -25.5 -61.5t-61.5 -25.5t-61.5 25.5t-25.5 61.5 q0 32 20.5 56.5t51.5 29.5zM855 709q23 0 38.5 -15.5t15.5 -38.5t-16 -39t-38 -16q-23 0 -39 16t-16 39q0 22 16 38t39 16zM345 709q23 0 39 -16t16 -38q0 -23 -16 -39t-39 -16q-22 0 -38 16t-16 39t15.5 38.5t38.5 15.5z" /> -<glyph unicode="" d="M649 54l-16 22q-90 125 -293 323q-71 70 -104.5 105.5t-77 89.5t-61 99t-17.5 91q0 131 98.5 229.5t230.5 98.5q143 0 241 -129q103 129 246 129q129 0 226 -98.5t97 -229.5q0 -46 -17.5 -91t-61 -99t-77 -89.5t-104.5 -105.5q-203 -198 -293 -323zM844 524l12 12 q64 62 97.5 97t64.5 79t31 72q0 71 -48 119t-105 48q-74 0 -132 -82l-118 -171l-114 174q-51 79 -123 79q-60 0 -109.5 -49t-49.5 -118q0 -27 30.5 -70t61.5 -75.5t95 -94.5l22 -22q93 -90 190 -201q82 92 195 203z" /> -<glyph unicode="" d="M476 406l19 -17l105 105l-212 212l389 389l247 -247l-95 -96l18 -18q46 -46 77 -99l29 29q35 35 62.5 88t27.5 96q0 93 -66 159l-141 141q-66 66 -159 66q-95 0 -159 -66l-283 -283q-66 -64 -66 -159q0 -93 66 -159zM123 193l141 -141q66 -66 159 -66q95 0 159 66 l283 283q66 66 66 159t-66 159l-141 141q-12 12 -19 17l-105 -105l212 -212l-389 -389l-247 248l95 95l-18 18q-46 45 -75 101l-55 -55q-66 -66 -66 -159q0 -94 66 -160z" /> -<glyph unicode="" d="M200 100v953q0 21 30 46t81 48t129 38t163 15t162 -15t127 -38t79 -48t29 -46v-953q0 -41 -29.5 -70.5t-70.5 -29.5h-600q-41 0 -70.5 29.5t-29.5 70.5zM900 1000h-600v-700h600v700zM600 46q43 0 73.5 30.5t30.5 73.5t-30.5 73.5t-73.5 30.5t-73.5 -30.5t-30.5 -73.5 t30.5 -73.5t73.5 -30.5z" /> -<glyph unicode="" d="M700 1029v-307l64 -14q34 -7 64 -16.5t70 -31.5t67.5 -52t47.5 -80.5t20 -112.5q0 -139 -89 -224t-244 -96v-77h-100v78q-152 17 -237 104q-40 40 -52.5 93.5t-15.5 139.5h139q5 -77 48.5 -126.5t117.5 -64.5v335l-27 7q-46 14 -79 26.5t-72 36t-62.5 52t-40 72.5 t-16.5 99q0 92 44 159.5t109 101t144 40.5v78h100v-79q38 -4 72.5 -13.5t75.5 -31.5t71 -53.5t51.5 -84t24.5 -118.5h-159q-8 72 -35 109.5t-101 50.5zM600 755v274q-61 -8 -97.5 -37.5t-36.5 -102.5q0 -29 8 -51t16.5 -34t29.5 -22.5t31 -13.5t38 -10q7 -2 11 -3zM700 548 v-311q170 18 170 151q0 64 -44 99.5t-126 60.5z" /> -<glyph unicode="" d="M866 300l50 -147q-41 -25 -80.5 -36.5t-59 -13t-61.5 -1.5q-23 0 -128 33t-155 29q-39 -4 -82 -17t-66 -25l-24 -11l-55 145l16.5 11t15.5 10t13.5 9.5t14.5 12t14.5 14t17.5 18.5q48 55 54 126.5t-30 142.5h-221v100h166q-24 49 -44 104q-10 26 -14.5 55.5t-3 72.5 t25 90t68.5 87q97 88 263 88q129 0 230 -89t101 -208h-153q0 52 -34 89.5t-74 51.5t-76 14q-37 0 -79 -14.5t-62 -35.5q-41 -44 -41 -101q0 -11 2.5 -24.5t5.5 -24t9.5 -26.5t10.5 -25t14 -27.5t14 -25.5t15.5 -27t13.5 -24h242v-100h-197q8 -50 -2.5 -115t-31.5 -94 q-41 -59 -99 -113q35 11 84 18t70 7q32 1 102 -16t104 -17q76 0 136 30z" /> -<glyph unicode="" d="M300 0l298 300h-198v900h-200v-900h-198zM900 1200l298 -300h-198v-900h-200v900h-198z" /> -<glyph unicode="" d="M400 300h198l-298 -300l-298 300h198v900h200v-900zM1000 1200v-500h-100v100h-100v-100h-100v500h300zM901 1100h-100v-200h100v200zM700 500h300v-200h-99v-100h-100v100h99v100h-200v100zM800 100h200v-100h-300v200h100v-100z" /> -<glyph unicode="" d="M400 300h198l-298 -300l-298 300h198v900h200v-900zM1000 1200v-200h-99v-100h-100v100h99v100h-200v100h300zM800 800h200v-100h-300v200h100v-100zM700 500h300v-500h-100v100h-100v-100h-100v500zM801 200h100v200h-100v-200z" /> -<glyph unicode="" d="M300 0l298 300h-198v900h-200v-900h-198zM900 1100h-100v100h200v-500h-100v400zM1100 500v-500h-100v100h-200v400h300zM1001 400h-100v-200h100v200z" /> -<glyph unicode="" d="M300 0l298 300h-198v900h-200v-900h-198zM1100 1200v-500h-100v100h-200v400h300zM1001 1100h-100v-200h100v200zM900 400h-100v100h200v-500h-100v400z" /> -<glyph unicode="" d="M300 0l298 300h-198v900h-200v-900h-198zM900 1000h-200v200h200v-200zM1000 700h-300v200h300v-200zM1100 400h-400v200h400v-200zM1200 100h-500v200h500v-200z" /> -<glyph unicode="" d="M300 0l298 300h-198v900h-200v-900h-198zM1200 1000h-500v200h500v-200zM1100 700h-400v200h400v-200zM1000 400h-300v200h300v-200zM900 100h-200v200h200v-200z" /> -<glyph unicode="" d="M400 1100h300q162 0 281 -118.5t119 -281.5v-300q0 -165 -118.5 -282.5t-281.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5v300q0 165 117.5 282.5t282.5 117.5zM800 900h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5 t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5z" /> -<glyph unicode="" d="M700 0h-300q-163 0 -281.5 117.5t-118.5 282.5v300q0 163 119 281.5t281 118.5h300q165 0 282.5 -117.5t117.5 -282.5v-300q0 -165 -117.5 -282.5t-282.5 -117.5zM800 900h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5 t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5zM400 800v-500l333 250z" /> -<glyph unicode="" d="M0 400v300q0 163 117.5 281.5t282.5 118.5h300q163 0 281.5 -119t118.5 -281v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5zM900 300v500q0 41 -29.5 70.5t-70.5 29.5h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5 t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5zM800 700h-500l250 -333z" /> -<glyph unicode="" d="M1100 700v-300q0 -162 -118.5 -281t-281.5 -119h-300q-165 0 -282.5 118.5t-117.5 281.5v300q0 165 117.5 282.5t282.5 117.5h300q165 0 282.5 -117.5t117.5 -282.5zM900 300v500q0 41 -29.5 70.5t-70.5 29.5h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5 t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5zM550 733l-250 -333h500z" /> -<glyph unicode="" d="M500 1100h400q165 0 282.5 -117.5t117.5 -282.5v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-400v200h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5h-500v200zM700 550l-400 -350v200h-300v300h300v200z" /> -<glyph unicode="" d="M403 2l9 -1q13 0 26 16l538 630q15 19 6 36q-8 18 -32 16h-300q1 4 78 219.5t79 227.5q2 17 -6 27l-8 8h-9q-16 0 -25 -15q-4 -5 -98.5 -111.5t-228 -257t-209.5 -238.5q-17 -19 -7 -40q10 -19 32 -19h302q-155 -438 -160 -458q-5 -21 4 -32z" /> -<glyph unicode="" d="M800 200h-500q-41 0 -70.5 29.5t-29.5 70.5v500q0 41 29.5 70.5t70.5 29.5h500v185q-14 4 -114 7.5t-193 5.5l-93 2q-165 0 -282.5 -117.5t-117.5 -282.5v-300q0 -165 117.5 -282.5t282.5 -117.5h300q47 0 100 15v185zM900 200v200h-300v300h300v200l400 -350z" /> -<glyph unicode="" d="M1200 700l-149 149l-342 -353l-213 213l353 342l-149 149h500v-500zM1022 571l-122 -123v-148q0 -41 -29.5 -70.5t-70.5 -29.5h-500q-41 0 -70.5 29.5t-29.5 70.5v500q0 41 29.5 70.5t70.5 29.5h156l118 122l-74 78h-100q-165 0 -282.5 -117.5t-117.5 -282.5v-300 q0 -165 117.5 -282.5t282.5 -117.5h300q163 0 281.5 117.5t118.5 282.5v98z" /> -<glyph unicode="" d="M600 1196q162 0 299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299t80 299t217 217t299 80zM600 1014q-171 0 -292.5 -121.5t-121.5 -292.5t121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5zM600 794 q80 0 137 -57t57 -137t-57 -137t-137 -57t-137 57t-57 137t57 137t137 57z" /> -<glyph unicode="" d="M700 800v400h-300v-400h-300l445 -500l450 500h-295zM25 300h1048q11 0 19 -7.5t8 -17.5v-275h-1100v275q0 11 7 18t18 7zM1000 200h-100v-50h100v50z" /> -<glyph unicode="" d="M400 700v-300h300v300h295l-445 500l-450 -500h300zM25 300h1048q11 0 19 -7.5t8 -17.5v-275h-1100v275q0 11 7 18t18 7zM1000 200h-100v-50h100v50z" /> -<glyph unicode="" d="M405 400l596 596l-154 155l-442 -442l-150 151l-155 -155zM25 300h1048q11 0 19 -7.5t8 -17.5v-275h-1100v275q0 11 7 18t18 7zM1000 200h-100v-50h100v50z" /> -<glyph unicode="" d="M409 1103l-97 97l-212 -212l97 -98zM650 861l-149 149l-212 -212l149 -149l-238 -248h700v699zM25 300h1048q11 0 19 -7.5t8 -17.5v-275h-1100v275q0 11 7 18t18 7zM1000 200h-100v-50h100v50z" /> -<glyph unicode="" d="M539 950l-149 -149l212 -212l149 148l248 -237v700h-699zM297 709l-97 -97l212 -212l98 97zM25 300h1048q11 0 19 -7.5t8 -17.5v-275h-1100v275q0 11 7 18t18 7zM1000 200h-100v-50h100v50z" /> -<glyph unicode="" d="M1200 1199v-1079l-475 272l-310 -393v416h-392zM1166 1148l-672 -712v-226z" /> -<glyph unicode="" d="M1100 1000v-850q0 -21 -15 -35.5t-35 -14.5h-150v400h-700v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1200h-100v-200h100v200z" /> -<glyph unicode="" d="M578 500h-378v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-218l-276 -275l-120 120zM700 1200h-100v-200h100v200zM1300 538l-475 -476l-244 244l123 123l120 -120l353 352z" /> -<glyph unicode="" d="M529 500h-329v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-269l-103 -103l-170 170zM700 1200h-100v-200h100v200zM1167 6l-170 170l-170 -170l-127 127l170 170l-170 170l127 127l170 -170l170 170l127 -128 l-170 -169l170 -170z" /> -<glyph unicode="" d="M700 500h-500v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-300h-400v-200zM700 1000h-100v200h100v-200zM1000 600h-200v-300h-200l300 -300l300 300h-200v300z" /> -<glyph unicode="" d="M602 500h-402v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-402l-200 200zM700 1000h-100v200h100v-200zM1000 300h200l-300 300l-300 -300h200v-300h200v300z" /> -<glyph unicode="" d="M1200 900v150q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-150h1200zM0 800v-550q0 -21 14.5 -35.5t35.5 -14.5h1100q21 0 35.5 14.5t14.5 35.5v550h-1200zM100 500h400v-200h-400v200z" /> -<glyph unicode="" d="M500 1000h400v198l300 -298l-300 -298v198h-400v200zM100 800v200h100v-200h-100zM400 800h-100v200h100v-200zM700 300h-400v-198l-300 298l300 298v-198h400v-200zM800 500h100v-200h-100v200zM1000 500v-200h100v200h-100z" /> -<glyph unicode="" d="M1200 50v1106q0 31 -18 40.5t-44 -7.5l-276 -117q-25 -16 -43.5 -50.5t-18.5 -65.5v-359q0 -29 10.5 -55.5t25 -43t29 -28.5t25.5 -18l10 -5v-397q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5zM550 1200l50 -100v-400l-100 -203v-447q0 -21 -14.5 -35.5 t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v447l-100 203v400l50 100l50 -100v-300h100v300l50 100l50 -100v-300h100v300z" /> -<glyph unicode="" d="M1100 106v888q0 22 25 34.5t50 13.5l25 2v56h-400v-56q75 0 87.5 -6t12.5 -44v-394h-500v394q0 38 12.5 44t87.5 6v56h-400v-56q4 0 11 -0.5t24 -3t30 -7t24 -15t11 -24.5v-888q0 -22 -25 -34.5t-50 -13.5l-25 -2v-56h400v56q-75 0 -87.5 6t-12.5 44v394h500v-394 q0 -38 -12.5 -44t-87.5 -6v-56h400v56q-4 0 -11 0.5t-24 3t-30 7t-24 15t-11 24.5z" /> -<glyph unicode="" d="M675 1000l-100 100h-375l-100 -100h400l200 -200v-98l295 98h105v200h-425zM500 300v500q0 41 -29.5 70.5t-70.5 29.5h-300q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h300q41 0 70.5 29.5t29.5 70.5zM100 800h300v-200h-300v200zM700 565l400 133 v-163l-400 -133v163zM100 500h300v-200h-300v200zM805 300l295 98v-298h-425l-100 -100h-375l-100 100h400l200 200h105z" /> -<glyph unicode="" d="M179 1169l-162 -162q-1 -11 -0.5 -32.5t16 -90t46.5 -140t104 -177.5t175 -208q103 -103 207.5 -176t180 -103.5t137 -47t92.5 -16.5l31 1l163 162q16 17 13 40.5t-22 37.5l-192 136q-19 14 -45 12t-42 -19l-119 -118q-143 103 -267 227q-126 126 -227 268l118 118 q17 17 20 41.5t-11 44.5l-139 194q-14 19 -36.5 22t-40.5 -14z" /> -<glyph unicode="" d="M1200 712v200q-6 8 -19 20.5t-63 45t-112 57t-171 45t-235 20.5q-92 0 -175 -10.5t-141.5 -27t-108.5 -36.5t-81.5 -40t-53.5 -36.5t-31 -27.5l-9 -10v-200q0 -21 14.5 -33.5t34.5 -8.5l202 33q20 4 34.5 21t14.5 38v146q141 24 300 24t300 -24v-146q0 -21 14.5 -38 t34.5 -21l202 -33q20 -4 34.5 8.5t14.5 33.5zM800 650l365 -303q14 -14 24.5 -39.5t10.5 -45.5v-212q0 -21 -15 -35.5t-35 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v212q0 20 10.5 45.5t24.5 39.5l365 303v50q0 4 1 10.5t12 22.5t30 28.5t60 23t97 10.5t97 -10t60 -23.5 t30 -27.5t12 -24l1 -10v-50z" /> -<glyph unicode="" d="M175 200h950l-125 150v250l100 100v400h-100v-200h-100v200h-200v-200h-100v200h-200v-200h-100v200h-100v-400l100 -100v-250zM1200 100v-100h-1100v100h1100z" /> -<glyph unicode="" d="M600 1100h100q41 0 70.5 -29.5t29.5 -70.5v-1000h-300v1000q0 41 29.5 70.5t70.5 29.5zM1000 800h100q41 0 70.5 -29.5t29.5 -70.5v-700h-300v700q0 41 29.5 70.5t70.5 29.5zM400 0v400q0 41 -29.5 70.5t-70.5 29.5h-100q-41 0 -70.5 -29.5t-29.5 -70.5v-400h300z" /> -<glyph unicode="" d="M1200 800v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212zM1000 900h-900v-700h900v700zM200 800v-300h200v-100h-200v-100h300v300h-200v100h200v100h-300zM800 800h-200v-500h200v100h100v300h-100 v100zM800 700v-300h-100v300h100z" /> -<glyph unicode="" d="M1200 800v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212zM1000 900h-900v-700h900v700zM400 600h-100v200h-100v-500h100v200h100v-200h100v500h-100v-200zM800 800h-200v-500h200v100h100v300h-100 v100zM800 700v-300h-100v300h100z" /> -<glyph unicode="" d="M1200 800v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212zM1000 900h-900v-700h900v700zM200 800v-500h300v100h-200v300h200v100h-300zM600 800v-500h300v100h-200v300h200v100h-300z" /> -<glyph unicode="" d="M1200 800v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212zM1000 900h-900v-700h900v700zM500 700l-300 -150l300 -150v300zM600 400l300 150l-300 150v-300z" /> -<glyph unicode="" d="M1200 800v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212zM1000 900h-900v-700h900v700zM900 800v-500h-700v500h700zM300 400h130q41 0 68 42t27 107t-28.5 108t-66.5 43h-130v-300zM800 700h-130 q-38 0 -66.5 -43t-28.5 -108t27 -107t68 -42h130v300z" /> -<glyph unicode="" d="M1200 800v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212zM1000 900h-900v-700h900v700zM200 800v-300h200v-100h-200v-100h300v300h-200v100h200v100h-300zM800 300h100v500h-200v-100h100v-400z M601 300h100v100h-100v-100z" /> -<glyph unicode="" d="M1200 800v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212zM1000 900h-900v-700h900v700zM300 700v100h-100v-500h300v400h-200zM800 300h100v500h-200v-100h100v-400zM401 400h-100v200h100v-200z M601 300h100v100h-100v-100z" /> -<glyph unicode="" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM1000 900h-900v-700h900v700zM400 700h-200v100h300v-300h-99v-100h-100v100h99v200zM800 700h-100v100h200v-500h-100v400zM201 400h100v-100 h-100v100zM701 300h-100v100h100v-100z" /> -<glyph unicode="" d="M600 1196q162 0 299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299t80 299t217 217t299 80zM600 1014q-171 0 -292.5 -121.5t-121.5 -292.5t121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5zM800 700h-300 v-200h300v-100h-300l-100 100v200l100 100h300v-100z" /> -<glyph unicode="" d="M596 1196q162 0 299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299t80 299t217 217t299 80zM596 1014q-171 0 -292.5 -121.5t-121.5 -292.5t121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5zM800 700v-100 h-100v100h-200v-100h200v-100h-200v-100h-100v400h300zM800 400h-100v100h100v-100z" /> -<glyph unicode="" d="M800 300h128q120 0 205 86t85 208q0 120 -85 206.5t-205 86.5q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5q0 -80 56.5 -137t135.5 -57h222v300h400v-300zM700 200h200l-300 -300 l-300 300h200v300h200v-300z" /> -<glyph unicode="" d="M600 714l403 -403q94 26 154.5 104t60.5 178q0 121 -85 207.5t-205 86.5q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5q0 -80 56.5 -137t135.5 -57h8zM700 -100h-200v300h-200l300 300 l300 -300h-200v-300z" /> -<glyph unicode="" d="M700 200h400l-270 300h170l-270 300h170l-300 333l-300 -333h170l-270 -300h170l-270 -300h400v-155l-75 -45h350l-75 45v155z" /> -<glyph unicode="" d="M700 45v306q46 -30 100 -30q74 0 126.5 52.5t52.5 126.5q0 24 -9 55q50 32 79.5 83t29.5 112q0 90 -61.5 155.5t-150.5 71.5q-26 89 -99.5 145.5t-167.5 56.5q-116 0 -197.5 -81.5t-81.5 -197.5q0 -4 1 -12t1 -11q-14 2 -23 2q-74 0 -126.5 -52.5t-52.5 -126.5 q0 -53 28.5 -97t75.5 -65q-4 -16 -4 -38q0 -74 52.5 -126.5t126.5 -52.5q56 0 100 30v-306l-75 -45h350z" /> -<glyph unicode="💼" d="M800 1000h300q41 0 70.5 -29.5t29.5 -70.5v-400h-500v100h-200v-100h-500v400q0 41 29.5 70.5t70.5 29.5h300v100q0 41 29.5 70.5t70.5 29.5h200q41 0 70.5 -29.5t29.5 -70.5v-100zM500 1000h200v100h-200v-100zM1200 400v-200q0 -41 -29.5 -70.5t-70.5 -29.5h-1000 q-41 0 -70.5 29.5t-29.5 70.5v200h1200z" /> -<glyph unicode="📅" d="M1100 900v150q0 21 -14.5 35.5t-35.5 14.5h-150v100h-100v-100h-500v100h-100v-100h-150q-21 0 -35.5 -14.5t-14.5 -35.5v-150h1100zM0 800v-750q0 -20 14.5 -35t35.5 -15h1000q21 0 35.5 15t14.5 35v750h-1100zM100 600h100v-100h-100v100zM300 600h100v-100h-100v100z M500 600h100v-100h-100v100zM700 600h100v-100h-100v100zM900 600h100v-100h-100v100zM100 400h100v-100h-100v100zM300 400h100v-100h-100v100zM500 400h100v-100h-100v100zM700 400h100v-100h-100v100zM900 400h100v-100h-100v100zM100 200h100v-100h-100v100zM300 200 h100v-100h-100v100zM500 200h100v-100h-100v100zM700 200h100v-100h-100v100zM900 200h100v-100h-100v100z" /> -<glyph unicode="📌" d="M902 1185l283 -282q15 -15 15 -36t-15 -35q-14 -15 -35 -15t-35 15l-36 35l-279 -267v-300l-212 210l-208 -207l-380 -303l303 380l207 208l-210 212h300l267 279l-35 36q-15 14 -15 35t15 35q14 15 35 15t35 -15z" /> -<glyph unicode="📎" d="M518 119l69 -60l517 511q67 67 95 157t11 183q-16 87 -67 154t-130 103q-69 33 -152 33q-107 0 -197 -55q-40 -24 -111 -95l-512 -512q-68 -68 -81 -163t35 -173q35 -57 94 -89t129 -32q63 0 119 28q33 16 65 40.5t52.5 45.5t59.5 64q40 44 57 61l394 394q35 35 47 84 t-3 96q-27 87 -117 104q-20 2 -29 2q-46 0 -79.5 -17t-67.5 -51l-388 -396l-7 -7l69 -67l377 373q20 22 39 38q23 23 50 23q38 0 53 -36q16 -39 -20 -75l-547 -547q-52 -52 -125 -52q-55 0 -100 33t-54 96q-5 35 2.5 66t31.5 63t42 50t56 54q24 21 44 41l348 348 q52 52 82.5 79.5t84 54t107.5 26.5q25 0 48 -4q95 -17 154 -94.5t51 -175.5q-7 -101 -98 -192l-252 -249l-253 -256z" /> -<glyph unicode="📷" d="M1200 200v600q0 41 -29.5 70.5t-70.5 29.5h-150q-4 8 -11.5 21.5t-33 48t-53 61t-69 48t-83.5 21.5h-200q-41 0 -82 -20.5t-70 -50t-52 -59t-34 -50.5l-12 -20h-150q-41 0 -70.5 -29.5t-29.5 -70.5v-600q0 -41 29.5 -70.5t70.5 -29.5h1000q41 0 70.5 29.5t29.5 70.5z M1000 700h-100v100h100v-100zM844 500q0 -100 -72 -172t-172 -72t-172 72t-72 172t72 172t172 72t172 -72t72 -172zM706 500q0 44 -31 75t-75 31t-75 -31t-31 -75t31 -75t75 -31t75 31t31 75z" /> -<glyph unicode="🔒" d="M900 800h100q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-900q-41 0 -70.5 29.5t-29.5 70.5v600q0 41 29.5 70.5t70.5 29.5h100v200q0 82 59 141t141 59h300q82 0 141 -59t59 -141v-200zM400 800h300v150q0 21 -14.5 35.5t-35.5 14.5h-200 q-21 0 -35.5 -14.5t-14.5 -35.5v-150z" /> -<glyph unicode="🔔" d="M1062 400h17q20 0 33.5 -14.5t13.5 -35.5q0 -20 -13 -40t-31 -27q-22 -9 -63 -23t-167.5 -37t-251.5 -23t-245.5 20.5t-178.5 41.5l-58 20q-18 7 -31 27.5t-13 40.5q0 21 13.5 35.5t33.5 14.5h17l118 173l63 327q15 77 76 140t144 83l-18 32q-6 19 3 32t29 13h94 q20 0 29 -10.5t3 -29.5l-18 -37q83 -19 144 -82.5t76 -140.5l63 -327zM600 104q-54 0 -103 6q12 -49 40 -79.5t63 -30.5t63 30.5t39 79.5q-48 -6 -102 -6z" /> -<glyph unicode="🔖" d="M200 0l450 444l450 -443v1150q0 20 -14.5 35t-35.5 15h-800q-21 0 -35.5 -15t-14.5 -35v-1151z" /> -<glyph unicode="🔥" d="M400 755q2 -12 8 -41.5t8 -43t6 -39.5t3.5 -39.5t-1 -33.5t-6 -31.5t-13.5 -24t-21 -20.5t-31 -12q-38 -10 -67 13t-40.5 61.5t-15 81.5t10.5 75q-52 -46 -83.5 -101t-39 -107t-7.5 -85t5 -63q9 -56 44 -119.5t105 -108.5q31 -21 64 -16t62 23.5t57 49.5t48 61.5t35 60.5 q32 66 39 184.5t-13 157.5q79 -80 122 -164t26 -184q-5 -33 -20.5 -69.5t-37.5 -80.5q-10 -19 -14.5 -29t-12 -26t-9 -23.5t-3 -19t2.5 -15.5t11 -9.5t19.5 -5t30.5 2.5t42 8q57 20 91 34t87.5 44.5t87 64t65.5 88.5t47 122q38 172 -44.5 341.5t-246.5 278.5q22 -44 43 -129 q39 -159 -32 -154q-15 2 -33 9q-79 33 -120.5 100t-44 175.5t48.5 257.5q-13 -8 -34 -23.5t-72.5 -66.5t-88.5 -105.5t-60 -138t-8 -166.5z" /> -<glyph unicode="🔧" d="M948 778l251 126q13 -175 -151 -267q-123 -70 -253 -23l-596 -596q-15 -16 -36.5 -16t-36.5 16l-111 110q-15 15 -15 36.5t15 37.5l600 599q-33 101 6 201.5t135 154.5q164 92 306 -9l-259 -138z" /> -</font> -</defs></svg> \ No newline at end of file diff --git a/framework/yii/bootstrap/assets/fonts/glyphicons-halflings-regular.ttf b/framework/yii/bootstrap/assets/fonts/glyphicons-halflings-regular.ttf deleted file mode 100644 index be784dc..0000000 Binary files a/framework/yii/bootstrap/assets/fonts/glyphicons-halflings-regular.ttf and /dev/null differ diff --git a/framework/yii/bootstrap/assets/fonts/glyphicons-halflings-regular.woff b/framework/yii/bootstrap/assets/fonts/glyphicons-halflings-regular.woff deleted file mode 100644 index 2cc3e48..0000000 Binary files a/framework/yii/bootstrap/assets/fonts/glyphicons-halflings-regular.woff and /dev/null differ diff --git a/framework/yii/bootstrap/assets/js/bootstrap.js b/framework/yii/bootstrap/assets/js/bootstrap.js deleted file mode 100644 index 2c64257..0000000 --- a/framework/yii/bootstrap/assets/js/bootstrap.js +++ /dev/null @@ -1,1999 +0,0 @@ -/** -* bootstrap.js v3.0.0 by @fat and @mdo -* Copyright 2013 Twitter Inc. -* http://www.apache.org/licenses/LICENSE-2.0 -*/ -if (!jQuery) { throw new Error("Bootstrap requires jQuery") } - -/* ======================================================================== - * Bootstrap: transition.js v3.0.0 - * http://twbs.github.com/bootstrap/javascript.html#transitions - * ======================================================================== - * Copyright 2013 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ======================================================================== */ - - -+function ($) { "use strict"; - - // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/) - // ============================================================ - - function transitionEnd() { - var el = document.createElement('bootstrap') - - var transEndEventNames = { - 'WebkitTransition' : 'webkitTransitionEnd' - , 'MozTransition' : 'transitionend' - , 'OTransition' : 'oTransitionEnd otransitionend' - , 'transition' : 'transitionend' - } - - for (var name in transEndEventNames) { - if (el.style[name] !== undefined) { - return { end: transEndEventNames[name] } - } - } - } - - // http://blog.alexmaccaw.com/css-transitions - $.fn.emulateTransitionEnd = function (duration) { - var called = false, $el = this - $(this).one($.support.transition.end, function () { called = true }) - var callback = function () { if (!called) $($el).trigger($.support.transition.end) } - setTimeout(callback, duration) - return this - } - - $(function () { - $.support.transition = transitionEnd() - }) - -}(window.jQuery); - -/* ======================================================================== - * Bootstrap: alert.js v3.0.0 - * http://twbs.github.com/bootstrap/javascript.html#alerts - * ======================================================================== - * Copyright 2013 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ======================================================================== */ - - -+function ($) { "use strict"; - - // ALERT CLASS DEFINITION - // ====================== - - var dismiss = '[data-dismiss="alert"]' - var Alert = function (el) { - $(el).on('click', dismiss, this.close) - } - - Alert.prototype.close = function (e) { - var $this = $(this) - var selector = $this.attr('data-target') - - if (!selector) { - selector = $this.attr('href') - selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 - } - - var $parent = $(selector) - - if (e) e.preventDefault() - - if (!$parent.length) { - $parent = $this.hasClass('alert') ? $this : $this.parent() - } - - $parent.trigger(e = $.Event('close.bs.alert')) - - if (e.isDefaultPrevented()) return - - $parent.removeClass('in') - - function removeElement() { - $parent.trigger('closed.bs.alert').remove() - } - - $.support.transition && $parent.hasClass('fade') ? - $parent - .one($.support.transition.end, removeElement) - .emulateTransitionEnd(150) : - removeElement() - } - - - // ALERT PLUGIN DEFINITION - // ======================= - - var old = $.fn.alert - - $.fn.alert = function (option) { - return this.each(function () { - var $this = $(this) - var data = $this.data('bs.alert') - - if (!data) $this.data('bs.alert', (data = new Alert(this))) - if (typeof option == 'string') data[option].call($this) - }) - } - - $.fn.alert.Constructor = Alert - - - // ALERT NO CONFLICT - // ================= - - $.fn.alert.noConflict = function () { - $.fn.alert = old - return this - } - - - // ALERT DATA-API - // ============== - - $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close) - -}(window.jQuery); - -/* ======================================================================== - * Bootstrap: button.js v3.0.0 - * http://twbs.github.com/bootstrap/javascript.html#buttons - * ======================================================================== - * Copyright 2013 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ======================================================================== */ - - -+function ($) { "use strict"; - - // BUTTON PUBLIC CLASS DEFINITION - // ============================== - - var Button = function (element, options) { - this.$element = $(element) - this.options = $.extend({}, Button.DEFAULTS, options) - } - - Button.DEFAULTS = { - loadingText: 'loading...' - } - - Button.prototype.setState = function (state) { - var d = 'disabled' - var $el = this.$element - var val = $el.is('input') ? 'val' : 'html' - var data = $el.data() - - state = state + 'Text' - - if (!data.resetText) $el.data('resetText', $el[val]()) - - $el[val](data[state] || this.options[state]) - - // push to event loop to allow forms to submit - setTimeout(function () { - state == 'loadingText' ? - $el.addClass(d).attr(d, d) : - $el.removeClass(d).removeAttr(d); - }, 0) - } - - Button.prototype.toggle = function () { - var $parent = this.$element.closest('[data-toggle="buttons"]') - - if ($parent.length) { - var $input = this.$element.find('input') - .prop('checked', !this.$element.hasClass('active')) - .trigger('change') - if ($input.prop('type') === 'radio') $parent.find('.active').removeClass('active') - } - - this.$element.toggleClass('active') - } - - - // BUTTON PLUGIN DEFINITION - // ======================== - - var old = $.fn.button - - $.fn.button = function (option) { - return this.each(function () { - var $this = $(this) - var data = $this.data('bs.button') - var options = typeof option == 'object' && option - - if (!data) $this.data('bs.button', (data = new Button(this, options))) - - if (option == 'toggle') data.toggle() - else if (option) data.setState(option) - }) - } - - $.fn.button.Constructor = Button - - - // BUTTON NO CONFLICT - // ================== - - $.fn.button.noConflict = function () { - $.fn.button = old - return this - } - - - // BUTTON DATA-API - // =============== - - $(document).on('click.bs.button.data-api', '[data-toggle^=button]', function (e) { - var $btn = $(e.target) - if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn') - $btn.button('toggle') - e.preventDefault() - }) - -}(window.jQuery); - -/* ======================================================================== - * Bootstrap: carousel.js v3.0.0 - * http://twbs.github.com/bootstrap/javascript.html#carousel - * ======================================================================== - * Copyright 2012 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ======================================================================== */ - - -+function ($) { "use strict"; - - // CAROUSEL CLASS DEFINITION - // ========================= - - var Carousel = function (element, options) { - this.$element = $(element) - this.$indicators = this.$element.find('.carousel-indicators') - this.options = options - this.paused = - this.sliding = - this.interval = - this.$active = - this.$items = null - - this.options.pause == 'hover' && this.$element - .on('mouseenter', $.proxy(this.pause, this)) - .on('mouseleave', $.proxy(this.cycle, this)) - } - - Carousel.DEFAULTS = { - interval: 5000 - , pause: 'hover' - , wrap: true - } - - Carousel.prototype.cycle = function (e) { - e || (this.paused = false) - - this.interval && clearInterval(this.interval) - - this.options.interval - && !this.paused - && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) - - return this - } - - Carousel.prototype.getActiveIndex = function () { - this.$active = this.$element.find('.item.active') - this.$items = this.$active.parent().children() - - return this.$items.index(this.$active) - } - - Carousel.prototype.to = function (pos) { - var that = this - var activeIndex = this.getActiveIndex() - - if (pos > (this.$items.length - 1) || pos < 0) return - - if (this.sliding) return this.$element.one('slid', function () { that.to(pos) }) - if (activeIndex == pos) return this.pause().cycle() - - return this.slide(pos > activeIndex ? 'next' : 'prev', $(this.$items[pos])) - } - - Carousel.prototype.pause = function (e) { - e || (this.paused = true) - - if (this.$element.find('.next, .prev').length && $.support.transition.end) { - this.$element.trigger($.support.transition.end) - this.cycle(true) - } - - this.interval = clearInterval(this.interval) - - return this - } - - Carousel.prototype.next = function () { - if (this.sliding) return - return this.slide('next') - } - - Carousel.prototype.prev = function () { - if (this.sliding) return - return this.slide('prev') - } - - Carousel.prototype.slide = function (type, next) { - var $active = this.$element.find('.item.active') - var $next = next || $active[type]() - var isCycling = this.interval - var direction = type == 'next' ? 'left' : 'right' - var fallback = type == 'next' ? 'first' : 'last' - var that = this - - if (!$next.length) { - if (!this.options.wrap) return - $next = this.$element.find('.item')[fallback]() - } - - this.sliding = true - - isCycling && this.pause() - - var e = $.Event('slide.bs.carousel', { relatedTarget: $next[0], direction: direction }) - - if ($next.hasClass('active')) return - - if (this.$indicators.length) { - this.$indicators.find('.active').removeClass('active') - this.$element.one('slid', function () { - var $nextIndicator = $(that.$indicators.children()[that.getActiveIndex()]) - $nextIndicator && $nextIndicator.addClass('active') - }) - } - - if ($.support.transition && this.$element.hasClass('slide')) { - this.$element.trigger(e) - if (e.isDefaultPrevented()) return - $next.addClass(type) - $next[0].offsetWidth // force reflow - $active.addClass(direction) - $next.addClass(direction) - $active - .one($.support.transition.end, function () { - $next.removeClass([type, direction].join(' ')).addClass('active') - $active.removeClass(['active', direction].join(' ')) - that.sliding = false - setTimeout(function () { that.$element.trigger('slid') }, 0) - }) - .emulateTransitionEnd(600) - } else { - this.$element.trigger(e) - if (e.isDefaultPrevented()) return - $active.removeClass('active') - $next.addClass('active') - this.sliding = false - this.$element.trigger('slid') - } - - isCycling && this.cycle() - - return this - } - - - // CAROUSEL PLUGIN DEFINITION - // ========================== - - var old = $.fn.carousel - - $.fn.carousel = function (option) { - return this.each(function () { - var $this = $(this) - var data = $this.data('bs.carousel') - var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option) - var action = typeof option == 'string' ? option : options.slide - - if (!data) $this.data('bs.carousel', (data = new Carousel(this, options))) - if (typeof option == 'number') data.to(option) - else if (action) data[action]() - else if (options.interval) data.pause().cycle() - }) - } - - $.fn.carousel.Constructor = Carousel - - - // CAROUSEL NO CONFLICT - // ==================== - - $.fn.carousel.noConflict = function () { - $.fn.carousel = old - return this - } - - - // CAROUSEL DATA-API - // ================= - - $(document).on('click.bs.carousel.data-api', '[data-slide], [data-slide-to]', function (e) { - var $this = $(this), href - var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7 - var options = $.extend({}, $target.data(), $this.data()) - var slideIndex = $this.attr('data-slide-to') - if (slideIndex) options.interval = false - - $target.carousel(options) - - if (slideIndex = $this.attr('data-slide-to')) { - $target.data('bs.carousel').to(slideIndex) - } - - e.preventDefault() - }) - - $(window).on('load', function () { - $('[data-ride="carousel"]').each(function () { - var $carousel = $(this) - $carousel.carousel($carousel.data()) - }) - }) - -}(window.jQuery); - -/* ======================================================================== - * Bootstrap: collapse.js v3.0.0 - * http://twbs.github.com/bootstrap/javascript.html#collapse - * ======================================================================== - * Copyright 2012 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ======================================================================== */ - - -+function ($) { "use strict"; - - // COLLAPSE PUBLIC CLASS DEFINITION - // ================================ - - var Collapse = function (element, options) { - this.$element = $(element) - this.options = $.extend({}, Collapse.DEFAULTS, options) - this.transitioning = null - - if (this.options.parent) this.$parent = $(this.options.parent) - if (this.options.toggle) this.toggle() - } - - Collapse.DEFAULTS = { - toggle: true - } - - Collapse.prototype.dimension = function () { - var hasWidth = this.$element.hasClass('width') - return hasWidth ? 'width' : 'height' - } - - Collapse.prototype.show = function () { - if (this.transitioning || this.$element.hasClass('in')) return - - var startEvent = $.Event('show.bs.collapse') - this.$element.trigger(startEvent) - if (startEvent.isDefaultPrevented()) return - - var actives = this.$parent && this.$parent.find('> .panel > .in') - - if (actives && actives.length) { - var hasData = actives.data('bs.collapse') - if (hasData && hasData.transitioning) return - actives.collapse('hide') - hasData || actives.data('bs.collapse', null) - } - - var dimension = this.dimension() - - this.$element - .removeClass('collapse') - .addClass('collapsing') - [dimension](0) - - this.transitioning = 1 - - var complete = function () { - this.$element - .removeClass('collapsing') - .addClass('in') - [dimension]('auto') - this.transitioning = 0 - this.$element.trigger('shown.bs.collapse') - } - - if (!$.support.transition) return complete.call(this) - - var scrollSize = $.camelCase(['scroll', dimension].join('-')) - - this.$element - .one($.support.transition.end, $.proxy(complete, this)) - .emulateTransitionEnd(350) - [dimension](this.$element[0][scrollSize]) - } - - Collapse.prototype.hide = function () { - if (this.transitioning || !this.$element.hasClass('in')) return - - var startEvent = $.Event('hide.bs.collapse') - this.$element.trigger(startEvent) - if (startEvent.isDefaultPrevented()) return - - var dimension = this.dimension() - - this.$element - [dimension](this.$element[dimension]()) - [0].offsetHeight - - this.$element - .addClass('collapsing') - .removeClass('collapse') - .removeClass('in') - - this.transitioning = 1 - - var complete = function () { - this.transitioning = 0 - this.$element - .trigger('hidden.bs.collapse') - .removeClass('collapsing') - .addClass('collapse') - } - - if (!$.support.transition) return complete.call(this) - - this.$element - [dimension](0) - .one($.support.transition.end, $.proxy(complete, this)) - .emulateTransitionEnd(350) - } - - Collapse.prototype.toggle = function () { - this[this.$element.hasClass('in') ? 'hide' : 'show']() - } - - - // COLLAPSE PLUGIN DEFINITION - // ========================== - - var old = $.fn.collapse - - $.fn.collapse = function (option) { - return this.each(function () { - var $this = $(this) - var data = $this.data('bs.collapse') - var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option) - - if (!data) $this.data('bs.collapse', (data = new Collapse(this, options))) - if (typeof option == 'string') data[option]() - }) - } - - $.fn.collapse.Constructor = Collapse - - - // COLLAPSE NO CONFLICT - // ==================== - - $.fn.collapse.noConflict = function () { - $.fn.collapse = old - return this - } - - - // COLLAPSE DATA-API - // ================= - - $(document).on('click.bs.collapse.data-api', '[data-toggle=collapse]', function (e) { - var $this = $(this), href - var target = $this.attr('data-target') - || e.preventDefault() - || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7 - var $target = $(target) - var data = $target.data('bs.collapse') - var option = data ? 'toggle' : $this.data() - var parent = $this.attr('data-parent') - var $parent = parent && $(parent) - - if (!data || !data.transitioning) { - if ($parent) $parent.find('[data-toggle=collapse][data-parent="' + parent + '"]').not($this).addClass('collapsed') - $this[$target.hasClass('in') ? 'addClass' : 'removeClass']('collapsed') - } - - $target.collapse(option) - }) - -}(window.jQuery); - -/* ======================================================================== - * Bootstrap: dropdown.js v3.0.0 - * http://twbs.github.com/bootstrap/javascript.html#dropdowns - * ======================================================================== - * Copyright 2012 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ======================================================================== */ - - -+function ($) { "use strict"; - - // DROPDOWN CLASS DEFINITION - // ========================= - - var backdrop = '.dropdown-backdrop' - var toggle = '[data-toggle=dropdown]' - var Dropdown = function (element) { - var $el = $(element).on('click.bs.dropdown', this.toggle) - } - - Dropdown.prototype.toggle = function (e) { - var $this = $(this) - - if ($this.is('.disabled, :disabled')) return - - var $parent = getParent($this) - var isActive = $parent.hasClass('open') - - clearMenus() - - if (!isActive) { - if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) { - // if mobile we we use a backdrop because click events don't delegate - $('<div class="dropdown-backdrop"/>').insertAfter($(this)).on('click', clearMenus) - } - - $parent.trigger(e = $.Event('show.bs.dropdown')) - - if (e.isDefaultPrevented()) return - - $parent - .toggleClass('open') - .trigger('shown.bs.dropdown') - - $this.focus() - } - - return false - } - - Dropdown.prototype.keydown = function (e) { - if (!/(38|40|27)/.test(e.keyCode)) return - - var $this = $(this) - - e.preventDefault() - e.stopPropagation() - - if ($this.is('.disabled, :disabled')) return - - var $parent = getParent($this) - var isActive = $parent.hasClass('open') - - if (!isActive || (isActive && e.keyCode == 27)) { - if (e.which == 27) $parent.find(toggle).focus() - return $this.click() - } - - var $items = $('[role=menu] li:not(.divider):visible a', $parent) - - if (!$items.length) return - - var index = $items.index($items.filter(':focus')) - - if (e.keyCode == 38 && index > 0) index-- // up - if (e.keyCode == 40 && index < $items.length - 1) index++ // down - if (!~index) index=0 - - $items.eq(index).focus() - } - - function clearMenus() { - $(backdrop).remove() - $(toggle).each(function (e) { - var $parent = getParent($(this)) - if (!$parent.hasClass('open')) return - $parent.trigger(e = $.Event('hide.bs.dropdown')) - if (e.isDefaultPrevented()) return - $parent.removeClass('open').trigger('hidden.bs.dropdown') - }) - } - - function getParent($this) { - var selector = $this.attr('data-target') - - if (!selector) { - selector = $this.attr('href') - selector = selector && /#/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 - } - - var $parent = selector && $(selector) - - return $parent && $parent.length ? $parent : $this.parent() - } - - - // DROPDOWN PLUGIN DEFINITION - // ========================== - - var old = $.fn.dropdown - - $.fn.dropdown = function (option) { - return this.each(function () { - var $this = $(this) - var data = $this.data('dropdown') - - if (!data) $this.data('dropdown', (data = new Dropdown(this))) - if (typeof option == 'string') data[option].call($this) - }) - } - - $.fn.dropdown.Constructor = Dropdown - - - // DROPDOWN NO CONFLICT - // ==================== - - $.fn.dropdown.noConflict = function () { - $.fn.dropdown = old - return this - } - - - // APPLY TO STANDARD DROPDOWN ELEMENTS - // =================================== - - $(document) - .on('click.bs.dropdown.data-api', clearMenus) - .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() }) - .on('click.bs.dropdown.data-api' , toggle, Dropdown.prototype.toggle) - .on('keydown.bs.dropdown.data-api', toggle + ', [role=menu]' , Dropdown.prototype.keydown) - -}(window.jQuery); - -/* ======================================================================== - * Bootstrap: modal.js v3.0.0 - * http://twbs.github.com/bootstrap/javascript.html#modals - * ======================================================================== - * Copyright 2012 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ======================================================================== */ - - -+function ($) { "use strict"; - - // MODAL CLASS DEFINITION - // ====================== - - var Modal = function (element, options) { - this.options = options - this.$element = $(element) - this.$backdrop = - this.isShown = null - - if (this.options.remote) this.$element.load(this.options.remote) - } - - Modal.DEFAULTS = { - backdrop: true - , keyboard: true - , show: true - } - - Modal.prototype.toggle = function (_relatedTarget) { - return this[!this.isShown ? 'show' : 'hide'](_relatedTarget) - } - - Modal.prototype.show = function (_relatedTarget) { - var that = this - var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget }) - - this.$element.trigger(e) - - if (this.isShown || e.isDefaultPrevented()) return - - this.isShown = true - - this.escape() - - this.$element.on('click.dismiss.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this)) - - this.backdrop(function () { - var transition = $.support.transition && that.$element.hasClass('fade') - - if (!that.$element.parent().length) { - that.$element.appendTo(document.body) // don't move modals dom position - } - - that.$element.show() - - if (transition) { - that.$element[0].offsetWidth // force reflow - } - - that.$element - .addClass('in') - .attr('aria-hidden', false) - - that.enforceFocus() - - var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget }) - - transition ? - that.$element.find('.modal-dialog') // wait for modal to slide in - .one($.support.transition.end, function () { - that.$element.focus().trigger(e) - }) - .emulateTransitionEnd(300) : - that.$element.focus().trigger(e) - }) - } - - Modal.prototype.hide = function (e) { - if (e) e.preventDefault() - - e = $.Event('hide.bs.modal') - - this.$element.trigger(e) - - if (!this.isShown || e.isDefaultPrevented()) return - - this.isShown = false - - this.escape() - - $(document).off('focusin.bs.modal') - - this.$element - .removeClass('in') - .attr('aria-hidden', true) - .off('click.dismiss.modal') - - $.support.transition && this.$element.hasClass('fade') ? - this.$element - .one($.support.transition.end, $.proxy(this.hideModal, this)) - .emulateTransitionEnd(300) : - this.hideModal() - } - - Modal.prototype.enforceFocus = function () { - $(document) - .off('focusin.bs.modal') // guard against infinite focus loop - .on('focusin.bs.modal', $.proxy(function (e) { - if (this.$element[0] !== e.target && !this.$element.has(e.target).length) { - this.$element.focus() - } - }, this)) - } - - Modal.prototype.escape = function () { - if (this.isShown && this.options.keyboard) { - this.$element.on('keyup.dismiss.bs.modal', $.proxy(function (e) { - e.which == 27 && this.hide() - }, this)) - } else if (!this.isShown) { - this.$element.off('keyup.dismiss.bs.modal') - } - } - - Modal.prototype.hideModal = function () { - var that = this - this.$element.hide() - this.backdrop(function () { - that.removeBackdrop() - that.$element.trigger('hidden.bs.modal') - }) - } - - Modal.prototype.removeBackdrop = function () { - this.$backdrop && this.$backdrop.remove() - this.$backdrop = null - } - - Modal.prototype.backdrop = function (callback) { - var that = this - var animate = this.$element.hasClass('fade') ? 'fade' : '' - - if (this.isShown && this.options.backdrop) { - var doAnimate = $.support.transition && animate - - this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />') - .appendTo(document.body) - - this.$element.on('click.dismiss.modal', $.proxy(function (e) { - if (e.target !== e.currentTarget) return - this.options.backdrop == 'static' - ? this.$element[0].focus.call(this.$element[0]) - : this.hide.call(this) - }, this)) - - if (doAnimate) this.$backdrop[0].offsetWidth // force reflow - - this.$backdrop.addClass('in') - - if (!callback) return - - doAnimate ? - this.$backdrop - .one($.support.transition.end, callback) - .emulateTransitionEnd(150) : - callback() - - } else if (!this.isShown && this.$backdrop) { - this.$backdrop.removeClass('in') - - $.support.transition && this.$element.hasClass('fade')? - this.$backdrop - .one($.support.transition.end, callback) - .emulateTransitionEnd(150) : - callback() - - } else if (callback) { - callback() - } - } - - - // MODAL PLUGIN DEFINITION - // ======================= - - var old = $.fn.modal - - $.fn.modal = function (option, _relatedTarget) { - return this.each(function () { - var $this = $(this) - var data = $this.data('bs.modal') - var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option) - - if (!data) $this.data('bs.modal', (data = new Modal(this, options))) - if (typeof option == 'string') data[option](_relatedTarget) - else if (options.show) data.show(_relatedTarget) - }) - } - - $.fn.modal.Constructor = Modal - - - // MODAL NO CONFLICT - // ================= - - $.fn.modal.noConflict = function () { - $.fn.modal = old - return this - } - - - // MODAL DATA-API - // ============== - - $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) { - var $this = $(this) - var href = $this.attr('href') - var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) //strip for ie7 - var option = $target.data('modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data()) - - e.preventDefault() - - $target - .modal(option, this) - .one('hide', function () { - $this.is(':visible') && $this.focus() - }) - }) - - $(document) - .on('show.bs.modal', '.modal', function () { $(document.body).addClass('modal-open') }) - .on('hidden.bs.modal', '.modal', function () { $(document.body).removeClass('modal-open') }) - -}(window.jQuery); - -/* ======================================================================== - * Bootstrap: tooltip.js v3.0.0 - * http://twbs.github.com/bootstrap/javascript.html#tooltip - * Inspired by the original jQuery.tipsy by Jason Frame - * ======================================================================== - * Copyright 2012 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ======================================================================== */ - - -+function ($) { "use strict"; - - // TOOLTIP PUBLIC CLASS DEFINITION - // =============================== - - var Tooltip = function (element, options) { - this.type = - this.options = - this.enabled = - this.timeout = - this.hoverState = - this.$element = null - - this.init('tooltip', element, options) - } - - Tooltip.DEFAULTS = { - animation: true - , placement: 'top' - , selector: false - , template: '<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>' - , trigger: 'hover focus' - , title: '' - , delay: 0 - , html: false - , container: false - } - - Tooltip.prototype.init = function (type, element, options) { - this.enabled = true - this.type = type - this.$element = $(element) - this.options = this.getOptions(options) - - var triggers = this.options.trigger.split(' ') - - for (var i = triggers.length; i--;) { - var trigger = triggers[i] - - if (trigger == 'click') { - this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this)) - } else if (trigger != 'manual') { - var eventIn = trigger == 'hover' ? 'mouseenter' : 'focus' - var eventOut = trigger == 'hover' ? 'mouseleave' : 'blur' - - this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this)) - this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this)) - } - } - - this.options.selector ? - (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) : - this.fixTitle() - } - - Tooltip.prototype.getDefaults = function () { - return Tooltip.DEFAULTS - } - - Tooltip.prototype.getOptions = function (options) { - options = $.extend({}, this.getDefaults(), this.$element.data(), options) - - if (options.delay && typeof options.delay == 'number') { - options.delay = { - show: options.delay - , hide: options.delay - } - } - - return options - } - - Tooltip.prototype.getDelegateOptions = function () { - var options = {} - var defaults = this.getDefaults() - - this._options && $.each(this._options, function (key, value) { - if (defaults[key] != value) options[key] = value - }) - - return options - } - - Tooltip.prototype.enter = function (obj) { - var self = obj instanceof this.constructor ? - obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type) - - clearTimeout(self.timeout) - - self.hoverState = 'in' - - if (!self.options.delay || !self.options.delay.show) return self.show() - - self.timeout = setTimeout(function () { - if (self.hoverState == 'in') self.show() - }, self.options.delay.show) - } - - Tooltip.prototype.leave = function (obj) { - var self = obj instanceof this.constructor ? - obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type) - - clearTimeout(self.timeout) - - self.hoverState = 'out' - - if (!self.options.delay || !self.options.delay.hide) return self.hide() - - self.timeout = setTimeout(function () { - if (self.hoverState == 'out') self.hide() - }, self.options.delay.hide) - } - - Tooltip.prototype.show = function () { - var e = $.Event('show.bs.'+ this.type) - - if (this.hasContent() && this.enabled) { - this.$element.trigger(e) - - if (e.isDefaultPrevented()) return - - var $tip = this.tip() - - this.setContent() - - if (this.options.animation) $tip.addClass('fade') - - var placement = typeof this.options.placement == 'function' ? - this.options.placement.call(this, $tip[0], this.$element[0]) : - this.options.placement - - var autoToken = /\s?auto?\s?/i - var autoPlace = autoToken.test(placement) - if (autoPlace) placement = placement.replace(autoToken, '') || 'top' - - $tip - .detach() - .css({ top: 0, left: 0, display: 'block' }) - .addClass(placement) - - this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element) - - var pos = this.getPosition() - var actualWidth = $tip[0].offsetWidth - var actualHeight = $tip[0].offsetHeight - - if (autoPlace) { - var $parent = this.$element.parent() - - var orgPlacement = placement - var docScroll = document.documentElement.scrollTop || document.body.scrollTop - var parentWidth = this.options.container == 'body' ? window.innerWidth : $parent.outerWidth() - var parentHeight = this.options.container == 'body' ? window.innerHeight : $parent.outerHeight() - var parentLeft = this.options.container == 'body' ? 0 : $parent.offset().left - - placement = placement == 'bottom' && pos.top + pos.height + actualHeight - docScroll > parentHeight ? 'top' : - placement == 'top' && pos.top - docScroll - actualHeight < 0 ? 'bottom' : - placement == 'right' && pos.right + actualWidth > parentWidth ? 'left' : - placement == 'left' && pos.left - actualWidth < parentLeft ? 'right' : - placement - - $tip - .removeClass(orgPlacement) - .addClass(placement) - } - - var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight) - - this.applyPlacement(calculatedOffset, placement) - this.$element.trigger('shown.bs.' + this.type) - } - } - - Tooltip.prototype.applyPlacement = function(offset, placement) { - var replace - var $tip = this.tip() - var width = $tip[0].offsetWidth - var height = $tip[0].offsetHeight - - // manually read margins because getBoundingClientRect includes difference - var marginTop = parseInt($tip.css('margin-top'), 10) - var marginLeft = parseInt($tip.css('margin-left'), 10) - - // we must check for NaN for ie 8/9 - if (isNaN(marginTop)) marginTop = 0 - if (isNaN(marginLeft)) marginLeft = 0 - - offset.top = offset.top + marginTop - offset.left = offset.left + marginLeft - - $tip - .offset(offset) - .addClass('in') - - // check to see if placing tip in new offset caused the tip to resize itself - var actualWidth = $tip[0].offsetWidth - var actualHeight = $tip[0].offsetHeight - - if (placement == 'top' && actualHeight != height) { - replace = true - offset.top = offset.top + height - actualHeight - } - - if (/bottom|top/.test(placement)) { - var delta = 0 - - if (offset.left < 0) { - delta = offset.left * -2 - offset.left = 0 - - $tip.offset(offset) - - actualWidth = $tip[0].offsetWidth - actualHeight = $tip[0].offsetHeight - } - - this.replaceArrow(delta - width + actualWidth, actualWidth, 'left') - } else { - this.replaceArrow(actualHeight - height, actualHeight, 'top') - } - - if (replace) $tip.offset(offset) - } - - Tooltip.prototype.replaceArrow = function(delta, dimension, position) { - this.arrow().css(position, delta ? (50 * (1 - delta / dimension) + "%") : '') - } - - Tooltip.prototype.setContent = function () { - var $tip = this.tip() - var title = this.getTitle() - - $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title) - $tip.removeClass('fade in top bottom left right') - } - - Tooltip.prototype.hide = function () { - var that = this - var $tip = this.tip() - var e = $.Event('hide.bs.' + this.type) - - function complete() { - if (that.hoverState != 'in') $tip.detach() - } - - this.$element.trigger(e) - - if (e.isDefaultPrevented()) return - - $tip.removeClass('in') - - $.support.transition && this.$tip.hasClass('fade') ? - $tip - .one($.support.transition.end, complete) - .emulateTransitionEnd(150) : - complete() - - this.$element.trigger('hidden.bs.' + this.type) - - return this - } - - Tooltip.prototype.fixTitle = function () { - var $e = this.$element - if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') { - $e.attr('data-original-title', $e.attr('title') || '').attr('title', '') - } - } - - Tooltip.prototype.hasContent = function () { - return this.getTitle() - } - - Tooltip.prototype.getPosition = function () { - var el = this.$element[0] - return $.extend({}, (typeof el.getBoundingClientRect == 'function') ? el.getBoundingClientRect() : { - width: el.offsetWidth - , height: el.offsetHeight - }, this.$element.offset()) - } - - Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) { - return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } : - placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } : - placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } : - /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width } - } - - Tooltip.prototype.getTitle = function () { - var title - var $e = this.$element - var o = this.options - - title = $e.attr('data-original-title') - || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title) - - return title - } - - Tooltip.prototype.tip = function () { - return this.$tip = this.$tip || $(this.options.template) - } - - Tooltip.prototype.arrow = function () { - return this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow') - } - - Tooltip.prototype.validate = function () { - if (!this.$element[0].parentNode) { - this.hide() - this.$element = null - this.options = null - } - } - - Tooltip.prototype.enable = function () { - this.enabled = true - } - - Tooltip.prototype.disable = function () { - this.enabled = false - } - - Tooltip.prototype.toggleEnabled = function () { - this.enabled = !this.enabled - } - - Tooltip.prototype.toggle = function (e) { - var self = e ? $(e.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type) : this - self.tip().hasClass('in') ? self.leave(self) : self.enter(self) - } - - Tooltip.prototype.destroy = function () { - this.hide().$element.off('.' + this.type).removeData('bs.' + this.type) - } - - - // TOOLTIP PLUGIN DEFINITION - // ========================= - - var old = $.fn.tooltip - - $.fn.tooltip = function (option) { - return this.each(function () { - var $this = $(this) - var data = $this.data('bs.tooltip') - var options = typeof option == 'object' && option - - if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options))) - if (typeof option == 'string') data[option]() - }) - } - - $.fn.tooltip.Constructor = Tooltip - - - // TOOLTIP NO CONFLICT - // =================== - - $.fn.tooltip.noConflict = function () { - $.fn.tooltip = old - return this - } - -}(window.jQuery); - -/* ======================================================================== - * Bootstrap: popover.js v3.0.0 - * http://twbs.github.com/bootstrap/javascript.html#popovers - * ======================================================================== - * Copyright 2012 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ======================================================================== */ - - -+function ($) { "use strict"; - - // POPOVER PUBLIC CLASS DEFINITION - // =============================== - - var Popover = function (element, options) { - this.init('popover', element, options) - } - - if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js') - - Popover.DEFAULTS = $.extend({} , $.fn.tooltip.Constructor.DEFAULTS, { - placement: 'right' - , trigger: 'click' - , content: '' - , template: '<div class="popover"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>' - }) - - - // NOTE: POPOVER EXTENDS tooltip.js - // ================================ - - Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype) - - Popover.prototype.constructor = Popover - - Popover.prototype.getDefaults = function () { - return Popover.DEFAULTS - } - - Popover.prototype.setContent = function () { - var $tip = this.tip() - var title = this.getTitle() - var content = this.getContent() - - $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title) - $tip.find('.popover-content')[this.options.html ? 'html' : 'text'](content) - - $tip.removeClass('fade top bottom left right in') - - // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do - // this manually by checking the contents. - if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide() - } - - Popover.prototype.hasContent = function () { - return this.getTitle() || this.getContent() - } - - Popover.prototype.getContent = function () { - var $e = this.$element - var o = this.options - - return $e.attr('data-content') - || (typeof o.content == 'function' ? - o.content.call($e[0]) : - o.content) - } - - Popover.prototype.arrow = function () { - return this.$arrow = this.$arrow || this.tip().find('.arrow') - } - - Popover.prototype.tip = function () { - if (!this.$tip) this.$tip = $(this.options.template) - return this.$tip - } - - - // POPOVER PLUGIN DEFINITION - // ========================= - - var old = $.fn.popover - - $.fn.popover = function (option) { - return this.each(function () { - var $this = $(this) - var data = $this.data('bs.popover') - var options = typeof option == 'object' && option - - if (!data) $this.data('bs.popover', (data = new Popover(this, options))) - if (typeof option == 'string') data[option]() - }) - } - - $.fn.popover.Constructor = Popover - - - // POPOVER NO CONFLICT - // =================== - - $.fn.popover.noConflict = function () { - $.fn.popover = old - return this - } - -}(window.jQuery); - -/* ======================================================================== - * Bootstrap: scrollspy.js v3.0.0 - * http://twbs.github.com/bootstrap/javascript.html#scrollspy - * ======================================================================== - * Copyright 2012 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ======================================================================== */ - - -+function ($) { "use strict"; - - // SCROLLSPY CLASS DEFINITION - // ========================== - - function ScrollSpy(element, options) { - var href - var process = $.proxy(this.process, this) - - this.$element = $(element).is('body') ? $(window) : $(element) - this.$body = $('body') - this.$scrollElement = this.$element.on('scroll.bs.scroll-spy.data-api', process) - this.options = $.extend({}, ScrollSpy.DEFAULTS, options) - this.selector = (this.options.target - || ((href = $(element).attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7 - || '') + ' .nav li > a' - this.offsets = $([]) - this.targets = $([]) - this.activeTarget = null - - this.refresh() - this.process() - } - - ScrollSpy.DEFAULTS = { - offset: 10 - } - - ScrollSpy.prototype.refresh = function () { - var offsetMethod = this.$element[0] == window ? 'offset' : 'position' - - this.offsets = $([]) - this.targets = $([]) - - var self = this - var $targets = this.$body - .find(this.selector) - .map(function () { - var $el = $(this) - var href = $el.data('target') || $el.attr('href') - var $href = /^#\w/.test(href) && $(href) - - return ($href - && $href.length - && [[ $href[offsetMethod]().top + (!$.isWindow(self.$scrollElement.get(0)) && self.$scrollElement.scrollTop()), href ]]) || null - }) - .sort(function (a, b) { return a[0] - b[0] }) - .each(function () { - self.offsets.push(this[0]) - self.targets.push(this[1]) - }) - } - - ScrollSpy.prototype.process = function () { - var scrollTop = this.$scrollElement.scrollTop() + this.options.offset - var scrollHeight = this.$scrollElement[0].scrollHeight || this.$body[0].scrollHeight - var maxScroll = scrollHeight - this.$scrollElement.height() - var offsets = this.offsets - var targets = this.targets - var activeTarget = this.activeTarget - var i - - if (scrollTop >= maxScroll) { - return activeTarget != (i = targets.last()[0]) && this.activate(i) - } - - for (i = offsets.length; i--;) { - activeTarget != targets[i] - && scrollTop >= offsets[i] - && (!offsets[i + 1] || scrollTop <= offsets[i + 1]) - && this.activate( targets[i] ) - } - } - - ScrollSpy.prototype.activate = function (target) { - this.activeTarget = target - - $(this.selector) - .parents('.active') - .removeClass('active') - - var selector = this.selector - + '[data-target="' + target + '"],' - + this.selector + '[href="' + target + '"]' - - var active = $(selector) - .parents('li') - .addClass('active') - - if (active.parent('.dropdown-menu').length) { - active = active - .closest('li.dropdown') - .addClass('active') - } - - active.trigger('activate') - } - - - // SCROLLSPY PLUGIN DEFINITION - // =========================== - - var old = $.fn.scrollspy - - $.fn.scrollspy = function (option) { - return this.each(function () { - var $this = $(this) - var data = $this.data('bs.scrollspy') - var options = typeof option == 'object' && option - - if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options))) - if (typeof option == 'string') data[option]() - }) - } - - $.fn.scrollspy.Constructor = ScrollSpy - - - // SCROLLSPY NO CONFLICT - // ===================== - - $.fn.scrollspy.noConflict = function () { - $.fn.scrollspy = old - return this - } - - - // SCROLLSPY DATA-API - // ================== - - $(window).on('load', function () { - $('[data-spy="scroll"]').each(function () { - var $spy = $(this) - $spy.scrollspy($spy.data()) - }) - }) - -}(window.jQuery); - -/* ======================================================================== - * Bootstrap: tab.js v3.0.0 - * http://twbs.github.com/bootstrap/javascript.html#tabs - * ======================================================================== - * Copyright 2012 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ======================================================================== */ - - -+function ($) { "use strict"; - - // TAB CLASS DEFINITION - // ==================== - - var Tab = function (element) { - this.element = $(element) - } - - Tab.prototype.show = function () { - var $this = this.element - var $ul = $this.closest('ul:not(.dropdown-menu)') - var selector = $this.attr('data-target') - - if (!selector) { - selector = $this.attr('href') - selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 - } - - if ($this.parent('li').hasClass('active')) return - - var previous = $ul.find('.active:last a')[0] - var e = $.Event('show.bs.tab', { - relatedTarget: previous - }) - - $this.trigger(e) - - if (e.isDefaultPrevented()) return - - var $target = $(selector) - - this.activate($this.parent('li'), $ul) - this.activate($target, $target.parent(), function () { - $this.trigger({ - type: 'shown.bs.tab' - , relatedTarget: previous - }) - }) - } - - Tab.prototype.activate = function (element, container, callback) { - var $active = container.find('> .active') - var transition = callback - && $.support.transition - && $active.hasClass('fade') - - function next() { - $active - .removeClass('active') - .find('> .dropdown-menu > .active') - .removeClass('active') - - element.addClass('active') - - if (transition) { - element[0].offsetWidth // reflow for transition - element.addClass('in') - } else { - element.removeClass('fade') - } - - if (element.parent('.dropdown-menu')) { - element.closest('li.dropdown').addClass('active') - } - - callback && callback() - } - - transition ? - $active - .one($.support.transition.end, next) - .emulateTransitionEnd(150) : - next() - - $active.removeClass('in') - } - - - // TAB PLUGIN DEFINITION - // ===================== - - var old = $.fn.tab - - $.fn.tab = function ( option ) { - return this.each(function () { - var $this = $(this) - var data = $this.data('bs.tab') - - if (!data) $this.data('bs.tab', (data = new Tab(this))) - if (typeof option == 'string') data[option]() - }) - } - - $.fn.tab.Constructor = Tab - - - // TAB NO CONFLICT - // =============== - - $.fn.tab.noConflict = function () { - $.fn.tab = old - return this - } - - - // TAB DATA-API - // ============ - - $(document).on('click.bs.tab.data-api', '[data-toggle="tab"], [data-toggle="pill"]', function (e) { - e.preventDefault() - $(this).tab('show') - }) - -}(window.jQuery); - -/* ======================================================================== - * Bootstrap: affix.js v3.0.0 - * http://twbs.github.com/bootstrap/javascript.html#affix - * ======================================================================== - * Copyright 2012 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ======================================================================== */ - - -+function ($) { "use strict"; - - // AFFIX CLASS DEFINITION - // ====================== - - var Affix = function (element, options) { - this.options = $.extend({}, Affix.DEFAULTS, options) - this.$window = $(window) - .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this)) - .on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this)) - - this.$element = $(element) - this.affixed = - this.unpin = null - - this.checkPosition() - } - - Affix.RESET = 'affix affix-top affix-bottom' - - Affix.DEFAULTS = { - offset: 0 - } - - Affix.prototype.checkPositionWithEventLoop = function () { - setTimeout($.proxy(this.checkPosition, this), 1) - } - - Affix.prototype.checkPosition = function () { - if (!this.$element.is(':visible')) return - - var scrollHeight = $(document).height() - var scrollTop = this.$window.scrollTop() - var position = this.$element.offset() - var offset = this.options.offset - var offsetTop = offset.top - var offsetBottom = offset.bottom - - if (typeof offset != 'object') offsetBottom = offsetTop = offset - if (typeof offsetTop == 'function') offsetTop = offset.top() - if (typeof offsetBottom == 'function') offsetBottom = offset.bottom() - - var affix = this.unpin != null && (scrollTop + this.unpin <= position.top) ? false : - offsetBottom != null && (position.top + this.$element.height() >= scrollHeight - offsetBottom) ? 'bottom' : - offsetTop != null && (scrollTop <= offsetTop) ? 'top' : false - - if (this.affixed === affix) return - if (this.unpin) this.$element.css('top', '') - - this.affixed = affix - this.unpin = affix == 'bottom' ? position.top - scrollTop : null - - this.$element.removeClass(Affix.RESET).addClass('affix' + (affix ? '-' + affix : '')) - - if (affix == 'bottom') { - this.$element.offset({ top: document.body.offsetHeight - offsetBottom - this.$element.height() }) - } - } - - - // AFFIX PLUGIN DEFINITION - // ======================= - - var old = $.fn.affix - - $.fn.affix = function (option) { - return this.each(function () { - var $this = $(this) - var data = $this.data('bs.affix') - var options = typeof option == 'object' && option - - if (!data) $this.data('bs.affix', (data = new Affix(this, options))) - if (typeof option == 'string') data[option]() - }) - } - - $.fn.affix.Constructor = Affix - - - // AFFIX NO CONFLICT - // ================= - - $.fn.affix.noConflict = function () { - $.fn.affix = old - return this - } - - - // AFFIX DATA-API - // ============== - - $(window).on('load', function () { - $('[data-spy="affix"]').each(function () { - var $spy = $(this) - var data = $spy.data() - - data.offset = data.offset || {} - - if (data.offsetBottom) data.offset.bottom = data.offsetBottom - if (data.offsetTop) data.offset.top = data.offsetTop - - $spy.affix(data) - }) - }) - -}(window.jQuery); diff --git a/framework/yii/caching/ApcCache.php b/framework/yii/caching/ApcCache.php index 6c2754a..8a0d207 100644 --- a/framework/yii/caching/ApcCache.php +++ b/framework/yii/caching/ApcCache.php @@ -72,6 +72,17 @@ class ApcCache extends Cache } /** + * Stores multiple key-value pairs in cache. + * @param array $data array where key corresponds to cache key while value + * @param integer $expire the number of seconds in which the cached values will expire. 0 means never expire. + * @return array array of failed keys + */ + protected function setValues($data, $expire) + { + return array_keys(apc_store($data, null, $expire)); + } + + /** * Stores a value identified by a key into cache if the cache does not contain this key. * This is the implementation of the method declared in the parent class. * @param string $key the key identifying the value to be cached @@ -85,6 +96,17 @@ class ApcCache extends Cache } /** + * Adds multiple key-value pairs to cache. + * @param array $data array where key corresponds to cache key while value is the value stored + * @param integer $expire the number of seconds in which the cached values will expire. 0 means never expire. + * @return array array of failed keys + */ + protected function addValues($data, $expire) + { + return array_keys(apc_add($data, null, $expire)); + } + + /** * Deletes a value with the specified key from cache * This is the implementation of the method declared in the parent class. * @param string $key the key of the value to be deleted @@ -102,6 +124,10 @@ class ApcCache extends Cache */ protected function flushValues() { - return apc_clear_cache('user'); + if (extension_loaded('apcu')) { + return apc_clear_cache(); + } else { + return apc_clear_cache('user'); + } } } diff --git a/framework/yii/caching/Cache.php b/framework/yii/caching/Cache.php index 56c6d96..41a25b2 100644 --- a/framework/yii/caching/Cache.php +++ b/framework/yii/caching/Cache.php @@ -93,7 +93,7 @@ abstract class Cache extends Component implements \ArrayAccess * If the given key is a string containing alphanumeric characters only and no more than 32 characters, * then the key will be returned back prefixed with [[keyPrefix]]. Otherwise, a normalized key * is generated by serializing the given key, applying MD5 hashing, and prefixing with [[keyPrefix]]. - * + * * @param mixed $key the key to be normalized * @return string the generated cache key */ @@ -163,12 +163,12 @@ abstract class Cache extends Component implements \ArrayAccess */ public function mget($keys) { - $keyMap = array(); + $keyMap = []; foreach ($keys as $key) { $keyMap[$key] = $this->buildKey($key); } $values = $this->getValues(array_values($keyMap)); - $results = array(); + $results = []; foreach ($keyMap as $key => $newKey) { $results[$key] = false; if (isset($values[$newKey])) { @@ -192,7 +192,7 @@ abstract class Cache extends Component implements \ArrayAccess * If the cache already contains such a key, the existing value and * expiration time will be replaced with the new ones, respectively. * - * @param mixed $key a key identifying the value to be cached value. This can be a simple string or + * @param mixed $key a key identifying the value to be cached. This can be a simple string or * a complex data structure consisting of factors representing the key. * @param mixed $value the value to be cached * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire. @@ -207,18 +207,81 @@ abstract class Cache extends Component implements \ArrayAccess $dependency->evaluateDependency($this); } if ($this->serializer === null) { - $value = serialize(array($value, $dependency)); + $value = serialize([$value, $dependency]); } elseif ($this->serializer !== false) { - $value = call_user_func($this->serializer[0], array($value, $dependency)); + $value = call_user_func($this->serializer[0], [$value, $dependency]); } $key = $this->buildKey($key); return $this->setValue($key, $value, $expire); } /** + * Stores multiple items in cache. Each item contains a value identified by a key. + * If the cache already contains such a key, the existing value and + * expiration time will be replaced with the new ones, respectively. + * + * @param array $items the items to be cached, as key-value pairs. + * @param integer $expire default number of seconds in which the cached values will expire. 0 means never expire. + * @param Dependency $dependency dependency of the cached items. If the dependency changes, + * the corresponding values in the cache will be invalidated when it is fetched via [[get()]]. + * This parameter is ignored if [[serializer]] is false. + * @return boolean whether the items are successfully stored into cache + */ + public function mset($items, $expire = 0, $dependency = null) + { + if ($dependency !== null && $this->serializer !== false) { + $dependency->evaluateDependency($this); + } + + $data = []; + foreach ($items as $key => $value) { + $itemKey = $this->buildKey($key); + if ($this->serializer === null) { + $itemValue = serialize([$value, $dependency]); + } elseif ($this->serializer !== false) { + $itemValue = call_user_func($this->serializer[0], [$value, $dependency]); + } + + $data[$itemKey] = $itemValue; + } + return $this->setValues($data, $expire); + } + + /** + * Stores multiple items in cache. Each item contains a value identified by a key. + * If the cache already contains such a key, the existing value and expiration time will be preserved. + * + * @param array $items the items to be cached, as key-value pairs. + * @param integer $expire default number of seconds in which the cached values will expire. 0 means never expire. + * @param Dependency $dependency dependency of the cached items. If the dependency changes, + * the corresponding values in the cache will be invalidated when it is fetched via [[get()]]. + * This parameter is ignored if [[serializer]] is false. + * @return boolean whether the items are successfully stored into cache + */ + public function madd($items, $expire = 0, $dependency = null) + { + if ($dependency !== null && $this->serializer !== false) { + $dependency->evaluateDependency($this); + } + + $data = []; + foreach ($items as $key => $value) { + $itemKey = $this->buildKey($key); + if ($this->serializer === null) { + $itemValue = serialize([$value, $dependency]); + } elseif ($this->serializer !== false) { + $itemValue = call_user_func($this->serializer[0], [$value, $dependency]); + } + + $data[$itemKey] = $itemValue; + } + return $this->addValues($data, $expire); + } + + /** * Stores a value identified by a key into cache if the cache does not contain this key. * Nothing will be done if the cache already contains the key. - * @param mixed $key a key identifying the value to be cached value. This can be a simple string or + * @param mixed $key a key identifying the value to be cached. This can be a simple string or * a complex data structure consisting of factors representing the key. * @param mixed $value the value to be cached * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire. @@ -233,9 +296,9 @@ abstract class Cache extends Component implements \ArrayAccess $dependency->evaluateDependency($this); } if ($this->serializer === null) { - $value = serialize(array($value, $dependency)); + $value = serialize([$value, $dependency]); } elseif ($this->serializer !== false) { - $value = call_user_func($this->serializer[0], array($value, $dependency)); + $value = call_user_func($this->serializer[0], [$value, $dependency]); } $key = $this->buildKey($key); return $this->addValue($key, $value, $expire); @@ -319,7 +382,7 @@ abstract class Cache extends Component implements \ArrayAccess */ protected function getValues($keys) { - $results = array(); + $results = []; foreach ($keys as $key) { $results[$key] = $this->getValue($key); } @@ -327,6 +390,46 @@ abstract class Cache extends Component implements \ArrayAccess } /** + * Stores multiple key-value pairs in cache. + * The default implementation calls [[setValue()]] multiple times store values one by one. If the underlying cache + * storage supports multiset, this method should be overridden to exploit that feature. + * @param array $data array where key corresponds to cache key while value is the value stored + * @param integer $expire the number of seconds in which the cached values will expire. 0 means never expire. + * @return array array of failed keys + */ + protected function setValues($data, $expire) + { + $failedKeys = []; + foreach ($data as $key => $value) + { + if ($this->setValue($key, $value, $expire) === false) { + $failedKeys[] = $key; + } + } + return $failedKeys; + } + + /** + * Adds multiple key-value pairs to cache. + * The default implementation calls [[addValue()]] multiple times add values one by one. If the underlying cache + * storage supports multiadd, this method should be overridden to exploit that feature. + * @param array $data array where key corresponds to cache key while value is the value stored + * @param integer $expire the number of seconds in which the cached values will expire. 0 means never expire. + * @return array array of failed keys + */ + protected function addValues($data, $expire) + { + $failedKeys = []; + foreach ($data as $key => $value) + { + if ($this->addValue($key, $value, $expire) === false) { + $failedKeys[] = $key; + } + } + return $failedKeys; + } + + /** * Returns whether there is a cache entry with a specified key. * This method is required by the interface ArrayAccess. * @param string $key a key identifying the cached value diff --git a/framework/yii/caching/ChainedDependency.php b/framework/yii/caching/ChainedDependency.php index d3b9953..f8bfee3 100644 --- a/framework/yii/caching/ChainedDependency.php +++ b/framework/yii/caching/ChainedDependency.php @@ -23,7 +23,7 @@ class ChainedDependency extends Dependency * @var Dependency[] list of dependencies that this dependency is composed of. * Each array element must be a dependency object. */ - public $dependencies; + public $dependencies = []; /** * @var boolean whether this dependency is depending on every dependency in [[dependencies]]. * Defaults to true, meaning if any of the dependencies has changed, this dependency is considered changed. @@ -33,18 +33,6 @@ class ChainedDependency extends Dependency public $dependOnAll = true; /** - * Constructor. - * @param Dependency[] $dependencies list of dependencies that this dependency is composed of. - * Each array element should be a dependency object. - * @param array $config name-value pairs that will be used to initialize the object properties - */ - public function __construct($dependencies = array(), $config = array()) - { - $this->dependencies = $dependencies; - parent::__construct($config); - } - - /** * Evaluates the dependency by generating and saving the data related with dependency. * @param Cache $cache the cache component that is currently evaluating this dependency */ diff --git a/framework/yii/caching/DbCache.php b/framework/yii/caching/DbCache.php index b7b6692..cca2c55 100644 --- a/framework/yii/caching/DbCache.php +++ b/framework/yii/caching/DbCache.php @@ -23,11 +23,11 @@ use yii\db\Query; * The following example shows how you can configure the application to use DbCache: * * ~~~ - * 'cache' => array( + * 'cache' => [ * 'class' => 'yii\caching\DbCache', * // 'db' => 'mydb', * // 'cacheTable' => 'my_cache', - * ) + * ] * ~~~ * * @author Qiang Xue <qiang.xue@gmail.com> @@ -103,9 +103,9 @@ class DbCache extends Cache $key = $this->buildKey($key); $query = new Query; - $query->select(array('COUNT(*)')) + $query->select(['COUNT(*)']) ->from($this->cacheTable) - ->where('[[id]] = :id AND ([[expire]] = 0 OR [[expire]] >' . time() . ')', array(':id' => $key)); + ->where('[[id]] = :id AND ([[expire]] = 0 OR [[expire]] >' . time() . ')', [':id' => $key]); if ($this->db->enableQueryCache) { // temporarily disable and re-enable query caching $this->db->enableQueryCache = false; @@ -126,9 +126,9 @@ class DbCache extends Cache protected function getValue($key) { $query = new Query; - $query->select(array('data')) + $query->select(['data']) ->from($this->cacheTable) - ->where('[[id]] = :id AND ([[expire]] = 0 OR [[expire]] >' . time() . ')', array(':id' => $key)); + ->where('[[id]] = :id AND ([[expire]] = 0 OR [[expire]] >' . time() . ')', [':id' => $key]); if ($this->db->enableQueryCache) { // temporarily disable and re-enable query caching $this->db->enableQueryCache = false; @@ -148,12 +148,12 @@ class DbCache extends Cache protected function getValues($keys) { if (empty($keys)) { - return array(); + return []; } $query = new Query; - $query->select(array('id', 'data')) + $query->select(['id', 'data']) ->from($this->cacheTable) - ->where(array('id' => $keys)) + ->where(['id' => $keys]) ->andWhere('([[expire]] = 0 OR [[expire]] > ' . time() . ')'); if ($this->db->enableQueryCache) { @@ -164,7 +164,7 @@ class DbCache extends Cache $rows = $query->createCommand($this->db)->queryAll(); } - $results = array(); + $results = []; foreach ($keys as $key) { $results[$key] = false; } @@ -186,12 +186,10 @@ class DbCache extends Cache protected function setValue($key, $value, $expire) { $command = $this->db->createCommand() - ->update($this->cacheTable, array( + ->update($this->cacheTable, [ 'expire' => $expire > 0 ? $expire + time() : 0, - 'data' => array($value, \PDO::PARAM_LOB), - ), array( - 'id' => $key, - )); + 'data' => [$value, \PDO::PARAM_LOB], + ], ['id' => $key]); if ($command->execute()) { $this->gc(); @@ -222,11 +220,11 @@ class DbCache extends Cache try { $this->db->createCommand() - ->insert($this->cacheTable, array( + ->insert($this->cacheTable, [ 'id' => $key, 'expire' => $expire, - 'data' => array($value, \PDO::PARAM_LOB), - ))->execute(); + 'data' => [$value, \PDO::PARAM_LOB], + ])->execute(); return true; } catch (\Exception $e) { return false; @@ -242,7 +240,7 @@ class DbCache extends Cache protected function deleteValue($key) { $this->db->createCommand() - ->delete($this->cacheTable, array('id' => $key)) + ->delete($this->cacheTable, ['id' => $key]) ->execute(); return true; } diff --git a/framework/yii/caching/DbDependency.php b/framework/yii/caching/DbDependency.php index 0b559d7..ac6f2e7 100644 --- a/framework/yii/caching/DbDependency.php +++ b/framework/yii/caching/DbDependency.php @@ -34,20 +34,7 @@ class DbDependency extends Dependency /** * @var array the parameters (name => value) to be bound to the SQL statement specified by [[sql]]. */ - public $params; - - /** - * Constructor. - * @param string $sql the SQL query whose result is used to determine if the dependency has been changed. - * @param array $params the parameters (name => value) to be bound to the SQL statement specified by [[sql]]. - * @param array $config name-value pairs that will be used to initialize the object properties - */ - public function __construct($sql, $params = array(), $config = array()) - { - $this->sql = $sql; - $this->params = $params; - parent::__construct($config); - } + public $params = []; /** * Generates the data needed to determine if dependency has been changed. @@ -62,6 +49,9 @@ class DbDependency extends Dependency if (!$db instanceof Connection) { throw new InvalidConfigException("DbDependency::db must be the application component ID of a DB connection."); } + if ($this->sql === null) { + throw new InvalidConfigException("DbDependency::sql must be set."); + } if ($db->enableQueryCache) { // temporarily disable and re-enable query caching diff --git a/framework/yii/caching/Dependency.php b/framework/yii/caching/Dependency.php index 91c0568..c84f72d 100644 --- a/framework/yii/caching/Dependency.php +++ b/framework/yii/caching/Dependency.php @@ -34,7 +34,7 @@ abstract class Dependency extends \yii\base\Object /** * @var array static storage of cached data for reusable dependencies. */ - private static $_reusableData = array(); + private static $_reusableData = []; /** * @var string a unique hash value for this cache dependency. */ @@ -86,7 +86,7 @@ abstract class Dependency extends \yii\base\Object */ public static function resetReusableData() { - self::$_reusableData = array(); + self::$_reusableData = []; } /** diff --git a/framework/yii/caching/ExpressionDependency.php b/framework/yii/caching/ExpressionDependency.php index 533c2ab..f6642b4 100644 --- a/framework/yii/caching/ExpressionDependency.php +++ b/framework/yii/caching/ExpressionDependency.php @@ -27,7 +27,7 @@ class ExpressionDependency extends Dependency * A PHP expression can be any PHP code that evaluates to 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; + public $expression = 'true'; /** * @var mixed custom parameters associated with this dependency. You may get the value * of this property in [[expression]] using `$this->params`. @@ -35,19 +35,6 @@ class ExpressionDependency extends Dependency public $params; /** - * Constructor. - * @param string $expression the PHP expression whose result is used to determine the dependency. - * @param mixed $params the custom parameters associated with this dependency - * @param array $config name-value pairs that will be used to initialize the object properties - */ - public function __construct($expression = 'true', $params = null, $config = array()) - { - $this->expression = $expression; - $this->params = $params; - parent::__construct($config); - } - - /** * Generates the data needed to determine if dependency has been changed. * This method returns the result of the PHP expression. * @param Cache $cache the cache component that is currently evaluating this dependency diff --git a/framework/yii/caching/FileDependency.php b/framework/yii/caching/FileDependency.php index bcc48a8..11afde3 100644 --- a/framework/yii/caching/FileDependency.php +++ b/framework/yii/caching/FileDependency.php @@ -6,6 +6,7 @@ */ namespace yii\caching; +use yii\base\InvalidConfigException; /** * FileDependency represents a dependency based on a file's last modification time. @@ -25,24 +26,17 @@ class FileDependency extends Dependency public $fileName; /** - * Constructor. - * @param string $fileName name of the file whose change is to be checked. - * @param array $config name-value pairs that will be used to initialize the object properties - */ - public function __construct($fileName = null, $config = array()) - { - $this->fileName = $fileName; - parent::__construct($config); - } - - /** * Generates the data needed to determine if dependency has been changed. * This method returns the file's last modification time. * @param Cache $cache the cache component that is currently evaluating this dependency * @return mixed the data needed to determine if dependency has been changed. + * @throws InvalidConfigException if [[fileName]] is not set */ protected function generateDependencyData($cache) { + if ($this->fileName === null) { + throw new InvalidConfigException('FileDependency::fileName must be set'); + } return @filemtime($this->fileName); } } diff --git a/framework/yii/caching/GroupDependency.php b/framework/yii/caching/GroupDependency.php index d63cee4..1cf7869 100644 --- a/framework/yii/caching/GroupDependency.php +++ b/framework/yii/caching/GroupDependency.php @@ -6,6 +6,7 @@ */ namespace yii\caching; +use yii\base\InvalidConfigException; /** * GroupDependency marks a cached data item with a group name. @@ -19,30 +20,23 @@ namespace yii\caching; class GroupDependency extends Dependency { /** - * @var string the group name + * @var string the group name. This property must be set. */ public $group; /** - * Constructor. - * @param string $group the group name - * @param array $config name-value pairs that will be used to initialize the object properties - */ - public function __construct($group, $config = array()) - { - $this->group = $group; - parent::__construct($config); - } - - /** * Generates the data needed to determine if dependency has been changed. * This method does nothing in this class. * @param Cache $cache the cache component that is currently evaluating this dependency * @return mixed the data needed to determine if dependency has been changed. + * @throws InvalidConfigException if [[group]] is not set. */ protected function generateDependencyData($cache) { - $version = $cache->get(array(__CLASS__, $this->group)); + if ($this->group === null) { + throw new InvalidConfigException('GroupDependency::group must be set'); + } + $version = $cache->get([__CLASS__, $this->group]); if ($version === false) { $version = $this->invalidate($cache, $this->group); } @@ -53,10 +47,14 @@ class GroupDependency extends Dependency * Performs the actual dependency checking. * @param Cache $cache the cache component that is currently evaluating this dependency * @return boolean whether the dependency is changed or not. + * @throws InvalidConfigException if [[group]] is not set. */ public function getHasChanged($cache) { - $version = $cache->get(array(__CLASS__, $this->group)); + if ($this->group === null) { + throw new InvalidConfigException('GroupDependency::group must be set'); + } + $version = $cache->get([__CLASS__, $this->group]); return $version === false || $version !== $this->data; } @@ -69,7 +67,7 @@ class GroupDependency extends Dependency public static function invalidate($cache, $group) { $version = microtime(); - $cache->set(array(__CLASS__, $group), $version); + $cache->set([__CLASS__, $group], $version); return $version; } } diff --git a/framework/yii/caching/MemCache.php b/framework/yii/caching/MemCache.php index 69a90b4..3391796 100644 --- a/framework/yii/caching/MemCache.php +++ b/framework/yii/caching/MemCache.php @@ -28,25 +28,25 @@ use yii\base\InvalidConfigException; * To use MemCache as the cache application component, configure the application as follows, * * ~~~ - * array( - * 'components' => array( - * 'cache' => array( - * 'class' => 'MemCache', - * 'servers' => array( - * array( + * [ + * 'components' => [ + * 'cache' => [ + * 'class' => 'yii\caching\MemCache', + * 'servers' => [ + * [ * 'host' => 'server1', * 'port' => 11211, * 'weight' => 60, - * ), - * array( + * ], + * [ * 'host' => 'server2', * 'port' => 11211, * 'weight' => 40, - * ), - * ), - * ), - * ), - * ) + * ], + * ], + * ], + * ], + * ] * ~~~ * * In the above, two memcache servers are used: server1 and server2. You can configure more properties of @@ -76,7 +76,7 @@ class MemCache extends Cache /** * @var array list of memcache server configurations */ - private $_servers = array(); + private $_servers = []; /** * Initializes this application component. @@ -202,6 +202,27 @@ class MemCache extends Cache } /** + * Stores multiple key-value pairs in cache. + * @param array $data array where key corresponds to cache key while value is the value stored + * @param integer $expire the number of seconds in which the cached values will expire. 0 means never expire. + * @return array array of failed keys. Always empty in case of using memcached. + */ + protected function setValues($data, $expire) + { + if ($this->useMemcached) { + if ($expire > 0) { + $expire += time(); + } else { + $expire = 0; + } + $this->_cache->setMulti($data, $expire); + return []; + } else { + return parent::setValues($data, $expire); + } + } + + /** * Stores a value identified by a key into cache if the cache does not contain this key. * This is the implementation of the method declared in the parent class. * diff --git a/framework/yii/caching/RedisCache.php b/framework/yii/caching/RedisCache.php index 5c778fc..b64f000 100644 --- a/framework/yii/caching/RedisCache.php +++ b/framework/yii/caching/RedisCache.php @@ -10,7 +10,7 @@ namespace yii\caching; use yii\redis\Connection; /** - * RedisCache implements a cache application component based on [redis](http://redis.io/). + * RedisCache implements a cache application component based on [redis](http://redis.io/) version 2.6.12 or higher. * * RedisCache needs to be configured with [[hostname]], [[port]] and [[database]] of the server * to connect to. By default RedisCache assumes there is a redis server running on localhost at @@ -27,16 +27,16 @@ use yii\redis\Connection; * To use RedisCache as the cache application component, configure the application as follows, * * ~~~ - * array( - * 'components'=>array( - * 'cache'=>array( - * 'class'=>'RedisCache', - * 'hostname'=>'localhost', - * 'port'=>6379, - * 'database'=>0, - * ), - * ), - * ) + * [ + * 'components' => [ + * 'cache' => [ + * 'class' => 'RedisCache', + * 'hostname' => 'localhost', + * 'port' => 6379, + * 'database' => 0, + * ], + * ], + * ] * ~~~ * * @property Connection $connection The redis connection object. This property is read-only. @@ -93,12 +93,12 @@ class RedisCache extends Cache public function getConnection() { if ($this->_connection === null) { - $this->_connection = new Connection(array( + $this->_connection = new Connection([ 'dsn' => 'redis://' . $this->hostname . ':' . $this->port . '/' . $this->database, 'password' => $this->password, 'connectionTimeout' => $this->connectionTimeout, 'dataTimeout' => $this->dataTimeout, - )); + ]); } return $this->_connection; } @@ -115,96 +115,101 @@ class RedisCache extends Cache */ public function exists($key) { - return (bool) $this->_connection->executeCommand('EXISTS', array($this->buildKey($key))); + return (bool) $this->_connection->executeCommand('EXISTS', [$this->buildKey($key)]); } /** - * Retrieves a value from cache with a specified key. - * This is the implementation of the method declared in the parent class. - * @param string $key a unique key identifying the cached value - * @return string|boolean the value stored in cache, false if the value is not in the cache or expired. + * @inheritDocs */ protected function getValue($key) { - return $this->_connection->executeCommand('GET', array($key)); + return $this->_connection->executeCommand('GET', [$key]); } /** - * Retrieves multiple values from cache with the specified keys. - * @param array $keys a list of keys identifying the cached values - * @return array a list of cached values indexed by the keys + * @inheritDocs */ protected function getValues($keys) { $response = $this->_connection->executeCommand('MGET', $keys); - $result = array(); + $result = []; $i = 0; - foreach($keys as $key) { + foreach ($keys as $key) { $result[$key] = $response[$i++]; } return $result; } /** - * Stores a value identified by a key in cache. - * This is the implementation of the method declared in the parent class. - * - * @param string $key the key identifying the value to be cached - * @param string $value the value to be cached - * @param float $expire the number of seconds in which the cached value will expire. 0 means never expire. - * This can be a floating point number to specify the time in milliseconds. - * @return boolean true if the value is successfully stored into cache, false otherwise + * @inheritDocs */ - protected function setValue($key,$value,$expire) + protected function setValue($key, $value, $expire) { if ($expire == 0) { - return (bool) $this->_connection->executeCommand('SET', array($key, $value)); + return (bool) $this->_connection->executeCommand('SET', [$key, $value]); } else { $expire = (int) ($expire * 1000); - return (bool) $this->_connection->executeCommand('PSETEX', array($key, $expire, $value)); + return (bool) $this->_connection->executeCommand('SET', [$key, $value, 'PX', $expire]); } } /** - * Stores a value identified by a key into cache if the cache does not contain this key. - * This is the implementation of the method declared in the parent class. - * - * @param string $key the key identifying the value to be cached - * @param string $value the value to be cached - * @param float $expire the number of seconds in which the cached value will expire. 0 means never expire. - * This can be a floating point number to specify the time in milliseconds. - * @return boolean true if the value is successfully stored into cache, false otherwise + * @inheritDocs */ - protected function addValue($key,$value,$expire) + protected function setValues($data, $expire) { + $args = []; + foreach($data as $key => $value) { + $args[] = $key; + $args[] = $value; + } + + $failedKeys = []; if ($expire == 0) { - return (bool) $this->_connection->executeCommand('SETNX', array($key, $value)); + $this->_connection->executeCommand('MSET', $args); } else { - // TODO consider requiring redis version >= 2.6.12 that supports this in one command $expire = (int) ($expire * 1000); $this->_connection->executeCommand('MULTI'); - $this->_connection->executeCommand('SETNX', array($key, $value)); - $this->_connection->executeCommand('PEXPIRE', array($key, $expire)); - $response = $this->_connection->executeCommand('EXEC'); - return (bool) $response[0]; + $this->_connection->executeCommand('MSET', $args); + $index = []; + foreach ($data as $key => $value) { + $this->_connection->executeCommand('PEXPIRE', [$key, $expire]); + $index[] = $key; + } + $result = $this->_connection->executeCommand('EXEC'); + array_shift($result); + foreach($result as $i => $r) { + if ($r != 1) { + $failedKeys[] = $index[$i]; + } + } + } + return $failedKeys; + } + + /** + * @inheritDocs + */ + protected function addValue($key, $value, $expire) + { + if ($expire == 0) { + return (bool) $this->_connection->executeCommand('SET', [$key, $value, 'NX']); + } else { + $expire = (int) ($expire * 1000); + return (bool) $this->_connection->executeCommand('SET', [$key, $value, 'PX', $expire, 'NX']); } } /** - * Deletes a value with the specified key from cache - * This is the implementation of the method declared in the parent class. - * @param string $key the key of the value to be deleted - * @return boolean if no error happens during deletion + * @inheritDocs */ protected function deleteValue($key) { - return (bool) $this->_connection->executeCommand('DEL', array($key)); + return (bool) $this->_connection->executeCommand('DEL', [$key]); } /** - * Deletes all values from cache. - * This is the implementation of the method declared in the parent class. - * @return boolean whether the flush operation was successful. + * @inheritDocs */ protected function flushValues() { diff --git a/framework/yii/caching/WinCache.php b/framework/yii/caching/WinCache.php index 3679884..7f1eca8 100644 --- a/framework/yii/caching/WinCache.php +++ b/framework/yii/caching/WinCache.php @@ -72,6 +72,17 @@ class WinCache extends Cache } /** + * Stores multiple key-value pairs in cache. + * @param array $data array where key corresponds to cache key while value is the value stored + * @param integer $expire the number of seconds in which the cached values will expire. 0 means never expire. + * @return array array of failed keys + */ + protected function setValues($data, $expire) + { + return wincache_ucache_set($data, null, $expire); + } + + /** * Stores a value identified by a key into cache if the cache does not contain this key. * This is the implementation of the method declared in the parent class. * @@ -86,6 +97,19 @@ class WinCache extends Cache } /** + * Adds multiple key-value pairs to cache. + * The default implementation calls [[addValue()]] multiple times add values one by one. If the underlying cache + * storage supports multiadd, this method should be overridden to exploit that feature. + * @param array $data array where key corresponds to cache key while value is the value stored + * @param integer $expire the number of seconds in which the cached values will expire. 0 means never expire. + * @return array array of failed keys + */ + protected function addValues($data, $expire) + { + return wincache_ucache_add($data, null, $expire); + } + + /** * Deletes a value with the specified key from cache * This is the implementation of the method declared in the parent class. * @param string $key the key of the value to be deleted diff --git a/framework/yii/captcha/Captcha.php b/framework/yii/captcha/Captcha.php index fc1d881..76090a2 100644 --- a/framework/yii/captcha/Captcha.php +++ b/framework/yii/captcha/Captcha.php @@ -41,11 +41,11 @@ class Captcha extends InputWidget /** * @var array HTML attributes to be applied to the text input field. */ - public $options = array(); + public $options = []; /** * @var array HTML attributes to be applied to the CAPTCHA image tag. */ - public $imageOptions = array(); + public $imageOptions = []; /** * @var string the template for arranging the CAPTCHA image tag and the text input tag. * In this template, the token `{image}` will be replaced with the actual image tag, @@ -81,12 +81,12 @@ class Captcha extends InputWidget } else { $input = Html::textInput($this->name, $this->value, $this->options); } - $url = Yii::$app->getUrlManager()->createUrl($this->captchaAction, array('v' => uniqid())); + $url = Yii::$app->getUrlManager()->createUrl($this->captchaAction, ['v' => uniqid()]); $image = Html::img($url, $this->imageOptions); - echo strtr($this->template, array( + echo strtr($this->template, [ '{input}' => $input, '{image}' => $image, - )); + ]); } /** @@ -108,10 +108,10 @@ class Captcha extends InputWidget */ protected function getClientOptions() { - $options = array( - 'refreshUrl' => Html::url(array($this->captchaAction, CaptchaAction::REFRESH_GET_VAR => 1)), + $options = [ + 'refreshUrl' => Html::url([$this->captchaAction, CaptchaAction::REFRESH_GET_VAR => 1]), 'hashKey' => "yiiCaptcha/{$this->captchaAction}", - ); + ]; return $options; } diff --git a/framework/yii/captcha/CaptchaAction.php b/framework/yii/captcha/CaptchaAction.php index e1761a1..190ec35 100644 --- a/framework/yii/captcha/CaptchaAction.php +++ b/framework/yii/captcha/CaptchaAction.php @@ -119,13 +119,13 @@ class CaptchaAction extends Action $code = $this->getVerifyCode(true); /** @var \yii\web\Controller $controller */ $controller = $this->controller; - return json_encode(array( + return json_encode([ 'hash1' => $this->generateValidationHash($code), 'hash2' => $this->generateValidationHash(strtolower($code)), // we add a random 'v' parameter so that FireFox can refresh the image // when src attribute of image tag is changed - 'url' => $controller->createUrl($this->id, array('v' => uniqid())), - )); + 'url' => $controller->createUrl($this->id, ['v' => uniqid()]), + ]); } else { $this->setHttpHeaders(); return $this->renderImage($this->getVerifyCode()); diff --git a/framework/yii/captcha/CaptchaAsset.php b/framework/yii/captcha/CaptchaAsset.php index 50c75b7..4fc722f 100644 --- a/framework/yii/captcha/CaptchaAsset.php +++ b/framework/yii/captcha/CaptchaAsset.php @@ -10,16 +10,18 @@ namespace yii\captcha; use yii\web\AssetBundle; /** + * This asset bundle provides the javascript files needed for the [[Captcha]] widget. + * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ class CaptchaAsset extends AssetBundle { public $sourcePath = '@yii/assets'; - public $js = array( + public $js = [ 'yii.captcha.js', - ); - public $depends = array( + ]; + public $depends = [ 'yii\web\YiiAsset', - ); + ]; } diff --git a/framework/yii/captcha/CaptchaValidator.php b/framework/yii/captcha/CaptchaValidator.php index 97bfd1b..01c9d11 100644 --- a/framework/yii/captcha/CaptchaValidator.php +++ b/framework/yii/captcha/CaptchaValidator.php @@ -18,8 +18,6 @@ use yii\validators\Validator; * * CaptchaValidator should be used together with [[CaptchaAction]]. * - * @property \yii\captcha\CaptchaAction $captchaAction The action object. This property is read-only. - * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ @@ -71,16 +69,16 @@ class CaptchaValidator extends Validator */ public function validateValue($value) { - $captcha = $this->getCaptchaAction(); + $captcha = $this->createCaptchaAction(); return !is_array($value) && $captcha->validate($value, $this->caseSensitive); } /** - * Returns the CAPTCHA action object. - * @throws InvalidConfigException + * Creates the CAPTCHA action object from the route specified by [[captchaAction]]. * @return \yii\captcha\CaptchaAction the action object + * @throws InvalidConfigException */ - public function getCaptchaAction() + public function createCaptchaAction() { $ca = Yii::$app->createController($this->captchaAction); if ($ca !== false) { @@ -98,24 +96,23 @@ class CaptchaValidator extends Validator * Returns the JavaScript needed for performing client-side validation. * @param \yii\base\Model $object the data object being validated * @param string $attribute the name of the attribute to be validated. - * @param \yii\base\View $view the view object that is going to be used to render views or view files + * @param \yii\web\View $view the view object that is going to be used to render views or view files * containing a model form with this validator applied. * @return string the client-side validation script. */ public function clientValidateAttribute($object, $attribute, $view) { - $captcha = $this->getCaptchaAction(); + $captcha = $this->createCaptchaAction(); $code = $captcha->getVerifyCode(false); $hash = $captcha->generateValidationHash($this->caseSensitive ? $code : strtolower($code)); - $options = array( + $options = [ 'hash' => $hash, 'hashKey' => 'yiiCaptcha/' . $this->captchaAction, 'caseSensitive' => $this->caseSensitive, - 'message' => Html::encode(strtr($this->message, array( + 'message' => Html::encode(strtr($this->message, [ '{attribute}' => $object->getAttributeLabel($attribute), - '{value}' => $object->$attribute, - ))), - ); + ])), + ]; if ($this->skipOnEmpty) { $options['skipOnEmpty'] = 1; } diff --git a/framework/yii/classes.php b/framework/yii/classes.php index 40ca225..9f39ee9 100644 --- a/framework/yii/classes.php +++ b/framework/yii/classes.php @@ -10,7 +10,7 @@ * @license http://www.yiiframework.com/license/ */ -return array( +return [ 'yii\base\Action' => YII_PATH . '/base/Action.php', 'yii\base\ActionEvent' => YII_PATH . '/base/ActionEvent.php', 'yii\base\ActionFilter' => YII_PATH . '/base/ActionFilter.php', @@ -23,6 +23,7 @@ return array( 'yii\base\ErrorHandler' => YII_PATH . '/base/ErrorHandler.php', 'yii\base\Event' => YII_PATH . '/base/Event.php', 'yii\base\Exception' => YII_PATH . '/base/Exception.php', + 'yii\base\Extension' => YII_PATH . '/base/Extension.php', 'yii\base\Formatter' => YII_PATH . '/base/Formatter.php', 'yii\base\InlineAction' => YII_PATH . '/base/InlineAction.php', 'yii\base\InvalidCallException' => YII_PATH . '/base/InvalidCallException.php', @@ -42,26 +43,11 @@ return array( 'yii\base\UnknownPropertyException' => YII_PATH . '/base/UnknownPropertyException.php', 'yii\base\UserException' => YII_PATH . '/base/UserException.php', 'yii\base\View' => YII_PATH . '/base/View.php', + 'yii\base\ViewContextInterface' => YII_PATH . '/base/ViewContextInterface.php', 'yii\base\ViewEvent' => YII_PATH . '/base/ViewEvent.php', 'yii\base\ViewRenderer' => YII_PATH . '/base/ViewRenderer.php', 'yii\base\Widget' => YII_PATH . '/base/Widget.php', 'yii\behaviors\AutoTimestamp' => YII_PATH . '/behaviors/AutoTimestamp.php', - 'yii\bootstrap\Alert' => YII_PATH . '/bootstrap/Alert.php', - 'yii\bootstrap\BootstrapAsset' => YII_PATH . '/bootstrap/BootstrapAsset.php', - 'yii\bootstrap\BootstrapPluginAsset' => YII_PATH . '/bootstrap/BootstrapPluginAsset.php', - 'yii\bootstrap\BootstrapThemeAsset' => YII_PATH . '/bootstrap/BootstrapThemeAsset.php', - 'yii\bootstrap\Button' => YII_PATH . '/bootstrap/Button.php', - 'yii\bootstrap\ButtonDropdown' => YII_PATH . '/bootstrap/ButtonDropdown.php', - 'yii\bootstrap\ButtonGroup' => YII_PATH . '/bootstrap/ButtonGroup.php', - 'yii\bootstrap\Carousel' => YII_PATH . '/bootstrap/Carousel.php', - 'yii\bootstrap\Collapse' => YII_PATH . '/bootstrap/Collapse.php', - 'yii\bootstrap\Dropdown' => YII_PATH . '/bootstrap/Dropdown.php', - 'yii\bootstrap\Modal' => YII_PATH . '/bootstrap/Modal.php', - 'yii\bootstrap\Nav' => YII_PATH . '/bootstrap/Nav.php', - 'yii\bootstrap\NavBar' => YII_PATH . '/bootstrap/NavBar.php', - 'yii\bootstrap\Progress' => YII_PATH . '/bootstrap/Progress.php', - 'yii\bootstrap\Tabs' => YII_PATH . '/bootstrap/Tabs.php', - 'yii\bootstrap\Widget' => YII_PATH . '/bootstrap/Widget.php', 'yii\caching\ApcCache' => YII_PATH . '/caching/ApcCache.php', 'yii\caching\Cache' => YII_PATH . '/caching/Cache.php', 'yii\caching\ChainedDependency' => YII_PATH . '/caching/ChainedDependency.php', @@ -85,13 +71,17 @@ return array( 'yii\captcha\CaptchaValidator' => YII_PATH . '/captcha/CaptchaValidator.php', 'yii\data\ActiveDataProvider' => YII_PATH . '/data/ActiveDataProvider.php', 'yii\data\ArrayDataProvider' => YII_PATH . '/data/ArrayDataProvider.php', - 'yii\data\DataProvider' => YII_PATH . '/data/DataProvider.php', + 'yii\data\BaseDataProvider' => YII_PATH . '/data/BaseDataProvider.php', 'yii\data\DataProviderInterface' => YII_PATH . '/data/DataProviderInterface.php', 'yii\data\Pagination' => YII_PATH . '/data/Pagination.php', 'yii\data\Sort' => YII_PATH . '/data/Sort.php', 'yii\db\ActiveQuery' => YII_PATH . '/db/ActiveQuery.php', + 'yii\db\ActiveQueryInterface' => YII_PATH . '/db/ActiveQueryInterface.php', + 'yii\db\ActiveQueryTrait' => YII_PATH . '/db/ActiveQueryTrait.php', 'yii\db\ActiveRecord' => YII_PATH . '/db/ActiveRecord.php', 'yii\db\ActiveRelation' => YII_PATH . '/db/ActiveRelation.php', + 'yii\db\ActiveRelationInterface' => YII_PATH . '/db/ActiveRelationInterface.php', + 'yii\db\ActiveRelationTrait' => YII_PATH . '/db/ActiveRelationTrait.php', 'yii\db\ColumnSchema' => YII_PATH . '/db/ColumnSchema.php', 'yii\db\Command' => YII_PATH . '/db/Command.php', 'yii\db\Connection' => YII_PATH . '/db/Connection.php', @@ -101,6 +91,8 @@ return array( 'yii\db\Migration' => YII_PATH . '/db/Migration.php', 'yii\db\Query' => YII_PATH . '/db/Query.php', 'yii\db\QueryBuilder' => YII_PATH . '/db/QueryBuilder.php', + 'yii\db\QueryInterface' => YII_PATH . '/db/QueryInterface.php', + 'yii\db\QueryTrait' => YII_PATH . '/db/QueryTrait.php', 'yii\db\Schema' => YII_PATH . '/db/Schema.php', 'yii\db\StaleObjectException' => YII_PATH . '/db/StaleObjectException.php', 'yii\db\TableSchema' => YII_PATH . '/db/TableSchema.php', @@ -118,6 +110,7 @@ return array( 'yii\db\pgsql\Schema' => YII_PATH . '/db/pgsql/Schema.php', 'yii\db\sqlite\QueryBuilder' => YII_PATH . '/db/sqlite/QueryBuilder.php', 'yii\db\sqlite\Schema' => YII_PATH . '/db/sqlite/Schema.php', + 'yii\grid\ActionColumn' => YII_PATH . '/grid/ActionColumn.php', 'yii\grid\CheckboxColumn' => YII_PATH . '/grid/CheckboxColumn.php', 'yii\grid\Column' => YII_PATH . '/grid/Column.php', 'yii\grid\DataColumn' => YII_PATH . '/grid/DataColumn.php', @@ -125,27 +118,27 @@ return array( 'yii\grid\GridViewAsset' => YII_PATH . '/grid/GridViewAsset.php', 'yii\grid\SerialColumn' => YII_PATH . '/grid/SerialColumn.php', 'yii\helpers\ArrayHelper' => YII_PATH . '/helpers/ArrayHelper.php', - 'yii\helpers\ArrayHelperBase' => YII_PATH . '/helpers/ArrayHelperBase.php', + 'yii\helpers\BaseArrayHelper' => YII_PATH . '/helpers/BaseArrayHelper.php', + 'yii\helpers\BaseConsole' => YII_PATH . '/helpers/BaseConsole.php', + 'yii\helpers\BaseFileHelper' => YII_PATH . '/helpers/BaseFileHelper.php', + 'yii\helpers\BaseHtml' => YII_PATH . '/helpers/BaseHtml.php', + 'yii\helpers\BaseHtmlPurifier' => YII_PATH . '/helpers/BaseHtmlPurifier.php', + 'yii\helpers\BaseInflector' => YII_PATH . '/helpers/BaseInflector.php', + 'yii\helpers\BaseJson' => YII_PATH . '/helpers/BaseJson.php', + 'yii\helpers\BaseMarkdown' => YII_PATH . '/helpers/BaseMarkdown.php', + 'yii\helpers\BaseSecurity' => YII_PATH . '/helpers/BaseSecurity.php', + 'yii\helpers\BaseStringHelper' => YII_PATH . '/helpers/BaseStringHelper.php', + 'yii\helpers\BaseVarDumper' => YII_PATH . '/helpers/BaseVarDumper.php', 'yii\helpers\Console' => YII_PATH . '/helpers/Console.php', - 'yii\helpers\ConsoleBase' => YII_PATH . '/helpers/ConsoleBase.php', 'yii\helpers\FileHelper' => YII_PATH . '/helpers/FileHelper.php', - 'yii\helpers\FileHelperBase' => YII_PATH . '/helpers/FileHelperBase.php', 'yii\helpers\Html' => YII_PATH . '/helpers/Html.php', - 'yii\helpers\HtmlBase' => YII_PATH . '/helpers/HtmlBase.php', 'yii\helpers\HtmlPurifier' => YII_PATH . '/helpers/HtmlPurifier.php', - 'yii\helpers\HtmlPurifierBase' => YII_PATH . '/helpers/HtmlPurifierBase.php', 'yii\helpers\Inflector' => YII_PATH . '/helpers/Inflector.php', - 'yii\helpers\InflectorBase' => YII_PATH . '/helpers/InflectorBase.php', 'yii\helpers\Json' => YII_PATH . '/helpers/Json.php', - 'yii\helpers\JsonBase' => YII_PATH . '/helpers/JsonBase.php', 'yii\helpers\Markdown' => YII_PATH . '/helpers/Markdown.php', - 'yii\helpers\MarkdownBase' => YII_PATH . '/helpers/MarkdownBase.php', 'yii\helpers\Security' => YII_PATH . '/helpers/Security.php', - 'yii\helpers\SecurityBase' => YII_PATH . '/helpers/SecurityBase.php', 'yii\helpers\StringHelper' => YII_PATH . '/helpers/StringHelper.php', - 'yii\helpers\StringHelperBase' => YII_PATH . '/helpers/StringHelperBase.php', 'yii\helpers\VarDumper' => YII_PATH . '/helpers/VarDumper.php', - 'yii\helpers\VarDumperBase' => YII_PATH . '/helpers/VarDumperBase.php', 'yii\i18n\DbMessageSource' => YII_PATH . '/i18n/DbMessageSource.php', 'yii\i18n\Formatter' => YII_PATH . '/i18n/Formatter.php', 'yii\i18n\GettextFile' => YII_PATH . '/i18n/GettextFile.php', @@ -153,6 +146,7 @@ return array( 'yii\i18n\GettextMoFile' => YII_PATH . '/i18n/GettextMoFile.php', 'yii\i18n\GettextPoFile' => YII_PATH . '/i18n/GettextPoFile.php', 'yii\i18n\I18N' => YII_PATH . '/i18n/I18N.php', + 'yii\i18n\MessageFormatter' => YII_PATH . '/i18n/MessageFormatter.php', 'yii\i18n\MessageSource' => YII_PATH . '/i18n/MessageSource.php', 'yii\i18n\MissingTranslationEvent' => YII_PATH . '/i18n/MissingTranslationEvent.php', 'yii\i18n\PhpMessageSource' => YII_PATH . '/i18n/PhpMessageSource.php', @@ -161,6 +155,14 @@ return array( 'yii\log\FileTarget' => YII_PATH . '/log/FileTarget.php', 'yii\log\Logger' => YII_PATH . '/log/Logger.php', 'yii\log\Target' => YII_PATH . '/log/Target.php', + 'yii\mail\BaseMailer' => YII_PATH . '/mail/BaseMailer.php', + 'yii\mail\BaseMessage' => YII_PATH . '/mail/BaseMessage.php', + 'yii\mail\MailerInterface' => YII_PATH . '/mail/MailerInterface.php', + 'yii\mail\MessageInterface' => YII_PATH . '/mail/MessageInterface.php', + 'yii\mutex\DbMutex' => YII_PATH . '/mutex/DbMutex.php', + 'yii\mutex\FileMutex' => YII_PATH . '/mutex/FileMutex.php', + 'yii\mutex\Mutex' => YII_PATH . '/mutex/Mutex.php', + 'yii\mutex\MysqlMutex' => YII_PATH . '/mutex/MysqlMutex.php', 'yii\rbac\Assignment' => YII_PATH . '/rbac/Assignment.php', 'yii\rbac\DbManager' => YII_PATH . '/rbac/DbManager.php', 'yii\rbac\Item' => YII_PATH . '/rbac/Item.php', @@ -169,6 +171,8 @@ return array( 'yii\redis\Connection' => YII_PATH . '/redis/Connection.php', 'yii\redis\Transaction' => YII_PATH . '/redis/Transaction.php', 'yii\requirements\YiiRequirementChecker' => YII_PATH . '/requirements/YiiRequirementChecker.php', + 'yii\test\DbFixtureManager' => YII_PATH . '/test/DbFixtureManager.php', + 'yii\test\DbTestTrait' => YII_PATH . '/test/DbTestTrait.php', 'yii\validators\BooleanValidator' => YII_PATH . '/validators/BooleanValidator.php', 'yii\validators\CompareValidator' => YII_PATH . '/validators/CompareValidator.php', 'yii\validators\DateValidator' => YII_PATH . '/validators/DateValidator.php', @@ -177,6 +181,7 @@ return array( 'yii\validators\ExistValidator' => YII_PATH . '/validators/ExistValidator.php', 'yii\validators\FileValidator' => YII_PATH . '/validators/FileValidator.php', 'yii\validators\FilterValidator' => YII_PATH . '/validators/FilterValidator.php', + 'yii\validators\ImageValidator' => YII_PATH . '/validators/ImageValidator.php', 'yii\validators\InlineValidator' => YII_PATH . '/validators/InlineValidator.php', 'yii\validators\NumberValidator' => YII_PATH . '/validators/NumberValidator.php', 'yii\validators\PunycodeAsset' => YII_PATH . '/validators/PunycodeAsset.php', @@ -194,6 +199,7 @@ return array( 'yii\web\Application' => YII_PATH . '/web/Application.php', 'yii\web\AssetBundle' => YII_PATH . '/web/AssetBundle.php', 'yii\web\AssetConverter' => YII_PATH . '/web/AssetConverter.php', + 'yii\web\AssetConverterInterface' => YII_PATH . '/web/AssetConverterInterface.php', 'yii\web\AssetManager' => YII_PATH . '/web/AssetManager.php', 'yii\web\CacheSession' => YII_PATH . '/web/CacheSession.php', 'yii\web\Controller' => YII_PATH . '/web/Controller.php', @@ -204,7 +210,6 @@ return array( 'yii\web\HeaderCollection' => YII_PATH . '/web/HeaderCollection.php', 'yii\web\HttpCache' => YII_PATH . '/web/HttpCache.php', 'yii\web\HttpException' => YII_PATH . '/web/HttpException.php', - 'yii\web\AssetConverterInterface' => YII_PATH . '/web/AssetConverterInterface.php', 'yii\web\IdentityInterface' => YII_PATH . '/web/IdentityInterface.php', 'yii\web\JqueryAsset' => YII_PATH . '/web/JqueryAsset.php', 'yii\web\JsExpression' => YII_PATH . '/web/JsExpression.php', @@ -221,11 +226,13 @@ return array( 'yii\web\User' => YII_PATH . '/web/User.php', 'yii\web\UserEvent' => YII_PATH . '/web/UserEvent.php', 'yii\web\VerbFilter' => YII_PATH . '/web/VerbFilter.php', + 'yii\web\View' => YII_PATH . '/web/View.php', 'yii\web\XmlResponseFormatter' => YII_PATH . '/web/XmlResponseFormatter.php', 'yii\web\YiiAsset' => YII_PATH . '/web/YiiAsset.php', 'yii\widgets\ActiveField' => YII_PATH . '/widgets/ActiveField.php', 'yii\widgets\ActiveForm' => YII_PATH . '/widgets/ActiveForm.php', 'yii\widgets\ActiveFormAsset' => YII_PATH . '/widgets/ActiveFormAsset.php', + 'yii\widgets\BaseListView' => YII_PATH . '/widgets/BaseListView.php', 'yii\widgets\Block' => YII_PATH . '/widgets/Block.php', 'yii\widgets\Breadcrumbs' => YII_PATH . '/widgets/Breadcrumbs.php', 'yii\widgets\ContentDecorator' => YII_PATH . '/widgets/ContentDecorator.php', @@ -235,9 +242,8 @@ return array( 'yii\widgets\LinkPager' => YII_PATH . '/widgets/LinkPager.php', 'yii\widgets\LinkSorter' => YII_PATH . '/widgets/LinkSorter.php', 'yii\widgets\ListView' => YII_PATH . '/widgets/ListView.php', - 'yii\widgets\ListViewBase' => YII_PATH . '/widgets/ListViewBase.php', 'yii\widgets\MaskedInput' => YII_PATH . '/widgets/MaskedInput.php', 'yii\widgets\MaskedInputAsset' => YII_PATH . '/widgets/MaskedInputAsset.php', 'yii\widgets\Menu' => YII_PATH . '/widgets/Menu.php', 'yii\widgets\Spaceless' => YII_PATH . '/widgets/Spaceless.php', -); +]; diff --git a/framework/yii/console/Application.php b/framework/yii/console/Application.php index bbdd4e1..6701304 100644 --- a/framework/yii/console/Application.php +++ b/framework/yii/console/Application.php @@ -9,6 +9,7 @@ namespace yii\console; +use Yii; use yii\base\InvalidRouteException; /** @@ -124,12 +125,12 @@ class Application extends \yii\base\Application * @return integer the status code returned by the action execution. 0 means normal, and other values mean abnormal. * @throws Exception if the route is invalid */ - public function runAction($route, $params = array()) + public function runAction($route, $params = []) { try { return parent::runAction($route, $params); } catch (InvalidRouteException $e) { - throw new Exception(\Yii::t('yii', 'Unknown command "{command}".', array('{command}' => $route)), 0, $e); + throw new Exception(Yii::t('yii', 'Unknown command "{command}".', ['command' => $route]), 0, $e); } } @@ -139,13 +140,13 @@ class Application extends \yii\base\Application */ public function coreCommands() { - return array( + return [ 'message' => 'yii\console\controllers\MessageController', 'help' => 'yii\console\controllers\HelpController', 'migrate' => 'yii\console\controllers\MigrateController', 'cache' => 'yii\console\controllers\CacheController', 'asset' => 'yii\console\controllers\AssetController', - ); + ]; } /** @@ -155,13 +156,9 @@ class Application extends \yii\base\Application public function registerCoreComponents() { parent::registerCoreComponents(); - $this->setComponents(array( - 'request' => array( - 'class' => 'yii\console\Request', - ), - 'response' => array( - 'class' => 'yii\console\Response', - ), - )); + $this->setComponents([ + 'request' => ['class' => 'yii\console\Request'], + 'response' => ['class' => 'yii\console\Response'], + ]); } } diff --git a/framework/yii/console/Controller.php b/framework/yii/console/Controller.php index fd6d0de..47ac4b0 100644 --- a/framework/yii/console/Controller.php +++ b/framework/yii/console/Controller.php @@ -63,7 +63,7 @@ class Controller extends \yii\base\Controller * @throws InvalidRouteException if the requested action ID cannot be resolved into an action successfully. * @see createAction */ - public function runAction($id, $params = array()) + public function runAction($id, $params = []) { if (!empty($params)) { $options = $this->globalOptions(); @@ -89,7 +89,7 @@ class Controller extends \yii\base\Controller */ public function bindActionParams($action, $params) { - $args = array(); + $args = []; if (!empty($params)) { $options = $this->globalOptions(); foreach ($params as $name => $value) { @@ -98,9 +98,7 @@ class Controller extends \yii\base\Controller } elseif (is_int($name)) { $args[] = $value; } else { - throw new Exception(Yii::t('yii', 'Unknown option: --{name}', array( - '{name}' => $name, - ))); + throw new Exception(Yii::t('yii', 'Unknown option: --{name}', ['name' => $name])); } } } @@ -111,7 +109,7 @@ class Controller extends \yii\base\Controller $method = new \ReflectionMethod($action, 'run'); } - $missing = array(); + $missing = []; foreach ($method->getParameters() as $i => $param) { $name = $param->getName(); if (!isset($args[$i])) { @@ -124,9 +122,7 @@ class Controller extends \yii\base\Controller } if (!empty($missing)) { - throw new Exception(Yii::t('yii', 'Missing required arguments: {params}', array( - '{params}' => implode(', ', $missing), - ))); + throw new Exception(Yii::t('yii', 'Missing required arguments: {params}', ['params' => implode(', ', $missing)])); } return $args; @@ -220,7 +216,7 @@ class Controller extends \yii\base\Controller * - $error: the error value passed by reference if validation failed. * @return string the user input */ - public function prompt($text, $options = array()) + public function prompt($text, $options = []) { if ($this->interactive) { return Console::prompt($text, $options); @@ -255,7 +251,7 @@ class Controller extends \yii\base\Controller * * @return string An option character the user chose */ - public function select($prompt, $options = array()) + public function select($prompt, $options = []) { return Console::select($prompt, $options); } @@ -273,6 +269,6 @@ class Controller extends \yii\base\Controller */ public function globalOptions() { - return array('color', 'interactive'); + return ['color', 'interactive']; } } diff --git a/framework/yii/console/Request.php b/framework/yii/console/Request.php index dca0240..d4d3af0 100644 --- a/framework/yii/console/Request.php +++ b/framework/yii/console/Request.php @@ -8,6 +8,10 @@ namespace yii\console; /** + * The console Request represents the environment information for a console application. + * + * It is a wrapper for the PHP `$_SERVER` variable which holds information about the + * currently running PHP script and the command line arguments given to it. * * @property array $params The command line arguments. It does not include the entry script name. * @@ -29,7 +33,7 @@ class Request extends \yii\base\Request $this->_params = $_SERVER['argv']; array_shift($this->_params); } else { - $this->_params = array(); + $this->_params = []; } } return $this->_params; @@ -58,7 +62,7 @@ class Request extends \yii\base\Request $route = ''; } - $params = array(); + $params = []; foreach ($rawParams as $param) { if (preg_match('/^--(\w+)(=(.*))?$/', $param, $matches)) { $name = $matches[1]; @@ -68,6 +72,6 @@ class Request extends \yii\base\Request } } - return array($route, $params); + return [$route, $params]; } } diff --git a/framework/yii/console/Response.php b/framework/yii/console/Response.php index 9c23e83..f6e6dd0 100644 --- a/framework/yii/console/Response.php +++ b/framework/yii/console/Response.php @@ -8,6 +8,8 @@ namespace yii\console; /** + * The console Response represents the result of a console application by holding the [[exitCode]]. + * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ diff --git a/framework/yii/console/controllers/AssetController.php b/framework/yii/console/controllers/AssetController.php index 1e95dd8..cea9fed 100644 --- a/framework/yii/console/controllers/AssetController.php +++ b/framework/yii/console/controllers/AssetController.php @@ -43,24 +43,24 @@ class AssetController extends Controller /** * @var array list of asset bundles to be compressed. */ - public $bundles = array(); + public $bundles = []; /** * @var array list of asset bundles, which represents output compressed files. * You can specify the name of the output compressed file using 'css' and 'js' keys: * For example: * * ~~~ - * 'app\config\AllAsset' => array( + * 'app\config\AllAsset' => [ * 'js' => 'js/all-{ts}.js', * 'css' => 'css/all-{ts}.css', - * 'depends' => array( ... ), - * ) + * 'depends' => [ ... ], + * ] * ~~~ * * File names can contain placeholder "{ts}", which will be filled by current timestamp, while * file creation. */ - public $targets = array(); + public $targets = []; /** * @var string|callback JavaScript file compressor. * If a string, it is treated as shell command template, which should contain @@ -86,7 +86,7 @@ class AssetController extends Controller * @var array|\yii\web\AssetManager [[yii\web\AssetManager]] instance or its array configuration, which will be used * for assets processing. */ - private $_assetManager = array(); + private $_assetManager = []; /** * Returns the asset manager instance. @@ -136,7 +136,6 @@ class AssetController extends Controller $this->loadConfiguration($configFile); $bundles = $this->loadBundles($this->bundles); $targets = $this->loadTargets($this->targets, $bundles); - $this->publishBundles($bundles, $this->assetManager); $timestamp = time(); foreach ($targets as $name => $target) { echo "Creating output bundle '{$name}':\n"; @@ -182,7 +181,7 @@ class AssetController extends Controller echo "Collecting source bundles information...\n"; $am = $this->getAssetManager(); - $result = array(); + $result = []; foreach ($bundles as $name) { $result[$name] = $am->getBundle($name); } @@ -224,14 +223,14 @@ class AssetController extends Controller protected function loadTargets($targets, $bundles) { // build the dependency order of bundles - $registered = array(); + $registered = []; foreach ($bundles as $name => $bundle) { $this->registerBundle($bundles, $name, $registered); } $bundleOrders = array_combine(array_keys($registered), range(0, count($bundles) - 1)); // fill up the target which has empty 'depends'. - $referenced = array(); + $referenced = []; foreach ($targets as $name => $target) { if (empty($target['depends'])) { if (!isset($all)) { @@ -276,21 +275,6 @@ class AssetController extends Controller } /** - * Publishes given asset bundles. - * @param \yii\web\AssetBundle[] $bundles asset bundles to be published. - */ - protected function publishBundles($bundles) - { - echo "\nPublishing bundles:\n"; - $assetManager = $this->getAssetManager(); - foreach ($bundles as $name => $bundle) { - $bundle->publish($assetManager); - echo " '".$name."' published.\n"; - } - echo "\n"; - } - - /** * Builds output asset bundle. * @param \yii\web\AssetBundle $target output asset bundle * @param string $type either 'js' or 'css'. @@ -300,10 +284,10 @@ class AssetController extends Controller */ protected function buildTarget($target, $type, $bundles, $timestamp) { - $outputFile = strtr($target->$type, array( + $outputFile = strtr($target->$type, [ '{ts}' => $timestamp, - )); - $inputFiles = array(); + ]); + $inputFiles = []; foreach ($target->depends as $name) { if (isset($bundles[$name])) { @@ -319,7 +303,7 @@ class AssetController extends Controller } else { $this->compressCssFiles($inputFiles, $target->basePath . '/' . $outputFile); } - $target->$type = array($outputFile); + $target->$type = [$outputFile]; } /** @@ -332,7 +316,7 @@ class AssetController extends Controller { echo "Creating new bundle configuration...\n"; - $map = array(); + $map = []; foreach ($targets as $name => $target) { foreach ($target->depends as $bundle) { $map[$bundle] = $name; @@ -340,7 +324,7 @@ class AssetController extends Controller } foreach ($targets as $name => $target) { - $depends = array(); + $depends = []; foreach ($target->depends as $bn) { foreach ($bundles[$bn]->depends as $bundle) { $depends[$map[$bundle]] = true; @@ -352,15 +336,15 @@ class AssetController extends Controller // detect possible circular dependencies foreach ($targets as $name => $target) { - $registered = array(); + $registered = []; $this->registerBundle($targets, $name, $registered); } foreach ($map as $bundle => $target) { - $targets[$bundle] = Yii::createObject(array( + $targets[$bundle] = Yii::createObject([ 'class' => 'yii\\web\\AssetBundle', - 'depends' => array($target), - )); + 'depends' => [$target], + ]); } return $targets; } @@ -395,9 +379,9 @@ class AssetController extends Controller */ protected function saveTargets($targets, $bundleFile) { - $array = array(); + $array = []; foreach ($targets as $name => $target) { - foreach (array('js', 'css', 'depends', 'basePath', 'baseUrl') as $prop) { + foreach (['js', 'css', 'depends', 'basePath', 'baseUrl'] as $prop) { if (!empty($target->$prop)) { $array[$name][$prop] = $target->$prop; } @@ -435,10 +419,10 @@ EOD; if (is_string($this->jsCompressor)) { $tmpFile = $outputFile . '.tmp'; $this->combineJsFiles($inputFiles, $tmpFile); - echo shell_exec(strtr($this->jsCompressor, array( + echo shell_exec(strtr($this->jsCompressor, [ '{from}' => escapeshellarg($tmpFile), '{to}' => escapeshellarg($outputFile), - ))); + ])); @unlink($tmpFile); } else { call_user_func($this->jsCompressor, $this, $inputFiles, $outputFile); @@ -464,10 +448,10 @@ EOD; if (is_string($this->cssCompressor)) { $tmpFile = $outputFile . '.tmp'; $this->combineCssFiles($inputFiles, $tmpFile); - echo shell_exec(strtr($this->cssCompressor, array( + echo shell_exec(strtr($this->cssCompressor, [ '{from}' => escapeshellarg($tmpFile), '{to}' => escapeshellarg($outputFile), - ))); + ])); @unlink($tmpFile); } else { call_user_func($this->cssCompressor, $this, $inputFiles, $outputFile); @@ -525,7 +509,7 @@ EOD; */ protected function adjustCssUrl($cssContent, $inputFilePath, $outputFilePath) { - $sharedPathParts = array(); + $sharedPathParts = []; $inputFilePathParts = explode('/', $inputFilePath); $inputFilePathPartsCount = count($inputFilePathParts); $outputFilePathParts = explode('/', $outputFilePath); @@ -591,27 +575,27 @@ EOD; * Note that in the console environment, some path aliases like '@webroot' and '@web' may not exist. * Please define these missing path aliases. */ -return array( +return [ // The list of asset bundles to compress: - 'bundles' => array( + 'bundles' => [ // 'yii\web\YiiAsset', // 'yii\web\JqueryAsset', - ), + ], // Asset bundle for compression output: - 'targets' => array( - 'app\config\AllAsset' => array( + 'targets' => [ + 'app\config\AllAsset' => [ 'basePath' => 'path/to/web', 'baseUrl' => '', 'js' => 'js/all-{ts}.js', 'css' => 'css/all-{ts}.css', - ), - ), + ], + ], // Asset manager configuration: - 'assetManager' => array( + 'assetManager' => [ 'basePath' => __DIR__, 'baseUrl' => '', - ), -); + ], +]; EOD; if (file_exists($configFile)) { if (!$this->confirm("File '{$configFile}' already exists. Do you wish to overwrite it?")) { diff --git a/framework/yii/console/controllers/CacheController.php b/framework/yii/console/controllers/CacheController.php index 95765fa..43932d1 100644 --- a/framework/yii/console/controllers/CacheController.php +++ b/framework/yii/console/controllers/CacheController.php @@ -25,7 +25,7 @@ class CacheController extends Controller */ public function actionIndex() { - $caches = array(); + $caches = []; $components = Yii::$app->getComponents(); foreach ($components as $name => $component) { if ($component instanceof Cache) { @@ -52,7 +52,7 @@ class CacheController extends Controller */ public function actionFlush($component = 'cache') { - /** @var $cache Cache */ + /** @var Cache $cache */ $cache = Yii::$app->getComponent($component); if (!$cache || !$cache instanceof Cache) { throw new Exception('Application component "'.$component.'" is not defined or not a cache.'); diff --git a/framework/yii/console/controllers/HelpController.php b/framework/yii/console/controllers/HelpController.php index f0e72cd..eee1923 100644 --- a/framework/yii/console/controllers/HelpController.php +++ b/framework/yii/console/controllers/HelpController.php @@ -57,9 +57,9 @@ class HelpController extends Controller if ($command !== null) { $result = Yii::$app->createController($command); if ($result === false) { - throw new Exception(Yii::t('yii', 'No help for unknown command "{command}".', array( - '{command}' => $this->ansiFormat($command, Console::FG_YELLOW), - ))); + throw new Exception(Yii::t('yii', 'No help for unknown command "{command}".', [ + 'command' => $this->ansiFormat($command, Console::FG_YELLOW), + ])); } list($controller, $actionID) = $result; @@ -114,7 +114,7 @@ class HelpController extends Controller { $prefix = $module instanceof Application ? '' : $module->getUniqueID() . '/'; - $commands = array(); + $commands = []; foreach (array_keys($module->controllerMap) as $id) { $commands[] = $prefix . $id; } @@ -124,7 +124,7 @@ class HelpController extends Controller continue; } foreach ($this->getModuleCommands($child) as $command) { - $commands[] = $prefix . $id . '/' . $command; + $commands[] = $command; } } @@ -242,9 +242,9 @@ class HelpController extends Controller { $action = $controller->createAction($actionID); if ($action === null) { - throw new Exception(Yii::t('yii', 'No help for unknown sub-command "{command}".', array( - '{command}' => rtrim($controller->getUniqueId() . '/' . $actionID, '/'), - ))); + throw new Exception(Yii::t('yii', 'No help for unknown sub-command "{command}".', [ + 'command' => rtrim($controller->getUniqueId() . '/' . $actionID, '/'), + ])); } if ($action instanceof InlineAction) { $method = new \ReflectionMethod($controller, $action->actionMethod); @@ -266,7 +266,7 @@ class HelpController extends Controller } else { echo 'yii ' . $this->ansiFormat($action->getUniqueId(), Console::FG_YELLOW); } - list ($required, $optional) = $this->getArgHelps($method, isset($tags['param']) ? $tags['param'] : array()); + list ($required, $optional) = $this->getArgHelps($method, isset($tags['param']) ? $tags['param'] : []); foreach ($required as $arg => $description) { $this->stdout(' <' . $arg . '>', Console::FG_CYAN); } @@ -298,10 +298,10 @@ class HelpController extends Controller protected function getArgHelps($method, $tags) { if (is_string($tags)) { - $tags = array($tags); + $tags = [$tags]; } $params = $method->getParameters(); - $optional = $required = array(); + $optional = $required = []; foreach ($params as $i => $param) { $name = $param->getName(); $tag = isset($tags[$i]) ? $tags[$i] : ''; @@ -319,7 +319,7 @@ class HelpController extends Controller } } - return array($required, $optional); + return [$required, $optional]; } /** @@ -331,11 +331,11 @@ class HelpController extends Controller { $optionNames = $controller->globalOptions(); if (empty($optionNames)) { - return array(); + return []; } $class = new \ReflectionClass($controller); - $options = array(); + $options = []; foreach ($class->getProperties() as $property) { $name = $property->getName(); if (!in_array($name, $optionNames, true)) { @@ -371,7 +371,7 @@ class HelpController extends Controller */ protected function parseComment($comment) { - $tags = array(); + $tags = []; $comment = "@description \n" . strtr(trim(preg_replace('/^\s*\**( |\t)?/m', '', trim($comment, '/'))), "\r", ''); $parts = preg_split('/^\s*@/m', $comment, -1, PREG_SPLIT_NO_EMPTY); foreach ($parts as $part) { @@ -382,7 +382,7 @@ class HelpController extends Controller } elseif (is_array($tags[$name])) { $tags[$name][] = trim($matches[2]); } else { - $tags[$name] = array($tags[$name], trim($matches[2])); + $tags[$name] = [$tags[$name], trim($matches[2])]; } } } diff --git a/framework/yii/console/controllers/MessageController.php b/framework/yii/console/controllers/MessageController.php index 306eb41..8a0f10b 100644 --- a/framework/yii/console/controllers/MessageController.php +++ b/framework/yii/console/controllers/MessageController.php @@ -74,12 +74,12 @@ class MessageController extends Controller throw new Exception("The configuration file does not exist: $configFile"); } - $config = array_merge(array( + $config = array_merge([ 'translator' => 'Yii::t', 'overwrite' => false, 'removeUnused' => false, 'sort' => false, - ), require($configFile)); + ], require($configFile)); if (!isset($config['sourcePath'], $config['messagePath'], $config['languages'])) { throw new Exception('The configuration file must specify "sourcePath", "messagePath" and "languages".'); @@ -96,7 +96,7 @@ class MessageController extends Controller $files = FileHelper::findFiles(realpath($config['sourcePath']), $config); - $messages = array(); + $messages = []; foreach ($files as $file) { $messages = array_merge_recursive($messages, $this->extractMessages($file, $config['translator'])); } @@ -127,9 +127,9 @@ class MessageController extends Controller { echo "Extracting messages from $fileName...\n"; $subject = file_get_contents($fileName); - $messages = array(); + $messages = []; if (!is_array($translator)) { - $translator = array($translator); + $translator = [$translator]; } foreach ($translator as $currentTranslator) { $n = preg_match_all( @@ -168,8 +168,8 @@ class MessageController extends Controller echo "nothing new...skipped.\n"; return; } - $merged = array(); - $untranslated = array(); + $merged = []; + $untranslated = []; foreach ($messages as $message) { if (array_key_exists($message, $translated) && strlen($translated[$message]) > 0) { $merged[$message] = $translated[$message]; @@ -179,7 +179,7 @@ class MessageController extends Controller } ksort($merged); sort($untranslated); - $todo = array(); + $todo = []; foreach ($untranslated as $message) { $todo[$message] = ''; } @@ -202,7 +202,7 @@ class MessageController extends Controller } echo "translation merged.\n"; } else { - $merged = array(); + $merged = []; foreach ($messages as $message) { $merged[$message] = ''; } diff --git a/framework/yii/console/controllers/MigrateController.php b/framework/yii/console/controllers/MigrateController.php index e2c771c..0e36911 100644 --- a/framework/yii/console/controllers/MigrateController.php +++ b/framework/yii/console/controllers/MigrateController.php @@ -96,9 +96,9 @@ class MigrateController extends Controller */ public function globalOptions() { - return array_merge(parent::globalOptions(), array( + return array_merge(parent::globalOptions(), [ 'migrationPath', 'migrationTable', 'db', 'templateFile', 'interactive', 'color' - )); + ]); } /** @@ -360,10 +360,10 @@ class MigrateController extends Controller if ($this->confirm("Set migration history at $originalVersion?")) { $command = $this->db->createCommand(); for ($j = 0; $j <= $i; ++$j) { - $command->insert($this->migrationTable, array( + $command->insert($this->migrationTable, [ 'version' => $migrations[$j], 'apply_time' => time(), - ))->execute(); + ])->execute(); } echo "The migration history is set at $originalVersion.\nNo actual migration was performed.\n"; } @@ -381,9 +381,9 @@ class MigrateController extends Controller if ($this->confirm("Set migration history at $originalVersion?")) { $command = $this->db->createCommand(); for ($j = 0; $j < $i; ++$j) { - $command->delete($this->migrationTable, array( + $command->delete($this->migrationTable, [ 'version' => $migrations[$j], - ))->execute(); + ])->execute(); } echo "The migration history is set at $originalVersion.\nNo actual migration was performed.\n"; } @@ -490,9 +490,7 @@ class MigrateController extends Controller $file = $this->migrationPath . DIRECTORY_SEPARATOR . $name . '.php'; if ($this->confirm("Create new migration '$file'?")) { - $content = $this->renderFile(Yii::getAlias($this->templateFile), array( - 'className' => $name, - )); + $content = $this->renderFile(Yii::getAlias($this->templateFile), ['className' => $name]); file_put_contents($file, $content); echo "New migration created successfully.\n"; } @@ -513,10 +511,10 @@ class MigrateController extends Controller $start = microtime(true); $migration = $this->createMigration($class); if ($migration->up() !== false) { - $this->db->createCommand()->insert($this->migrationTable, array( + $this->db->createCommand()->insert($this->migrationTable, [ 'version' => $class, 'apply_time' => time(), - ))->execute(); + ])->execute(); $time = microtime(true) - $start; echo "*** applied $class (time: " . sprintf("%.3f", $time) . "s)\n\n"; return true; @@ -542,9 +540,9 @@ class MigrateController extends Controller $start = microtime(true); $migration = $this->createMigration($class); if ($migration->down() !== false) { - $this->db->createCommand()->delete($this->migrationTable, array( + $this->db->createCommand()->delete($this->migrationTable, [ 'version' => $class, - ))->execute(); + ])->execute(); $time = microtime(true) - $start; echo "*** reverted $class (time: " . sprintf("%.3f", $time) . "s)\n\n"; return true; @@ -564,9 +562,7 @@ class MigrateController extends Controller { $file = $this->migrationPath . DIRECTORY_SEPARATOR . $class . '.php'; require_once($file); - return new $class(array( - 'db' => $this->db, - )); + return new $class(['db' => $this->db]); } /** @@ -580,11 +576,11 @@ class MigrateController extends Controller $this->createMigrationHistoryTable(); } $query = new Query; - $rows = $query->select(array('version', 'apply_time')) + $rows = $query->select(['version', 'apply_time']) ->from($this->migrationTable) ->orderBy('version DESC') ->limit($limit) - ->createCommand() + ->createCommand($this->db) ->queryAll(); $history = ArrayHelper::map($rows, 'version', 'apply_time'); unset($history[self::BASE_MIGRATION]); @@ -597,14 +593,14 @@ class MigrateController extends Controller protected function createMigrationHistoryTable() { echo 'Creating migration history table "' . $this->migrationTable . '"...'; - $this->db->createCommand()->createTable($this->migrationTable, array( - 'version' => 'varchar(255) NOT NULL PRIMARY KEY', + $this->db->createCommand()->createTable($this->migrationTable, [ + 'version' => 'varchar(180) NOT NULL PRIMARY KEY', 'apply_time' => 'integer', - ))->execute(); - $this->db->createCommand()->insert($this->migrationTable, array( + ])->execute(); + $this->db->createCommand()->insert($this->migrationTable, [ 'version' => self::BASE_MIGRATION, 'apply_time' => time(), - ))->execute(); + ])->execute(); echo "done.\n"; } @@ -614,12 +610,12 @@ class MigrateController extends Controller */ protected function getNewMigrations() { - $applied = array(); + $applied = []; foreach ($this->getMigrationHistory(-1) as $version => $time) { $applied[substr($version, 1, 13)] = true; } - $migrations = array(); + $migrations = []; $handle = opendir($this->migrationPath); while (($file = readdir($handle)) !== false) { if ($file === '.' || $file === '..') { diff --git a/framework/yii/data/ActiveDataProvider.php b/framework/yii/data/ActiveDataProvider.php index aaf71b2..e9d5403 100644 --- a/framework/yii/data/ActiveDataProvider.php +++ b/framework/yii/data/ActiveDataProvider.php @@ -8,11 +8,11 @@ namespace yii\data; use Yii; +use yii\db\ActiveQueryInterface; use yii\base\InvalidConfigException; use yii\base\Model; -use yii\db\Query; -use yii\db\ActiveQuery; use yii\db\Connection; +use yii\db\QueryInterface; /** * ActiveDataProvider implements a data provider based on [[Query]] and [[ActiveQuery]]. @@ -22,12 +22,12 @@ use yii\db\Connection; * The following is an example of using ActiveDataProvider to provide ActiveRecord instances: * * ~~~ - * $provider = new ActiveDataProvider(array( + * $provider = new ActiveDataProvider([ * 'query' => Post::find(), - * 'pagination' => array( + * 'pagination' => [ * 'pageSize' => 20, - * ), - * )); + * ], + * ]); * * // get the posts in the current page * $posts = $provider->getModels(); @@ -37,30 +37,24 @@ use yii\db\Connection; * * ~~~ * $query = new Query; - * $provider = new ActiveDataProvider(array( + * $provider = new ActiveDataProvider([ * 'query' => $query->from('tbl_post'), - * 'pagination' => array( + * 'pagination' => [ * 'pageSize' => 20, - * ), - * )); + * ], + * ]); * * // get the posts in the current page * $posts = $provider->getModels(); * ~~~ * - * @property integer $count The number of data models in the current page. This property is read-only. - * @property array $keys The list of key values corresponding to [[models]]. Each data model in [[models]] is - * uniquely identified by the corresponding key value in this array. This property is read-only. - * @property array $models The list of data models in the current page. This property is read-only. - * @property integer $totalCount Total number of possible data models. - * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ -class ActiveDataProvider extends DataProvider +class ActiveDataProvider extends BaseDataProvider { /** - * @var Query the query that is used to fetch data models and [[totalCount]] + * @var QueryInterface the query that is used to fetch data models and [[totalCount]] * if it is not explicitly set. */ public $query; @@ -82,12 +76,8 @@ class ActiveDataProvider extends DataProvider */ public $db; - private $_models; - private $_keys; - private $_totalCount; - /** - * Initializes the DbCache component. + * Initializes the DB connection component. * This method will initialize the [[db]] property to make sure it refers to a valid DB connection. * @throws InvalidConfigException if [[db]] is invalid. */ @@ -103,123 +93,72 @@ class ActiveDataProvider extends DataProvider } /** - * Returns the number of data models in the current page. - * This is equivalent to `count($provider->models)`. - * When [[pagination]] is false, this is the same as [[totalCount]]. - * @return integer the number of data models in the current page. - */ - public function getCount() - { - return count($this->getModels()); - } - - /** - * Returns the total number of data models. - * When [[pagination]] is false, this returns the same value as [[count]]. - * If [[totalCount]] is not explicitly set, it will be calculated - * using [[query]] with a COUNT query. - * @return integer total number of possible data models. - * @throws InvalidConfigException + * @inheritdoc */ - public function getTotalCount() + protected function prepareModels() { - if ($this->getPagination() === false) { - return $this->getCount(); - } elseif ($this->_totalCount === null) { - if (!$this->query instanceof Query) { - throw new InvalidConfigException('The "query" property must be an instance of Query or its subclass.'); - } - $query = clone $this->query; - $this->_totalCount = $query->limit(-1)->offset(-1)->count('*', $this->db); + if (!$this->query instanceof QueryInterface) { + throw new InvalidConfigException('The "query" property must be an instance of a class that implements the QueryInterface e.g. yii\db\Query or its subclasses.'); } - return $this->_totalCount; - } - - /** - * Sets the total number of data models. - * @param integer $value the total number of data models. - */ - public function setTotalCount($value) - { - $this->_totalCount = $value; - } - - /** - * Returns the data models in the current page. - * @return array the list of data models in the current page. - * @throws InvalidConfigException if [[query]] is not set or invalid. - */ - public function getModels() - { - if ($this->_models === null) { - if (!$this->query instanceof Query) { - throw new InvalidConfigException('The "query" property must be an instance of Query or its subclass.'); - } - if (($pagination = $this->getPagination()) !== false) { - $pagination->totalCount = $this->getTotalCount(); - $this->query->limit($pagination->getLimit())->offset($pagination->getOffset()); - } - if (($sort = $this->getSort()) !== false) { - $this->query->addOrderBy($sort->getOrders()); - } - $this->_models = $this->query->all($this->db); + if (($pagination = $this->getPagination()) !== false) { + $pagination->totalCount = $this->getTotalCount(); + $this->query->limit($pagination->getLimit())->offset($pagination->getOffset()); + } + if (($sort = $this->getSort()) !== false) { + $this->query->addOrderBy($sort->getOrders()); } - return $this->_models; + return $this->query->all($this->db); } /** - * Returns the key values associated with the data models. - * @return array the list of key values corresponding to [[models]]. Each data model in [[models]] - * is uniquely identified by the corresponding key value in this array. + * @inheritdoc */ - public function getKeys() + protected function prepareKeys($models) { - if ($this->_keys === null) { - $this->_keys = array(); - $models = $this->getModels(); - if ($this->key !== null) { + $keys = []; + if ($this->key !== null) { + foreach ($models as $model) { + if (is_string($this->key)) { + $keys[] = $model[$this->key]; + } else { + $keys[] = call_user_func($this->key, $model); + } + } + return $keys; + } elseif ($this->query instanceof ActiveQueryInterface) { + /** @var \yii\db\ActiveRecord $class */ + $class = $this->query->modelClass; + $pks = $class::primaryKey(); + if (count($pks) === 1) { + $pk = $pks[0]; foreach ($models as $model) { - if (is_string($this->key)) { - $this->_keys[] = $model[$this->key]; - } else { - $this->_keys[] = call_user_func($this->key, $model); - } + $keys[] = $model[$pk]; } - } elseif ($this->query instanceof ActiveQuery) { - /** @var \yii\db\ActiveRecord $class */ - $class = $this->query->modelClass; - $pks = $class::primaryKey(); - if (count($pks) === 1) { - $pk = $pks[0]; - foreach ($models as $model) { - $this->_keys[] = $model[$pk]; - } - } else { - foreach ($models as $model) { - $keys = array(); - foreach ($pks as $pk) { - $keys[] = $model[$pk]; - } - $this->_keys[] = json_encode($keys); + } else { + foreach ($models as $model) { + $kk = []; + foreach ($pks as $pk) { + $kk[] = $model[$pk]; } + $keys[] = json_encode($kk); } - } else { - $this->_keys = array_keys($models); } + return $keys; + } else { + return array_keys($models); } - return $this->_keys; } /** - * Refreshes the data provider. - * After calling this method, if [[getModels()]], [[getKeys()]] or [[getTotalCount()]] is called again, - * they will re-execute the query and return the latest data available. + * @inheritdoc */ - public function refresh() + protected function prepareTotalCount() { - $this->_models = null; - $this->_totalCount = null; - $this->_keys = null; + if (!$this->query instanceof QueryInterface) { + throw new InvalidConfigException('The "query" property must be an instance of a class that implements the QueryInterface e.g. yii\db\Query or its subclasses.'); + } + $query = clone $this->query; + return (int) $query->limit(-1)->offset(-1)->orderBy([])->count('*', $this->db); } /** @@ -228,17 +167,15 @@ class ActiveDataProvider extends DataProvider public function setSort($value) { parent::setSort($value); - if (($sort = $this->getSort()) !== false && empty($sort->attributes) && - $this->query instanceof ActiveQuery) { - + if (($sort = $this->getSort()) !== false && empty($sort->attributes) && $this->query instanceof ActiveQueryInterface) { /** @var Model $model */ $model = new $this->query->modelClass; - foreach($model->attributes() as $attribute) { - $sort->attributes[$attribute] = array( - 'asc' => array($attribute => Sort::ASC), - 'desc' => array($attribute => Sort::DESC), + foreach ($model->attributes() as $attribute) { + $sort->attributes[$attribute] = [ + 'asc' => [$attribute => SORT_ASC], + 'desc' => [$attribute => SORT_DESC], 'label' => $model->getAttributeLabel($attribute), - ); + ]; } } } diff --git a/framework/yii/data/ArrayDataProvider.php b/framework/yii/data/ArrayDataProvider.php index d6eaca7..2b694c7 100644 --- a/framework/yii/data/ArrayDataProvider.php +++ b/framework/yii/data/ArrayDataProvider.php @@ -29,17 +29,15 @@ use yii\helpers\ArrayHelper; * * ~~~ * $query = new Query; - * $provider = new ArrayDataProvider(array( + * $provider = new ArrayDataProvider([ * 'allModels' => $query->from('tbl_post')->all(), - * 'sort' => array( - * 'attributes' => array( - * 'id', 'username', 'email', - * ), - * ), - * 'pagination' => array( + * 'sort' => [ + * 'attributes' => ['id', 'username', 'email'], + * ], + * 'pagination' => [ * 'pageSize' => 10, - * ), - * )); + * ], + * ]); * // get the posts in the current page * $posts = $provider->getModels(); * ~~~ @@ -47,15 +45,10 @@ use yii\helpers\ArrayHelper; * Note: if you want to use the sorting feature, you must configure the [[sort]] property * so that the provider knows which columns can be sorted. * - * @property array $keys The list of key values corresponding to [[models]]. Each data model in [[models]] is - * uniquely identified by the corresponding key value in this array. - * @property array $models The list of data models in the current page. - * @property integer $totalCount Total number of possible data models. - * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ -class ArrayDataProvider extends DataProvider +class ArrayDataProvider extends BaseDataProvider { /** * @var string|callable the column that is used as the key of the data models. @@ -71,101 +64,54 @@ class ArrayDataProvider extends DataProvider */ public $allModels; - private $_totalCount; /** - * Returns the total number of data models. - * @return integer total number of possible data models. + * @inheritdoc */ - public function getTotalCount() + protected function prepareModels() { - if ($this->getPagination() === false) { - return $this->getCount(); - } elseif ($this->_totalCount === null) { - $this->_totalCount = count($this->allModels); + if (($models = $this->allModels) === null) { + return []; } - return $this->_totalCount; - } - - /** - * Sets the total number of data models. - * @param integer $value the total number of data models. - */ - public function setTotalCount($value) - { - $this->_totalCount = $value; - } - - private $_models; - /** - * Returns the data models in the current page. - * @return array the list of data models in the current page. - */ - public function getModels() - { - if ($this->_models === null) { - if (($models = $this->allModels) === null) { - return array(); - } - - if (($sort = $this->getSort()) !== false) { - $models = $this->sortModels($models, $sort); - } - - if (($pagination = $this->getPagination()) !== false) { - $pagination->totalCount = $this->getTotalCount(); - $models = array_slice($models, $pagination->getOffset(), $pagination->getLimit()); - } + if (($sort = $this->getSort()) !== false) { + $models = $this->sortModels($models, $sort); + } - $this->_models = $models; + if (($pagination = $this->getPagination()) !== false) { + $pagination->totalCount = $this->getTotalCount(); + $models = array_slice($models, $pagination->getOffset(), $pagination->getLimit()); } - return $this->_models; - } - /** - * Sets the data models in the current page. - * @param array $models the models in the current page - */ - public function setModels($models) - { - $this->_models = $models; + return $models; } - private $_keys; - /** - * Returns the key values associated with the data models. - * @return array the list of key values corresponding to [[models]]. Each data model in [[models]] - * is uniquely identified by the corresponding key value in this array. + * @inheritdoc */ - public function getKeys() + protected function prepareKeys($models) { - if ($this->_keys === null) { - $this->_keys = array(); - $models = $this->getModels(); - if ($this->key !== null) { - foreach ($models as $model) { - if (is_string($this->key)) { - $this->_keys[] = $model[$this->key]; - } else { - $this->_keys[] = call_user_func($this->key, $model); - } + if ($this->key !== null) { + $keys = []; + foreach ($models as $model) { + if (is_string($this->key)) { + $keys[] = $model[$this->key]; + } else { + $keys[] = call_user_func($this->key, $model); } - } else { - $this->_keys = array_keys($models); } + return $keys; + } else { + return array_keys($models); } - return $this->_keys; } /** - * Sets the key values associated with the data models. - * @param array $keys the list of key values corresponding to [[models]]. + * @inheritdoc */ - public function setKeys($keys) + protected function prepareTotalCount() { - $this->_keys = $keys; + return count($this->allModels); } /** diff --git a/framework/yii/data/DataProvider.php b/framework/yii/data/BaseDataProvider.php similarity index 51% rename from framework/yii/data/DataProvider.php rename to framework/yii/data/BaseDataProvider.php index b29f616..cf094c7 100644 --- a/framework/yii/data/DataProvider.php +++ b/framework/yii/data/BaseDataProvider.php @@ -12,21 +12,23 @@ use yii\base\Component; use yii\base\InvalidParamException; /** - * DataProvider is the base class of data provider classes. - * - * It implements the [[getPagination()]] and [[getSort()]] methods as specified by the [[DataProviderInterface]]. + * BaseDataProvider provides a base class that implements the [[DataProviderInterface]]. * * @property integer $count The number of data models in the current page. This property is read-only. + * @property array $keys The list of key values corresponding to [[models]]. Each data model in [[models]] is + * uniquely identified by the corresponding key value in this array. + * @property array $models The list of data models in the current page. * @property Pagination|boolean $pagination The pagination object. If this is false, it means the pagination * is disabled. Note that the type of this property differs in getter and setter. See [[getPagination()]] and * [[setPagination()]] for details. * @property Sort|boolean $sort The sorting object. If this is false, it means the sorting is disabled. Note * that the type of this property differs in getter and setter. See [[getSort()]] and [[setSort()]] for details. + * @property integer $totalCount Total number of possible data models. * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ -abstract class DataProvider extends Component implements DataProviderInterface +abstract class BaseDataProvider extends Component implements DataProviderInterface { /** * @var string an ID that uniquely identifies the data provider among all data providers. @@ -37,17 +39,133 @@ abstract class DataProvider extends Component implements DataProviderInterface private $_sort; private $_pagination; + private $_keys; + private $_models; + private $_totalCount; + + + /** + * Prepares the data models that will be made available in the current page. + * @return array the available data models + */ + abstract protected function prepareModels(); + + /** + * Prepares the keys associated with the currently available data models. + * @param array $models the available data models + * @return array the keys + */ + abstract protected function prepareKeys($models); + + /** + * Returns a value indicating the total number of data models in this data provider. + * @return integer total number of data models in this data provider. + */ + abstract protected function prepareTotalCount(); + + /** + * Prepares the data models and keys. + * + * This method will prepare the data models and keys that can be retrieved via + * [[getModels()]] and [[getKeys()]]. + * + * This method will be implicitly called by [[getModels()]] and [[getKeys()]] if it has not been called before. + * + * @param boolean $forcePrepare whether to force data preparation even if it has been done before. + */ + public function prepare($forcePrepare = false) + { + if ($forcePrepare || $this->_models === null) { + $this->_models = $this->prepareModels(); + } + if ($forcePrepare || $this->_keys === null) { + $this->_keys = $this->prepareKeys($this->_models); + } + } + + /** + * Returns the data models in the current page. + * @return array the list of data models in the current page. + */ + public function getModels() + { + $this->prepare(); + return $this->_models; + } /** + * Sets the data models in the current page. + * @param array $models the models in the current page + */ + public function setModels($models) + { + $this->_models = $models; + } + + /** + * Returns the key values associated with the data models. + * @return array the list of key values corresponding to [[models]]. Each data model in [[models]] + * is uniquely identified by the corresponding key value in this array. + */ + public function getKeys() + { + $this->prepare(); + return $this->_keys; + } + + /** + * Sets the key values associated with the data models. + * @param array $keys the list of key values corresponding to [[models]]. + */ + public function setKeys($keys) + { + $this->_keys = $keys; + } + + /** + * Returns the number of data models in the current page. + * @return integer the number of data models in the current page. + */ + public function getCount() + { + return count($this->getModels()); + } + + /** + * Returns the total number of data models. + * When [[pagination]] is false, this returns the same value as [[count]]. + * Otherwise, it will call [[prepareTotalCount()]] to get the count. + * @return integer total number of possible data models. + */ + public function getTotalCount() + { + if ($this->getPagination() === false) { + return $this->getCount(); + } elseif ($this->_totalCount === null) { + $this->_totalCount = $this->prepareTotalCount(); + } + return $this->_totalCount; + } + + /** + * Sets the total number of data models. + * @param integer $value the total number of data models. + */ + public function setTotalCount($value) + { + $this->_totalCount = $value; + } + + /** + * Returns the pagination object used by this data provider. + * Note that you should call [[prepare()]] or [[getModels()]] first to get correct values + * of [[Pagination::totalCount]] and [[Pagination::pageCount]]. * @return Pagination|boolean the pagination object. If this is false, it means the pagination is disabled. */ public function getPagination() { if ($this->_pagination === null) { - $this->_pagination = new Pagination; - if ($this->id !== null) { - $this->_pagination->pageVar = $this->id . '-page'; - } + $this->setPagination([]); } return $this->_pagination; } @@ -67,9 +185,7 @@ abstract class DataProvider extends Component implements DataProviderInterface public function setPagination($value) { if (is_array($value)) { - $config = array( - 'class' => Pagination::className(), - ); + $config = ['class' => Pagination::className()]; if ($this->id !== null) { $config['pageVar'] = $this->id . '-page'; } @@ -87,7 +203,7 @@ abstract class DataProvider extends Component implements DataProviderInterface public function getSort() { if ($this->_sort === null) { - $this->setSort(array()); + $this->setSort([]); } return $this->_sort; } @@ -107,9 +223,7 @@ abstract class DataProvider extends Component implements DataProviderInterface public function setSort($value) { if (is_array($value)) { - $config = array( - 'class' => Sort::className(), - ); + $config = ['class' => Sort::className()]; if ($this->id !== null) { $config['sortVar'] = $this->id . '-sort'; } @@ -122,11 +236,14 @@ abstract class DataProvider extends Component implements DataProviderInterface } /** - * Returns the number of data models in the current page. - * @return integer the number of data models in the current page. + * Refreshes the data provider. + * After calling this method, if [[getModels()]], [[getKeys()]] or [[getTotalCount()]] is called again, + * they will re-execute the query and return the latest data available. */ - public function getCount() + public function refresh() { - return count($this->getModels()); + $this->_totalCount = null; + $this->_models = null; + $this->_keys = null; } } diff --git a/framework/yii/data/DataProviderInterface.php b/framework/yii/data/DataProviderInterface.php index f0bc39d..1dea1e6 100644 --- a/framework/yii/data/DataProviderInterface.php +++ b/framework/yii/data/DataProviderInterface.php @@ -19,6 +19,18 @@ namespace yii\data; interface DataProviderInterface { /** + * Prepares the data models and keys. + * + * This method will prepare the data models and keys that can be retrieved via + * [[getModels()]] and [[getKeys()]]. + * + * This method will be implicitly called by [[getModels()]] and [[getKeys()]] if it has not been called before. + * + * @param boolean $forcePrepare whether to force data preparation even if it has been done before. + */ + public function prepare($forcePrepare = false); + + /** * Returns the number of data models in the current page. * This is equivalent to `count($provider->getModels())`. * When [[pagination]] is false, this is the same as [[totalCount]]. diff --git a/framework/yii/data/Pagination.php b/framework/yii/data/Pagination.php index 04af828..6a85dd2 100644 --- a/framework/yii/data/Pagination.php +++ b/framework/yii/data/Pagination.php @@ -9,6 +9,7 @@ namespace yii\data; use Yii; use yii\base\Object; +use yii\web\Request; /** * Pagination represents information relevant to pagination of data items. @@ -26,17 +27,17 @@ use yii\base\Object; * ~~~ * function actionIndex() * { - * $query = Article::find()->where(array('status' => 1)); + * $query = Article::find()->where(['status' => 1]); * $countQuery = clone $query; - * $pages = new Pagination(array('totalCount' => $countQuery->count())); + * $pages = new Pagination(['totalCount' => $countQuery->count()]); * $models = $query->offset($pages->offset) * ->limit($pages->limit) * ->all(); * - * return $this->render('index', array( + * return $this->render('index', [ * 'models' => $models, * 'pages' => $pages, - * )); + * ]); * } * ~~~ * @@ -48,9 +49,9 @@ use yii\base\Object; * } * * // display pagination - * echo LinkPager::widget(array( + * echo LinkPager::widget([ * 'pagination' => $pages, - * )); + * ]); * ~~~ * * @property integer $limit The limit of the data. This may be used to set the LIMIT value for a SQL statement @@ -83,7 +84,7 @@ class Pagination extends Object public $route; /** * @var array parameters (name => value) that should be used to obtain the current page number - * and to create new pagination URLs. If not set, $_GET will be used instead. + * and to create new pagination URLs. If not set, all parameters from $_GET will be used instead. * * The array element indexed by [[pageVar]] is considered to be the current page number. * If the element does not exist, the current page number is considered 0. @@ -131,7 +132,10 @@ class Pagination extends Object public function getPage($recalculate = false) { if ($this->_page === null || $recalculate) { - $params = $this->params === null ? $_GET : $this->params; + if (($params = $this->params) === null) { + $request = Yii::$app->getRequest(); + $params = $request instanceof Request ? $request->get() : []; + } if (isset($params[$this->pageVar]) && is_scalar($params[$this->pageVar])) { $this->_page = (int)$params[$this->pageVar] - 1; if ($this->validatePage) { @@ -169,13 +173,16 @@ class Pagination extends Object */ public function createUrl($page) { - $params = $this->params === null ? $_GET : $this->params; + if (($params = $this->params) === null) { + $request = Yii::$app->getRequest(); + $params = $request instanceof Request ? $request->get() : []; + } if ($page > 0 || $page >= 0 && $this->forcePageVar) { $params[$this->pageVar] = $page + 1; } else { unset($params[$this->pageVar]); } - $route = $this->route === null ? Yii::$app->controller->route : $this->route; + $route = $this->route === null ? Yii::$app->controller->getRoute() : $this->route; return Yii::$app->getUrlManager()->createUrl($route, $params); } diff --git a/framework/yii/data/Sort.php b/framework/yii/data/Sort.php index 78fe2e0..7612641 100644 --- a/framework/yii/data/Sort.php +++ b/framework/yii/data/Sort.php @@ -25,27 +25,27 @@ use yii\helpers\Inflector; * ~~~ * function actionIndex() * { - * $sort = new Sort(array( - * 'attributes' => array( + * $sort = new Sort([ + * 'attributes' => [ * 'age', - * 'name' => array( - * 'asc' => array('first_name' => Sort::ASC, 'last_name' => Sort::ASC), - * 'desc' => array('first_name' => Sort::DESC, 'last_name' => Sort::DESC), - * 'default' => Sort::DESC, + * 'name' => [ + * 'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC], + * 'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC], + * 'default' => SORT_DESC, * 'label' => 'Name', - * ), - * ), - * )); + * ], + * ], + * ]); * * $models = Article::find() - * ->where(array('status' => 1)) + * ->where(['status' => 1]) * ->orderBy($sort->orders) * ->all(); * - * return $this->render('index', array( + * return $this->render('index', [ * 'models' => $models, * 'sort' => $sort, - * )); + * ]); * } * ~~~ * @@ -66,7 +66,7 @@ use yii\helpers\Inflector; * that can lead to pages with the data sorted by the corresponding attributes. * * @property array $attributeOrders Sort directions indexed by attribute names. Sort direction can be either - * [[Sort::ASC]] for ascending order or [[Sort::DESC]] for descending order. This property is read-only. + * `SORT_ASC` for ascending order or `SORT_DESC` for descending order. This property is read-only. * @property array $orders The columns (keys) and their corresponding sort directions (values). This can be * passed to [[\yii\db\Query::orderBy()]] to construct a DB query. This property is read-only. * @@ -76,16 +76,6 @@ use yii\helpers\Inflector; class Sort extends Object { /** - * Sort ascending - */ - const ASC = false; - - /** - * Sort descending - */ - const DESC = true; - - /** * @var boolean whether the sorting can be applied to multiple attributes simultaneously. * Defaults to false, which means each time the data can only be sorted by one attribute. */ @@ -96,27 +86,27 @@ class Sort extends Object * described using the following example: * * ~~~ - * array( + * [ * 'age', - * 'name' => array( - * 'asc' => array('first_name' => Sort::ASC, 'last_name' => Sort::ASC), - * 'desc' => array('first_name' => Sort::DESC, 'last_name' => Sort::DESC), - * 'default' => Sort::DESC, + * 'name' => [ + * 'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC], + * 'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC], + * 'default' => SORT_DESC, * 'label' => 'Name', - * ), - * ) + * ], + * ] * ~~~ * * In the above, two attributes are declared: "age" and "user". The "age" attribute is * a simple attribute which is equivalent to the following: * * ~~~ - * 'age' => array( - * 'asc' => array('age' => Sort::ASC), - * 'desc' => array('age' => Sort::DESC), - * 'default' => Sort::ASC, + * 'age' => [ + * 'asc' => ['age' => SORT_ASC], + * 'desc' => ['age' => SORT_DESC], + * 'default' => SORT_ASC, * 'label' => Inflector::camel2words('age'), - * ) + * ] * ~~~ * * The "user" attribute is a composite attribute: @@ -135,7 +125,7 @@ class Sort extends Object * Note that if the Sort object is already created, you can only use the full format * to configure every attribute. Each attribute must include these elements: asc and desc. */ - public $attributes = array(); + public $attributes = []; /** * @var string the name of the parameter that specifies which attributes to be sorted * in which direction. Defaults to 'sort'. @@ -152,10 +142,10 @@ class Sort extends Object * The array keys are attribute names and the array values are the corresponding sort directions. For example, * * ~~~ - * array( - * 'name' => Sort::ASC, - * 'create_time' => Sort::DESC, - * ) + * [ + * 'name' => SORT_ASC, + * 'create_time' => SORT_DESC, + * ] * ~~~ * * @see attributeOrders @@ -170,9 +160,9 @@ class Sort extends Object * @var array separators used in the generated URL. This must be an array consisting of * two elements. The first element specifies the character separating different * attributes, while the second element specifies the character separating attribute name - * and the corresponding sort direction. Defaults to `array('.', '-')`. + * and the corresponding sort direction. Defaults to `['.', '-']`. */ - public $separators = array('.', '-'); + public $separators = ['.', '-']; /** * @var array parameters (name => value) that should be used to obtain the current sort directions * and to create new sort URLs. If not set, $_GET will be used instead. @@ -195,18 +185,18 @@ class Sort extends Object */ public function init() { - $attributes = array(); + $attributes = []; foreach ($this->attributes as $name => $attribute) { if (!is_array($attribute)) { - $attributes[$attribute] = array( - 'asc' => array($attribute => self::ASC), - 'desc' => array($attribute => self::DESC), - ); + $attributes[$attribute] = [ + 'asc' => [$attribute => SORT_ASC], + 'desc' => [$attribute => SORT_DESC], + ]; } elseif (!isset($attribute['asc'], $attribute['desc'])) { - $attributes[$name] = array_merge(array( - 'asc' => array($name => self::ASC), - 'desc' => array($name => self::DESC), - ), $attribute); + $attributes[$name] = array_merge([ + 'asc' => [$name => SORT_ASC], + 'desc' => [$name => SORT_DESC], + ], $attribute); } else { $attributes[$name] = $attribute; } @@ -223,10 +213,10 @@ class Sort extends Object public function getOrders($recalculate = false) { $attributeOrders = $this->getAttributeOrders($recalculate); - $orders = array(); + $orders = []; foreach ($attributeOrders as $attribute => $direction) { $definition = $this->attributes[$attribute]; - $columns = $definition[$direction === self::ASC ? 'asc' : 'desc']; + $columns = $definition[$direction === SORT_ASC ? 'asc' : 'desc']; foreach ($columns as $name => $dir) { $orders[$name] = $dir; } @@ -234,19 +224,22 @@ class Sort extends Object return $orders; } + /** + * @var array the currently requested sort order as computed by [[getAttributeOrders]]. + */ private $_attributeOrders; /** * Returns the currently requested sort information. * @param boolean $recalculate whether to recalculate the sort directions * @return array sort directions indexed by attribute names. - * Sort direction can be either [[Sort::ASC]] for ascending order or - * [[Sort::DESC]] for descending order. + * Sort direction can be either `SORT_ASC` for ascending order or + * `SORT_DESC` for descending order. */ public function getAttributeOrders($recalculate = false) { if ($this->_attributeOrders === null || $recalculate) { - $this->_attributeOrders = array(); + $this->_attributeOrders = []; $params = $this->params === null ? $_GET : $this->params; if (isset($params[$this->sortVar]) && is_scalar($params[$this->sortVar])) { $attributes = explode($this->separators[0], $params[$this->sortVar]); @@ -259,7 +252,7 @@ class Sort extends Object } if (isset($this->attributes[$attribute])) { - $this->_attributeOrders[$attribute] = $descending; + $this->_attributeOrders[$attribute] = $descending ? SORT_DESC : SORT_ASC; if (!$this->enableMultiSort) { return $this->_attributeOrders; } @@ -276,8 +269,8 @@ class Sort extends Object /** * Returns the sort direction of the specified attribute in the current request. * @param string $attribute the attribute name - * @return boolean|null Sort direction of the attribute. Can be either [[Sort::ASC]] - * for ascending order or [[Sort::DESC]] for descending order. Null is returned + * @return boolean|null Sort direction of the attribute. Can be either `SORT_ASC` + * for ascending order or `SORT_DESC` for descending order. Null is returned * if the attribute is invalid or does not need to be sorted. */ public function getAttributeOrder($attribute) @@ -299,10 +292,10 @@ class Sort extends Object * @return string the generated hyperlink * @throws InvalidConfigException if the attribute is unknown */ - public function link($attribute, $options = array()) + public function link($attribute, $options = []) { if (($direction = $this->getAttributeOrder($attribute)) !== null) { - $class = $direction ? 'desc' : 'asc'; + $class = $direction === SORT_DESC ? 'desc' : 'asc'; if (isset($options['class'])) { $options['class'] .= ' ' . $class; } else { @@ -362,21 +355,21 @@ class Sort extends Object $definition = $this->attributes[$attribute]; $directions = $this->getAttributeOrders(); if (isset($directions[$attribute])) { - $descending = !$directions[$attribute]; + $direction = $directions[$attribute] === SORT_DESC ? SORT_ASC : SORT_DESC; unset($directions[$attribute]); } else { - $descending = !empty($definition['default']); + $direction = isset($definition['default']) ? $definition['default'] : SORT_ASC; } if ($this->enableMultiSort) { - $directions = array_merge(array($attribute => $descending), $directions); + $directions = array_merge([$attribute => $direction], $directions); } else { - $directions = array($attribute => $descending); + $directions = [$attribute => $direction]; } - $sorts = array(); - foreach ($directions as $attribute => $descending) { - $sorts[] = $descending ? $attribute . $this->separators[1] . $this->descTag : $attribute; + $sorts = []; + foreach ($directions as $attribute => $direction) { + $sorts[] = $direction === SORT_DESC ? $attribute . $this->separators[1] . $this->descTag : $attribute; } return implode($this->separators[0], $sorts); } diff --git a/framework/yii/db/ActiveQuery.php b/framework/yii/db/ActiveQuery.php index 12997ee..517bf22 100644 --- a/framework/yii/db/ActiveQuery.php +++ b/framework/yii/db/ActiveQuery.php @@ -11,8 +11,7 @@ namespace yii\db; /** * ActiveQuery represents a DB query associated with an Active Record class. * - * ActiveQuery instances are usually created by [[ActiveRecord::find()]], [[ActiveRecord::findBySql()]] - * and [[ActiveRecord::count()]]. + * ActiveQuery instances are usually created by [[ActiveRecord::find()]] and [[ActiveRecord::findBySql()]]. * * ActiveQuery mainly provides the following methods to retrieve the query results: * @@ -42,29 +41,13 @@ namespace yii\db; * ~~~ * * @author Qiang Xue <qiang.xue@gmail.com> + * @author Carsten Brandt <mail@cebe.cc> * @since 2.0 */ -class ActiveQuery extends Query +class ActiveQuery extends Query implements ActiveQueryInterface { - /** - * @var string the name of the ActiveRecord class. - */ - public $modelClass; - /** - * @var array list of relations that this query should be performed with - */ - public $with; - /** - * @var string|callable $column the name of the column by which the query results should be indexed by. - * This can also be a callable (e.g. anonymous function) that returns the index value based on the given - * row or model data. For more details, see [[indexBy()]]. - */ - public $indexBy; - /** - * @var boolean whether to return each record as an array. If false (default), an object - * of [[modelClass]] will be created to represent each record. - */ - public $asArray; + use ActiveQueryTrait; + /** * @var string the SQL statement to be executed for retrieving AR records. * This is set by [[ActiveRecord::findBySql()]]. @@ -73,25 +56,6 @@ class ActiveQuery extends Query /** - * PHP magic method. - * This method allows calling static method defined in [[modelClass]] via this query object. - * It is mainly implemented for supporting the feature of scope. - * @param string $name the method name to be called - * @param array $params the parameters passed to the method - * @return mixed the method return result - */ - public function __call($name, $params) - { - if (method_exists($this->modelClass, $name)) { - array_unshift($params, $this); - call_user_func_array(array($this->modelClass, $name), $params); - return $this; - } else { - return parent::__call($name, $params); - } - } - - /** * Executes query and returns all results as an array. * @param Connection $db the DB connection used to create the DB command. * If null, the DB connection returned by [[modelClass]] will be used. @@ -104,11 +68,11 @@ class ActiveQuery extends Query if (!empty($rows)) { $models = $this->createModels($rows); if (!empty($this->with)) { - $this->populateRelations($models, $this->with); + $this->findWith($this->with, $models); } return $models; } else { - return array(); + return []; } } @@ -128,13 +92,13 @@ class ActiveQuery extends Query if ($this->asArray) { $model = $row; } else { - /** @var $class ActiveRecord */ + /** @var ActiveRecord $class */ $class = $this->modelClass; $model = $class::create($row); } if (!empty($this->with)) { - $models = array($model); - $this->populateRelations($models, $this->with); + $models = [$model]; + $this->findWith($this->with, $models); $model = $models[0]; } return $model; @@ -151,7 +115,7 @@ class ActiveQuery extends Query */ public function createCommand($db = null) { - /** @var $modelClass ActiveRecord */ + /** @var ActiveRecord $modelClass */ $modelClass = $this->modelClass; if ($db === null) { $db = $modelClass::getDb(); @@ -162,167 +126,12 @@ class ActiveQuery extends Query if ($this->from === null) { $tableName = $modelClass::tableName(); if ($this->select === null && !empty($this->join)) { - $this->select = array("$tableName.*"); + $this->select = ["$tableName.*"]; } - $this->from = array($tableName); + $this->from = [$tableName]; } list ($this->sql, $params) = $db->getQueryBuilder()->build($this); } return $db->createCommand($this->sql, $params); } - - /** - * Sets the [[asArray]] property. - * @param boolean $value whether to return the query results in terms of arrays instead of Active Records. - * @return ActiveQuery the query object itself - */ - public function asArray($value = true) - { - $this->asArray = $value; - return $this; - } - - /** - * Specifies the relations with which this query should be performed. - * - * The parameters to this method can be either one or multiple strings, or a single array - * of relation names and the optional callbacks to customize the relations. - * - * The followings are some usage examples: - * - * ~~~ - * // find customers together with their orders and country - * Customer::find()->with('orders', 'country')->all(); - * // find customers together with their country and orders of status 1 - * Customer::find()->with(array( - * 'orders' => function($query) { - * $query->andWhere('status = 1'); - * }, - * 'country', - * ))->all(); - * ~~~ - * - * @return ActiveQuery the query object itself - */ - public function with() - { - $this->with = func_get_args(); - if (isset($this->with[0]) && is_array($this->with[0])) { - // the parameter is given as an array - $this->with = $this->with[0]; - } - return $this; - } - - /** - * Sets the [[indexBy]] property. - * @param string|callable $column the name of the column by which the query results should be indexed by. - * This can also be a callable (e.g. anonymous function) that returns the index value based on the given - * row or model data. The signature of the callable should be: - * - * ~~~ - * // $model is an AR instance when `asArray` is false, - * // or an array of column values when `asArray` is true. - * function ($model) - * { - * // return the index value corresponding to $model - * } - * ~~~ - * - * @return ActiveQuery the query object itself - */ - public function indexBy($column) - { - $this->indexBy = $column; - return $this; - } - - private function createModels($rows) - { - $models = array(); - if ($this->asArray) { - if ($this->indexBy === null) { - return $rows; - } - foreach ($rows as $row) { - if (is_string($this->indexBy)) { - $key = $row[$this->indexBy]; - } else { - $key = call_user_func($this->indexBy, $row); - } - $models[$key] = $row; - } - } else { - /** @var $class ActiveRecord */ - $class = $this->modelClass; - if ($this->indexBy === null) { - foreach ($rows as $row) { - $models[] = $class::create($row); - } - } else { - foreach ($rows as $row) { - $model = $class::create($row); - if (is_string($this->indexBy)) { - $key = $model->{$this->indexBy}; - } else { - $key = call_user_func($this->indexBy, $model); - } - $models[$key] = $model; - } - } - } - return $models; - } - - private function populateRelations(&$models, $with) - { - $primaryModel = new $this->modelClass; - $relations = $this->normalizeRelations($primaryModel, $with); - foreach ($relations as $name => $relation) { - if ($relation->asArray === null) { - // inherit asArray from primary query - $relation->asArray = $this->asArray; - } - $relation->findWith($name, $models); - } - } - - /** - * @param ActiveRecord $model - * @param array $with - * @return ActiveRelation[] - */ - private function normalizeRelations($model, $with) - { - $relations = array(); - foreach ($with as $name => $callback) { - if (is_integer($name)) { - $name = $callback; - $callback = null; - } - if (($pos = strpos($name, '.')) !== false) { - // with sub-relations - $childName = substr($name, $pos + 1); - $name = substr($name, 0, $pos); - } else { - $childName = null; - } - - $t = strtolower($name); - if (!isset($relations[$t])) { - $relation = $model->getRelation($name); - $relation->primaryModel = null; - $relations[$t] = $relation; - } else { - $relation = $relations[$t]; - } - - if (isset($childName)) { - $relation->with[$childName] = $callback; - } elseif ($callback !== null) { - call_user_func($callback, $relation); - } - } - return $relations; - } } diff --git a/framework/yii/db/ActiveQueryInterface.php b/framework/yii/db/ActiveQueryInterface.php new file mode 100644 index 0000000..a2e132f --- /dev/null +++ b/framework/yii/db/ActiveQueryInterface.php @@ -0,0 +1,77 @@ +<?php +/** + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\db; + +/** + * ActiveQueryInterface defines the common interface to be implemented by active record query classes. + * + * A class implementing this interface should also use [[ActiveQueryTrait]]. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @author Carsten Brandt <mail@cebe.cc> + * @since 2.0 + */ +interface ActiveQueryInterface extends QueryInterface +{ + /** + * Sets the [[asArray]] property. + * @param boolean $value whether to return the query results in terms of arrays instead of Active Records. + * @return static the query object itself + */ + public function asArray($value = true); + + /** + * Sets the [[indexBy]] property. + * @param string|callable $column the name of the column by which the query results should be indexed by. + * This can also be a callable (e.g. anonymous function) that returns the index value based on the given + * row or model data. The signature of the callable should be: + * + * ~~~ + * // $model is an AR instance when `asArray` is false, + * // or an array of column values when `asArray` is true. + * function ($model) + * { + * // return the index value corresponding to $model + * } + * ~~~ + * + * @return static the query object itself + */ + public function indexBy($column); + + /** + * Specifies the relations with which this query should be performed. + * + * The parameters to this method can be either one or multiple strings, or a single array + * of relation names and the optional callbacks to customize the relations. + * + * A relation name can refer to a relation defined in [[modelClass]] + * or a sub-relation that stands for a relation of a related record. + * For example, `orders.address` means the `address` relation defined + * in the model class corresponding to the `orders` relation. + * + * The followings are some usage examples: + * + * ~~~ + * // find customers together with their orders and country + * Customer::find()->with('orders', 'country')->all(); + * // find customers together with their orders and the orders' shipping address + * Customer::find()->with('orders.address')->all(); + * // find customers together with their country and orders of status 1 + * Customer::find()->with([ + * 'orders' => function($query) { + * $query->andWhere('status = 1'); + * }, + * 'country', + * ])->all(); + * ~~~ + * + * @return static the query object itself + */ + public function with(); +} diff --git a/framework/yii/db/ActiveQueryTrait.php b/framework/yii/db/ActiveQueryTrait.php new file mode 100644 index 0000000..7aae6e6 --- /dev/null +++ b/framework/yii/db/ActiveQueryTrait.php @@ -0,0 +1,201 @@ +<?php +/** + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\db; + +/** + * ActiveQueryTrait implements the common methods and properties for active record query classes. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @author Carsten Brandt <mail@cebe.cc> + * @since 2.0 + */ +trait ActiveQueryTrait +{ + /** + * @var string the name of the ActiveRecord class. + */ + public $modelClass; + /** + * @var array a list of relations that this query should be performed with + */ + public $with; + /** + * @var boolean whether to return each record as an array. If false (default), an object + * of [[modelClass]] will be created to represent each record. + */ + public $asArray; + + + /** + * PHP magic method. + * This method allows calling static method defined in [[modelClass]] via this query object. + * It is mainly implemented for supporting the feature of scope. + * @param string $name the method name to be called + * @param array $params the parameters passed to the method + * @return mixed the method return result + */ + public function __call($name, $params) + { + if (method_exists($this->modelClass, $name)) { + array_unshift($params, $this); + call_user_func_array([$this->modelClass, $name], $params); + return $this; + } else { + return parent::__call($name, $params); + } + } + + /** + * Sets the [[asArray]] property. + * @param boolean $value whether to return the query results in terms of arrays instead of Active Records. + * @return static the query object itself + */ + public function asArray($value = true) + { + $this->asArray = $value; + return $this; + } + + /** + * Specifies the relations with which this query should be performed. + * + * The parameters to this method can be either one or multiple strings, or a single array + * of relation names and the optional callbacks to customize the relations. + * + * A relation name can refer to a relation defined in [[modelClass]] + * or a sub-relation that stands for a relation of a related record. + * For example, `orders.address` means the `address` relation defined + * in the model class corresponding to the `orders` relation. + * + * The followings are some usage examples: + * + * ~~~ + * // find customers together with their orders and country + * Customer::find()->with('orders', 'country')->all(); + * // find customers together with their orders and the orders' shipping address + * Customer::find()->with('orders.address')->all(); + * // find customers together with their country and orders of status 1 + * Customer::find()->with([ + * 'orders' => function($query) { + * $query->andWhere('status = 1'); + * }, + * 'country', + * ])->all(); + * ~~~ + * + * @return static the query object itself + */ + public function with() + { + $this->with = func_get_args(); + if (isset($this->with[0]) && is_array($this->with[0])) { + // the parameter is given as an array + $this->with = $this->with[0]; + } + return $this; + } + + /** + * Converts found rows into model instances + * @param array $rows + * @return array|ActiveRecord[] + */ + private function createModels($rows) + { + $models = []; + if ($this->asArray) { + if ($this->indexBy === null) { + return $rows; + } + foreach ($rows as $row) { + if (is_string($this->indexBy)) { + $key = $row[$this->indexBy]; + } else { + $key = call_user_func($this->indexBy, $row); + } + $models[$key] = $row; + } + } else { + /** @var ActiveRecord $class */ + $class = $this->modelClass; + if ($this->indexBy === null) { + foreach ($rows as $row) { + $models[] = $class::create($row); + } + } else { + foreach ($rows as $row) { + $model = $class::create($row); + if (is_string($this->indexBy)) { + $key = $model->{$this->indexBy}; + } else { + $key = call_user_func($this->indexBy, $model); + } + $models[$key] = $model; + } + } + } + return $models; + } + + /** + * Finds records corresponding to one or multiple relations and populates them into the primary models. + * @param array $with a list of relations that this query should be performed with. Please + * refer to [[with()]] for details about specifying this parameter. + * @param ActiveRecord[] $models the primary models + */ + public function findWith($with, &$models) + { + $primaryModel = new $this->modelClass; + $relations = $this->normalizeRelations($primaryModel, $with); + foreach ($relations as $name => $relation) { + if ($relation->asArray === null) { + // inherit asArray from primary query + $relation->asArray = $this->asArray; + } + $relation->populateRelation($name, $models); + } + } + + /** + * @param ActiveRecord $model + * @param array $with + * @return ActiveRelationInterface[] + */ + private function normalizeRelations($model, $with) + { + $relations = []; + foreach ($with as $name => $callback) { + if (is_integer($name)) { + $name = $callback; + $callback = null; + } + if (($pos = strpos($name, '.')) !== false) { + // with sub-relations + $childName = substr($name, $pos + 1); + $name = substr($name, 0, $pos); + } else { + $childName = null; + } + + if (!isset($relations[$name])) { + $relation = $model->getRelation($name); + $relation->primaryModel = null; + $relations[$name] = $relation; + } else { + $relation = $relations[$name]; + } + + if (isset($childName)) { + $relation->with[$childName] = $callback; + } elseif ($callback !== null) { + call_user_func($callback, $relation); + } + } + return $relations; + } +} diff --git a/framework/yii/db/ActiveRecord.php b/framework/yii/db/ActiveRecord.php index 262ceed..83e5c7e 100644 --- a/framework/yii/db/ActiveRecord.php +++ b/framework/yii/db/ActiveRecord.php @@ -29,11 +29,14 @@ use yii\helpers\Inflector; * @property mixed $oldPrimaryKey The old primary key value. An array (column name => column value) is * returned if the primary key is composite or `$asArray` is true. A string is returned otherwise (null will be * returned if the key value is null). This property is read-only. + * @property array $populatedRelations An array of relation data indexed by relation names. This property is + * read-only. * @property mixed $primaryKey The primary key value. An array (column name => column value) is returned if * the primary key is composite or `$asArray` is true. A string is returned otherwise (null will be returned if * the key value is null). This property is read-only. * * @author Qiang Xue <qiang.xue@gmail.com> + * @author Carsten Brandt <mail@cebe.cc> * @since 2.0 */ class ActiveRecord extends Model @@ -95,7 +98,7 @@ class ActiveRecord extends Model /** * @var array attribute values indexed by attribute names */ - private $_attributes = array(); + private $_attributes = []; /** * @var array old attribute values indexed by attribute names. */ @@ -103,7 +106,7 @@ class ActiveRecord extends Model /** * @var array related models indexed by the relation names */ - private $_related; + private $_related = []; /** @@ -144,7 +147,7 @@ class ActiveRecord extends Model // query by primary key $primaryKey = static::primaryKey(); if (isset($primaryKey[0])) { - return $query->where(array($primaryKey[0] => $q))->one(); + return $query->where([$primaryKey[0] => $q])->one(); } else { throw new InvalidConfigException(get_called_class() . ' must have a primary key.'); } @@ -170,7 +173,7 @@ class ActiveRecord extends Model * @param array $params parameters to be bound to the SQL statement during execution. * @return ActiveQuery the newly created [[ActiveQuery]] instance */ - public static function findBySql($sql, $params = array()) + public static function findBySql($sql, $params = []) { $query = static::createQuery(); $query->sql = $sql; @@ -182,7 +185,7 @@ class ActiveRecord extends Model * For example, to change the status to be 1 for all customers whose status is 2: * * ~~~ - * Customer::updateAll(array('status' => 1), 'status = 2'); + * Customer::updateAll(['status' => 1], 'status = 2'); * ~~~ * * @param array $attributes attribute values (name-value pairs) to be saved into the table @@ -191,7 +194,7 @@ class ActiveRecord extends Model * @param array $params the parameters (name => value) to be bound to the query. * @return integer the number of rows updated */ - public static function updateAll($attributes, $condition = '', $params = array()) + public static function updateAll($attributes, $condition = '', $params = []) { $command = static::getDb()->createCommand(); $command->update(static::tableName(), $attributes, $condition, $params); @@ -203,7 +206,7 @@ class ActiveRecord extends Model * For example, to increment all customers' age by 1, * * ~~~ - * Customer::updateAllCounters(array('age' => 1)); + * Customer::updateAllCounters(['age' => 1]); * ~~~ * * @param array $counters the counters to be updated (attribute name => increment value). @@ -214,11 +217,11 @@ class ActiveRecord extends Model * Do not name the parameters as `:bp0`, `:bp1`, etc., because they are used internally by this method. * @return integer the number of rows updated */ - public static function updateAllCounters($counters, $condition = '', $params = array()) + public static function updateAllCounters($counters, $condition = '', $params = []) { $n = 0; foreach ($counters as $name => $value) { - $counters[$name] = new Expression("[[$name]]+:bp{$n}", array(":bp{$n}" => $value)); + $counters[$name] = new Expression("[[$name]]+:bp{$n}", [":bp{$n}" => $value]); $n++; } $command = static::getDb()->createCommand(); @@ -241,7 +244,7 @@ class ActiveRecord extends Model * @param array $params the parameters (name => value) to be bound to the query. * @return integer the number of rows deleted */ - public static function deleteAll($condition = '', $params = array()) + public static function deleteAll($condition = '', $params = []) { $command = static::getDb()->createCommand(); $command->delete(static::tableName(), $condition, $params); @@ -250,16 +253,14 @@ class ActiveRecord extends Model /** * Creates an [[ActiveQuery]] instance. - * This method is called by [[find()]], [[findBySql()]] and [[count()]] to start a SELECT query. + * This method is called by [[find()]], [[findBySql()]] to start a SELECT query. * You may override this method to return a customized query (e.g. `CustomerQuery` specified * written for querying `Customer` purpose.) * @return ActiveQuery the newly created [[ActiveQuery]] instance. */ public static function createQuery() { - return new ActiveQuery(array( - 'modelClass' => get_called_class(), - )); + return new ActiveQuery(['modelClass' => get_called_class()]); } /** @@ -346,13 +347,13 @@ class ActiveRecord extends Model * that need to be transactional. For example, * * ~~~ - * return array( + * return [ * 'admin' => self::OP_INSERT, * 'api' => self::OP_INSERT | self::OP_UPDATE | self::OP_DELETE, * // the above is equivalent to the following: * // 'api' => self::OP_ALL, * - * ); + * ]; * ~~~ * * The above declaration specifies that in the "admin" scenario, the insert operation ([[insert()]]) @@ -364,7 +365,7 @@ class ActiveRecord extends Model */ public function transactions() { - return array(); + return []; } /** @@ -372,22 +373,21 @@ class ActiveRecord extends Model * This method is overridden so that attributes and related objects can be accessed like properties. * @param string $name property name * @return mixed property value - * @see getAttribute + * @see getAttribute() */ public function __get($name) { if (isset($this->_attributes[$name]) || array_key_exists($name, $this->_attributes)) { return $this->_attributes[$name]; - } elseif (isset($this->getTableSchema()->columns[$name])) { + } elseif ($this->hasAttribute($name)) { return null; } else { - $t = strtolower($name); - if (isset($this->_related[$t]) || $this->_related !== null && array_key_exists($t, $this->_related)) { - return $this->_related[$t]; + if (isset($this->_related[$name]) || array_key_exists($name, $this->_related)) { + return $this->_related[$name]; } $value = parent::__get($name); - if ($value instanceof ActiveRelation || $value instanceof \yii\redis\ActiveRelation) { // TODO this should be done differently remove dep on redis - return $this->_related[$t] = $value->multiple ? $value->all() : $value->one(); + if ($value instanceof ActiveRelationInterface) { + return $this->_related[$name] = $value->multiple ? $value->all() : $value->one(); } else { return $value; } @@ -432,12 +432,11 @@ class ActiveRecord extends Model */ public function __unset($name) { - if (isset($this->getTableSchema()->columns[$name])) { + if ($this->hasAttribute($name)) { unset($this->_attributes[$name]); } else { - $t = strtolower($name); - if (isset($this->_related[$t])) { - unset($this->_related[$t]); + if (isset($this->_related[$name])) { + unset($this->_related[$name]); } else { parent::__unset($name); } @@ -458,7 +457,7 @@ class ActiveRecord extends Model * ~~~ * public function getCountry() * { - * return $this->hasOne('Country', array('id' => 'country_id')); + * return $this->hasOne(Country::className(), ['id' => 'country_id']); * } * ~~~ * @@ -476,12 +475,12 @@ class ActiveRecord extends Model */ public function hasOne($class, $link) { - return new ActiveRelation(array( - 'modelClass' => $this->getNamespacedClass($class), + return $this->createActiveRelation([ + 'modelClass' => $class, 'primaryModel' => $this, 'link' => $link, 'multiple' => false, - )); + ]); } /** @@ -498,7 +497,7 @@ class ActiveRecord extends Model * ~~~ * public function getOrders() * { - * return $this->hasMany('Order', array('customer_id' => 'id')); + * return $this->hasMany(Order::className(), ['customer_id' => 'id']); * } * ~~~ * @@ -514,23 +513,54 @@ class ActiveRecord extends Model */ public function hasMany($class, $link) { - return new ActiveRelation(array( - 'modelClass' => $this->getNamespacedClass($class), + return $this->createActiveRelation([ + 'modelClass' => $class, 'primaryModel' => $this, 'link' => $link, 'multiple' => true, - )); + ]); + } + + /** + * Creates an [[ActiveRelation]] instance. + * This method is called by [[hasOne()]] and [[hasMany()]] to create a relation instance. + * You may override this method to return a customized relation. + * @param array $config the configuration passed to the ActiveRelation class. + * @return ActiveRelation the newly created [[ActiveRelation]] instance. + */ + protected function createActiveRelation($config = []) + { + return new ActiveRelation($config); } /** * Populates the named relation with the related records. * Note that this method does not check if the relation exists or not. - * @param string $name the relation name (case-insensitive) + * @param string $name the relation name (case-sensitive) * @param ActiveRecord|array|null the related records to be populated into the relation. */ public function populateRelation($name, $records) { - $this->_related[strtolower($name)] = $records; + $this->_related[$name] = $records; + } + + /** + * Check whether the named relation has been populated with records. + * @param string $name the relation name (case-sensitive) + * @return bool whether relation has been populated with records. + */ + public function isRelationPopulated($name) + { + return array_key_exists($name, $this->_related); + } + + /** + * Returns all populated relations. + * @return array an array of relation data indexed by relation names. + */ + public function getPopulatedRelations() + { + return $this->_related; } /** @@ -544,12 +574,22 @@ class ActiveRecord extends Model } /** + * Returns a value indicating whether the model has an attribute with the specified name. + * @param string $name the name of the attribute + * @return boolean whether the model has an attribute with the specified name. + */ + public function hasAttribute($name) + { + return isset($this->_attributes[$name]) || isset($this->getTableSchema()->columns[$name]); + } + + /** * Returns the named attribute value. * If this record is the result of a query and the attribute is not loaded, * null will be returned. * @param string $name the attribute name * @return mixed the attribute value. Null if the attribute is not set or does not exist. - * @see hasAttribute + * @see hasAttribute() */ public function getAttribute($name) { @@ -561,7 +601,7 @@ class ActiveRecord extends Model * @param string $name the attribute name * @param mixed $value the attribute value. * @throws InvalidParamException if the named attribute does not exist. - * @see hasAttribute + * @see hasAttribute() */ public function setAttribute($name, $value) { @@ -573,22 +613,12 @@ class ActiveRecord extends Model } /** - * Returns a value indicating whether the model has an attribute with the specified name. - * @param string $name the name of the attribute - * @return boolean whether the model has an attribute with the specified name. - */ - public function hasAttribute($name) - { - return isset($this->_attributes[$name]) || isset($this->getTableSchema()->columns[$name]); - } - - /** * Returns the old attribute values. * @return array the old attribute values (name-value pairs) */ public function getOldAttributes() { - return $this->_oldAttributes === null ? array() : $this->_oldAttributes; + return $this->_oldAttributes === null ? [] : $this->_oldAttributes; } /** @@ -608,7 +638,7 @@ class ActiveRecord extends Model * @param string $name the attribute name * @return mixed the old attribute value. Null if the attribute is not loaded before * or does not exist. - * @see hasAttribute + * @see hasAttribute() */ public function getOldAttribute($name) { @@ -620,11 +650,11 @@ class ActiveRecord extends Model * @param string $name the attribute name * @param mixed $value the old attribute value. * @throws InvalidParamException if the named attribute does not exist. - * @see hasAttribute + * @see hasAttribute() */ public function setOldAttribute($name, $value) { - if (isset($this->_oldAttributes[$name]) || isset($this->getTableSchema()->columns[$name])) { + if (isset($this->_oldAttributes[$name]) || $this->hasAttribute($name)) { $this->_oldAttributes[$name] = $value; } else { throw new InvalidParamException(get_class($this) . ' has no attribute named "' . $name . '".'); @@ -657,7 +687,7 @@ class ActiveRecord extends Model $names = $this->attributes(); } $names = array_flip($names); - $attributes = array(); + $attributes = []; if ($this->_oldAttributes === null) { foreach ($this->_attributes as $name => $value) { if (isset($names[$name])) { @@ -924,7 +954,7 @@ class ActiveRecord extends Model * * ~~~ * $post = Post::find($id); - * $post->updateCounters(array('view_count' => 1)); + * $post->updateCounters(['view_count' => 1]); * ~~~ * * @param array $counters the counters to be updated (attribute name => increment value) @@ -1013,7 +1043,7 @@ class ActiveRecord extends Model /** * Sets the value indicating whether the record is new. * @param boolean $value whether the record is new and should be inserted when calling [[save()]]. - * @see getIsNewRecord + * @see getIsNewRecord() */ public function setIsNewRecord($value) { @@ -1127,26 +1157,20 @@ class ActiveRecord extends Model /** * Repopulates this active record with the latest data. - * @param array $attributes * @return boolean whether the row still exists in the database. If true, the latest data - * will be populated to this active record. + * will be populated to this active record. Otherwise, this record will remain unchanged. */ - public function refresh($attributes = null) + public function refresh() { $record = $this->find($this->getPrimaryKey(true)); if ($record === null) { return false; } - if ($attributes === null) { - foreach ($this->attributes() as $name) { - $this->_attributes[$name] = $record->_attributes[$name]; - } - $this->_oldAttributes = $this->_attributes; - } else { - foreach ($attributes as $name) { - $this->_oldAttributes[$name] = $this->_attributes[$name] = $record->_attributes[$name]; - } + foreach ($this->attributes() as $name) { + $this->_attributes[$name] = $record->_attributes[$name]; } + $this->_oldAttributes = $this->_attributes; + $this->_related = []; return true; } @@ -1176,7 +1200,7 @@ class ActiveRecord extends Model if (count($keys) === 1 && !$asArray) { return isset($this->_attributes[$keys[0]]) ? $this->_attributes[$keys[0]] : null; } else { - $values = array(); + $values = []; foreach ($keys as $name) { $values[$name] = isset($this->_attributes[$name]) ? $this->_attributes[$name] : null; } @@ -1202,7 +1226,7 @@ class ActiveRecord extends Model if (count($keys) === 1 && !$asArray) { return isset($this->_oldAttributes[$keys[0]]) ? $this->_oldAttributes[$keys[0]] : null; } else { - $values = array(); + $values = []; foreach ($keys as $name) { $values[$name] = isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null; } @@ -1272,8 +1296,10 @@ class ActiveRecord extends Model $getter = 'get' . $name; try { $relation = $this->$getter(); - if ($relation instanceof ActiveRelation || $relation instanceof \yii\redis\ActiveRelation) { // TODO this should be done differently remove dep on redis + if ($relation instanceof ActiveRelationInterface) { return $relation; + } else { + throw new InvalidParamException(get_class($this) . ' has no relation named "' . $name . '".'); } } catch (UnknownMethodException $e) { throw new InvalidParamException(get_class($this) . ' has no relation named "' . $name . '".', 0, $e); @@ -1292,14 +1318,14 @@ class ActiveRecord extends Model * * Note that this method requires that the primary key value is not null. * - * @param string $name the name of the relationship + * @param string $name the case sensitive name of the relationship * @param ActiveRecord $model the model to be linked with the current one. * @param array $extraColumns additional column values to be saved into the pivot table. * This parameter is only meaningful for a relationship involving a pivot table * (i.e., a relation set with `[[ActiveRelation::via()]]` or `[[ActiveRelation::viaTable()]]`.) * @throws InvalidCallException if the method is unable to link two models. */ - public function link($name, $model, $extraColumns = array()) + public function link($name, $model, $extraColumns = []) { $relation = $this->getRelation($name); @@ -1308,16 +1334,16 @@ class ActiveRecord extends Model throw new InvalidCallException('Unable to link models: both models must NOT be newly created.'); } if (is_array($relation->via)) { - /** @var $viaRelation ActiveRelation */ + /** @var ActiveRelation $viaRelation */ list($viaName, $viaRelation) = $relation->via; $viaClass = $viaRelation->modelClass; // unset $viaName so that it can be reloaded to reflect the change - unset($this->_related[strtolower($viaName)]); + unset($this->_related[$viaName]); } else { $viaRelation = $relation->via; $viaTable = reset($relation->via->from); } - $columns = array(); + $columns = []; foreach ($viaRelation->link as $a => $b) { $columns[$a] = $this->$b; } @@ -1379,7 +1405,7 @@ class ActiveRecord extends Model * The model with the foreign key of the relationship will be deleted if `$delete` is true. * Otherwise, the foreign key will be set null and the model will be saved without validation. * - * @param string $name the name of the relationship. + * @param string $name the case sensitive name of the relationship. * @param ActiveRecord $model the model to be unlinked from the current one. * @param boolean $delete whether to delete the model that contains the foreign key. * If false, the model's foreign key will be set null and saved. @@ -1392,15 +1418,15 @@ class ActiveRecord extends Model if ($relation->via !== null) { if (is_array($relation->via)) { - /** @var $viaRelation ActiveRelation */ + /** @var ActiveRelation $viaRelation */ list($viaName, $viaRelation) = $relation->via; $viaClass = $viaRelation->modelClass; - unset($this->_related[strtolower($viaName)]); + unset($this->_related[$viaName]); } else { $viaRelation = $relation->via; $viaTable = reset($relation->via->from); } - $columns = array(); + $columns = []; foreach ($viaRelation->link as $a => $b) { $columns[$a] = $this->$b; } @@ -1412,7 +1438,7 @@ class ActiveRecord extends Model if ($delete) { $viaClass::deleteAll($columns); } else { - $nulls = array(); + $nulls = []; foreach (array_keys($columns) as $a) { $nulls[$a] = null; } @@ -1424,7 +1450,7 @@ class ActiveRecord extends Model if ($delete) { $command->delete($viaTable, $columns)->execute(); } else { - $nulls = array(); + $nulls = []; foreach (array_keys($columns) as $a) { $nulls[$a] = null; } @@ -1452,7 +1478,7 @@ class ActiveRecord extends Model if (!$relation->multiple) { unset($this->_related[$name]); } elseif (isset($this->_related[$name])) { - /** @var $b ActiveRecord */ + /** @var ActiveRecord $b */ foreach ($this->_related[$name] as $a => $b) { if ($model->getPrimaryKey() == $b->getPrimaryKey()) { unset($this->_related[$name][$a]); @@ -1462,24 +1488,6 @@ class ActiveRecord extends Model } /** - * Changes the given class name into a namespaced one. - * If the given class name is already namespaced, no change will be made. - * Otherwise, the class name will be changed to use the same namespace as - * the current AR class. - * @param string $class the class name to be namespaced - * @return string the namespaced class name - */ - protected static function getNamespacedClass($class) - { - if (strpos($class, '\\') === false) { - $reflector = new \ReflectionClass(static::className()); - return $reflector->getNamespaceName() . '\\' . $class; - } else { - return $class; - } - } - - /** * @param array $link * @param ActiveRecord $foreignModel * @param ActiveRecord $primaryModel @@ -1510,7 +1518,7 @@ class ActiveRecord extends Model return false; } } - return true; + return count($keys) === count($pks); } /** diff --git a/framework/yii/db/ActiveRelation.php b/framework/yii/db/ActiveRelation.php index f6a6f6a..b016c5c 100644 --- a/framework/yii/db/ActiveRelation.php +++ b/framework/yii/db/ActiveRelation.php @@ -8,8 +8,6 @@ namespace yii\db; -use yii\base\InvalidConfigException; - /** * ActiveRelation represents a relation between two Active Record classes. * @@ -23,60 +21,12 @@ use yii\base\InvalidConfigException; * If a relation involves a pivot table, it may be specified by [[via()]] or [[viaTable()]] method. * * @author Qiang Xue <qiang.xue@gmail.com> + * @author Carsten Brandt <mail@cebe.cc> * @since 2.0 */ -class ActiveRelation extends ActiveQuery +class ActiveRelation extends ActiveQuery implements ActiveRelationInterface { - /** - * @var boolean whether this relation should populate all query results into AR instances. - * If false, only the first row of the results will be retrieved. - */ - public $multiple; - /** - * @var ActiveRecord the primary model that this relation is associated with. - * This is used only in lazy loading with dynamic query options. - */ - public $primaryModel; - /** - * @var array the columns of the primary and foreign tables that establish the relation. - * The array keys must be columns of the table for this relation, and the array values - * must be the corresponding columns from the primary table. - * Do not prefix or quote the column names as this will be done automatically by Yii. - */ - public $link; - /** - * @var array|ActiveRelation the query associated with the pivot table. Please call [[via()]] - * or [[viaTable()]] to set this property instead of directly setting it. - */ - public $via; - - /** - * Clones internal objects. - */ - public function __clone() - { - if (is_object($this->via)) { - // make a clone of "via" object so that the same query object can be reused multiple times - $this->via = clone $this->via; - } - } - - /** - * Specifies the relation associated with the pivot table. - * @param string $relationName the relation name. This refers to a relation declared in [[primaryModel]]. - * @param callable $callable a PHP callback for customizing the relation associated with the pivot table. - * Its signature should be `function($query)`, where `$query` is the query to be customized. - * @return ActiveRelation the relation object itself. - */ - public function via($relationName, $callable = null) - { - $relation = $this->primaryModel->getRelation($relationName); - $this->via = array($relationName, $relation); - if ($callable !== null) { - call_user_func($callable, $relation); - } - return $this; - } + use ActiveRelationTrait; /** * Specifies the pivot table. @@ -86,17 +36,17 @@ class ActiveRelation extends ActiveQuery * in the [[primaryModel]] table. * @param callable $callable a PHP callback for customizing the relation associated with the pivot table. * Its signature should be `function($query)`, where `$query` is the query to be customized. - * @return ActiveRelation + * @return static */ public function viaTable($tableName, $link, $callable = null) { - $relation = new ActiveRelation(array( + $relation = new ActiveRelation([ 'modelClass' => get_class($this->primaryModel), - 'from' => array($tableName), + 'from' => [$tableName], 'link' => $link, 'multiple' => true, 'asArray' => true, - )); + ]); $this->via = $relation; if ($callable !== null) { call_user_func($callable, $relation); @@ -116,11 +66,11 @@ class ActiveRelation extends ActiveQuery // lazy loading if ($this->via instanceof self) { // via pivot table - $viaModels = $this->via->findPivotRows(array($this->primaryModel)); + $viaModels = $this->via->findPivotRows([$this->primaryModel]); $this->filterByModels($viaModels); } elseif (is_array($this->via)) { // via relation - /** @var $viaQuery ActiveRelation */ + /** @var ActiveRelation $viaQuery */ list($viaName, $viaQuery) = $this->via; if ($viaQuery->multiple) { $viaModels = $viaQuery->all(); @@ -128,188 +78,13 @@ class ActiveRelation extends ActiveQuery } else { $model = $viaQuery->one(); $this->primaryModel->populateRelation($viaName, $model); - $viaModels = $model === null ? array() : array($model); + $viaModels = $model === null ? [] : [$model]; } $this->filterByModels($viaModels); } else { - $this->filterByModels(array($this->primaryModel)); + $this->filterByModels([$this->primaryModel]); } } return parent::createCommand($db); } - - /** - * Finds the related records and populates them into the primary models. - * This method is internally used by [[ActiveQuery]]. Do not call it directly. - * @param string $name the relation name - * @param array $primaryModels primary models - * @return array the related models - * @throws InvalidConfigException - */ - public function findWith($name, &$primaryModels) - { - if (!is_array($this->link)) { - throw new InvalidConfigException('Invalid link: it must be an array of key-value pairs.'); - } - - if ($this->via instanceof self) { - // via pivot table - /** @var $viaQuery ActiveRelation */ - $viaQuery = $this->via; - $viaModels = $viaQuery->findPivotRows($primaryModels); - $this->filterByModels($viaModels); - } elseif (is_array($this->via)) { - // via relation - /** @var $viaQuery ActiveRelation */ - list($viaName, $viaQuery) = $this->via; - $viaQuery->primaryModel = null; - $viaModels = $viaQuery->findWith($viaName, $primaryModels); - $this->filterByModels($viaModels); - } else { - $this->filterByModels($primaryModels); - } - - if (count($primaryModels) === 1 && !$this->multiple) { - $model = $this->one(); - foreach ($primaryModels as $i => $primaryModel) { - if ($primaryModel instanceof ActiveRecord) { - $primaryModel->populateRelation($name, $model); - } else { - $primaryModels[$i][$name] = $model; - } - } - return array($model); - } else { - $models = $this->all(); - if (isset($viaModels, $viaQuery)) { - $buckets = $this->buildBuckets($models, $this->link, $viaModels, $viaQuery->link); - } else { - $buckets = $this->buildBuckets($models, $this->link); - } - - $link = array_values(isset($viaQuery) ? $viaQuery->link : $this->link); - foreach ($primaryModels as $i => $primaryModel) { - $key = $this->getModelKey($primaryModel, $link); - $value = isset($buckets[$key]) ? $buckets[$key] : ($this->multiple ? array() : null); - if ($primaryModel instanceof ActiveRecord) { - $primaryModel->populateRelation($name, $value); - } else { - $primaryModels[$i][$name] = $value; - } - } - return $models; - } - } - - /** - * @param array $models - * @param array $link - * @param array $viaModels - * @param array $viaLink - * @return array - */ - private function buildBuckets($models, $link, $viaModels = null, $viaLink = null) - { - $buckets = array(); - $linkKeys = array_keys($link); - foreach ($models as $i => $model) { - $key = $this->getModelKey($model, $linkKeys); - if ($this->indexBy !== null) { - $buckets[$key][$i] = $model; - } else { - $buckets[$key][] = $model; - } - } - - if ($viaModels !== null) { - $viaBuckets = array(); - $viaLinkKeys = array_keys($viaLink); - $linkValues = array_values($link); - foreach ($viaModels as $viaModel) { - $key1 = $this->getModelKey($viaModel, $viaLinkKeys); - $key2 = $this->getModelKey($viaModel, $linkValues); - if (isset($buckets[$key2])) { - foreach ($buckets[$key2] as $i => $bucket) { - if ($this->indexBy !== null) { - $viaBuckets[$key1][$i] = $bucket; - } else { - $viaBuckets[$key1][] = $bucket; - } - } - } - } - $buckets = $viaBuckets; - } - - if (!$this->multiple) { - foreach ($buckets as $i => $bucket) { - $buckets[$i] = reset($bucket); - } - } - return $buckets; - } - - /** - * @param ActiveRecord|array $model - * @param array $attributes - * @return string - */ - private function getModelKey($model, $attributes) - { - if (count($attributes) > 1) { - $key = array(); - foreach ($attributes as $attribute) { - $key[] = $model[$attribute]; - } - return serialize($key); - } else { - $attribute = reset($attributes); - return $model[$attribute]; - } - } - - /** - * @param array $models - */ - private function filterByModels($models) - { - $attributes = array_keys($this->link); - $values = array(); - if (count($attributes) === 1) { - // single key - $attribute = reset($this->link); - foreach ($models as $model) { - if (($value = $model[$attribute]) !== null) { - $values[] = $value; - } - } - } else { - // composite keys - foreach ($models as $model) { - $v = array(); - foreach ($this->link as $attribute => $link) { - $v[$attribute] = $model[$link]; - } - $values[] = $v; - } - } - $this->andWhere(array('in', $attributes, array_unique($values, SORT_REGULAR))); - } - - /** - * @param ActiveRecord[] $primaryModels - * @return array - */ - private function findPivotRows($primaryModels) - { - if (empty($primaryModels)) { - return array(); - } - $this->filterByModels($primaryModels); - /** @var $primaryModel ActiveRecord */ - $primaryModel = reset($primaryModels); - $db = $primaryModel->getDb(); - list ($sql, $params) = $db->getQueryBuilder()->build($this); - return $db->createCommand($sql, $params)->queryAll(); - } } diff --git a/framework/yii/db/ActiveRelationInterface.php b/framework/yii/db/ActiveRelationInterface.php new file mode 100644 index 0000000..84e0648 --- /dev/null +++ b/framework/yii/db/ActiveRelationInterface.php @@ -0,0 +1,29 @@ +<?php +/** + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\db; + +/** + * ActiveRelationInterface defines the common interface to be implemented by active record relation classes. + * + * A class implementing this interface should also use [[ActiveRelationTrait]]. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @author Carsten Brandt <mail@cebe.cc> + * @since 2.0 + */ +interface ActiveRelationInterface extends ActiveQueryInterface +{ + /** + * Specifies the relation associated with the pivot table. + * @param string $relationName the relation name. This refers to a relation declared in [[primaryModel]]. + * @param callable $callable a PHP callback for customizing the relation associated with the pivot table. + * Its signature should be `function($query)`, where `$query` is the query to be customized. + * @return static the relation object itself. + */ + public function via($relationName, $callable = null); +} diff --git a/framework/yii/db/ActiveRelationTrait.php b/framework/yii/db/ActiveRelationTrait.php new file mode 100644 index 0000000..be42eb6 --- /dev/null +++ b/framework/yii/db/ActiveRelationTrait.php @@ -0,0 +1,245 @@ +<?php +/** + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\db; + +use yii\base\InvalidConfigException; + +/** + * ActiveRelationTrait implements the common methods and properties for active record relation classes. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @author Carsten Brandt <mail@cebe.cc> + * @since 2.0 + */ +trait ActiveRelationTrait +{ + /** + * @var boolean whether this relation should populate all query results into AR instances. + * If false, only the first row of the results will be retrieved. + */ + public $multiple; + /** + * @var ActiveRecord the primary model that this relation is associated with. + * This is used only in lazy loading with dynamic query options. + */ + public $primaryModel; + /** + * @var array the columns of the primary and foreign tables that establish the relation. + * The array keys must be columns of the table for this relation, and the array values + * must be the corresponding columns from the primary table. + * Do not prefix or quote the column names as this will be done automatically by Yii. + */ + public $link; + /** + * @var array the query associated with the pivot table. Please call [[via()]] + * to set this property instead of directly setting it. + */ + public $via; + + /** + * Clones internal objects. + */ + public function __clone() + { + // make a clone of "via" object so that the same query object can be reused multiple times + if (is_object($this->via)) { + $this->via = clone $this->via; + } elseif (is_array($this->via)) { + $this->via = [$this->via[0], clone $this->via[1]]; + } + } + + /** + * Specifies the relation associated with the pivot table. + * @param string $relationName the relation name. This refers to a relation declared in [[primaryModel]]. + * @param callable $callable a PHP callback for customizing the relation associated with the pivot table. + * Its signature should be `function($query)`, where `$query` is the query to be customized. + * @return static the relation object itself. + */ + public function via($relationName, $callable = null) + { + $relation = $this->primaryModel->getRelation($relationName); + $this->via = [$relationName, $relation]; + if ($callable !== null) { + call_user_func($callable, $relation); + } + return $this; + } + + /** + * Finds the related records and populates them into the primary models. + * @param string $name the relation name + * @param array $primaryModels primary models + * @return array the related models + * @throws InvalidConfigException if [[link]] is invalid + */ + public function populateRelation($name, &$primaryModels) + { + if (!is_array($this->link)) { + throw new InvalidConfigException('Invalid link: it must be an array of key-value pairs.'); + } + + if ($this->via instanceof self) { + // via pivot table + /** @var ActiveRelationTrait $viaQuery */ + $viaQuery = $this->via; + $viaModels = $viaQuery->findPivotRows($primaryModels); + $this->filterByModels($viaModels); + } elseif (is_array($this->via)) { + // via relation + /** @var ActiveRelationTrait $viaQuery */ + list($viaName, $viaQuery) = $this->via; + $viaQuery->primaryModel = null; + $viaModels = $viaQuery->populateRelation($viaName, $primaryModels); + $this->filterByModels($viaModels); + } else { + $this->filterByModels($primaryModels); + } + + if (count($primaryModels) === 1 && !$this->multiple) { + $model = $this->one(); + foreach ($primaryModels as $i => $primaryModel) { + if ($primaryModel instanceof ActiveRecord) { + $primaryModel->populateRelation($name, $model); + } else { + $primaryModels[$i][$name] = $model; + } + } + return [$model]; + } else { + $models = $this->all(); + if (isset($viaModels, $viaQuery)) { + $buckets = $this->buildBuckets($models, $this->link, $viaModels, $viaQuery->link); + } else { + $buckets = $this->buildBuckets($models, $this->link); + } + + $link = array_values(isset($viaQuery) ? $viaQuery->link : $this->link); + foreach ($primaryModels as $i => $primaryModel) { + $key = $this->getModelKey($primaryModel, $link); + $value = isset($buckets[$key]) ? $buckets[$key] : ($this->multiple ? [] : null); + if ($primaryModel instanceof ActiveRecord) { + $primaryModel->populateRelation($name, $value); + } else { + $primaryModels[$i][$name] = $value; + } + } + return $models; + } + } + + /** + * @param array $models + * @param array $link + * @param array $viaModels + * @param array $viaLink + * @return array + */ + private function buildBuckets($models, $link, $viaModels = null, $viaLink = null) + { + $buckets = []; + $linkKeys = array_keys($link); + foreach ($models as $i => $model) { + $key = $this->getModelKey($model, $linkKeys); + if ($this->indexBy !== null) { + $buckets[$key][$i] = $model; + } else { + $buckets[$key][] = $model; + } + } + + if ($viaModels !== null) { + $viaBuckets = []; + $viaLinkKeys = array_keys($viaLink); + $linkValues = array_values($link); + foreach ($viaModels as $viaModel) { + $key1 = $this->getModelKey($viaModel, $viaLinkKeys); + $key2 = $this->getModelKey($viaModel, $linkValues); + if (isset($buckets[$key2])) { + foreach ($buckets[$key2] as $i => $bucket) { + if ($this->indexBy !== null) { + $viaBuckets[$key1][$i] = $bucket; + } else { + $viaBuckets[$key1][] = $bucket; + } + } + } + } + $buckets = $viaBuckets; + } + + if (!$this->multiple) { + foreach ($buckets as $i => $bucket) { + $buckets[$i] = reset($bucket); + } + } + return $buckets; + } + + /** + * @param ActiveRecord|array $model + * @param array $attributes + * @return string + */ + private function getModelKey($model, $attributes) + { + if (count($attributes) > 1) { + $key = []; + foreach ($attributes as $attribute) { + $key[] = $model[$attribute]; + } + return serialize($key); + } else { + $attribute = reset($attributes); + return $model[$attribute]; + } + } + + /** + * @param array $models + */ + private function filterByModels($models) + { + $attributes = array_keys($this->link); + $values = []; + if (count($attributes) === 1) { + // single key + $attribute = reset($this->link); + foreach ($models as $model) { + if (($value = $model[$attribute]) !== null) { + $values[] = $value; + } + } + } else { + // composite keys + foreach ($models as $model) { + $v = []; + foreach ($this->link as $attribute => $link) { + $v[$attribute] = $model[$link]; + } + $values[] = $v; + } + } + $this->andWhere(['in', $attributes, array_unique($values, SORT_REGULAR)]); + } + + /** + * @param ActiveRecord[] $primaryModels + * @return array + */ + private function findPivotRows($primaryModels) + { + if (empty($primaryModels)) { + return []; + } + $this->filterByModels($primaryModels); + /** @var ActiveRecord $primaryModel */ + $primaryModel = reset($primaryModels); + return $this->asArray()->all($primaryModel->getDb()); + } +} diff --git a/framework/yii/db/ColumnSchema.php b/framework/yii/db/ColumnSchema.php index cd2d9fa..3e7f6cf 100644 --- a/framework/yii/db/ColumnSchema.php +++ b/framework/yii/db/ColumnSchema.php @@ -87,12 +87,12 @@ class ColumnSchema extends Object */ public function typecast($value) { + if ($value === '' && $this->type !== Schema::TYPE_TEXT && $this->type !== Schema::TYPE_STRING && $this->type !== Schema::TYPE_BINARY) { + return null; + } if ($value === null || gettype($value) === $this->phpType || $value instanceof Expression) { return $value; } - if ($value === '' && $this->type !== Schema::TYPE_TEXT && $this->type !== Schema::TYPE_STRING) { - return null; - } switch ($this->phpType) { case 'string': return (string)$value; diff --git a/framework/yii/db/Command.php b/framework/yii/db/Command.php index bfb8a26..6ed0d9c 100644 --- a/framework/yii/db/Command.php +++ b/framework/yii/db/Command.php @@ -36,10 +36,10 @@ use yii\caching\Cache; * [[update()]], etc. For example, * * ~~~ - * $connection->createCommand()->insert('tbl_user', array( + * $connection->createCommand()->insert('tbl_user', [ * 'name' => 'Sam', * 'age' => 30, - * ))->execute(); + * ])->execute(); * ~~~ * * To build SELECT SQL statements, please use [[QueryBuilder]] instead. @@ -67,13 +67,15 @@ class Command extends \yii\base\Component */ public $fetchMode = \PDO::FETCH_ASSOC; /** - * @var string the SQL statement that this command represents + * @var array the parameters (name => value) that are bound to the current PDO statement. + * This property is maintained by methods such as [[bindValue()]]. + * Do not modify it directly. */ - private $_sql; + public $params = []; /** - * @var array the parameter log information (name => value) + * @var string the SQL statement that this command represents */ - private $_params = array(); + private $_sql; /** * Returns the SQL statement for this command. @@ -88,14 +90,14 @@ class Command extends \yii\base\Component * Specifies the SQL statement to be executed. * The previous SQL execution (if any) will be cancelled, and [[params]] will be cleared as well. * @param string $sql the SQL statement to be set. - * @return Command this command instance + * @return static this command instance */ public function setSql($sql) { if ($sql !== $this->_sql) { $this->cancel(); $this->_sql = $this->db->quoteSql($sql); - $this->_params = array(); + $this->params = []; } return $this; } @@ -108,11 +110,11 @@ class Command extends \yii\base\Component */ public function getRawSql() { - if (empty($this->_params)) { + if (empty($this->params)) { return $this->_sql; } else { - $params = array(); - foreach ($this->_params as $name => $value) { + $params = []; + foreach ($this->params as $name => $value) { if (is_string($value)) { $params[$name] = $this->db->quoteValue($value); } elseif ($value === null) { @@ -174,22 +176,23 @@ class Command extends \yii\base\Component * @param integer $dataType SQL data type of the parameter. If null, the type is determined by the PHP type of the value. * @param integer $length length of the data type * @param mixed $driverOptions the driver-specific options - * @return Command the current command being executed + * @return static the current command being executed * @see http://www.php.net/manual/en/function.PDOStatement-bindParam.php */ public function bindParam($name, &$value, $dataType = null, $length = null, $driverOptions = null) { $this->prepare(); if ($dataType === null) { - $this->pdoStatement->bindParam($name, $value, $this->getPdoType($value)); - } elseif ($length === null) { + $dataType = $this->db->getSchema()->getPdoType($value); + } + if ($length === null) { $this->pdoStatement->bindParam($name, $value, $dataType); } elseif ($driverOptions === null) { $this->pdoStatement->bindParam($name, $value, $dataType, $length); } else { $this->pdoStatement->bindParam($name, $value, $dataType, $length, $driverOptions); } - $this->_params[$name] =& $value; + $this->params[$name] =& $value; return $this; } @@ -201,18 +204,17 @@ class Command extends \yii\base\Component * placeholders, this will be the 1-indexed position of the parameter. * @param mixed $value The value to bind to the parameter * @param integer $dataType SQL data type of the parameter. If null, the type is determined by the PHP type of the value. - * @return Command the current command being executed + * @return static the current command being executed * @see http://www.php.net/manual/en/function.PDOStatement-bindValue.php */ public function bindValue($name, $value, $dataType = null) { $this->prepare(); if ($dataType === null) { - $this->pdoStatement->bindValue($name, $value, $this->getPdoType($value)); - } else { - $this->pdoStatement->bindValue($name, $value, $dataType); + $dataType = $this->db->getSchema()->getPdoType($value); } - $this->_params[$name] = $value; + $this->pdoStatement->bindValue($name, $value, $dataType); + $this->params[$name] = $value; return $this; } @@ -222,10 +224,10 @@ class Command extends \yii\base\Component * Note that the SQL data type of each value is determined by its PHP type. * @param array $values the values to be bound. This must be given in terms of an associative * array with array keys being the parameter names, and array values the corresponding parameter values, - * e.g. `array(':name' => 'John', ':age' => 25)`. By default, the PDO type of each value is determined - * by its PHP type. You may explicitly specify the PDO type by using an array: `array(value, type)`, - * e.g. `array(':name' => 'John', ':profile' => array($profile, \PDO::PARAM_LOB))`. - * @return Command the current command being executed + * e.g. `[':name' => 'John', ':age' => 25]`. By default, the PDO type of each value is determined + * by its PHP type. You may explicitly specify the PDO type by using an array: `[value, type]`, + * e.g. `[':name' => 'John', ':profile' => [$profile, \PDO::PARAM_LOB]]`. + * @return static the current command being executed */ public function bindValues($values) { @@ -236,35 +238,16 @@ class Command extends \yii\base\Component $type = $value[1]; $value = $value[0]; } else { - $type = $this->getPdoType($value); + $type = $this->db->getSchema()->getPdoType($value); } $this->pdoStatement->bindValue($name, $value, $type); - $this->_params[$name] = $value; + $this->params[$name] = $value; } } return $this; } /** - * Determines the PDO type for the given PHP data value. - * @param mixed $data the data whose PDO type is to be determined - * @return integer the PDO type - * @see http://www.php.net/manual/en/pdo.constants.php - */ - private function getPdoType($data) - { - static $typeMap = array( // php type => PDO type - 'boolean' => \PDO::PARAM_BOOL, - 'integer' => \PDO::PARAM_INT, - 'string' => \PDO::PARAM_STR, - 'resource' => \PDO::PARAM_LOB, - 'NULL' => \PDO::PARAM_NULL, - ); - $type = gettype($data); - return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR; - } - - /** * Executes the SQL statement. * This method should only be used for executing non-query SQL statement, such as `INSERT`, `DELETE`, `UPDATE` SQLs. * No result set will be returned. @@ -277,7 +260,7 @@ class Command extends \yii\base\Component $rawSql = $this->getRawSql(); - Yii::trace($rawSql, __METHOD__); + Yii::info($rawSql, __METHOD__); if ($sql == '') { return 0; @@ -381,20 +364,20 @@ class Command extends \yii\base\Component $db = $this->db; $rawSql = $this->getRawSql(); - Yii::trace($rawSql, __METHOD__); + Yii::info($rawSql, __METHOD__); - /** @var $cache \yii\caching\Cache */ + /** @var \yii\caching\Cache $cache */ if ($db->enableQueryCache && $method !== '') { $cache = is_string($db->queryCache) ? Yii::$app->getComponent($db->queryCache) : $db->queryCache; } if (isset($cache) && $cache instanceof Cache) { - $cacheKey = array( + $cacheKey = [ __CLASS__, $db->dsn, $db->username, $rawSql, - ); + ]; if (($result = $cache->get($cacheKey)) !== false) { Yii::trace('Query result served from cache', __METHOD__); return $result; @@ -414,7 +397,7 @@ class Command extends \yii\base\Component if ($fetchMode === null) { $fetchMode = $this->fetchMode; } - $result = call_user_func_array(array($this->pdoStatement, $method), (array)$fetchMode); + $result = call_user_func_array([$this->pdoStatement, $method], (array)$fetchMode); $this->pdoStatement->closeCursor(); } @@ -439,10 +422,10 @@ class Command extends \yii\base\Component * For example, * * ~~~ - * $connection->createCommand()->insert('tbl_user', array( + * $connection->createCommand()->insert('tbl_user', [ * 'name' => 'Sam', * 'age' => 30, - * ))->execute(); + * ])->execute(); * ~~~ * * The method will properly escape the column names, and bind the values to be inserted. @@ -455,7 +438,7 @@ class Command extends \yii\base\Component */ public function insert($table, $columns) { - $params = array(); + $params = []; $sql = $this->db->getQueryBuilder()->insert($table, $columns, $params); return $this->setSql($sql)->bindValues($params); } @@ -465,11 +448,11 @@ class Command extends \yii\base\Component * For example, * * ~~~ - * $connection->createCommand()->batchInsert('tbl_user', array('name', 'age'), array( - * array('Tom', 30), - * array('Jane', 20), - * array('Linda', 25), - * ))->execute(); + * $connection->createCommand()->batchInsert('tbl_user', ['name', 'age'], [ + * ['Tom', 30], + * ['Jane', 20], + * ['Linda', 25], + * ])->execute(); * ~~~ * * Note that the values in each row must match the corresponding column names. @@ -490,9 +473,7 @@ class Command extends \yii\base\Component * For example, * * ~~~ - * $connection->createCommand()->update('tbl_user', array( - * 'status' => 1, - * ), 'age > 30')->execute(); + * $connection->createCommand()->update('tbl_user', ['status' => 1], 'age > 30')->execute(); * ~~~ * * The method will properly escape the column names and bind the values to be updated. @@ -506,7 +487,7 @@ class Command extends \yii\base\Component * @param array $params the parameters to be bound to the command * @return Command the command object itself */ - public function update($table, $columns, $condition = '', $params = array()) + public function update($table, $columns, $condition = '', $params = []) { $sql = $this->db->getQueryBuilder()->update($table, $columns, $condition, $params); return $this->setSql($sql)->bindValues($params); @@ -530,7 +511,7 @@ class Command extends \yii\base\Component * @param array $params the parameters to be bound to the command * @return Command the command object itself */ - public function delete($table, $condition = '', $params = array()) + public function delete($table, $condition = '', $params = []) { $sql = $this->db->getQueryBuilder()->delete($table, $condition, $params); return $this->setSql($sql)->bindValues($params); diff --git a/framework/yii/db/Connection.php b/framework/yii/db/Connection.php index 69bf6a5..ef5be87 100644 --- a/framework/yii/db/Connection.php +++ b/framework/yii/db/Connection.php @@ -28,11 +28,11 @@ use yii\caching\Cache; * the DB connection: * * ~~~ - * $connection = new \yii\db\Connection(array( + * $connection = new \yii\db\Connection([ * 'dsn' => $dsn, * 'username' => $username, * 'password' => $password, - * )); + * ]); * $connection->open(); * ~~~ * @@ -76,17 +76,17 @@ use yii\caching\Cache; * configuration like the following: * * ~~~ - * array( - * 'components' => array( - * 'db' => array( + * [ + * 'components' => [ + * 'db' => [ * 'class' => '\yii\db\Connection', * 'dsn' => 'mysql:host=127.0.0.1;dbname=demo', * 'username' => 'root', * 'password' => '', * 'charset' => 'utf8', - * ), - * ), - * ) + * ], + * ], + * ] * ~~~ * * @property string $driverName Name of the DB driver. This property is read-only. @@ -159,7 +159,7 @@ class Connection extends Component * The table names may contain schema prefix, if any. Do not quote the table names. * @see enableSchemaCache */ - public $schemaCacheExclude = array(); + public $schemaCacheExclude = []; /** * @var Cache|string the cache object or the ID of the cache application component that * is used to cache the table metadata. @@ -205,8 +205,7 @@ class Connection extends Component * as specified by the database. * * Note that if you're using GBK or BIG5 then it's highly recommended to - * update to PHP 5.3.6+ and to specify charset via DSN like - * 'mysql:dbname=mydatabase;host=127.0.0.1;charset=GBK;'. + * specify charset via DSN like 'mysql:dbname=mydatabase;host=127.0.0.1;charset=GBK;'. */ public $charset; /** @@ -234,7 +233,7 @@ class Connection extends Component * You normally do not need to set this property unless you want to use your own * [[Schema]] class to support DBMS that is not supported by Yii. */ - public $schemaMap = array( + public $schemaMap = [ 'pgsql' => 'yii\db\pgsql\Schema', // PostgreSQL 'mysqli' => 'yii\db\mysql\Schema', // MySQL 'mysql' => 'yii\db\mysql\Schema', // MySQL @@ -245,7 +244,7 @@ class Connection extends Component 'mssql' => 'yii\db\mssql\Schema', // older MSSQL driver on MS Windows hosts 'dblib' => 'yii\db\mssql\Schema', // dblib drivers on GNU/Linux (and maybe other OSes) hosts 'cubrid' => 'yii\db\cubrid\Schema', // CUBRID - ); + ]; /** * @var Transaction the currently active transaction */ @@ -362,7 +361,7 @@ class Connection extends Component if ($this->emulatePrepare !== null && constant('PDO::ATTR_EMULATE_PREPARES')) { $this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, $this->emulatePrepare); } - if ($this->charset !== null && in_array($this->getDriverName(), array('pgsql', 'mysql', 'mysqli', 'cubrid'))) { + if ($this->charset !== null && in_array($this->getDriverName(), ['pgsql', 'mysql', 'mysqli', 'cubrid'])) { $this->pdo->exec('SET NAMES ' . $this->pdo->quote($this->charset)); } $this->trigger(self::EVENT_AFTER_OPEN); @@ -374,13 +373,13 @@ class Connection extends Component * @param array $params the parameters to be bound to the SQL statement * @return Command the DB command */ - public function createCommand($sql = null, $params = array()) + public function createCommand($sql = null, $params = []) { $this->open(); - $command = new Command(array( + $command = new Command([ 'db' => $this, 'sql' => $sql, - )); + ]); return $command->bindValues($params); } @@ -400,9 +399,7 @@ class Connection extends Component public function beginTransaction() { $this->open(); - $this->_transaction = new Transaction(array( - 'db' => $this, - )); + $this->_transaction = new Transaction(['db' => $this]); $this->_transaction->begin(); return $this->_transaction; } @@ -508,13 +505,12 @@ class Connection extends Component */ public function quoteSql($sql) { - $db = $this; return preg_replace_callback('/(\\{\\{(%?[\w\-\. ]+%?)\\}\\}|\\[\\[([\w\-\. ]+)\\]\\])/', - function ($matches) use ($db) { + function ($matches) { if (isset($matches[3])) { - return $db->quoteColumnName($matches[3]); + return $this->quoteColumnName($matches[3]); } else { - return str_replace('%', $db->tablePrefix, $db->quoteTableName($matches[2])); + return str_replace('%', $this->tablePrefix, $this->quoteTableName($matches[2])); } }, $sql); } diff --git a/framework/yii/db/DataReader.php b/framework/yii/db/DataReader.php index f2990c1..213db52 100644 --- a/framework/yii/db/DataReader.php +++ b/framework/yii/db/DataReader.php @@ -62,7 +62,7 @@ class DataReader extends \yii\base\Object implements \Iterator, \Countable * @param Command $command the command generating the query result * @param array $config name-value pairs that will be used to initialize the object properties */ - public function __construct(Command $command, $config = array()) + public function __construct(Command $command, $config = []) { $this->_statement = $command->pdoStatement; $this->_statement->setFetchMode(\PDO::FETCH_ASSOC); @@ -97,7 +97,7 @@ class DataReader extends \yii\base\Object implements \Iterator, \Countable public function setFetchMode($mode) { $params = func_get_args(); - call_user_func_array(array($this->_statement, 'setFetchMode'), $params); + call_user_func_array([$this->_statement, 'setFetchMode'], $params); } /** diff --git a/framework/yii/db/Exception.php b/framework/yii/db/Exception.php index 25ae39f..f502043 100644 --- a/framework/yii/db/Exception.php +++ b/framework/yii/db/Exception.php @@ -19,7 +19,7 @@ class Exception extends \yii\base\Exception * @var array the error info provided by a PDO exception. This is the same as returned * by [PDO::errorInfo](http://www.php.net/manual/en/pdo.errorinfo.php). */ - public $errorInfo = array(); + public $errorInfo = []; /** * Constructor. @@ -28,7 +28,7 @@ class Exception extends \yii\base\Exception * @param integer $code PDO error code * @param \Exception $previous The previous exception used for the exception chaining. */ - public function __construct($message, $errorInfo = array(), $code = 0, \Exception $previous = null) + public function __construct($message, $errorInfo = [], $code = 0, \Exception $previous = null) { $this->errorInfo = $errorInfo; parent::__construct($message, $code, $previous); diff --git a/framework/yii/db/Expression.php b/framework/yii/db/Expression.php index 77e9f60..7fa9124 100644 --- a/framework/yii/db/Expression.php +++ b/framework/yii/db/Expression.php @@ -34,7 +34,7 @@ class Expression extends \yii\base\Object * The keys are placeholders appearing in [[expression]] and the values * are the corresponding parameter values. */ - public $params = array(); + public $params = []; /** * Constructor. @@ -42,7 +42,7 @@ class Expression extends \yii\base\Object * @param array $params parameters * @param array $config name-value pairs that will be used to initialize the object properties */ - public function __construct($expression, $params = array(), $config = array()) + public function __construct($expression, $params = [], $config = []) { $this->expression = $expression; $this->params = $params; diff --git a/framework/yii/db/Migration.php b/framework/yii/db/Migration.php index 7368788..37fdf3f 100644 --- a/framework/yii/db/Migration.php +++ b/framework/yii/db/Migration.php @@ -135,7 +135,7 @@ class Migration extends \yii\base\Component * @param array $params input parameters (name => value) for the SQL execution. * See [[Command::execute()]] for more details. */ - public function execute($sql, $params = array()) + public function execute($sql, $params = []) { echo " > execute SQL: $sql ..."; $time = microtime(true); @@ -158,6 +158,21 @@ class Migration extends \yii\base\Component } /** + * Creates and executes an batch INSERT SQL statement. + * The method will properly escape the column names, and bind the values to be inserted. + * @param string $table the table that new rows will be inserted into. + * @param array $columns the column names. + * @param array $rows the rows to be batch inserted into the table + */ + public function batchInsert($table, $columns, $rows) + { + echo " > insert into $table ..."; + $time = microtime(true); + $this->db->createCommand()->batchInsert($table, $columns, $rows)->execute(); + echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + } + + /** * Creates and executes an UPDATE SQL statement. * The method will properly escape the column names and bind the values to be updated. * @param string $table the table to be updated. @@ -166,7 +181,7 @@ class Migration extends \yii\base\Component * refer to [[Query::where()]] on how to specify conditions. * @param array $params the parameters to be bound to the query. */ - public function update($table, $columns, $condition = '', $params = array()) + public function update($table, $columns, $condition = '', $params = []) { echo " > update $table ..."; $time = microtime(true); @@ -181,7 +196,7 @@ class Migration extends \yii\base\Component * refer to [[Query::where()]] on how to specify conditions. * @param array $params the parameters to be bound to the query. */ - public function delete($table, $condition = '', $params = array()) + public function delete($table, $condition = '', $params = []) { echo " > delete from $table ..."; $time = microtime(true); diff --git a/framework/yii/db/Query.php b/framework/yii/db/Query.php index d1e7864..50ed105 100644 --- a/framework/yii/db/Query.php +++ b/framework/yii/db/Query.php @@ -33,23 +33,15 @@ use yii\base\Component; * ~~~ * * @author Qiang Xue <qiang.xue@gmail.com> + * @author Carsten Brandt <mail@cebe.cc> * @since 2.0 */ -class Query extends Component +class Query extends Component implements QueryInterface { - /** - * Sort ascending - * @see orderBy - */ - const SORT_ASC = false; - /** - * Sort descending - * @see orderBy - */ - const SORT_DESC = true; + use QueryTrait; /** - * @var array the columns being selected. For example, `array('id', 'name')`. + * @var array the columns being selected. For example, `['id', 'name']`. * This is used to construct the SELECT clause in a SQL statement. If not set, if means selecting all columns. * @see select() */ @@ -65,35 +57,13 @@ class Query extends Component */ public $distinct; /** - * @var array the table(s) to be selected from. For example, `array('tbl_user', 'tbl_post')`. + * @var array the table(s) to be selected from. For example, `['tbl_user', 'tbl_post']`. * This is used to construct the FROM clause in a SQL statement. * @see from() */ public $from; /** - * @var string|array query condition. This refers to the WHERE clause in a SQL statement. - * For example, `age > 31 AND team = 1`. - * @see where() - */ - public $where; - /** - * @var integer maximum number of records to be returned. If not set or less than 0, it means no limit. - */ - public $limit; - /** - * @var integer zero-based offset from where the records are to be returned. If not set or - * less than 0, it means starting from the beginning. - */ - public $offset; - /** - * @var array how to sort the query results. This is used to construct the ORDER BY clause in a SQL statement. - * The array keys are the columns to be sorted by, and the array values are the corresponding sort directions which - * can be either [[Query::SORT_ASC]] or [[Query::SORT_DESC]]. The array may also contain [[Expression]] objects. - * If that is the case, the expressions will be converted into strings without any change. - */ - public $orderBy; - /** - * @var array how to group the query results. For example, `array('company', 'department')`. + * @var array how to group the query results. For example, `['company', 'department']`. * This is used to construct the GROUP BY clause in a SQL statement. */ public $groupBy; @@ -102,16 +72,16 @@ class Query extends Component * of one join which has the following structure: * * ~~~ - * array($joinType, $tableName, $joinCondition) + * [$joinType, $tableName, $joinCondition] * ~~~ * * For example, * * ~~~ - * array( - * array('INNER JOIN', 'tbl_user', 'tbl_user.id = author_id'), - * array('LEFT JOIN', 'tbl_team', 'tbl_team.id = team_id'), - * ) + * [ + * ['INNER JOIN', 'tbl_user', 'tbl_user.id = author_id'], + * ['LEFT JOIN', 'tbl_team', 'tbl_team.id = team_id'], + * ] * ~~~ */ public $join; @@ -127,15 +97,9 @@ class Query extends Component public $union; /** * @var array list of query parameter values indexed by parameter placeholders. - * For example, `array(':name' => 'Dan', ':age' => 31)`. + * For example, `[':name' => 'Dan', ':age' => 31]`. */ public $params; - /** - * @var string|callable $column the name of the column by which the query results should be indexed by. - * This can also be a callable (e.g. anonymous function) that returns the index value based on the given - * row data. For more details, see [[indexBy()]]. This property is only used by [[all()]]. - */ - public $indexBy; /** @@ -154,27 +118,6 @@ class Query extends Component } /** - * Sets the [[indexBy]] property. - * @param string|callable $column the name of the column by which the query results should be indexed by. - * This can also be a callable (e.g. anonymous function) that returns the index value based on the given - * row data. The signature of the callable should be: - * - * ~~~ - * function ($row) - * { - * // return the index value corresponding to $row - * } - * ~~~ - * - * @return Query the query object itself - */ - public function indexBy($column) - { - $this->indexBy = $column; - return $this; - } - - /** * Executes the query and returns all results as an array. * @param Connection $db the database connection used to generate the SQL statement. * If this parameter is not given, the `db` application component will be used. @@ -186,7 +129,7 @@ class Query extends Component if ($this->indexBy === null) { return $rows; } - $result = array(); + $result = []; foreach ($rows as $row) { if (is_string($this->indexBy)) { $key = $row[$this->indexBy]; @@ -239,12 +182,12 @@ class Query extends Component * @param string $q the COUNT expression. Defaults to '*'. * Make sure you properly quote column names in the expression. * @param Connection $db the database connection used to generate the SQL statement. - * If this parameter is not given, the `db` application component will be used. + * If this parameter is not given (or null), the `db` application component will be used. * @return integer number of records */ public function count($q = '*', $db = null) { - $this->select = array("COUNT($q)"); + $this->select = ["COUNT($q)"]; return $this->createCommand($db)->queryScalar(); } @@ -258,7 +201,7 @@ class Query extends Component */ public function sum($q, $db = null) { - $this->select = array("SUM($q)"); + $this->select = ["SUM($q)"]; return $this->createCommand($db)->queryScalar(); } @@ -272,7 +215,7 @@ class Query extends Component */ public function average($q, $db = null) { - $this->select = array("AVG($q)"); + $this->select = ["AVG($q)"]; return $this->createCommand($db)->queryScalar(); } @@ -286,7 +229,7 @@ class Query extends Component */ public function min($q, $db = null) { - $this->select = array("MIN($q)"); + $this->select = ["MIN($q)"]; return $this->createCommand($db)->queryScalar(); } @@ -300,7 +243,7 @@ class Query extends Component */ public function max($q, $db = null) { - $this->select = array("MAX($q)"); + $this->select = ["MAX($q)"]; return $this->createCommand($db)->queryScalar(); } @@ -312,20 +255,20 @@ class Query extends Component */ public function exists($db = null) { - $this->select = array(new Expression('1')); + $this->select = [new Expression('1')]; return $this->scalar($db) !== false; } /** * Sets the SELECT part of the query. * @param string|array $columns the columns to be selected. - * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. array('id', 'name')). + * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. ['id', 'name']). * Columns can contain table prefixes (e.g. "tbl_user.id") and/or column aliases (e.g. "tbl_user.id AS user_id"). * The method will automatically quote the column names unless a column contains some parenthesis * (which means the column contains a DB expression). * @param string $option additional option that should be appended to the 'SELECT' keyword. For example, * in MySQL, the option 'SQL_CALC_FOUND_ROWS' can be used. - * @return Query the query object itself + * @return static the query object itself */ public function select($columns, $option = null) { @@ -340,7 +283,7 @@ class Query extends Component /** * Sets the value indicating whether to SELECT DISTINCT or not. * @param bool $value whether to SELECT DISTINCT or not. - * @return Query the query object itself + * @return static the query object itself */ public function distinct($value = true) { @@ -351,11 +294,11 @@ class Query extends Component /** * Sets the FROM part of the query. * @param string|array $tables the table(s) to be selected from. This can be either a string (e.g. `'tbl_user'`) - * or an array (e.g. `array('tbl_user', 'tbl_profile')`) specifying one or several table names. + * or an array (e.g. `['tbl_user', 'tbl_profile']`) specifying one or several table names. * Table names can contain schema prefixes (e.g. `'public.tbl_user'`) and/or table aliases (e.g. `'tbl_user u'`). * The method will automatically quote the table names unless it contains some parenthesis * (which means the table is given as a sub-query or DB expression). - * @return Query the query object itself + * @return static the query object itself */ public function from($tables) { @@ -375,48 +318,48 @@ class Query extends Component * The $condition parameter should be either a string (e.g. 'id=1') or an array. * If the latter, it must be in one of the following two formats: * - * - hash format: `array('column1' => value1, 'column2' => value2, ...)` - * - operator format: `array(operator, operand1, operand2, ...)` + * - hash format: `['column1' => value1, 'column2' => value2, ...]` + * - operator format: `[operator, operand1, operand2, ...]` * * A condition in hash format represents the following SQL expression in general: * `column1=value1 AND column2=value2 AND ...`. In case when a value is an array, * an `IN` expression will be generated. And if a value is null, `IS NULL` will be used * in the generated expression. Below are some examples: * - * - `array('type' => 1, 'status' => 2)` generates `(type = 1) AND (status = 2)`. - * - `array('id' => array(1, 2, 3), 'status' => 2)` generates `(id IN (1, 2, 3)) AND (status = 2)`. - * - `array('status' => null) generates `status IS NULL`. + * - `['type' => 1, 'status' => 2]` generates `(type = 1) AND (status = 2)`. + * - `['id' => [1, 2, 3], 'status' => 2]` generates `(id IN (1, 2, 3)) AND (status = 2)`. + * - `['status' => null] generates `status IS NULL`. * * A condition in operator format generates the SQL expression according to the specified operator, which * can be one of the followings: * * - `and`: the operands should be concatenated together using `AND`. For example, - * `array('and', 'id=1', 'id=2')` will generate `id=1 AND id=2`. If an operand is an array, + * `['and', 'id=1', 'id=2']` will generate `id=1 AND id=2`. If an operand is an array, * it will be converted into a string using the rules described here. For example, - * `array('and', 'type=1', array('or', 'id=1', 'id=2'))` will generate `type=1 AND (id=1 OR id=2)`. + * `['and', 'type=1', ['or', 'id=1', 'id=2']]` will generate `type=1 AND (id=1 OR id=2)`. * The method will NOT do any quoting or escaping. * * - `or`: similar to the `and` operator except that the operands are concatenated using `OR`. * * - `between`: operand 1 should be the column name, and operand 2 and 3 should be the * starting and ending values of the range that the column is in. - * For example, `array('between', 'id', 1, 10)` will generate `id BETWEEN 1 AND 10`. + * For example, `['between', 'id', 1, 10]` will generate `id BETWEEN 1 AND 10`. * * - `not between`: similar to `between` except the `BETWEEN` is replaced with `NOT BETWEEN` * in the generated condition. * * - `in`: operand 1 should be a column or DB expression, and operand 2 be an array representing * the range of the values that the column or DB expression should be in. For example, - * `array('in', 'id', array(1, 2, 3))` will generate `id IN (1, 2, 3)`. + * `['in', 'id', [1, 2, 3]]` will generate `id IN (1, 2, 3)`. * The method will properly quote the column name and escape values in the range. * * - `not in`: similar to the `in` operator except that `IN` is replaced with `NOT IN` in the generated condition. * * - `like`: operand 1 should be a column or DB expression, and operand 2 be a string or an array representing * the values that the column or DB expression should be like. - * For example, `array('like', 'name', '%tester%')` will generate `name LIKE '%tester%'`. + * For example, `['like', 'name', '%tester%']` will generate `name LIKE '%tester%'`. * When the value range is given as an array, multiple `LIKE` predicates will be generated and concatenated - * using `AND`. For example, `array('like', 'name', array('%test%', '%sample%'))` will generate + * using `AND`. For example, `['like', 'name', ['%test%', '%sample%']]` will generate * `name LIKE '%test%' AND name LIKE '%sample%'`. * The method will properly quote the column name and escape values in the range. * @@ -431,11 +374,11 @@ class Query extends Component * * @param string|array $condition the conditions that should be put in the WHERE part. * @param array $params the parameters (name => value) to be bound to the query. - * @return Query the query object itself + * @return static the query object itself * @see andWhere() * @see orWhere() */ - public function where($condition, $params = array()) + public function where($condition, $params = []) { $this->where = $condition; $this->addParams($params); @@ -448,16 +391,16 @@ class Query extends Component * @param string|array $condition the new WHERE condition. Please refer to [[where()]] * on how to specify this parameter. * @param array $params the parameters (name => value) to be bound to the query. - * @return Query the query object itself + * @return static the query object itself * @see where() * @see orWhere() */ - public function andWhere($condition, $params = array()) + public function andWhere($condition, $params = []) { if ($this->where === null) { $this->where = $condition; } else { - $this->where = array('and', $this->where, $condition); + $this->where = ['and', $this->where, $condition]; } $this->addParams($params); return $this; @@ -469,16 +412,16 @@ class Query extends Component * @param string|array $condition the new WHERE condition. Please refer to [[where()]] * on how to specify this parameter. * @param array $params the parameters (name => value) to be bound to the query. - * @return Query the query object itself + * @return static the query object itself * @see where() * @see andWhere() */ - public function orWhere($condition, $params = array()) + public function orWhere($condition, $params = []) { if ($this->where === null) { $this->where = $condition; } else { - $this->where = array('or', $this->where, $condition); + $this->where = ['or', $this->where, $condition]; } $this->addParams($params); return $this; @@ -497,9 +440,9 @@ class Query extends Component * @param array $params the parameters (name => value) to be bound to the query. * @return Query the query object itself */ - public function join($type, $table, $on = '', $params = array()) + public function join($type, $table, $on = '', $params = []) { - $this->join[] = array($type, $table, $on); + $this->join[] = [$type, $table, $on]; return $this->addParams($params); } @@ -514,9 +457,9 @@ class Query extends Component * @param array $params the parameters (name => value) to be bound to the query. * @return Query the query object itself */ - public function innerJoin($table, $on = '', $params = array()) + public function innerJoin($table, $on = '', $params = []) { - $this->join[] = array('INNER JOIN', $table, $on); + $this->join[] = ['INNER JOIN', $table, $on]; return $this->addParams($params); } @@ -531,9 +474,9 @@ class Query extends Component * @param array $params the parameters (name => value) to be bound to the query * @return Query the query object itself */ - public function leftJoin($table, $on = '', $params = array()) + public function leftJoin($table, $on = '', $params = []) { - $this->join[] = array('LEFT JOIN', $table, $on); + $this->join[] = ['LEFT JOIN', $table, $on]; return $this->addParams($params); } @@ -548,19 +491,19 @@ class Query extends Component * @param array $params the parameters (name => value) to be bound to the query * @return Query the query object itself */ - public function rightJoin($table, $on = '', $params = array()) + public function rightJoin($table, $on = '', $params = []) { - $this->join[] = array('RIGHT JOIN', $table, $on); + $this->join[] = ['RIGHT JOIN', $table, $on]; return $this->addParams($params); } /** * Sets the GROUP BY part of the query. * @param string|array $columns the columns to be grouped by. - * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. array('id', 'name')). + * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. ['id', 'name']). * 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 + * @return static the query object itself * @see addGroupBy() */ public function groupBy($columns) @@ -575,10 +518,10 @@ class Query extends Component /** * Adds additional group-by columns to the existing ones. * @param string|array $columns additional columns to be grouped by. - * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. array('id', 'name')). + * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. ['id', 'name']). * 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 + * @return static the query object itself * @see groupBy() */ public function addGroupBy($columns) @@ -599,11 +542,11 @@ class Query extends Component * @param string|array $condition the conditions to be put after HAVING. * Please refer to [[where()]] on how to specify this parameter. * @param array $params the parameters (name => value) to be bound to the query. - * @return Query the query object itself + * @return static the query object itself * @see andHaving() * @see orHaving() */ - public function having($condition, $params = array()) + public function having($condition, $params = []) { $this->having = $condition; $this->addParams($params); @@ -616,16 +559,16 @@ class Query extends Component * @param string|array $condition the new HAVING condition. Please refer to [[where()]] * on how to specify this parameter. * @param array $params the parameters (name => value) to be bound to the query. - * @return Query the query object itself + * @return static the query object itself * @see having() * @see orHaving() */ - public function andHaving($condition, $params = array()) + public function andHaving($condition, $params = []) { if ($this->having === null) { $this->having = $condition; } else { - $this->having = array('and', $this->having, $condition); + $this->having = ['and', $this->having, $condition]; } $this->addParams($params); return $this; @@ -637,102 +580,25 @@ class Query extends Component * @param string|array $condition the new HAVING condition. Please refer to [[where()]] * on how to specify this parameter. * @param array $params the parameters (name => value) to be bound to the query. - * @return Query the query object itself + * @return static the query object itself * @see having() * @see andHaving() */ - public function orHaving($condition, $params = array()) + public function orHaving($condition, $params = []) { if ($this->having === null) { $this->having = $condition; } else { - $this->having = array('or', $this->having, $condition); + $this->having = ['or', $this->having, $condition]; } $this->addParams($params); return $this; } /** - * 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, '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 - * @see addOrderBy() - */ - public function orderBy($columns) - { - $this->orderBy = $this->normalizeOrderBy($columns); - return $this; - } - - /** - * 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, '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 - * @see orderBy() - */ - public function addOrderBy($columns) - { - $columns = $this->normalizeOrderBy($columns); - if ($this->orderBy === null) { - $this->orderBy = $columns; - } else { - $this->orderBy = array_merge($this->orderBy, $columns); - } - return $this; - } - - protected function normalizeOrderBy($columns) - { - if (is_array($columns)) { - return $columns; - } else { - $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY); - $result = array(); - foreach ($columns as $column) { - if (preg_match('/^(.*?)\s+(asc|desc)$/i', $column, $matches)) { - $result[$matches[1]] = strcasecmp($matches[2], 'desc') ? self::SORT_ASC : self::SORT_DESC; - } else { - $result[$column] = self::SORT_ASC; - } - } - return $result; - } - } - - /** - * Sets the LIMIT part of the query. - * @param integer $limit the limit. Use null or negative value to disable limit. - * @return Query the query object itself - */ - public function limit($limit) - { - $this->limit = $limit; - return $this; - } - - /** - * Sets the OFFSET part of the query. - * @param integer $offset the offset. Use null or negative value to disable offset. - * @return Query the query object itself - */ - public function offset($offset) - { - $this->offset = $offset; - return $this; - } - - /** * Appends a SQL statement using UNION operator. * @param string|Query $sql the SQL statement to be appended using UNION - * @return Query the query object itself + * @return static the query object itself */ public function union($sql) { @@ -743,8 +609,8 @@ class Query extends Component /** * Sets the parameters to be bound to the query. * @param array $params list of query parameter values indexed by parameter placeholders. - * For example, `array(':name' => 'Dan', ':age' => 31)`. - * @return Query the query object itself + * For example, `[':name' => 'Dan', ':age' => 31]`. + * @return static the query object itself * @see addParams() */ public function params($params) @@ -756,8 +622,8 @@ class Query extends Component /** * Adds additional parameters to be bound to the query. * @param array $params list of query parameter values indexed by parameter placeholders. - * For example, `array(':name' => 'Dan', ':age' => 31)`. - * @return Query the query object itself + * For example, `[':name' => 'Dan', ':age' => 31]`. + * @return static the query object itself * @see params() */ public function addParams($params) diff --git a/framework/yii/db/QueryBuilder.php b/framework/yii/db/QueryBuilder.php index f210f65..0a547ae 100644 --- a/framework/yii/db/QueryBuilder.php +++ b/framework/yii/db/QueryBuilder.php @@ -39,14 +39,14 @@ class QueryBuilder extends \yii\base\Object * This is mainly used to support creating/modifying tables using DB-independent data type specifications. * Child classes should override this property to declare supported type mappings. */ - public $typeMap = array(); + public $typeMap = []; /** * Constructor. * @param Connection $connection the database connection. * @param array $config name-value pairs that will be used to initialize the object properties */ - public function __construct($connection, $config = array()) + public function __construct($connection, $config = []) { $this->db = $connection; parent::__construct($config); @@ -61,7 +61,7 @@ class QueryBuilder extends \yii\base\Object public function build($query) { $params = $query->params; - $clauses = array( + $clauses = [ $this->buildSelect($query->select, $query->distinct, $query->selectOption), $this->buildFrom($query->from), $this->buildJoin($query->join, $params), @@ -71,8 +71,8 @@ class QueryBuilder extends \yii\base\Object $this->buildUnion($query->union, $params), $this->buildOrderBy($query->orderBy), $this->buildLimit($query->limit, $query->offset), - ); - return array(implode($this->separator, array_filter($clauses)), $params); + ]; + return [implode($this->separator, array_filter($clauses)), $params]; } /** @@ -80,10 +80,10 @@ class QueryBuilder extends \yii\base\Object * For example, * * ~~~ - * $sql = $queryBuilder->insert('tbl_user', array( + * $sql = $queryBuilder->insert('tbl_user', [ * 'name' => 'Sam', * 'age' => 30, - * ), $params); + * ], $params); * ~~~ * * The method will properly escape the table and column names. @@ -99,10 +99,10 @@ class QueryBuilder extends \yii\base\Object if (($tableSchema = $this->db->getTableSchema($table)) !== null) { $columnSchemas = $tableSchema->columns; } else { - $columnSchemas = array(); + $columnSchemas = []; } - $names = array(); - $placeholders = array(); + $names = []; + $placeholders = []; foreach ($columns as $name => $value) { $names[] = $this->db->quoteColumnName($name); if ($value instanceof Expression) { @@ -127,11 +127,11 @@ class QueryBuilder extends \yii\base\Object * For example, * * ~~~ - * $connection->createCommand()->batchInsert('tbl_user', array('name', 'age'), array( - * array('Tom', 30), - * array('Jane', 20), - * array('Linda', 25), - * ))->execute(); + * $connection->createCommand()->batchInsert('tbl_user', ['name', 'age'], [ + * ['Tom', 30], + * ['Jane', 20], + * ['Linda', 25], + * ])->execute(); * ~~~ * * Note that the values in each row must match the corresponding column names. @@ -153,10 +153,8 @@ class QueryBuilder extends \yii\base\Object * For example, * * ~~~ - * $params = array(); - * $sql = $queryBuilder->update('tbl_user', array( - * 'status' => 1, - * ), 'age > 30', $params); + * $params = []; + * $sql = $queryBuilder->update('tbl_user', ['status' => 1], 'age > 30', $params); * ~~~ * * The method will properly escape the table and column names. @@ -174,10 +172,10 @@ class QueryBuilder extends \yii\base\Object if (($tableSchema = $this->db->getTableSchema($table)) !== null) { $columnSchemas = $tableSchema->columns; } else { - $columnSchemas = array(); + $columnSchemas = []; } - $lines = array(); + $lines = []; foreach ($columns as $name => $value) { if ($value instanceof Expression) { $lines[] = $this->db->quoteColumnName($name) . '=' . $value->expression; @@ -234,11 +232,11 @@ class QueryBuilder extends \yii\base\Object * For example, * * ~~~ - * $sql = $queryBuilder->createTable('tbl_user', array( + * $sql = $queryBuilder->createTable('tbl_user', [ * 'id' => 'pk', * 'name' => 'string', * 'age' => 'integer', - * )); + * ]); * ~~~ * * @param string $table the name of the table to be created. The name will be properly quoted by the method. @@ -248,7 +246,7 @@ class QueryBuilder extends \yii\base\Object */ public function createTable($table, $columns, $options = null) { - $cols = array(); + $cols = []; foreach ($columns as $name => $type) { if (is_string($name)) { $cols[] = "\t" . $this->db->quoteColumnName($name) . ' ' . $this->getColumnType($type); @@ -680,12 +678,12 @@ class QueryBuilder extends \yii\base\Object if (empty($columns)) { return ''; } - $orders = array(); + $orders = []; foreach ($columns as $name => $direction) { if (is_object($direction)) { $orders[] = (string)$direction; } else { - $orders[] = $this->db->quoteColumnName($name) . ($direction === Query::SORT_DESC ? ' DESC' : ''); + $orders[] = $this->db->quoteColumnName($name) . ($direction === SORT_DESC ? ' DESC' : ''); } } @@ -767,7 +765,7 @@ class QueryBuilder extends \yii\base\Object */ public function buildCondition($condition, &$params) { - static $builders = array( + static $builders = [ 'AND' => 'buildAndCondition', 'OR' => 'buildAndCondition', 'BETWEEN' => 'buildBetweenCondition', @@ -778,7 +776,7 @@ class QueryBuilder extends \yii\base\Object 'NOT LIKE' => 'buildLikeCondition', 'OR LIKE' => 'buildLikeCondition', 'OR NOT LIKE' => 'buildLikeCondition', - ); + ]; if (!is_array($condition)) { return (string)$condition; @@ -799,12 +797,18 @@ class QueryBuilder extends \yii\base\Object } } - private function buildHashCondition($condition, &$params) + /** + * Creates a condition based on column-value pairs. + * @param array $condition the condition specification. + * @param array $params the binding parameters to be populated + * @return string the generated SQL expression + */ + public function buildHashCondition($condition, &$params) { - $parts = array(); + $parts = []; foreach ($condition as $column => $value) { if (is_array($value)) { // IN condition - $parts[] = $this->buildInCondition('IN', array($column, $value), $params); + $parts[] = $this->buildInCondition('IN', [$column, $value], $params); } else { if (strpos($column, '(') === false) { $column = $this->db->quoteColumnName($column); @@ -826,9 +830,16 @@ class QueryBuilder extends \yii\base\Object return count($parts) === 1 ? $parts[0] : '(' . implode(') AND (', $parts) . ')'; } - private function buildAndCondition($operator, $operands, &$params) + /** + * Connects two or more SQL expressions with the `AND` or `OR` operator. + * @param string $operator the operator to use for connecting the given operands + * @param array $operands the SQL expressions to connect. + * @param array $params the binding parameters to be populated + * @return string the generated SQL expression + */ + public function buildAndCondition($operator, $operands, &$params) { - $parts = array(); + $parts = []; foreach ($operands as $operand) { if (is_array($operand)) { $operand = $this->buildCondition($operand, $params); @@ -844,7 +855,16 @@ class QueryBuilder extends \yii\base\Object } } - private function buildBetweenCondition($operator, $operands, &$params) + /** + * Creates an SQL expressions with the `BETWEEN` operator. + * @param string $operator the operator to use (e.g. `BETWEEN` or `NOT BETWEEN`) + * @param array $operands the first operand is the column name. The second and third operands + * describe the interval that column value should be in. + * @param array $params the binding parameters to be populated + * @return string the generated SQL expression + * @throws Exception if wrong number of operands have been given. + */ + public function buildBetweenCondition($operator, $operands, &$params) { if (!isset($operands[0], $operands[1], $operands[2])) { throw new Exception("Operator '$operator' requires three operands."); @@ -863,7 +883,19 @@ class QueryBuilder extends \yii\base\Object return "$column $operator $phName1 AND $phName2"; } - private function buildInCondition($operator, $operands, &$params) + /** + * Creates an SQL expressions with the `IN` operator. + * @param string $operator the operator to use (e.g. `IN` or `NOT IN`) + * @param array $operands the first operand is the column name. If it is an array + * a composite IN condition will be generated. + * The second operand is an array of values that column value should be among. + * If it is an empty array the generated expression will be a `false` value if + * operator is `IN` and empty if operator is `NOT IN`. + * @param array $params the binding parameters to be populated + * @return string the generated SQL expression + * @throws Exception if wrong number of operands have been given. + */ + public function buildInCondition($operator, $operands, &$params) { if (!isset($operands[0], $operands[1])) { throw new Exception("Operator '$operator' requires two operands."); @@ -873,7 +905,7 @@ class QueryBuilder extends \yii\base\Object $values = (array)$values; - if (empty($values) || $column === array()) { + if (empty($values) || $column === []) { return $operator === 'IN' ? '0=1' : ''; } @@ -913,9 +945,9 @@ class QueryBuilder extends \yii\base\Object protected function buildCompositeInCondition($operator, $columns, $values, &$params) { - $vss = array(); + $vss = []; foreach ($values as $value) { - $vs = array(); + $vs = []; foreach ($columns as $column) { if (isset($value[$column])) { $phName = self::PARAM_PREFIX . count($params); @@ -935,7 +967,19 @@ class QueryBuilder extends \yii\base\Object return '(' . implode(', ', $columns) . ") $operator (" . implode(', ', $vss) . ')'; } - private function buildLikeCondition($operator, $operands, &$params) + /** + * Creates an SQL expressions with the `LIKE` operator. + * @param string $operator the operator to use (e.g. `LIKE`, `NOT LIKE`, `OR LIKE` or `OR NOT LIKE`) + * @param array $operands the first operand is the column name. + * The second operand is a single value or an array of values that column value + * should be compared with. + * If it is an empty array the generated expression will be a `false` value if + * operator is `LIKE` or `OR LIKE` and empty if operator is `NOT LIKE` or `OR NOT LIKE`. + * @param array $params the binding parameters to be populated + * @return string the generated SQL expression + * @throws Exception if wrong number of operands have been given. + */ + public function buildLikeCondition($operator, $operands, &$params) { if (!isset($operands[0], $operands[1])) { throw new Exception("Operator '$operator' requires two operands."); @@ -960,7 +1004,7 @@ class QueryBuilder extends \yii\base\Object $column = $this->db->quoteColumnName($column); } - $parts = array(); + $parts = []; foreach ($values as $value) { $phName = self::PARAM_PREFIX . count($params); $params[$phName] = $value; diff --git a/framework/yii/db/QueryInterface.php b/framework/yii/db/QueryInterface.php new file mode 100644 index 0000000..f3cc312 --- /dev/null +++ b/framework/yii/db/QueryInterface.php @@ -0,0 +1,206 @@ +<?php +/** + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\db; + +/** + * The QueryInterface defines the minimum set of methods to be implemented by a database query. + * + * The default implementation of this interface is provided by [[QueryTrait]]. + * + * It has support for getting [[one]] instance or [[all]]. + * Allows pagination via [[limit]] and [[offset]]. + * Sorting is supported via [[orderBy]] and items can be limited to match some conditions using [[where]]. + * + * By calling [[createCommand()]], we can get a [[Command]] instance which can be further + * used to perform/execute the DB query against a database. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @author Carsten Brandt <mail@cebe.cc> + * @since 2.0 + */ +interface QueryInterface +{ + /** + * Executes the query and returns all results as an array. + * @param Connection $db the database connection used to execute the query. + * If this parameter is not given, the `db` application component will be used. + * @return array the query results. If the query results in nothing, an empty array will be returned. + */ + public function all($db = null); + + /** + * Executes the query and returns a single row of result. + * @param Connection $db the database connection used to execute the query. + * If this parameter is not given, the `db` application component will be used. + * @return array|boolean the first row (in terms of an array) of the query result. False is returned if the query + * results in nothing. + */ + public function one($db = null); + + /** + * Returns the number of records. + * @param string $q the COUNT expression. Defaults to '*'. + * @param Connection $db the database connection used to execute the query. + * If this parameter is not given, the `db` application component will be used. + * @return integer number of records + */ + public function count($q = '*', $db = null); + + /** + * Returns a value indicating whether the query result contains any row of data. + * @param Connection $db the database connection used to execute the query. + * If this parameter is not given, the `db` application component will be used. + * @return boolean whether the query result contains any row of data. + */ + public function exists($db = null); + + /** + * Sets the [[indexBy]] property. + * @param string|callable $column the name of the column by which the query results should be indexed by. + * This can also be a callable (e.g. anonymous function) that returns the index value based on the given + * row data. The signature of the callable should be: + * + * ~~~ + * function ($row) + * { + * // return the index value corresponding to $row + * } + * ~~~ + * + * @return static the query object itself + */ + public function indexBy($column); + + /** + * Sets the WHERE part of the query. + * + * The method requires a $condition parameter. + * + * The $condition parameter should be an array in one of the following two formats: + * + * - hash format: `['column1' => value1, 'column2' => value2, ...]` + * - operator format: `[operator, operand1, operand2, ...]` + * + * A condition in hash format represents the following SQL expression in general: + * `column1=value1 AND column2=value2 AND ...`. In case when a value is an array, + * an `IN` expression will be generated. And if a value is null, `IS NULL` will be used + * in the generated expression. Below are some examples: + * + * - `['type' => 1, 'status' => 2]` generates `(type = 1) AND (status = 2)`. + * - `['id' => [1, 2, 3], 'status' => 2]` generates `(id IN (1, 2, 3)) AND (status = 2)`. + * - `['status' => null] generates `status IS NULL`. + * + * A condition in operator format generates the SQL expression according to the specified operator, which + * can be one of the followings: + * + * - `and`: the operands should be concatenated together using `AND`. For example, + * `['and', 'id=1', 'id=2']` will generate `id=1 AND id=2`. If an operand is an array, + * it will be converted into a string using the rules described here. For example, + * `['and', 'type=1', ['or', 'id=1', 'id=2']]` will generate `type=1 AND (id=1 OR id=2)`. + * The method will NOT do any quoting or escaping. + * + * - `or`: similar to the `and` operator except that the operands are concatenated using `OR`. + * + * - `between`: operand 1 should be the column name, and operand 2 and 3 should be the + * starting and ending values of the range that the column is in. + * For example, `['between', 'id', 1, 10]` will generate `id BETWEEN 1 AND 10`. + * + * - `not between`: similar to `between` except the `BETWEEN` is replaced with `NOT BETWEEN` + * in the generated condition. + * + * - `in`: operand 1 should be a column or DB expression, and operand 2 be an array representing + * the range of the values that the column or DB expression should be in. For example, + * `['in', 'id', [1, 2, 3]]` will generate `id IN (1, 2, 3)`. + * The method will properly quote the column name and escape values in the range. + * + * - `not in`: similar to the `in` operator except that `IN` is replaced with `NOT IN` in the generated condition. + * + * - `like`: operand 1 should be a column or DB expression, and operand 2 be a string or an array representing + * the values that the column or DB expression should be like. + * For example, `['like', 'name', '%tester%']` will generate `name LIKE '%tester%'`. + * When the value range is given as an array, multiple `LIKE` predicates will be generated and concatenated + * using `AND`. For example, `['like', 'name', ['%test%', '%sample%']]` will generate + * `name LIKE '%test%' AND name LIKE '%sample%'`. + * The method will properly quote the column name and escape values in the range. + * + * - `or like`: similar to the `like` operator except that `OR` is used to concatenate the `LIKE` + * predicates when operand 2 is an array. + * + * - `not like`: similar to the `like` operator except that `LIKE` is replaced with `NOT LIKE` + * in the generated condition. + * + * - `or not like`: similar to the `not like` operator except that `OR` is used to concatenate + * the `NOT LIKE` predicates. + * + * @param array $condition the conditions that should be put in the WHERE part. + * @return static the query object itself + * @see andWhere() + * @see orWhere() + */ + public function where($condition); + + /** + * Adds an additional WHERE condition to the existing one. + * The new condition and the existing one will be joined using the 'AND' operator. + * @param string|array $condition the new WHERE condition. Please refer to [[where()]] + * on how to specify this parameter. + * @return static the query object itself + * @see where() + * @see orWhere() + */ + public function andWhere($condition); + + /** + * Adds an additional WHERE condition to the existing one. + * The new condition and the existing one will be joined using the 'OR' operator. + * @param string|array $condition the new WHERE condition. Please refer to [[where()]] + * on how to specify this parameter. + * @return static the query object itself + * @see where() + * @see andWhere() + */ + public function orWhere($condition); + + /** + * 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. `['id' => SORT_ASC, 'name' => 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 static the query object itself + * @see addOrderBy() + */ + public function orderBy($columns); + + /** + * 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. `['id' => SORT_ASC, 'name' => 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 static the query object itself + * @see orderBy() + */ + public function addOrderBy($columns); + + /** + * Sets the LIMIT part of the query. + * @param integer $limit the limit. Use null or negative value to disable limit. + * @return static the query object itself + */ + public function limit($limit); + + /** + * Sets the OFFSET part of the query. + * @param integer $offset the offset. Use null or negative value to disable offset. + * @return static the query object itself + */ + public function offset($offset); +} \ No newline at end of file diff --git a/framework/yii/db/QueryTrait.php b/framework/yii/db/QueryTrait.php new file mode 100644 index 0000000..a963869 --- /dev/null +++ b/framework/yii/db/QueryTrait.php @@ -0,0 +1,208 @@ +<?php +/** + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\db; + +/** + * The BaseQuery trait represents the minimum method set of a database Query. + * + * It has support for getting [[one]] instance or [[all]]. + * Allows pagination via [[limit]] and [[offset]]. + * Sorting is supported via [[orderBy]] and items can be limited to match some conditions unsing [[where]]. + * + * By calling [[createCommand()]], we can get a [[Command]] instance which can be further + * used to perform/execute the DB query against a database. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @author Carsten Brandt <mail@cebe.cc> + * @since 2.0 + */ +trait QueryTrait +{ + /** + * @var string|array query condition. This refers to the WHERE clause in a SQL statement. + * For example, `age > 31 AND team = 1`. + * @see where() + */ + public $where; + /** + * @var integer maximum number of records to be returned. If not set or less than 0, it means no limit. + */ + public $limit; + /** + * @var integer zero-based offset from where the records are to be returned. If not set or + * less than 0, it means starting from the beginning. + */ + public $offset; + /** + * @var array how to sort the query results. This is used to construct the ORDER BY clause in a SQL statement. + * The array keys are the columns to be sorted by, and the array values are the corresponding sort directions which + * can be either [SORT_ASC](http://php.net/manual/en/array.constants.php#constant.sort-asc) + * or [SORT_DESC](http://php.net/manual/en/array.constants.php#constant.sort-desc). + * The array may also contain [[Expression]] objects. If that is the case, the expressions + * will be converted into strings without any change. + */ + public $orderBy; + /** + * @var string|callable $column the name of the column by which the query results should be indexed by. + * This can also be a callable (e.g. anonymous function) that returns the index value based on the given + * row data. For more details, see [[indexBy()]]. This property is only used by [[all()]]. + */ + public $indexBy; + + /** + * Sets the [[indexBy]] property. + * @param string|callable $column the name of the column by which the query results should be indexed by. + * This can also be a callable (e.g. anonymous function) that returns the index value based on the given + * row data. The signature of the callable should be: + * + * ~~~ + * function ($row) + * { + * // return the index value corresponding to $row + * } + * ~~~ + * + * @return static the query object itself + */ + public function indexBy($column) + { + $this->indexBy = $column; + return $this; + } + + /** + * Sets the WHERE part of the query. + * + * See [[QueryInterface::where()]] for detailed documentation. + * + * @param array $condition the conditions that should be put in the WHERE part. + * @return static the query object itself + * @see andWhere() + * @see orWhere() + */ + public function where($condition) + { + $this->where = $condition; + return $this; + } + + /** + * Adds an additional WHERE condition to the existing one. + * The new condition and the existing one will be joined using the 'AND' operator. + * @param string|array $condition the new WHERE condition. Please refer to [[where()]] + * on how to specify this parameter. + * @return static the query object itself + * @see where() + * @see orWhere() + */ + public function andWhere($condition) + { + if ($this->where === null) { + $this->where = $condition; + } else { + $this->where = ['and', $this->where, $condition]; + } + return $this; + } + + /** + * Adds an additional WHERE condition to the existing one. + * The new condition and the existing one will be joined using the 'OR' operator. + * @param string|array $condition the new WHERE condition. Please refer to [[where()]] + * on how to specify this parameter. + * @return static the query object itself + * @see where() + * @see andWhere() + */ + public function orWhere($condition) + { + if ($this->where === null) { + $this->where = $condition; + } else { + $this->where = ['or', $this->where, $condition]; + } + return $this; + } + + /** + * 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. `['id' => SORT_ASC, 'name' => 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 static the query object itself + * @see addOrderBy() + */ + public function orderBy($columns) + { + $this->orderBy = $this->normalizeOrderBy($columns); + return $this; + } + + /** + * 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. `['id' => SORT_ASC, 'name' => 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 static the query object itself + * @see orderBy() + */ + public function addOrderBy($columns) + { + $columns = $this->normalizeOrderBy($columns); + if ($this->orderBy === null) { + $this->orderBy = $columns; + } else { + $this->orderBy = array_merge($this->orderBy, $columns); + } + return $this; + } + + protected function normalizeOrderBy($columns) + { + if (is_array($columns)) { + return $columns; + } else { + $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY); + $result = []; + foreach ($columns as $column) { + if (preg_match('/^(.*?)\s+(asc|desc)$/i', $column, $matches)) { + $result[$matches[1]] = strcasecmp($matches[2], 'desc') ? SORT_ASC : SORT_DESC; + } else { + $result[$column] = SORT_ASC; + } + } + return $result; + } + } + + /** + * Sets the LIMIT part of the query. + * @param integer $limit the limit. Use null or negative value to disable limit. + * @return static the query object itself + */ + public function limit($limit) + { + $this->limit = $limit; + return $this; + } + + /** + * Sets the OFFSET part of the query. + * @param integer $offset the offset. Use null or negative value to disable offset. + * @return static the query object itself + */ + public function offset($offset) + { + $this->offset = $offset; + return $this; + } +} \ No newline at end of file diff --git a/framework/yii/db/Schema.php b/framework/yii/db/Schema.php index 1d86616..f2ae94c 100644 --- a/framework/yii/db/Schema.php +++ b/framework/yii/db/Schema.php @@ -58,11 +58,11 @@ abstract class Schema extends Object /** * @var array list of ALL table names in the database */ - private $_tableNames = array(); + private $_tableNames = []; /** * @var array list of loaded table metadata (table name => TableSchema) */ - private $_tables = array(); + private $_tables = []; /** * @var QueryBuilder the query builder for this database */ @@ -92,14 +92,16 @@ abstract class Schema extends Object $realName = $this->getRawTableName($name); if ($db->enableSchemaCache && !in_array($name, $db->schemaCacheExclude, true)) { - /** @var $cache Cache */ + /** @var Cache $cache */ $cache = is_string($db->schemaCache) ? Yii::$app->getComponent($db->schemaCache) : $db->schemaCache; if ($cache instanceof Cache) { $key = $this->getCacheKey($name); if ($refresh || ($table = $cache->get($key)) === false) { $table = $this->loadTableSchema($realName); if ($table !== null) { - $cache->set($key, $table, $db->schemaCacheDuration, new GroupDependency($this->getCacheGroup())); + $cache->set($key, $table, $db->schemaCacheDuration, new GroupDependency([ + 'group' => $this->getCacheGroup(), + ])); } } return $this->_tables[$name] = $table; @@ -115,12 +117,12 @@ abstract class Schema extends Object */ protected function getCacheKey($name) { - return array( + return [ __CLASS__, $this->db->dsn, $this->db->username, $name, - ); + ]; } /** @@ -130,11 +132,11 @@ abstract class Schema extends Object */ protected function getCacheGroup() { - return md5(serialize(array( + return md5(serialize([ __CLASS__, $this->db->dsn, $this->db->username, - ))); + ])); } /** @@ -147,7 +149,7 @@ abstract class Schema extends Object */ public function getTableSchemas($schema = '', $refresh = false) { - $tables = array(); + $tables = []; foreach ($this->getTableNames($schema, $refresh) as $name) { if ($schema !== '') { $name = $schema . '.' . $name; @@ -187,19 +189,39 @@ abstract class Schema extends Object } /** + * Determines the PDO type for the given PHP data value. + * @param mixed $data the data whose PDO type is to be determined + * @return integer the PDO type + * @see http://www.php.net/manual/en/pdo.constants.php + */ + public function getPdoType($data) + { + static $typeMap = [ + // php type => PDO type + 'boolean' => \PDO::PARAM_BOOL, + 'integer' => \PDO::PARAM_INT, + 'string' => \PDO::PARAM_STR, + 'resource' => \PDO::PARAM_LOB, + 'NULL' => \PDO::PARAM_NULL, + ]; + $type = gettype($data); + return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR; + } + + /** * Refreshes the schema. * This method cleans up all cached table schemas so that they can be re-created later * to reflect the database schema change. */ public function refresh() { - /** @var $cache Cache */ + /** @var Cache $cache */ $cache = is_string($this->db->schemaCache) ? Yii::$app->getComponent($this->db->schemaCache) : $this->db->schemaCache; if ($this->db->enableSchemaCache && $cache instanceof Cache) { GroupDependency::invalidate($cache, $this->getCacheGroup()); } - $this->_tableNames = array(); - $this->_tables = array(); + $this->_tableNames = []; + $this->_tables = []; } /** @@ -269,7 +291,7 @@ abstract class Schema extends Object * then this method will do nothing. * @param string $name table name * @return string the properly quoted table name - * @see quoteSimpleTableName + * @see quoteSimpleTableName() */ public function quoteTableName($name) { @@ -294,7 +316,7 @@ abstract class Schema extends Object * then this method will do nothing. * @param string $name column name * @return string the properly quoted column name - * @see quoteSimpleColumnName + * @see quoteSimpleColumnName() */ public function quoteColumnName($name) { @@ -358,13 +380,13 @@ abstract class Schema extends Object */ protected function getColumnPhpType($column) { - static $typeMap = array( // abstract type => php type + static $typeMap = [ // abstract type => php type 'smallint' => 'integer', 'integer' => 'integer', 'bigint' => 'integer', 'boolean' => 'boolean', 'float' => 'double', - ); + ]; if (isset($typeMap[$column->type])) { if ($column->type === 'bigint') { return PHP_INT_SIZE == 8 && !$column->unsigned ? 'integer' : 'string'; diff --git a/framework/yii/db/TableSchema.php b/framework/yii/db/TableSchema.php index 910061d..91ce78d 100644 --- a/framework/yii/db/TableSchema.php +++ b/framework/yii/db/TableSchema.php @@ -31,7 +31,7 @@ class TableSchema extends Object /** * @var string[] primary keys of this table. */ - public $primaryKey = array(); + public $primaryKey = []; /** * @var string sequence name for the primary key. Null if no sequence. */ @@ -40,18 +40,18 @@ class TableSchema extends Object * @var array foreign keys of this table. Each array element is of the following structure: * * ~~~ - * array( + * [ * 'ForeignTableName', * 'fk1' => 'pk1', // pk1 is in foreign table * 'fk2' => 'pk2', // if composite foreign key - * ) + * ] * ~~~ */ - public $foreignKeys = array(); + public $foreignKeys = []; /** * @var ColumnSchema[] column metadata of this table. Each array element is a [[ColumnSchema]] object, indexed by column names. */ - public $columns = array(); + public $columns = []; /** * Gets the named column metadata. @@ -81,7 +81,7 @@ class TableSchema extends Object public function fixPrimaryKey($keys) { if (!is_array($keys)) { - $keys = array($keys); + $keys = [$keys]; } $this->primaryKey = $keys; foreach ($this->columns as $column) { diff --git a/framework/yii/db/cubrid/QueryBuilder.php b/framework/yii/db/cubrid/QueryBuilder.php index 4b7ef43..e80e1d6 100644 --- a/framework/yii/db/cubrid/QueryBuilder.php +++ b/framework/yii/db/cubrid/QueryBuilder.php @@ -20,7 +20,7 @@ class QueryBuilder extends \yii\db\QueryBuilder /** * @var array mapping from abstract column types (keys) to physical column types (values). */ - public $typeMap = array( + public $typeMap = [ Schema::TYPE_PK => 'int NOT NULL AUTO_INCREMENT PRIMARY KEY', Schema::TYPE_BIGPK => 'bigint NOT NULL AUTO_INCREMENT PRIMARY KEY', Schema::TYPE_STRING => 'varchar(255)', @@ -37,7 +37,7 @@ class QueryBuilder extends \yii\db\QueryBuilder Schema::TYPE_BINARY => 'blob', Schema::TYPE_BOOLEAN => 'smallint', Schema::TYPE_MONEY => 'decimal(19,4)', - ); + ]; /** * Creates a SQL statement for resetting the sequence value of a table's primary key. @@ -73,11 +73,11 @@ class QueryBuilder extends \yii\db\QueryBuilder * For example, * * ~~~ - * $connection->createCommand()->batchInsert('tbl_user', array('name', 'age'), array( - * array('Tom', 30), - * array('Jane', 20), - * array('Linda', 25), - * ))->execute(); + * $connection->createCommand()->batchInsert('tbl_user', ['name', 'age'], [ + * ['Tom', 30], + * ['Jane', 20], + * ['Linda', 25], + * ])->execute(); * ~~~ * * Note that the values in each row must match the corresponding column names. @@ -92,16 +92,16 @@ class QueryBuilder extends \yii\db\QueryBuilder if (($tableSchema = $this->db->getTableSchema($table)) !== null) { $columnSchemas = $tableSchema->columns; } else { - $columnSchemas = array(); + $columnSchemas = []; } foreach ($columns as $i => $name) { $columns[$i] = $this->db->quoteColumnName($name); } - $values = array(); + $values = []; foreach ($rows as $row) { - $vs = array(); + $vs = []; foreach ($row as $i => $value) { if (!is_array($value) && isset($columnSchemas[$columns[$i]])) { $value = $columnSchemas[$columns[$i]]->typecast($value); diff --git a/framework/yii/db/cubrid/Schema.php b/framework/yii/db/cubrid/Schema.php index ba7fcae..458f2e3 100644 --- a/framework/yii/db/cubrid/Schema.php +++ b/framework/yii/db/cubrid/Schema.php @@ -24,7 +24,7 @@ class Schema extends \yii\db\Schema * Please refer to [CUBRID manual](http://www.cubrid.org/manual/91/en/sql/datatype.html) for * details on data types. */ - public $typeMap = array( + public $typeMap = [ // Numeric data types 'short' => self::TYPE_SMALLINT, 'smallint' => self::TYPE_SMALLINT, @@ -62,7 +62,7 @@ class Schema extends \yii\db\Schema 'list' => self::TYPE_STRING, 'sequence' => self::TYPE_STRING, 'enum' => self::TYPE_STRING, - ); + ]; /** * Quotes a table name for use in a query. @@ -101,7 +101,8 @@ class Schema extends \yii\db\Schema $this->db->open(); // workaround for broken PDO::quote() implementation in CUBRID 9.1.0 http://jira.cubrid.org/browse/APIS-658 - if (version_compare($this->db->pdo->getAttribute(\PDO::ATTR_CLIENT_VERSION), '9.1.0', '<=')) { + $version = $this->db->pdo->getAttribute(\PDO::ATTR_CLIENT_VERSION); + if (version_compare($version, '8.4.4.0002', '<') || $version[0] == '9' && version_compare($version, '9.2.0.0002', '<=')) { return "'" . addcslashes(str_replace("'", "''", $str), "\000\n\r\\\032") . "'"; } else { return $this->db->pdo->quote($str); @@ -137,23 +138,27 @@ class Schema extends \yii\db\Schema foreach ($columns as $info) { $column = $this->loadColumnSchema($info); $table->columns[$column->name] = $column; - if ($column->isPrimaryKey) { - $table->primaryKey[] = $column->name; - if ($column->autoIncrement) { - $table->sequenceName = ''; - } + } + + $primaryKeys = $this->db->pdo->cubrid_schema(\PDO::CUBRID_SCH_PRIMARY_KEY, $table->name); + foreach ($primaryKeys as $key) { + $column = $table->columns[$key['ATTR_NAME']]; + $column->isPrimaryKey = true; + $table->primaryKey[] = $column->name; + if ($column->autoIncrement) { + $table->sequenceName = ''; } } $foreignKeys = $this->db->pdo->cubrid_schema(\PDO::CUBRID_SCH_IMPORTED_KEYS, $table->name); - foreach($foreignKeys as $key) { + foreach ($foreignKeys as $key) { if (isset($table->foreignKeys[$key['FK_NAME']])) { $table->foreignKeys[$key['FK_NAME']][$key['FKCOLUMN_NAME']] = $key['PKCOLUMN_NAME']; } else { - $table->foreignKeys[$key['FK_NAME']] = array( + $table->foreignKeys[$key['FK_NAME']] = [ $key['PKTABLE_NAME'], $key['FKCOLUMN_NAME'] => $key['PKCOLUMN_NAME'] - ); + ]; } } $table->foreignKeys = array_values($table->foreignKeys); @@ -175,7 +180,7 @@ class Schema extends \yii\db\Schema $column->name = $info['Field']; $column->allowNull = $info['Null'] === 'YES'; - $column->isPrimaryKey = strpos($info['Key'], 'PRI') !== false; + $column->isPrimaryKey = false; // primary key will be set by loadTableSchema() later $column->autoIncrement = stripos($info['Extra'], 'auto_increment') !== false; $column->dbType = strtolower($info['Type']); @@ -228,8 +233,8 @@ class Schema extends \yii\db\Schema { $this->db->open(); $tables = $this->db->pdo->cubrid_schema(\PDO::CUBRID_SCH_TABLE); - $tableNames = array(); - foreach($tables as $table) { + $tableNames = []; + foreach ($tables as $table) { // do not list system tables if ($table['TYPE'] != 0) { $tableNames[] = $table['NAME']; @@ -237,4 +242,24 @@ class Schema extends \yii\db\Schema } return $tableNames; } + + /** + * Determines the PDO type for the given PHP data value. + * @param mixed $data the data whose PDO type is to be determined + * @return integer the PDO type + * @see http://www.php.net/manual/en/pdo.constants.php + */ + public function getPdoType($data) + { + static $typeMap = [ + // php type => PDO type + 'boolean' => \PDO::PARAM_INT, // PARAM_BOOL is not supported by CUBRID PDO + 'integer' => \PDO::PARAM_INT, + 'string' => \PDO::PARAM_STR, + 'resource' => \PDO::PARAM_LOB, + 'NULL' => \PDO::PARAM_NULL, + ]; + $type = gettype($data); + return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR; + } } diff --git a/framework/yii/db/mssql/QueryBuilder.php b/framework/yii/db/mssql/QueryBuilder.php index aeb5be8..c9bf7ca 100644 --- a/framework/yii/db/mssql/QueryBuilder.php +++ b/framework/yii/db/mssql/QueryBuilder.php @@ -20,7 +20,7 @@ class QueryBuilder extends \yii\db\QueryBuilder /** * @var array mapping from abstract column types (keys) to physical column types (values). */ - public $typeMap = array( + public $typeMap = [ Schema::TYPE_PK => 'int IDENTITY PRIMARY KEY', Schema::TYPE_BIGPK => 'bigint IDENTITY PRIMARY KEY', Schema::TYPE_STRING => 'varchar(255)', @@ -37,7 +37,7 @@ class QueryBuilder extends \yii\db\QueryBuilder Schema::TYPE_BINARY => 'binary', Schema::TYPE_BOOLEAN => 'tinyint(1)', Schema::TYPE_MONEY => 'decimal(19,4)', - ); + ]; // public function update($table, $columns, $condition, &$params) // { diff --git a/framework/yii/db/mssql/Schema.php b/framework/yii/db/mssql/Schema.php index 9def3b4..deb92f9 100644 --- a/framework/yii/db/mssql/Schema.php +++ b/framework/yii/db/mssql/Schema.php @@ -25,7 +25,7 @@ class Schema extends \yii\db\Schema /** * @var array mapping from physical column types (keys) to abstract column types (values) */ - public $typeMap = array( + public $typeMap = [ // exact numerics 'bigint' => self::TYPE_BIGINT, 'numeric' => self::TYPE_DECIMAL, @@ -72,7 +72,7 @@ class Schema extends \yii\db\Schema 'sql_variant' => self::TYPE_STRING, 'xml' => self::TYPE_STRING, 'table' => self::TYPE_STRING, - ); + ]; /** * Quotes a table name for use in a query. @@ -118,6 +118,8 @@ class Schema extends \yii\db\Schema if ($this->findColumns($table)) { $this->findForeignKeys($table); return $table; + } else { + return null; } } @@ -128,7 +130,7 @@ class Schema extends \yii\db\Schema */ protected function resolveTableNames($table, $name) { - $parts = explode('.', str_replace(array('[', ']'), '', $name)); + $parts = explode('.', str_replace(['[', ']'], '', $name)); $partCount = count($parts); if ($partCount == 3) { // catalog name, schema name and table name passed @@ -158,7 +160,7 @@ class Schema extends \yii\db\Schema $column->name = $info['column_name']; $column->allowNull = $info['is_nullable'] == 'YES'; $column->dbType = $info['data_type']; - $column->enumValues = array(); // mssql has only vague equivalents to enum + $column->enumValues = []; // mssql has only vague equivalents to enum $column->isPrimaryKey = null; // primary key will be determined in findColumns() method $column->autoIncrement = $info['is_identity'] == 1; $column->unsigned = stripos($column->dbType, 'unsigned') !== false; @@ -283,7 +285,7 @@ WHERE SQL; $table->primaryKey = $this->db - ->createCommand($sql, array(':tableName' => $table->name, ':schemaName' => $table->schemaName)) + ->createCommand($sql, [':tableName' => $table->name, ':schemaName' => $table->schemaName]) ->queryColumn(); } @@ -322,10 +324,10 @@ JOIN {$keyColumnUsageTableName} AS [kcu2] ON WHERE [kcu1].[table_name] = :tableName SQL; - $rows = $this->db->createCommand($sql, array(':tableName' => $table->name))->queryAll(); - $table->foreignKeys = array(); + $rows = $this->db->createCommand($sql, [':tableName' => $table->name])->queryAll(); + $table->foreignKeys = []; foreach ($rows as $row) { - $table->foreignKeys[] = array($row['uq_table_name'], $row['fk_column_name'] => $row['uq_column_name']); + $table->foreignKeys[] = [$row['uq_table_name'], $row['fk_column_name'] => $row['uq_column_name']]; } } @@ -346,7 +348,7 @@ FROM [information_schema].[tables] AS [t] WHERE [t].[table_schema] = :schema AND [t].[table_type] = 'BASE TABLE' SQL; - $names = $this->db->createCommand($sql, array(':schema' => $schema))->queryColumn(); + $names = $this->db->createCommand($sql, [':schema' => $schema])->queryColumn(); if ($schema !== static::DEFAULT_SCHEMA) { foreach ($names as $index => $name) { $names[$index] = $schema . '.' . $name; diff --git a/framework/yii/db/mysql/QueryBuilder.php b/framework/yii/db/mysql/QueryBuilder.php index 386de2f..50e717c 100644 --- a/framework/yii/db/mysql/QueryBuilder.php +++ b/framework/yii/db/mysql/QueryBuilder.php @@ -21,7 +21,7 @@ class QueryBuilder extends \yii\db\QueryBuilder /** * @var array mapping from abstract column types (keys) to physical column types (values). */ - public $typeMap = array( + public $typeMap = [ Schema::TYPE_PK => 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY', Schema::TYPE_BIGPK => 'bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY', Schema::TYPE_STRING => 'varchar(255)', @@ -38,7 +38,7 @@ class QueryBuilder extends \yii\db\QueryBuilder Schema::TYPE_BINARY => 'blob', Schema::TYPE_BOOLEAN => 'tinyint(1)', Schema::TYPE_MONEY => 'decimal(19,4)', - ); + ]; /** * Builds a SQL statement for renaming a column. @@ -146,11 +146,11 @@ class QueryBuilder extends \yii\db\QueryBuilder * For example, * * ~~~ - * $connection->createCommand()->batchInsert('tbl_user', array('name', 'age'), array( - * array('Tom', 30), - * array('Jane', 20), - * array('Linda', 25), - * ))->execute(); + * $connection->createCommand()->batchInsert('tbl_user', ['name', 'age'], [ + * ['Tom', 30], + * ['Jane', 20], + * ['Linda', 25], + * ])->execute(); * ~~~ * * Note that the values in each row must match the corresponding column names. @@ -165,16 +165,16 @@ class QueryBuilder extends \yii\db\QueryBuilder if (($tableSchema = $this->db->getTableSchema($table)) !== null) { $columnSchemas = $tableSchema->columns; } else { - $columnSchemas = array(); + $columnSchemas = []; } foreach ($columns as $i => $name) { $columns[$i] = $this->db->quoteColumnName($name); } - $values = array(); + $values = []; foreach ($rows as $row) { - $vs = array(); + $vs = []; foreach ($row as $i => $value) { if (!is_array($value) && isset($columnSchemas[$columns[$i]])) { $value = $columnSchemas[$columns[$i]]->typecast($value); diff --git a/framework/yii/db/mysql/Schema.php b/framework/yii/db/mysql/Schema.php index 998f49a..f55d38f 100644 --- a/framework/yii/db/mysql/Schema.php +++ b/framework/yii/db/mysql/Schema.php @@ -21,7 +21,7 @@ class Schema extends \yii\db\Schema /** * @var array mapping from physical column types (keys) to abstract column types (values) */ - public $typeMap = array( + public $typeMap = [ 'tinyint' => self::TYPE_SMALLINT, 'bit' => self::TYPE_SMALLINT, 'smallint' => self::TYPE_SMALLINT, @@ -47,7 +47,7 @@ class Schema extends \yii\db\Schema 'time' => self::TYPE_TIME, 'timestamp' => self::TYPE_TIMESTAMP, 'enum' => self::TYPE_STRING, - ); + ]; /** * Quotes a table name for use in a query. @@ -225,7 +225,7 @@ class Schema extends \yii\db\Schema foreach ($matches as $match) { $fks = array_map('trim', explode(',', str_replace('`', '', $match[1]))); $pks = array_map('trim', explode(',', str_replace('`', '', $match[3]))); - $constraint = array(str_replace('`', '', $match[2])); + $constraint = [str_replace('`', '', $match[2])]; foreach ($fks as $k => $name) { $constraint[$name] = $pks[$k]; } diff --git a/framework/yii/db/pgsql/QueryBuilder.php b/framework/yii/db/pgsql/QueryBuilder.php index 33c7bf6..09a620d 100644 --- a/framework/yii/db/pgsql/QueryBuilder.php +++ b/framework/yii/db/pgsql/QueryBuilder.php @@ -20,7 +20,7 @@ class QueryBuilder extends \yii\db\QueryBuilder /** * @var array mapping from abstract column types (keys) to physical column types (values). */ - public $typeMap = array( + public $typeMap = [ Schema::TYPE_PK => 'serial NOT NULL PRIMARY KEY', Schema::TYPE_BIGPK => 'bigserial NOT NULL PRIMARY KEY', Schema::TYPE_STRING => 'varchar(255)', @@ -37,5 +37,44 @@ class QueryBuilder extends \yii\db\QueryBuilder Schema::TYPE_BINARY => 'bytea', Schema::TYPE_BOOLEAN => 'boolean', Schema::TYPE_MONEY => 'numeric(19,4)', - ); + ]; + + /** + * Builds a SQL statement for dropping an index. + * @param string $name the name of the index to be dropped. The name will be properly quoted by the method. + * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method. + * @return string the SQL statement for dropping an index. + */ + public function dropIndex($name, $table) + { + return 'DROP INDEX ' . $this->db->quoteTableName($name); + } + + /** + * Builds a SQL statement for renaming a DB table. + * @param string $oldName the table to be renamed. The name will be properly quoted by the method. + * @param string $newName the new table name. The name will be properly quoted by the method. + * @return string the SQL statement for renaming a DB table. + */ + public function renameTable($oldName, $newName) + { + return 'ALTER TABLE ' . $this->db->quoteTableName($oldName) . ' RENAME TO ' . $this->db->quoteTableName($newName); + } + + /** + * Builds a SQL statement for changing the definition of a column. + * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method. + * @param string $column the name of the column to be changed. The name will be properly quoted by the method. + * @param string $type the new column type. The [[getColumnType()]] method will be invoked to convert abstract + * column type (if any) into the physical one. Anything that is not recognized as abstract type will be kept + * in the generated SQL. For example, 'string' will be turned into 'varchar(255)', while 'string not null' + * will become 'varchar(255) not null'. + * @return string the SQL statement for changing the definition of a column. + */ + public function alterColumn($table, $column, $type) + { + return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ALTER COLUMN ' + . $this->db->quoteColumnName($column) . ' TYPE ' + . $this->getColumnType($type); + } } diff --git a/framework/yii/db/pgsql/Schema.php b/framework/yii/db/pgsql/Schema.php index d131342..d7885f2 100644 --- a/framework/yii/db/pgsql/Schema.php +++ b/framework/yii/db/pgsql/Schema.php @@ -31,9 +31,10 @@ class Schema extends \yii\db\Schema * @var array mapping from physical column types (keys) to abstract * column types (values) */ - public $typeMap = array( + public $typeMap = [ 'abstime' => self::TYPE_TIMESTAMP, 'bit' => self::TYPE_STRING, + 'bool' => self::TYPE_BOOLEAN, 'boolean' => self::TYPE_BOOLEAN, 'box' => self::TYPE_STRING, 'character' => self::TYPE_STRING, @@ -47,6 +48,8 @@ class Schema extends \yii\db\Schema 'double precision' => self::TYPE_DECIMAL, 'inet' => self::TYPE_STRING, 'smallint' => self::TYPE_SMALLINT, + 'int4' => self::TYPE_INTEGER, + 'int8' => self::TYPE_BIGINT, 'integer' => self::TYPE_INTEGER, 'bigint' => self::TYPE_BIGINT, 'interval' => self::TYPE_STRING, @@ -70,7 +73,7 @@ class Schema extends \yii\db\Schema 'bit varying' => self::TYPE_STRING, 'character varying' => self::TYPE_STRING, 'xml' => self::TYPE_STRING - ); + ]; /** * Creates a query builder for the PostgreSQL database. @@ -129,6 +132,28 @@ class Schema extends \yii\db\Schema } /** + * Determines the PDO type for the given PHP data value. + * @param mixed $data the data whose PDO type is to be determined + * @return integer the PDO type + * @see http://www.php.net/manual/en/pdo.constants.php + */ + public function getPdoType($data) + { + // php type => PDO type + static $typeMap = [ + // https://github.com/yiisoft/yii2/issues/1115 + // Cast boolean to integer values to work around problems with PDO casting false to string '' https://bugs.php.net/bug.php?id=33876 + 'boolean' => \PDO::PARAM_INT, + 'integer' => \PDO::PARAM_INT, + 'string' => \PDO::PARAM_STR, + 'resource' => \PDO::PARAM_LOB, + 'NULL' => \PDO::PARAM_NULL, + ]; + $type = gettype($data); + return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR; + } + + /** * Returns all table names in the database. * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. * If not empty, the returned table names will be prefixed with the schema name. @@ -146,7 +171,7 @@ EOD; $command = $this->db->createCommand($sql); $command->bindParam(':schema', $schema); $rows = $command->queryAll(); - $names = array(); + $names = []; foreach ($rows as $row) { if ($schema === $this->defaultSchema) { $names[] = $row['table_name']; @@ -198,7 +223,7 @@ SQL; } else { $foreignTable = $constraint['foreign_table_name']; } - $citem = array($foreignTable); + $citem = [$foreignTable]; foreach ($columns as $idx => $column) { $citem[$fcolumns[$idx]] = $column; } @@ -217,18 +242,17 @@ SQL; $schemaName = $this->db->quoteValue($table->schemaName); $sql = <<<SQL SELECT - current_database() as table_catalog, - d.nspname AS table_schema, - c.relname AS table_name, - a.attname AS column_name, - t.typname AS data_type, - a.attlen AS character_maximum_length, - pg_catalog.col_description(c.oid, a.attnum) AS column_comment, - a.atttypmod AS modifier, - a.attnotnull = false AS is_nullable, - CAST(pg_get_expr(ad.adbin, ad.adrelid) AS varchar) AS column_default, - coalesce(pg_get_expr(ad.adbin, ad.adrelid) ~ 'nextval',false) AS is_autoinc, - array_to_string((select array_agg(enumlabel) from pg_enum where enumtypid=a.atttypid)::varchar[],',') as enum_values, + d.nspname AS table_schema, + c.relname AS table_name, + a.attname AS column_name, + t.typname AS data_type, + a.attlen AS character_maximum_length, + pg_catalog.col_description(c.oid, a.attnum) AS column_comment, + a.atttypmod AS modifier, + a.attnotnull = false AS is_nullable, + CAST(pg_get_expr(ad.adbin, ad.adrelid) AS varchar) AS column_default, + coalesce(pg_get_expr(ad.adbin, ad.adrelid) ~ 'nextval',false) AS is_autoinc, + array_to_string((select array_agg(enumlabel) from pg_enum where enumtypid=a.atttypid)::varchar[],',') as enum_values, CASE atttypid WHEN 21 /*int2*/ THEN 16 WHEN 23 /*int4*/ THEN 32 @@ -264,7 +288,7 @@ FROM LEFT JOIN pg_namespace d ON d.oid = c.relnamespace LEFT join pg_constraint ct on ct.conrelid=c.oid and ct.contype='p' WHERE - a.attnum > 0 + a.attnum > 0 and t.typname != '' and c.relname = {$tableName} and d.nspname = {$schemaName} ORDER BY @@ -280,8 +304,8 @@ SQL; $table->columns[$column->name] = $column; if ($column->isPrimaryKey === true) { $table->primaryKey[] = $column->name; - if ($table->sequenceName === null && preg_match("/nextval\('\w+'(::regclass)?\)/", $column->defaultValue) === 1) { - $table->sequenceName = preg_replace(array('/nextval/', '/::/', '/regclass/', '/\'\)/', '/\(\'/'), '', $column->defaultValue); + if ($table->sequenceName === null && preg_match("/nextval\\('\"?\\w+\"?'(::regclass)?\\)/", $column->defaultValue) === 1) { + $table->sequenceName = preg_replace(['/nextval/', '/::/', '/regclass/', '/\'\)/', '/\(\'/'], '', $column->defaultValue); } } } @@ -301,8 +325,8 @@ SQL; $column->comment = $info['column_comment']; $column->dbType = $info['data_type']; $column->defaultValue = $info['column_default']; - $column->enumValues = explode(',', str_replace(array("''"), array("'"), $info['enum_values'])); - $column->unsigned = false; // has no meanining in PG + $column->enumValues = explode(',', str_replace(["''"], ["'"], $info['enum_values'])); + $column->unsigned = false; // has no meaning in PG $column->isPrimaryKey = $info['is_pkey']; $column->name = $info['column_name']; $column->precision = $info['numeric_precision']; diff --git a/framework/yii/db/sqlite/QueryBuilder.php b/framework/yii/db/sqlite/QueryBuilder.php index 4e210f8..4a5407f 100644 --- a/framework/yii/db/sqlite/QueryBuilder.php +++ b/framework/yii/db/sqlite/QueryBuilder.php @@ -22,7 +22,7 @@ class QueryBuilder extends \yii\db\QueryBuilder /** * @var array mapping from abstract column types (keys) to physical column types (values). */ - public $typeMap = array( + public $typeMap = [ Schema::TYPE_PK => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL', Schema::TYPE_BIGPK => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL', Schema::TYPE_STRING => 'varchar(255)', @@ -39,7 +39,7 @@ class QueryBuilder extends \yii\db\QueryBuilder Schema::TYPE_BINARY => 'blob', Schema::TYPE_BOOLEAN => 'boolean', Schema::TYPE_MONEY => 'decimal(19,4)', - ); + ]; /** * Creates a SQL statement for resetting the sequence value of a table's primary key. @@ -80,6 +80,7 @@ class QueryBuilder extends \yii\db\QueryBuilder * @param boolean $check whether to turn on or off the integrity check. * @param string $schema the schema of the tables. Meaningless for SQLite. * @param string $table the table name. Meaningless for SQLite. + * @return string the SQL statement for checking integrity * @throws NotSupportedException this is not supported by SQLite */ public function checkIntegrity($check = true, $schema = '', $table = '') diff --git a/framework/yii/db/sqlite/Schema.php b/framework/yii/db/sqlite/Schema.php index bca26c1..38fbf3a 100644 --- a/framework/yii/db/sqlite/Schema.php +++ b/framework/yii/db/sqlite/Schema.php @@ -21,7 +21,7 @@ class Schema extends \yii\db\Schema /** * @var array mapping from physical column types (keys) to abstract column types (values) */ - public $typeMap = array( + public $typeMap = [ 'tinyint' => self::TYPE_SMALLINT, 'bit' => self::TYPE_SMALLINT, 'smallint' => self::TYPE_SMALLINT, @@ -47,7 +47,7 @@ class Schema extends \yii\db\Schema 'time' => self::TYPE_TIME, 'timestamp' => self::TYPE_TIMESTAMP, 'enum' => self::TYPE_STRING, - ); + ]; /** * Creates a query builder for the MySQL database. @@ -128,7 +128,7 @@ class Schema extends \yii\db\Schema foreach ($keys as $key) { $id = (int)$key['id']; if (!isset($table->foreignKeys[$id])) { - $table->foreignKeys[$id] = array($key['table'], $key['from'] => $key['to']); + $table->foreignKeys[$id] = [$key['table'], $key['from'] => $key['to']]; } else { // composite FK $table->foreignKeys[$id][$key['from']] = $key['to']; @@ -153,7 +153,7 @@ class Schema extends \yii\db\Schema $column->type = self::TYPE_STRING; if (preg_match('/^(\w+)(?:\(([^\)]+)\))?/', $column->dbType, $matches)) { - $type = $matches[1]; + $type = strtolower($matches[1]); if (isset($this->typeMap[$type])) { $column->type = $this->typeMap[$type]; } diff --git a/framework/yii/grid/ActionColumn.php b/framework/yii/grid/ActionColumn.php index 72fafb6..2ee1db2 100644 --- a/framework/yii/grid/ActionColumn.php +++ b/framework/yii/grid/ActionColumn.php @@ -10,17 +10,17 @@ namespace yii\grid; use Yii; use Closure; use yii\helpers\Html; -use yii\helpers\Inflector; -use yii\helpers\StringHelper; /** + * ActionColumn is a column for the [[GridView]] widget that displays buttons for viewing and manipulating the items. + * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ class ActionColumn extends Column { public $template = '{view} {update} {delete}'; - public $buttons = array(); + public $buttons = []; public $urlCreator; public function init() @@ -35,29 +35,29 @@ class ActionColumn extends Column $this->buttons['view'] = function ($model, $column) { /** @var ActionColumn $column */ $url = $column->createUrl($model, 'view'); - return Html::a('<span class="glyphicon glyphicon-eye-open"></span>', $url, array( + return Html::a('<span class="glyphicon glyphicon-eye-open"></span>', $url, [ 'title' => Yii::t('yii', 'View'), - )); + ]); }; } if (!isset($this->buttons['update'])) { $this->buttons['update'] = function ($model, $column) { /** @var ActionColumn $column */ $url = $column->createUrl($model, 'update'); - return Html::a('<span class="glyphicon glyphicon-pencil"></span>', $url, array( + return Html::a('<span class="glyphicon glyphicon-pencil"></span>', $url, [ 'title' => Yii::t('yii', 'Update'), - )); + ]); }; } if (!isset($this->buttons['delete'])) { $this->buttons['delete'] = function ($model, $column) { /** @var ActionColumn $column */ $url = $column->createUrl($model, 'delete'); - return Html::a('<span class="glyphicon glyphicon-trash"></span>', $url, array( + return Html::a('<span class="glyphicon glyphicon-trash"></span>', $url, [ 'title' => Yii::t('yii', 'Delete'), 'data-confirm' => Yii::t('yii', 'Are you sure to delete this item?'), 'data-method' => 'post', - )); + ]); }; } } @@ -72,12 +72,11 @@ class ActionColumn extends Column if ($this->urlCreator instanceof Closure) { return call_user_func($this->urlCreator, $model, $action); } else { - $route = Inflector::camel2id(StringHelper::basename(get_class($model))) . '/' . $action; $params = $model->getPrimaryKey(true); if (count($params) === 1) { - $params = array('id' => reset($params)); + $params = ['id' => reset($params)]; } - return Yii::$app->getUrlManager()->createUrl($route, $params); + return Yii::$app->controller->createUrl($action, $params); } } @@ -89,11 +88,10 @@ class ActionColumn extends Column */ protected function renderDataCellContent($model, $index) { - $column = $this; - return preg_replace_callback('/\\{(\w+)\\}/', function ($matches) use ($model, $column) { + return preg_replace_callback('/\\{(\w+)\\}/', function ($matches) use ($model) { $name = $matches[1]; - if (isset($column->buttons[$name])) { - return call_user_func($column->buttons[$name], $model, $column); + if (isset($this->buttons[$name])) { + return call_user_func($this->buttons[$name], $model, $this); } else { return ''; } diff --git a/framework/yii/grid/CheckboxColumn.php b/framework/yii/grid/CheckboxColumn.php index e9170f4..d029648 100644 --- a/framework/yii/grid/CheckboxColumn.php +++ b/framework/yii/grid/CheckboxColumn.php @@ -27,7 +27,7 @@ use yii\helpers\Html; class CheckboxColumn extends Column { public $name = 'selection'; - public $checkboxOptions = array(); + public $checkboxOptions = []; public $multiple = true; @@ -52,17 +52,17 @@ class CheckboxColumn extends Column { $name = rtrim($this->name, '[]') . '_all'; $id = $this->grid->options['id']; - $options = json_encode(array( + $options = json_encode([ 'name' => $this->name, 'multiple' => $this->multiple, 'checkAll' => $name, - )); + ]); $this->grid->getView()->registerJs("jQuery('#$id').yiiGridView('setSelectionColumn', $options);"); if ($this->header !== null || !$this->multiple) { return parent::renderHeaderCellContent(); } else { - return Html::checkBox($name, false, array('class' => 'select-on-check-all')); + return Html::checkBox($name, false, ['class' => 'select-on-check-all']); } } diff --git a/framework/yii/grid/Column.php b/framework/yii/grid/Column.php index b49f73e..ec0c886 100644 --- a/framework/yii/grid/Column.php +++ b/framework/yii/grid/Column.php @@ -39,17 +39,17 @@ class Column extends Object * @var boolean whether this column is visible. Defaults to true. */ public $visible = true; - public $options = array(); - public $headerOptions = array(); + public $options = []; + public $headerOptions = []; /** * @var array|\Closure */ - public $contentOptions = array(); - public $footerOptions = array(); + public $contentOptions = []; + public $footerOptions = []; /** * @var array the HTML attributes for the filter cell tag. */ - public $filterOptions=array(); + public $filterOptions=[]; /** diff --git a/framework/yii/grid/DataColumn.php b/framework/yii/grid/DataColumn.php index 295dece..bd6eacb 100644 --- a/framework/yii/grid/DataColumn.php +++ b/framework/yii/grid/DataColumn.php @@ -15,6 +15,10 @@ use yii\helpers\Html; use yii\helpers\Inflector; /** + * DataColumn is the default column type for the [[GridView]] widget. + * + * It is used to show data columns and allows sorting them. + * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ @@ -41,10 +45,10 @@ class DataColumn extends Column */ public $value; /** - * @var string in which format should the value of each data model be displayed as (e.g. "text", "html"). - * Supported formats are determined by the [[GridView::formatter|formatter]] used by the [[GridView]]. - * Default format is "text" which will format the value as an HTML-encoded plain text when - * [[\yii\base\Formatter]] or [[\yii\i18n\Formatter]] is used. + * @var string|array in which format should the value of each data model be displayed as (e.g. "text", "html", + * ['date', 'Y-m-d']). Supported formats are determined by the [[GridView::formatter|formatter]] used by + * the [[GridView]]. Default format is "text" which will format the value as an HTML-encoded plain text when + * [[\yii\base\Formatter::format()]] or [[\yii\i18n\Formatter::format()]] is used. */ public $format = 'text'; /** @@ -57,7 +61,7 @@ class DataColumn extends Column * @var array the HTML attributes for the link tag in the header cell * generated by [[Sort::link]] when sorting is enabled for this column. */ - public $sortLinkOptions = array(); + public $sortLinkOptions = []; /** * @var string|array|boolean the HTML code representing a filter input (e.g. a text field, a dropdown list) * that is used for this data column. This property is effective only when [[GridView::filterModel]] is set. @@ -68,6 +72,12 @@ class DataColumn extends Column * - If you don't want a filter for this data column, set this value to be false. */ public $filter; + /** + * @var array the HTML attributes for the filter input fields. This property is used in combination with + * the [[filter]] property. When [[filter]] is not set or is an array, this property will be used to + * render the HTML attributes for the generated filter input fields. + */ + public $filterInputOptions = ['class' => 'form-control', 'id' => null]; protected function renderHeaderCellContent() @@ -99,7 +109,7 @@ class DataColumn extends Column if ($this->attribute !== null && $this->enableSorting && ($sort = $provider->getSort()) !== false && $sort->hasAttribute($this->attribute)) { - return $sort->link($this->attribute, array_merge($this->sortLinkOptions, array('label' => Html::encode($label)))); + return $sort->link($this->attribute, array_merge($this->sortLinkOptions, ['label' => Html::encode($label)])); } else { return Html::encode($label); } @@ -109,11 +119,14 @@ class DataColumn extends Column { if (is_string($this->filter)) { return $this->filter; - } elseif ($this->filter !== false && $this->grid->filterModel instanceof Model && $this->attribute !== null) { + } elseif ($this->filter !== false && $this->grid->filterModel instanceof Model && + $this->attribute !== null && $this->grid->filterModel->isAttributeActive($this->attribute)) + { if (is_array($this->filter)) { - return Html::activeDropDownList($this->grid->filterModel, $this->attribute, $this->filter, array('prompt' => '')); + $options = array_merge(['prompt' => ''], $this->filterInputOptions); + return Html::activeDropDownList($this->grid->filterModel, $this->attribute, $this->filter, $options); } else { - return Html::activeTextInput($this->grid->filterModel, $this->attribute); + return Html::activeTextInput($this->grid->filterModel, $this->attribute, $this->filterInputOptions); } } else { return parent::renderFilterCellContent(); diff --git a/framework/yii/grid/GridView.php b/framework/yii/grid/GridView.php index a783a75..de99a18 100644 --- a/framework/yii/grid/GridView.php +++ b/framework/yii/grid/GridView.php @@ -11,16 +11,19 @@ use Yii; use Closure; use yii\base\Formatter; use yii\base\InvalidConfigException; -use yii\base\Widget; -use yii\db\ActiveRecord; use yii\helpers\Html; -use yii\widgets\ListViewBase; +use yii\helpers\Json; +use yii\widgets\BaseListView; /** + * The GridView widget is used to display data in a grid. + * + * It provides features like sorting, paging and also filtering the data. + * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ -class GridView extends ListViewBase +class GridView extends BaseListView { const FILTER_POS_HEADER = 'header'; const FILTER_POS_FOOTER = 'footer'; @@ -40,19 +43,19 @@ class GridView extends ListViewBase * @var array the HTML attributes for the caption element * @see caption */ - public $captionOptions = array(); + public $captionOptions = []; /** * @var array the HTML attributes for the grid table element */ - public $tableOptions = array('class' => 'table table-striped table-bordered'); + public $tableOptions = ['class' => 'table table-striped table-bordered']; /** * @var array the HTML attributes for the table header row */ - public $headerRowOptions = array(); + public $headerRowOptions = []; /** * @var array the HTML attributes for the table footer row */ - public $footerRowOptions = array(); + public $footerRowOptions = []; /** * @var array|Closure the HTML attributes for the table body rows. This can be either an array * specifying the common HTML attributes for all body rows, or an anonymous function that @@ -68,7 +71,7 @@ class GridView extends ListViewBase * - `$index`: the zero-based index of the data model in the model array returned by [[dataProvider]] * - `$grid`: the GridView object */ - public $rowOptions = array(); + public $rowOptions = []; /** * @var Closure an anonymous function that is called once BEFORE rendering each data model. * It should have the similar signature as [[rowOptions]]. The return result of the function @@ -90,6 +93,10 @@ class GridView extends ListViewBase */ public $showFooter = false; /** + * @var boolean whether to show the grid view if [[dataProvider]] returns no data. + */ + public $showOnEmpty = true; + /** * @var array|Formatter the formatter used to format model attribute values into displayable texts. * This can be either an instance of [[Formatter]] or an configuration array for creating the [[Formatter]] * instance. If this property is not set, the "formatter" application component will be used. @@ -100,20 +107,16 @@ class GridView extends ListViewBase * for one particular grid column. For example, * * ~~~php - * array( - * array( - * 'class' => SerialColumn::className(), - * ), - * array( + * [ + * ['class' => SerialColumn::className()], + * [ * 'class' => DataColumn::className(), * 'attribute' => 'name', * 'format' => 'text', * 'label' => 'Name', - * ), - * array( - * 'class' => CheckboxColumn::className(), - * ), - * ) + * ], + * ['class' => CheckboxColumn::className()], + * ] * ~~~ * * If a column is of class [[DataColumn]], the "class" element can be omitted. @@ -123,7 +126,7 @@ class GridView extends ListViewBase * For example, the above "name" column can also be specified as: `"name:text:Name"`. * Both "format" and "label" are optional. They will take default values if absent. */ - public $columns = array(); + public $columns = []; public $emptyCell = ' '; /** * @var \yii\base\Model the model that keeps the user-entered filter data. When this property is set, @@ -137,6 +140,14 @@ class GridView extends ListViewBase */ public $filterModel; /** + * @var string|array the URL for returning the filtering result. [[Html::url()]] will be called to + * normalize the URL. If not set, the current controller action will be used. + * When the user makes change to any filter input, the current filtering inputs will be appended + * as GET parameters to this URL. + */ + public $filterUrl; + public $filterSelector; + /** * @var string whether the filters should be displayed in the grid view. Valid values include: * * - [[FILTER_POS_HEADER]]: the filters will be displayed on top of each column's header cell. @@ -147,7 +158,7 @@ class GridView extends ListViewBase /** * @var array the HTML attributes for the filter row element */ - public $filterRowOptions = array('class' => 'filters'); + public $filterRowOptions = ['class' => 'filters']; /** * Initializes the grid view. @@ -167,6 +178,9 @@ class GridView extends ListViewBase if (!isset($this->options['id'])) { $this->options['id'] = $this->getId(); } + if (!isset($this->filterRowOptions['id'])) { + $this->filterRowOptions['id'] = $this->options['id'] . '-filters'; + } $this->initColumns(); } @@ -177,24 +191,45 @@ class GridView extends ListViewBase public function run() { $id = $this->options['id']; + $options = Json::encode($this->getClientOptions()); $view = $this->getView(); GridViewAsset::register($view); - $view->registerJs("jQuery('#$id').yiiGridView();"); + $view->registerJs("jQuery('#$id').yiiGridView($options);"); parent::run(); } + + /** + * Returns the options for the grid view JS widget. + * @return array the options + */ + protected function getClientOptions() + { + $filterUrl = isset($this->filterUrl) ? $this->filterUrl : [Yii::$app->controller->action->id]; + $id = $this->filterRowOptions['id']; + $filterSelector = "#$id input, #$id select"; + if (isset($this->filterSelector)) { + $filterSelector .= ', ' . $this->filterSelector; + } + + return [ + 'filterUrl' => Html::url($filterUrl), + 'filterSelector' => $filterSelector, + ]; + } + /** * Renders the data models for the grid view. */ public function renderItems() { - $content = array_filter(array( + $content = array_filter([ $this->renderCaption(), $this->renderColumnGroup(), $this->showHeader ? $this->renderTableHeader() : false, $this->showFooter ? $this->renderTableFooter() : false, $this->renderTableBody(), - )); + ]); return Html::tag('table', implode("\n", $content), $this->tableOptions); } @@ -218,7 +253,7 @@ class GridView extends ListViewBase } } if ($requireColumnGroup) { - $cols = array(); + $cols = []; foreach ($this->columns as $column) { $cols[] = Html::tag('col', '', $column->options); } @@ -234,7 +269,7 @@ class GridView extends ListViewBase */ public function renderTableHeader() { - $cells = array(); + $cells = []; foreach ($this->columns as $column) { /** @var Column $column */ $cells[] = $column->renderHeaderCell(); @@ -254,7 +289,7 @@ class GridView extends ListViewBase */ public function renderTableFooter() { - $cells = array(); + $cells = []; foreach ($this->columns as $column) { /** @var Column $column */ $cells[] = $column->renderFooterCell(); @@ -272,7 +307,7 @@ class GridView extends ListViewBase public function renderFilters() { if ($this->filterModel !== null) { - $cells = array(); + $cells = []; foreach ($this->columns as $column) { /** @var Column $column */ $cells[] = $column->renderFilterCell(); @@ -291,7 +326,7 @@ class GridView extends ListViewBase { $models = array_values($this->dataProvider->getModels()); $keys = $this->dataProvider->getKeys(); - $rows = array(); + $rows = []; foreach ($models as $index => $model) { $key = $keys[$index]; if ($this->beforeRow !== null) { @@ -310,7 +345,13 @@ class GridView extends ListViewBase } } } - return "<tbody>\n" . implode("\n", $rows) . "\n</tbody>"; + + if (empty($rows)) { + $colspan = count($this->columns); + return "<tbody>\n<tr><td colspan=\"$colspan\">" . $this->renderEmpty() . "</td></tr>\n</tbody>"; + } else { + return "<tbody>\n" . implode("\n", $rows) . "\n</tbody>"; + } } /** @@ -322,7 +363,7 @@ class GridView extends ListViewBase */ public function renderTableRow($model, $key, $index) { - $cells = array(); + $cells = []; /** @var Column $column */ foreach ($this->columns as $column) { $cells[] = $column->renderDataCell($model, $index); @@ -348,10 +389,10 @@ class GridView extends ListViewBase if (is_string($column)) { $column = $this->createDataColumn($column); } else { - $column = Yii::createObject(array_merge(array( + $column = Yii::createObject(array_merge([ 'class' => $this->dataColumnClass ?: DataColumn::className(), 'grid' => $this, - ), $column)); + ], $column)); } if (!$column->visible) { unset($this->columns[$i]); @@ -372,13 +413,13 @@ class GridView extends ListViewBase if (!preg_match('/^([\w\.]+)(:(\w*))?(:(.*))?$/', $text, $matches)) { throw new InvalidConfigException('The column must be specified in the format of "attribute", "attribute:format" or "attribute:format:label'); } - return Yii::createObject(array( + return Yii::createObject([ 'class' => $this->dataColumnClass ?: DataColumn::className(), 'grid' => $this, 'attribute' => $matches[1], 'format' => isset($matches[3]) ? $matches[3] : 'text', 'label' => isset($matches[5]) ? $matches[5] : null, - )); + ]); } protected function guessColumns() diff --git a/framework/yii/grid/GridViewAsset.php b/framework/yii/grid/GridViewAsset.php index decf674..a67999d 100644 --- a/framework/yii/grid/GridViewAsset.php +++ b/framework/yii/grid/GridViewAsset.php @@ -10,6 +10,7 @@ namespace yii\grid; use yii\web\AssetBundle; /** + * This asset bundle provides the javascript files for the [[GridView]] widget. * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 @@ -17,10 +18,10 @@ use yii\web\AssetBundle; class GridViewAsset extends AssetBundle { public $sourcePath = '@yii/assets'; - public $js = array( + public $js = [ 'yii.gridView.js', - ); - public $depends = array( + ]; + public $depends = [ 'yii\web\YiiAsset', - ); + ]; } diff --git a/framework/yii/helpers/ArrayHelper.php b/framework/yii/helpers/ArrayHelper.php index 085104b..9d428f5 100644 --- a/framework/yii/helpers/ArrayHelper.php +++ b/framework/yii/helpers/ArrayHelper.php @@ -14,6 +14,6 @@ namespace yii\helpers; * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ -class ArrayHelper extends ArrayHelperBase +class ArrayHelper extends BaseArrayHelper { } diff --git a/framework/yii/helpers/ArrayHelperBase.php b/framework/yii/helpers/BaseArrayHelper.php similarity index 76% rename from framework/yii/helpers/ArrayHelperBase.php rename to framework/yii/helpers/BaseArrayHelper.php index 59129de..da63238 100644 --- a/framework/yii/helpers/ArrayHelperBase.php +++ b/framework/yii/helpers/BaseArrayHelper.php @@ -12,14 +12,14 @@ use yii\base\Arrayable; use yii\base\InvalidParamException; /** - * ArrayHelperBase provides concrete implementation for [[ArrayHelper]]. + * BaseArrayHelper provides concrete implementation for [[ArrayHelper]]. * - * Do not use ArrayHelperBase. Use [[ArrayHelper]] instead. + * Do not use BaseArrayHelper. Use [[ArrayHelper]] instead. * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ -class ArrayHelperBase +class BaseArrayHelper { /** * Converts an object or an array of objects into an array. @@ -28,8 +28,8 @@ class ArrayHelperBase * The properties specified for each class is an array of the following format: * * ~~~ - * array( - * 'app\models\Post' => array( + * [ + * 'app\models\Post' => [ * 'id', * 'title', * // the key name in array result => property name @@ -38,30 +38,30 @@ class ArrayHelperBase * 'length' => function ($post) { * return strlen($post->content); * }, - * ), - * ) + * ], + * ] * ~~~ * * The result of `ArrayHelper::toArray($post, $properties)` could be like the following: * * ~~~ - * array( + * [ * 'id' => 123, * 'title' => 'test', * 'createTime' => '2013-01-01 12:00AM', * 'length' => 301, - * ) + * ] * ~~~ * * @param boolean $recursive whether to recursively converts properties which are objects into arrays. * @return array the array representation of the object */ - public static function toArray($object, $properties = array(), $recursive = true) + public static function toArray($object, $properties = [], $recursive = true) { if (!empty($properties) && is_object($object)) { $className = get_class($object); if (!empty($properties[$className])) { - $result = array(); + $result = []; foreach ($properties[$className] as $key => $name) { if (is_int($key)) { $result[$name] = $object->$name; @@ -78,7 +78,7 @@ class ArrayHelperBase return $object; } } - $result = array(); + $result = []; foreach ($object as $key => $value) { if ($recursive && (is_array($value) || is_object($value))) { $result[$key] = static::toArray($value, true); @@ -163,11 +163,11 @@ class ArrayHelperBase * Usage examples, * * ~~~ - * // $array = array('type' => 'A', 'options' => array(1, 2)); + * // $array = ['type' => 'A', 'options' => [1, 2]]; * // working with array * $type = \yii\helpers\ArrayHelper::remove($array, 'type'); * // $array content - * // $array = array('options' => array(1, 2)); + * // $array = ['options' => [1, 2]]; * ~~~ * * @param array $array the array to extract value from @@ -197,16 +197,16 @@ class ArrayHelperBase * For example, * * ~~~ - * $array = array( - * array('id' => '123', 'data' => 'abc'), - * array('id' => '345', 'data' => 'def'), - * ); + * $array = [ + * ['id' => '123', 'data' => 'abc'], + * ['id' => '345', 'data' => 'def'], + * ]; * $result = ArrayHelper::index($array, 'id'); * // the result is: - * // array( - * // '123' => array('id' => '123', 'data' => 'abc'), - * // '345' => array('id' => '345', 'data' => 'def'), - * // ) + * // [ + * // '123' => ['id' => '123', 'data' => 'abc'], + * // '345' => ['id' => '345', 'data' => 'def'], + * // ] * * // using anonymous function * $result = ArrayHelper::index($array, function ($element) { @@ -220,7 +220,7 @@ class ArrayHelperBase */ public static function index($array, $key) { - $result = array(); + $result = []; foreach ($array as $element) { $value = static::getValue($element, $key); $result[$value] = $element; @@ -235,12 +235,12 @@ class ArrayHelperBase * For example, * * ~~~ - * $array = array( - * array('id' => '123', 'data' => 'abc'), - * array('id' => '345', 'data' => 'def'), - * ); + * $array = [ + * ['id' => '123', 'data' => 'abc'], + * ['id' => '345', 'data' => 'def'], + * ]; * $result = ArrayHelper::getColumn($array, 'id'); - * // the result is: array( '123', '345') + * // the result is: ['123', '345'] * * // using anonymous function * $result = ArrayHelper::getColumn($array, function ($element) { @@ -256,7 +256,7 @@ class ArrayHelperBase */ public static function getColumn($array, $name, $keepKeys = true) { - $result = array(); + $result = []; if ($keepKeys) { foreach ($array as $k => $element) { $result[$k] = static::getValue($element, $name); @@ -278,31 +278,31 @@ class ArrayHelperBase * For example, * * ~~~ - * $array = array( - * array('id' => '123', 'name' => 'aaa', 'class' => 'x'), - * array('id' => '124', 'name' => 'bbb', 'class' => 'x'), - * array('id' => '345', 'name' => 'ccc', 'class' => 'y'), + * $array = [ + * ['id' => '123', 'name' => 'aaa', 'class' => 'x'], + * ['id' => '124', 'name' => 'bbb', 'class' => 'x'], + * ['id' => '345', 'name' => 'ccc', 'class' => 'y'], * ); * * $result = ArrayHelper::map($array, 'id', 'name'); * // the result is: - * // array( + * // [ * // '123' => 'aaa', * // '124' => 'bbb', * // '345' => 'ccc', - * // ) + * // ] * * $result = ArrayHelper::map($array, 'id', 'name', 'class'); * // the result is: - * // array( - * // 'x' => array( + * // [ + * // 'x' => [ * // '123' => 'aaa', * // '124' => 'bbb', - * // ), - * // 'y' => array( + * // ], + * // 'y' => [ * // '345' => 'ccc', - * // ), - * // ) + * // ], + * // ] * ~~~ * * @param array $array @@ -313,7 +313,7 @@ class ArrayHelperBase */ public static function map($array, $from, $to, $group = null) { - $result = array(); + $result = []; foreach ($array as $element) { $key = static::getValue($element, $from); $value = static::getValue($element, $to); @@ -333,28 +333,25 @@ class ArrayHelperBase * elements, a property name of the objects, or an anonymous function returning the values for comparison * purpose. The anonymous function signature should be: `function($item)`. * To sort by multiple keys, provide an array of keys here. - * @param boolean|array $descending whether to sort in descending or ascending order. When - * sorting by multiple keys with different descending orders, use an array of descending flags. + * @param integer|array $direction the sorting direction. It can be either `SORT_ASC` or `SORT_DESC`. + * When sorting by multiple keys with different sorting directions, use an array of sorting directions. * @param integer|array $sortFlag the PHP sort flag. Valid values include - * `SORT_REGULAR`, `SORT_NUMERIC`, `SORT_STRING` and `SORT_LOCALE_STRING`. + * `SORT_REGULAR`, `SORT_NUMERIC`, `SORT_STRING`, `SORT_LOCALE_STRING`, `SORT_NATURAL` and `SORT_FLAG_CASE`. * 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 $descending or $sortFlag parameters do not have * correct number of elements as that of $key. */ - public static function multisort(&$array, $key, $descending = false, $sortFlag = SORT_REGULAR, $caseSensitive = true) + public static function multisort(&$array, $key, $direction = SORT_ASC, $sortFlag = SORT_REGULAR) { - $keys = is_array($key) ? $key : array($key); + $keys = is_array($key) ? $key : [$key]; if (empty($keys) || empty($array)) { return; } $n = count($keys); - if (is_scalar($descending)) { - $descending = array_fill(0, $n, $descending); - } elseif (count($descending) !== $n) { + if (is_scalar($direction)) { + $direction = array_fill(0, $n, $direction); + } elseif (count($direction) !== $n) { throw new InvalidParamException('The length of $descending parameter must be the same as that of $keys.'); } if (is_scalar($sortFlag)) { @@ -362,30 +359,11 @@ class ArrayHelperBase } elseif (count($sortFlag) !== $n) { 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(); + $args = []; foreach ($keys as $i => $key) { $flag = $sortFlag[$i]; - $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; - } - } else { - $args[] = static::getColumn($array, $key); - } - $args[] = $descending[$i] ? SORT_DESC : SORT_ASC; + $args[] = static::getColumn($array, $key); + $args[] = $direction[$i]; $args[] = $flag; } $args[] = &$array; @@ -409,7 +387,7 @@ class ArrayHelperBase if ($charset === null) { $charset = Yii::$app->charset; } - $d = array(); + $d = []; foreach ($data as $key => $value) { if (!$valuesOnly && is_string($key)) { $key = htmlspecialchars($key, ENT_QUOTES, $charset); @@ -435,7 +413,7 @@ class ArrayHelperBase */ public static function htmlDecode($data, $valuesOnly = true) { - $d = array(); + $d = []; foreach ($data as $key => $value) { if (!$valuesOnly && is_string($key)) { $key = htmlspecialchars_decode($key, ENT_QUOTES); diff --git a/framework/yii/helpers/ConsoleBase.php b/framework/yii/helpers/BaseConsole.php similarity index 85% rename from framework/yii/helpers/ConsoleBase.php rename to framework/yii/helpers/BaseConsole.php index a985291..480badf 100644 --- a/framework/yii/helpers/ConsoleBase.php +++ b/framework/yii/helpers/BaseConsole.php @@ -8,14 +8,14 @@ namespace yii\helpers; /** - * ConsoleBase provides concrete implementation for [[Console]]. + * BaseConsole provides concrete implementation for [[Console]]. * - * Do not use ConsoleBase. Use [[Console]] instead. + * Do not use BaseConsole. Use [[Console]] instead. * * @author Carsten Brandt <mail@cebe.cc> * @since 2.0 */ -class ConsoleBase +class BaseConsole { const FG_BLACK = 30; const FG_RED = 31; @@ -263,7 +263,7 @@ class ConsoleBase * This is equal to calling * * ```php - * echo Console::ansiFormatCode(array(Console::RESET)) + * echo Console::ansiFormatCode([Console::RESET]) * ``` */ public static function endAnsiFormat() @@ -280,7 +280,7 @@ class ConsoleBase * and also [[xtermFgColor]] and [[xtermBgColor]] to specify a format. * @return string */ - public static function ansiFormat($string, $format = array()) + public static function ansiFormat($string, $format = []) { $code = implode(';', $format); return "\033[0m" . ($code !== '' ? "\033[" . $code . "m" : '') . $string . "\033[0m"; @@ -337,74 +337,74 @@ class ConsoleBase return preg_replace_callback( '/\033\[[\d;]+m/', function ($ansi) use (&$tags) { - $styleA = array(); + $styleA = []; foreach (explode(';', $ansi) as $controlCode) { switch ($controlCode) { case self::FG_BLACK: - $style = array('color' => '#000000'); + $style = ['color' => '#000000']; break; case self::FG_BLUE: - $style = array('color' => '#000078'); + $style = ['color' => '#000078']; break; case self::FG_CYAN: - $style = array('color' => '#007878'); + $style = ['color' => '#007878']; break; case self::FG_GREEN: - $style = array('color' => '#007800'); + $style = ['color' => '#007800']; break; case self::FG_GREY: - $style = array('color' => '#787878'); + $style = ['color' => '#787878']; break; case self::FG_PURPLE: - $style = array('color' => '#780078'); + $style = ['color' => '#780078']; break; case self::FG_RED: - $style = array('color' => '#780000'); + $style = ['color' => '#780000']; break; case self::FG_YELLOW: - $style = array('color' => '#787800'); + $style = ['color' => '#787800']; break; case self::BG_BLACK: - $style = array('background-color' => '#000000'); + $style = ['background-color' => '#000000']; break; case self::BG_BLUE: - $style = array('background-color' => '#000078'); + $style = ['background-color' => '#000078']; break; case self::BG_CYAN: - $style = array('background-color' => '#007878'); + $style = ['background-color' => '#007878']; break; case self::BG_GREEN: - $style = array('background-color' => '#007800'); + $style = ['background-color' => '#007800']; break; case self::BG_GREY: - $style = array('background-color' => '#787878'); + $style = ['background-color' => '#787878']; break; case self::BG_PURPLE: - $style = array('background-color' => '#780078'); + $style = ['background-color' => '#780078']; break; case self::BG_RED: - $style = array('background-color' => '#780000'); + $style = ['background-color' => '#780000']; break; case self::BG_YELLOW: - $style = array('background-color' => '#787800'); + $style = ['background-color' => '#787800']; break; case self::BOLD: - $style = array('font-weight' => 'bold'); + $style = ['font-weight' => 'bold']; break; case self::ITALIC: - $style = array('font-style' => 'italic'); + $style = ['font-style' => 'italic']; break; case self::UNDERLINE: - $style = array('text-decoration' => array('underline')); + $style = ['text-decoration' => ['underline']]; break; case self::OVERLINED: - $style = array('text-decoration' => array('overline')); + $style = ['text-decoration' => ['overline']]; break; case self::CROSSED_OUT: - $style = array('text-decoration' => array('line-through')); + $style = ['text-decoration' => ['line-through']]; break; case self::BLINK: - $style = array('text-decoration' => array('blink')); + $style = ['text-decoration' => ['blink']]; break; case self::NEGATIVE: // ??? case self::CONCEALED: @@ -422,7 +422,7 @@ class ConsoleBase $styleA = ArrayHelper::merge($styleA, $style); } - $styleString[] = array(); + $styleString[] = []; foreach ($styleA as $name => $content) { if ($name === 'text-decoration') { $content = implode(' ', $content); @@ -480,41 +480,41 @@ class ConsoleBase // TODO rework/refactor according to https://github.com/yiisoft/yii2/issues/746 public static function renderColoredString($string, $colored = true) { - static $conversions = array( - '%y' => array(self::FG_YELLOW), - '%g' => array(self::FG_GREEN), - '%b' => array(self::FG_BLUE), - '%r' => array(self::FG_RED), - '%p' => array(self::FG_PURPLE), - '%m' => array(self::FG_PURPLE), - '%c' => array(self::FG_CYAN), - '%w' => array(self::FG_GREY), - '%k' => array(self::FG_BLACK), - '%n' => array(0), // reset - '%Y' => array(self::FG_YELLOW, self::BOLD), - '%G' => array(self::FG_GREEN, self::BOLD), - '%B' => array(self::FG_BLUE, self::BOLD), - '%R' => array(self::FG_RED, self::BOLD), - '%P' => array(self::FG_PURPLE, self::BOLD), - '%M' => array(self::FG_PURPLE, self::BOLD), - '%C' => array(self::FG_CYAN, self::BOLD), - '%W' => array(self::FG_GREY, self::BOLD), - '%K' => array(self::FG_BLACK, self::BOLD), - '%N' => array(0, self::BOLD), - '%3' => array(self::BG_YELLOW), - '%2' => array(self::BG_GREEN), - '%4' => array(self::BG_BLUE), - '%1' => array(self::BG_RED), - '%5' => array(self::BG_PURPLE), - '%6' => array(self::BG_PURPLE), - '%7' => array(self::BG_CYAN), - '%0' => array(self::BG_GREY), - '%F' => array(self::BLINK), - '%U' => array(self::UNDERLINE), - '%8' => array(self::NEGATIVE), - '%9' => array(self::BOLD), - '%_' => array(self::BOLD) - ); + static $conversions = [ + '%y' => [self::FG_YELLOW], + '%g' => [self::FG_GREEN], + '%b' => [self::FG_BLUE], + '%r' => [self::FG_RED], + '%p' => [self::FG_PURPLE], + '%m' => [self::FG_PURPLE], + '%c' => [self::FG_CYAN], + '%w' => [self::FG_GREY], + '%k' => [self::FG_BLACK], + '%n' => [0], // reset + '%Y' => [self::FG_YELLOW, self::BOLD], + '%G' => [self::FG_GREEN, self::BOLD], + '%B' => [self::FG_BLUE, self::BOLD], + '%R' => [self::FG_RED, self::BOLD], + '%P' => [self::FG_PURPLE, self::BOLD], + '%M' => [self::FG_PURPLE, self::BOLD], + '%C' => [self::FG_CYAN, self::BOLD], + '%W' => [self::FG_GREY, self::BOLD], + '%K' => [self::FG_BLACK, self::BOLD], + '%N' => [0, self::BOLD], + '%3' => [self::BG_YELLOW], + '%2' => [self::BG_GREEN], + '%4' => [self::BG_BLUE], + '%1' => [self::BG_RED], + '%5' => [self::BG_PURPLE], + '%6' => [self::BG_PURPLE], + '%7' => [self::BG_CYAN], + '%0' => [self::BG_GREY], + '%F' => [self::BLINK], + '%U' => [self::UNDERLINE], + '%8' => [self::NEGATIVE], + '%9' => [self::BOLD], + '%_' => [self::BOLD], + ]; if ($colored) { $string = str_replace('%%', '% ', $string); @@ -588,26 +588,26 @@ class ConsoleBase } if (static::isRunningOnWindows()) { - $output = array(); + $output = []; exec('mode con', $output); if (isset($output) && strpos($output[1], 'CON') !== false) { - return $size = array((int)preg_replace('~[^0-9]~', '', $output[3]), (int)preg_replace('~[^0-9]~', '', $output[4])); + return $size = [(int)preg_replace('~[^0-9]~', '', $output[3]), (int)preg_replace('~[^0-9]~', '', $output[4])]; } } else { // try stty if available - $stty = array(); + $stty = []; if (exec('stty -a 2>&1', $stty) && preg_match('/rows\s+(\d+);\s*columns\s+(\d+);/mi', implode(' ', $stty), $matches)) { - return $size = array($matches[2], $matches[1]); + return $size = [$matches[2], $matches[1]]; } // fallback to tput, which may not be updated on terminal resize if (($width = (int) exec('tput cols 2>&1')) > 0 && ($height = (int) exec('tput lines 2>&1')) > 0) { - return $size = array($width, $height); + return $size = [$width, $height]; } // fallback to ENV variables, which may not be updated on terminal resize if (($width = (int) getenv('COLUMNS')) > 0 && ($height = (int) getenv('LINES')) > 0) { - return $size = array($width, $height); + return $size = [$width, $height]; } } @@ -697,17 +697,17 @@ class ConsoleBase * - $error: the error value passed by reference if validation failed. * @return string the user input */ - public static function prompt($text, $options = array()) + public static function prompt($text, $options = []) { $options = ArrayHelper::merge( - $options, - array( + [ 'required' => false, 'default' => null, 'pattern' => null, 'validator' => null, 'error' => 'Invalid input.', - ) + ], + $options ); $error = null; @@ -727,7 +727,7 @@ class ConsoleBase static::output($options['error']); goto top; } elseif ($options['validator'] && - !call_user_func_array($options['validator'], array($input, &$error)) + !call_user_func_array($options['validator'], [$input, &$error]) ) { static::output(isset($error) ? $error : $options['error']); goto top; @@ -759,7 +759,7 @@ class ConsoleBase * * @return string An option character the user chose */ - public static function select($prompt, $options = array()) + public static function select($prompt, $options = []) { top: static::stdout("$prompt [" . implode(',', array_keys($options)) . ",?]: "); diff --git a/framework/yii/helpers/FileHelperBase.php b/framework/yii/helpers/BaseFileHelper.php similarity index 89% rename from framework/yii/helpers/FileHelperBase.php rename to framework/yii/helpers/BaseFileHelper.php index 0e480da..9533b95 100644 --- a/framework/yii/helpers/FileHelperBase.php +++ b/framework/yii/helpers/BaseFileHelper.php @@ -12,15 +12,15 @@ namespace yii\helpers; use Yii; /** - * FileHelperBase provides concrete implementation for [[FileHelper]]. + * BaseFileHelper provides concrete implementation for [[FileHelper]]. * - * Do not use FileHelperBase. Use [[FileHelper]] instead. + * Do not use BaseFileHelper. Use [[FileHelper]] instead. * * @author Qiang Xue <qiang.xue@gmail.com> * @author Alex Makarov <sam@rmcreative.ru> * @since 2.0 */ -class FileHelperBase +class BaseFileHelper { /** * Normalizes a file/directory path. @@ -33,7 +33,7 @@ class FileHelperBase */ public static function normalizePath($path, $ds = DIRECTORY_SEPARATOR) { - return rtrim(strtr($path, array('/' => $ds, '\\' => $ds)), $ds); + return rtrim(strtr($path, ['/' => $ds, '\\' => $ds]), $ds); } /** @@ -110,7 +110,7 @@ class FileHelperBase */ public static function getMimeTypeByExtension($file, $magicFile = null) { - static $mimeTypes = array(); + static $mimeTypes = []; if ($magicFile === null) { $magicFile = __DIR__ . '/mimeTypes.php'; } @@ -155,11 +155,15 @@ class FileHelperBase * and '.svn/' matches directory paths ending with '.svn'. Note, the '/' characters in a pattern matches * both '/' and '\' in the paths. * - recursive: boolean, whether the files under the subdirectories should also be copied. Defaults to true. + * - beforeCopy: callback, a PHP callback that is called before copying each sub-directory or file. + * If the callback returns false, the copy operation for the sub-directory or file will be cancelled. + * The signature of the callback should be: `function ($from, $to)`, where `$from` is the sub-directory or + * file to be copied from, while `$to` is the copy target. * - afterCopy: callback, a PHP callback that is called after each sub-directory or file is successfully copied. * The signature of the callback should be: `function ($from, $to)`, where `$from` is the sub-directory or * file copied from, while `$to` is the copy target. */ - public static function copyDirectory($src, $dst, $options = array()) + public static function copyDirectory($src, $dst, $options = []) { if (!is_dir($dst)) { static::createDirectory($dst, isset($options['dirMode']) ? $options['dirMode'] : 0775, true); @@ -173,6 +177,9 @@ class FileHelperBase $from = $src . DIRECTORY_SEPARATOR . $file; $to = $dst . DIRECTORY_SEPARATOR . $file; if (static::filterPath($from, $options)) { + if (isset($options['beforeCopy']) && !call_user_func($options['beforeCopy'], $from, $to)) { + continue; + } if (is_file($from)) { copy($from, $to); if (isset($options['fileMode'])) { @@ -240,9 +247,9 @@ class FileHelperBase * - recursive: boolean, whether the files under the subdirectories should also be looked for. Defaults to true. * @return array files found under the directory. The file list is sorted. */ - public static function findFiles($dir, $options = array()) + public static function findFiles($dir, $options = []) { - $list = array(); + $list = []; $handle = opendir($dir); while (($file = readdir($handle)) !== false) { if ($file === '.' || $file === '..') { diff --git a/framework/yii/helpers/HtmlBase.php b/framework/yii/helpers/BaseHtml.php similarity index 84% rename from framework/yii/helpers/HtmlBase.php rename to framework/yii/helpers/BaseHtml.php index a5786cb..eb29670 100644 --- a/framework/yii/helpers/HtmlBase.php +++ b/framework/yii/helpers/BaseHtml.php @@ -13,20 +13,20 @@ use yii\web\Request; use yii\base\Model; /** - * HtmlBase provides concrete implementation for [[Html]]. + * BaseHtml provides concrete implementation for [[Html]]. * - * Do not use HtmlBase. Use [[Html]] instead. + * Do not use BaseHtml. Use [[Html]] instead. * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ -class HtmlBase +class BaseHtml { /** * @var array list of void elements (element name => 1) * @see http://www.w3.org/TR/html-markup/syntax.html#void-element */ - public static $voidElements = array( + public static $voidElements = [ 'area' => 1, 'base' => 1, 'br' => 1, @@ -43,12 +43,12 @@ class HtmlBase 'source' => 1, 'track' => 1, 'wbr' => 1, - ); + ]; /** * @var array the preferred order of attributes in a tag. This mainly affects the order of the attributes * that are rendered by [[renderTagAttributes()]]. */ - public static $attributeOrder = array( + public static $attributeOrder = [ 'type', 'id', 'class', @@ -77,7 +77,7 @@ class HtmlBase 'title', 'rel', 'media', - ); + ]; /** * Encodes special characters into HTML entities. @@ -86,7 +86,7 @@ class HtmlBase * @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 decode() * @see http://www.php.net/manual/en/function.htmlspecialchars.php */ public static function encode($content, $doubleEncode = true) @@ -99,7 +99,7 @@ class HtmlBase * This is the opposite of [[encode()]]. * @param string $content the content to be decoded * @return string the decoded content - * @see encode + * @see encode() * @see http://www.php.net/manual/en/function.htmlspecialchars-decode.php */ public static function decode($content) @@ -116,10 +116,10 @@ class HtmlBase * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. * If a value is null, the corresponding attribute will not be rendered. * @return string the generated HTML tag - * @see beginTag - * @see endTag + * @see beginTag() + * @see endTag() */ - public static function tag($name, $content = '', $options = array()) + public static function tag($name, $content = '', $options = []) { $html = "<$name" . static::renderTagAttributes($options) . '>'; return isset(static::$voidElements[strtolower($name)]) ? $html : "$html$content</$name>"; @@ -132,10 +132,10 @@ class HtmlBase * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. * If a value is null, the corresponding attribute will not be rendered. * @return string the generated start tag - * @see endTag - * @see tag + * @see endTag() + * @see tag() */ - public static function beginTag($name, $options = array()) + public static function beginTag($name, $options = []) { return "<$name" . static::renderTagAttributes($options) . '>'; } @@ -144,8 +144,8 @@ class HtmlBase * Generates an end tag. * @param string $name the tag name * @return string the generated end tag - * @see beginTag - * @see tag + * @see beginTag() + * @see tag() */ public static function endTag($name) { @@ -161,7 +161,7 @@ class HtmlBase * If the options does not contain "type", a "type" attribute with value "text/css" will be used. * @return string the generated style tag */ - public static function style($content, $options = array()) + public static function style($content, $options = []) { return static::tag('style', $content, $options); } @@ -175,7 +175,7 @@ class HtmlBase * If the options does not contain "type", a "type" attribute with value "text/javascript" will be rendered. * @return string the generated script tag */ - public static function script($content, $options = array()) + public static function script($content, $options = []) { return static::tag('script', $content, $options); } @@ -187,9 +187,9 @@ class HtmlBase * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. * If a value is null, the corresponding attribute will not be rendered. * @return string the generated link tag - * @see url + * @see url() */ - public static function cssFile($url, $options = array()) + public static function cssFile($url, $options = []) { $options['rel'] = 'stylesheet'; $options['href'] = static::url($url); @@ -203,9 +203,9 @@ class HtmlBase * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. * If a value is null, the corresponding attribute will not be rendered. * @return string the generated script tag - * @see url + * @see url() */ - public static function jsFile($url, $options = array()) + public static function jsFile($url, $options = []) { $options['src'] = static::url($url); return static::tag('script', '', $options); @@ -222,13 +222,13 @@ class HtmlBase * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. * If a value is null, the corresponding attribute will not be rendered. * @return string the generated form start tag. - * @see endForm + * @see endForm() */ - public static function beginForm($action = '', $method = 'post', $options = array()) + public static function beginForm($action = '', $method = 'post', $options = []) { $action = static::url($action); - $hiddenInputs = array(); + $hiddenInputs = []; $request = Yii::$app->getRequest(); if ($request instanceof Request) { @@ -237,7 +237,7 @@ class HtmlBase $hiddenInputs[] = static::hiddenInput($request->restVar, $method); $method = 'post'; } - if ($request->enableCsrfValidation) { + if ($request->enableCsrfValidation && !strcasecmp($method, 'post')) { $hiddenInputs[] = static::hiddenInput($request->csrfVar, $request->getCsrfToken()); } } @@ -271,7 +271,7 @@ class HtmlBase /** * Generates a form end tag. * @return string the generated tag - * @see beginForm + * @see beginForm() */ public static function endForm() { @@ -290,9 +290,9 @@ class HtmlBase * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. * If a value is null, the corresponding attribute will not be rendered. * @return string the generated hyperlink - * @see url + * @see url() */ - public static function a($text, $url = null, $options = array()) + public static function a($text, $url = null, $options = []) { if ($url !== null) { $options['href'] = static::url($url); @@ -312,7 +312,7 @@ class HtmlBase * If a value is null, the corresponding attribute will not be rendered. * @return string the generated mailto link */ - public static function mailto($text, $email = null, $options = array()) + public static function mailto($text, $email = null, $options = []) { $options['href'] = 'mailto:' . ($email === null ? $text : $email); return static::tag('a', $text, $options); @@ -326,7 +326,7 @@ class HtmlBase * If a value is null, the corresponding attribute will not be rendered. * @return string the generated image tag */ - public static function img($src, $options = array()) + public static function img($src, $options = []) { $options['src'] = static::url($src); if (!isset($options['alt'])) { @@ -347,7 +347,7 @@ class HtmlBase * If a value is null, the corresponding attribute will not be rendered. * @return string the generated label tag */ - public static function label($content, $for = null, $options = array()) + public static function label($content, $for = null, $options = []) { $options['for'] = $for; return static::tag('label', $content, $options); @@ -363,7 +363,7 @@ class HtmlBase * If a value is null, the corresponding attribute will not be rendered. * @return string the generated button tag */ - public static function button($content = 'Button', $options = array()) + public static function button($content = 'Button', $options = []) { return static::tag('button', $content, $options); } @@ -378,7 +378,7 @@ class HtmlBase * If a value is null, the corresponding attribute will not be rendered. * @return string the generated submit button tag */ - public static function submitButton($content = 'Submit', $options = array()) + public static function submitButton($content = 'Submit', $options = []) { $options['type'] = 'submit'; return static::button($content, $options); @@ -394,7 +394,7 @@ class HtmlBase * If a value is null, the corresponding attribute will not be rendered. * @return string the generated reset button tag */ - public static function resetButton($content = 'Reset', $options = array()) + public static function resetButton($content = 'Reset', $options = []) { $options['type'] = 'reset'; return static::button($content, $options); @@ -410,7 +410,7 @@ class HtmlBase * If a value is null, the corresponding attribute will not be rendered. * @return string the generated input tag */ - public static function input($type, $name = null, $value = null, $options = array()) + public static function input($type, $name = null, $value = null, $options = []) { $options['type'] = $type; $options['name'] = $name; @@ -426,7 +426,7 @@ class HtmlBase * If a value is null, the corresponding attribute will not be rendered. * @return string the generated button tag */ - public static function buttonInput($label = 'Button', $options = array()) + public static function buttonInput($label = 'Button', $options = []) { $options['type'] = 'button'; $options['value'] = $label; @@ -441,7 +441,7 @@ class HtmlBase * If a value is null, the corresponding attribute will not be rendered. * @return string the generated button tag */ - public static function submitInput($label = 'Submit', $options = array()) + public static function submitInput($label = 'Submit', $options = []) { $options['type'] = 'submit'; $options['value'] = $label; @@ -455,7 +455,7 @@ class HtmlBase * Attributes whose value is null will be ignored and not put in the tag returned. * @return string the generated button tag */ - public static function resetInput($label = 'Reset', $options = array()) + public static function resetInput($label = 'Reset', $options = []) { $options['type'] = 'reset'; $options['value'] = $label; @@ -471,7 +471,7 @@ class HtmlBase * If a value is null, the corresponding attribute will not be rendered. * @return string the generated button tag */ - public static function textInput($name, $value = null, $options = array()) + public static function textInput($name, $value = null, $options = []) { return static::input('text', $name, $value, $options); } @@ -485,7 +485,7 @@ class HtmlBase * If a value is null, the corresponding attribute will not be rendered. * @return string the generated button tag */ - public static function hiddenInput($name, $value = null, $options = array()) + public static function hiddenInput($name, $value = null, $options = []) { return static::input('hidden', $name, $value, $options); } @@ -499,7 +499,7 @@ class HtmlBase * If a value is null, the corresponding attribute will not be rendered. * @return string the generated button tag */ - public static function passwordInput($name, $value = null, $options = array()) + public static function passwordInput($name, $value = null, $options = []) { return static::input('password', $name, $value, $options); } @@ -516,7 +516,7 @@ class HtmlBase * If a value is null, the corresponding attribute will not be rendered. * @return string the generated button tag */ - public static function fileInput($name, $value = null, $options = array()) + public static function fileInput($name, $value = null, $options = []) { return static::input('file', $name, $value, $options); } @@ -530,7 +530,7 @@ class HtmlBase * If a value is null, the corresponding attribute will not be rendered. * @return string the generated text area tag */ - public static function textarea($name, $value = '', $options = array()) + public static function textarea($name, $value = '', $options = []) { $options['name'] = $name; return static::tag('textarea', static::encode($value), $options); @@ -555,7 +555,7 @@ class HtmlBase * * @return string the generated radio button tag */ - public static function radio($name, $checked = false, $options = array()) + public static function radio($name, $checked = false, $options = []) { $options['checked'] = (boolean)$checked; $value = array_key_exists('value', $options) ? $options['value'] : '1'; @@ -568,10 +568,10 @@ class HtmlBase } if (isset($options['label'])) { $label = $options['label']; - $labelOptions = isset($options['labelOptions']) ? $options['labelOptions'] : array(); + $labelOptions = isset($options['labelOptions']) ? $options['labelOptions'] : []; unset($options['label'], $options['labelOptions']); $content = static::label(static::input('radio', $name, $value, $options) . ' ' . $label, null, $labelOptions); - return $hidden . static::tag('div', $content, array('class' => 'radio')); + return $hidden . static::tag('div', $content, ['class' => 'radio']); } else { return $hidden . static::input('radio', $name, $value, $options); } @@ -596,7 +596,7 @@ class HtmlBase * * @return string the generated checkbox tag */ - public static function checkbox($name, $checked = false, $options = array()) + public static function checkbox($name, $checked = false, $options = []) { $options['checked'] = (boolean)$checked; $value = array_key_exists('value', $options) ? $options['value'] : '1'; @@ -609,10 +609,10 @@ class HtmlBase } if (isset($options['label'])) { $label = $options['label']; - $labelOptions = isset($options['labelOptions']) ? $options['labelOptions'] : array(); + $labelOptions = isset($options['labelOptions']) ? $options['labelOptions'] : []; unset($options['label'], $options['labelOptions']); $content = static::label(static::input('checkbox', $name, $value, $options) . ' ' . $label, null, $labelOptions); - return $hidden . static::tag('div', $content, array('class' => 'checkbox')); + return $hidden . static::tag('div', $content, ['class' => 'checkbox']); } else { return $hidden . static::input('checkbox', $name, $value, $options); } @@ -637,10 +637,10 @@ class HtmlBase * and the array values are the extra attributes for the corresponding option tags. For example, * * ~~~ - * array( - * 'value1' => array('disabled' => true), - * 'value2' => array('label' => 'value 2'), - * ); + * [ + * 'value1' => ['disabled' => true], + * 'value2' => ['label' => 'value 2'], + * ]; * ~~~ * * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', @@ -651,8 +651,11 @@ class HtmlBase * * @return string the generated drop-down list tag */ - public static function dropDownList($name, $selection = null, $items = array(), $options = array()) + public static function dropDownList($name, $selection = null, $items = [], $options = []) { + if (!empty($options['multiple'])) { + return static::listBox($name, $selection, $items, $options); + } $options['name'] = $name; $selectOptions = static::renderSelectOptions($selection, $items, $options); return static::tag('select', "\n" . $selectOptions . "\n", $options); @@ -677,10 +680,10 @@ class HtmlBase * and the array values are the extra attributes for the corresponding option tags. For example, * * ~~~ - * array( - * 'value1' => array('disabled' => true), - * 'value2' => array('label' => 'value 2'), - * ); + * [ + * 'value1' => ['disabled' => true], + * 'value2' => ['label' => 'value 2'], + * ]; * ~~~ * * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', @@ -694,7 +697,7 @@ class HtmlBase * * @return string the generated list box tag */ - public static function listBox($name, $selection = null, $items = array(), $options = array()) + public static function listBox($name, $selection = null, $items = [], $options = []) { if (!isset($options['size'])) { $options['size'] = 4; @@ -724,7 +727,7 @@ class HtmlBase * @param string $name the name attribute of each checkbox. * @param string|array $selection the selected value(s). * @param array $items the data item used to generate the checkboxes. - * The array keys are the labels, while the array values are the corresponding checkbox values. + * The array values are the labels, while the array keys are the corresponding checkbox values. * @param array $options options (name => config) for the checkbox list container tag. * The following options are specially handled: * @@ -746,7 +749,7 @@ class HtmlBase * value and the checked status of the checkbox input, respectively. * @return string the generated checkbox list */ - public static function checkboxList($name, $selection = null, $items = array(), $options = array()) + public static function checkboxList($name, $selection = null, $items = [], $options = []) { if (substr($name, -2) !== '[]') { $name .= '[]'; @@ -754,7 +757,7 @@ class HtmlBase $formatter = isset($options['item']) ? $options['item'] : null; $encode = !isset($options['encode']) || $options['encode']; - $lines = array(); + $lines = []; $index = 0; foreach ($items as $value => $label) { $checked = $selection !== null && @@ -763,10 +766,10 @@ class HtmlBase if ($formatter !== null) { $lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value); } else { - $lines[] = static::checkbox($name, $checked, array( + $lines[] = static::checkbox($name, $checked, [ 'value' => $value, 'label' => $encode ? static::encode($label) : $label, - )); + ]); } $index++; } @@ -812,11 +815,11 @@ class HtmlBase * value and the checked status of the radio button input, respectively. * @return string the generated radio button list */ - public static function radioList($name, $selection = null, $items = array(), $options = array()) + public static function radioList($name, $selection = null, $items = [], $options = []) { $encode = !isset($options['encode']) || $options['encode']; $formatter = isset($options['item']) ? $options['item'] : null; - $lines = array(); + $lines = []; $index = 0; foreach ($items as $value => $label) { $checked = $selection !== null && @@ -825,10 +828,10 @@ class HtmlBase if ($formatter !== null) { $lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value); } else { - $lines[] = static::radio($name, $checked, array( + $lines[] = static::radio($name, $checked, [ 'value' => $value, 'label' => $encode ? static::encode($label) : $label, - )); + ]); } $index++; } @@ -868,7 +871,7 @@ class HtmlBase * * @return string the generated unordered list. An empty string is returned if `$items` is empty. */ - public static function ul($items, $options = array()) + public static function ul($items, $options = []) { if (empty($items)) { return ''; @@ -876,9 +879,9 @@ class HtmlBase $tag = isset($options['tag']) ? $options['tag'] : 'ul'; $encode = !isset($options['encode']) || $options['encode']; $formatter = isset($options['item']) ? $options['item'] : null; - $itemOptions = isset($options['itemOptions']) ? $options['itemOptions'] : array(); + $itemOptions = isset($options['itemOptions']) ? $options['itemOptions'] : []; unset($options['tag'], $options['encode'], $options['item'], $options['itemOptions']); - $results = array(); + $results = []; foreach ($items as $index => $item) { if ($formatter !== null) { $results[] = call_user_func($formatter, $item, $index); @@ -910,7 +913,7 @@ class HtmlBase * * @return string the generated ordered list. An empty string is returned if `$items` is empty. */ - public static function ol($items, $options = array()) + public static function ol($items, $options = []) { $options['tag'] = 'ol'; return static::ul($items, $options); @@ -933,7 +936,7 @@ class HtmlBase * * @return string the generated label tag */ - public static function activeLabel($model, $attribute, $options = array()) + public static function activeLabel($model, $attribute, $options = []) { $attribute = static::getAttributeName($attribute); $label = isset($options['label']) ? $options['label'] : static::encode($model->getAttributeLabel($attribute)); @@ -957,7 +960,7 @@ class HtmlBase * * @return string the generated label tag */ - public static function error($model, $attribute, $options = array()) + public static function error($model, $attribute, $options = []) { $attribute = static::getAttributeName($attribute); $error = $model->getFirstError($attribute); @@ -978,7 +981,7 @@ class HtmlBase * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. * @return string the generated input tag */ - public static function activeInput($type, $model, $attribute, $options = array()) + public static function activeInput($type, $model, $attribute, $options = []) { $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); $value = isset($options['value']) ? $options['value'] : static::getAttributeValue($model, $attribute); @@ -999,7 +1002,7 @@ class HtmlBase * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. * @return string the generated input tag */ - public static function activeTextInput($model, $attribute, $options = array()) + public static function activeTextInput($model, $attribute, $options = []) { return static::activeInput('text', $model, $attribute, $options); } @@ -1015,7 +1018,7 @@ class HtmlBase * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. * @return string the generated input tag */ - public static function activeHiddenInput($model, $attribute, $options = array()) + public static function activeHiddenInput($model, $attribute, $options = []) { return static::activeInput('hidden', $model, $attribute, $options); } @@ -1031,7 +1034,7 @@ class HtmlBase * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. * @return string the generated input tag */ - public static function activePasswordInput($model, $attribute, $options = array()) + public static function activePasswordInput($model, $attribute, $options = []) { return static::activeInput('password', $model, $attribute, $options); } @@ -1047,11 +1050,11 @@ class HtmlBase * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. * @return string the generated input tag */ - public static function activeFileInput($model, $attribute, $options = array()) + public static function activeFileInput($model, $attribute, $options = []) { // add a hidden field so that if a model only has a file field, we can // still use isset($_POST[$modelClass]) to detect if the input is submitted - return static::activeHiddenInput($model, $attribute, array('id' => null, 'value' => '')) + return static::activeHiddenInput($model, $attribute, ['id' => null, 'value' => '']) . static::activeInput('file', $model, $attribute, $options); } @@ -1065,7 +1068,7 @@ class HtmlBase * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. * @return string the generated textarea tag */ - public static function activeTextarea($model, $attribute, $options = array()) + public static function activeTextarea($model, $attribute, $options = []) { $name = static::getInputName($model, $attribute); $value = static::getAttributeValue($model, $attribute); @@ -1097,7 +1100,7 @@ class HtmlBase * * @return string the generated radio button tag */ - public static function activeRadio($model, $attribute, $options = array()) + public static function activeRadio($model, $attribute, $options = []) { $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); $checked = static::getAttributeValue($model, $attribute); @@ -1132,7 +1135,7 @@ class HtmlBase * * @return string the generated checkbox tag */ - public static function activeCheckbox($model, $attribute, $options = array()) + public static function activeCheckbox($model, $attribute, $options = []) { $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); $checked = static::getAttributeValue($model, $attribute); @@ -1166,10 +1169,10 @@ class HtmlBase * and the array values are the extra attributes for the corresponding option tags. For example, * * ~~~ - * array( - * 'value1' => array('disabled' => true), - * 'value2' => array('label' => 'value 2'), - * ); + * [ + * 'value1' => ['disabled' => true], + * 'value2' => ['label' => 'value 2'], + * ]; * ~~~ * * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', @@ -1180,7 +1183,7 @@ class HtmlBase * * @return string the generated drop-down list tag */ - public static function activeDropDownList($model, $attribute, $items, $options = array()) + public static function activeDropDownList($model, $attribute, $items, $options = []) { $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); $checked = static::getAttributeValue($model, $attribute); @@ -1211,10 +1214,10 @@ class HtmlBase * and the array values are the extra attributes for the corresponding option tags. For example, * * ~~~ - * array( - * 'value1' => array('disabled' => true), - * 'value2' => array('label' => 'value 2'), - * ); + * [ + * 'value1' => ['disabled' => true], + * 'value2' => ['label' => 'value 2'], + * ]; * ~~~ * * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', @@ -1228,7 +1231,7 @@ class HtmlBase * * @return string the generated list box tag */ - public static function activeListBox($model, $attribute, $items, $options = array()) + public static function activeListBox($model, $attribute, $items, $options = []) { $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); $checked = static::getAttributeValue($model, $attribute); @@ -1250,7 +1253,7 @@ class HtmlBase * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format * about attribute expression. * @param array $items the data item used to generate the checkboxes. - * The array keys are the labels, while the array values are the corresponding checkbox values. + * The array values are the labels, while the array keys are the corresponding checkbox values. * Note that the labels will NOT be HTML-encoded, while the values will. * @param array $options options (name => config) for the checkbox list. The following options are specially handled: * @@ -1269,7 +1272,7 @@ class HtmlBase * value and the checked status of the checkbox input. * @return string the generated checkbox list */ - public static function activeCheckboxList($model, $attribute, $items, $options = array()) + public static function activeCheckboxList($model, $attribute, $items, $options = []) { $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); $checked = static::getAttributeValue($model, $attribute); @@ -1309,7 +1312,7 @@ class HtmlBase * value and the checked status of the radio button input. * @return string the generated radio button list */ - public static function activeRadioList($model, $attribute, $items, $options = array()) + public static function activeRadioList($model, $attribute, $items, $options = []) { $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); $checked = static::getAttributeValue($model, $attribute); @@ -1340,27 +1343,27 @@ class HtmlBase * * @return string the generated list options */ - public static function renderSelectOptions($selection, $items, &$tagOptions = array()) + public static function renderSelectOptions($selection, $items, &$tagOptions = []) { - $lines = array(); + $lines = []; if (isset($tagOptions['prompt'])) { $prompt = str_replace(' ', ' ', static::encode($tagOptions['prompt'])); - $lines[] = static::tag('option', $prompt, array('value' => '')); + $lines[] = static::tag('option', $prompt, ['value' => '']); } - $options = isset($tagOptions['options']) ? $tagOptions['options'] : array(); - $groups = isset($tagOptions['groups']) ? $tagOptions['groups'] : array(); + $options = isset($tagOptions['options']) ? $tagOptions['options'] : []; + $groups = isset($tagOptions['groups']) ? $tagOptions['groups'] : []; unset($tagOptions['prompt'], $tagOptions['options'], $tagOptions['groups']); foreach ($items as $key => $value) { if (is_array($value)) { - $groupAttrs = isset($groups[$key]) ? $groups[$key] : array(); + $groupAttrs = isset($groups[$key]) ? $groups[$key] : []; $groupAttrs['label'] = $key; - $attrs = array('options' => $options, 'groups' => $groups); + $attrs = ['options' => $options, 'groups' => $groups]; $content = static::renderSelectOptions($selection, $value, $attrs); $lines[] = static::tag('optgroup', "\n" . $content . "\n", $groupAttrs); } else { - $attrs = isset($options[$key]) ? $options[$key] : array(); + $attrs = isset($options[$key]) ? $options[$key] : []; $attrs['value'] = (string)$key; $attrs['selected'] = $selection !== null && (!is_array($selection) && !strcmp($key, $selection) @@ -1385,7 +1388,7 @@ class HtmlBase public static function renderTagAttributes($attributes) { if (count($attributes) > 1) { - $sorted = array(); + $sorted = []; foreach (static::$attributeOrder as $name) { if (isset($attributes[$name])) { $sorted[$name] = $attributes[$name]; @@ -1418,7 +1421,7 @@ class HtmlBase * will be prefixed with [[\yii\web\Request::baseUrl]] and returned. * - is an array: the first array element is considered a route, while the rest of the name-value * pairs are treated as the parameters to be used for URL creation using [[\yii\web\Controller::createUrl()]]. - * For example: `array('post/index', 'page' => 2)`, `array('index')`. + * For example: `['post/index', 'page' => 2]`, `['index']`. * In case there is no controller, [[\yii\web\UrlManager::createUrl()]] will be used. * * @param array|string $url the parameter to be used to generate a valid URL @@ -1594,6 +1597,6 @@ class HtmlBase public static function getInputId($model, $attribute) { $name = strtolower(static::getInputName($model, $attribute)); - return str_replace(array('[]', '][', '[', ']', ' '), array('', '-', '-', '', '-'), $name); + return str_replace(['[]', '][', '[', ']', ' '], ['', '-', '-', '', '-'], $name); } } diff --git a/framework/yii/helpers/HtmlPurifierBase.php b/framework/yii/helpers/BaseHtmlPurifier.php similarity index 90% rename from framework/yii/helpers/HtmlPurifierBase.php rename to framework/yii/helpers/BaseHtmlPurifier.php index e89a589..17d2122 100644 --- a/framework/yii/helpers/HtmlPurifierBase.php +++ b/framework/yii/helpers/BaseHtmlPurifier.php @@ -7,14 +7,14 @@ namespace yii\helpers; /** - * HtmlPurifierBase provides concrete implementation for [[HtmlPurifier]]. + * BaseHtmlPurifier provides concrete implementation for [[HtmlPurifier]]. * - * Do not use HtmlPurifierBase. Use [[HtmlPurifier]] instead. + * Do not use BaseHtmlPurifier. Use [[HtmlPurifier]] instead. * * @author Alexander Makarov <sam@rmcreative.ru> * @since 2.0 */ -class HtmlPurifierBase +class BaseHtmlPurifier { /** * Passes markup through HTMLPurifier making it safe to output to end user diff --git a/framework/yii/helpers/InflectorBase.php b/framework/yii/helpers/BaseInflector.php similarity index 90% rename from framework/yii/helpers/InflectorBase.php rename to framework/yii/helpers/BaseInflector.php index 87c1ff4..2f4f01b 100644 --- a/framework/yii/helpers/InflectorBase.php +++ b/framework/yii/helpers/BaseInflector.php @@ -10,20 +10,20 @@ namespace yii\helpers; use Yii; /** - * InflectorBase provides concrete implementation for [[Inflector]]. + * BaseInflector provides concrete implementation for [[Inflector]]. * - * Do not use InflectorBase. Use [[Inflector]] instead. + * Do not use BaseInflector. Use [[Inflector]] instead. * * @author Antonio Ramirez <amigo.cobos@gmail.com> * @since 2.0 */ -class InflectorBase +class BaseInflector { /** * @var array the rules for converting a word into its plural form. * The keys are the regular expressions and the values are the corresponding replacements. */ - public static $plurals = array( + public static $plurals = [ '/([nrlm]ese|deer|fish|sheep|measles|ois|pox|media)$/i' => '\1', '/^(sea[- ]bass)$/i' => '\1', '/(m)ove$/i' => '\1oves', @@ -53,12 +53,12 @@ class InflectorBase '/s$/' => 's', '/^$/' => '', '/$/' => 's', - ); + ]; /** * @var array the rules for converting a word into its singular form. * The keys are the regular expressions and the values are the corresponding replacements. */ - public static $singulars = array( + public static $singulars = [ '/([nrlm]ese|deer|fish|sheep|measles|ois|pox|media|ss)$/i' => '\1', '/^(sea[- ]bass)$/i' => '\1', '/(s)tatuses$/i' => '\1tatus', @@ -97,12 +97,12 @@ class InflectorBase '/eaus$/' => 'eau', '/^(.*us)$/' => '\\1', '/s$/i' => '', - ); + ]; /** * @var array the special rules for converting a word between its plural form and singular form. * The keys are the special words in singular form, and the values are the corresponding plural form. */ - public static $specials = array( + public static $specials = [ 'atlas' => 'atlases', 'beef' => 'beefs', 'brother' => 'brothers', @@ -214,11 +214,11 @@ class InflectorBase 'whiting' => 'whiting', 'wildebeest' => 'wildebeest', 'Yengeese' => 'Yengeese', - ); + ]; /** * @var array map of special chars and its translation. This is used by [[slug()]]. */ - public static $transliteration = array( + public static $transliteration = [ '/ä|æ|ǽ/' => 'ae', '/ö|œ/' => 'oe', '/ü/' => 'ue', @@ -269,7 +269,7 @@ class InflectorBase '/ij/' => 'ij', '/Œ/' => 'OE', '/ƒ/' => 'f' - ); + ]; /** * Converts a word to its plural form. @@ -280,8 +280,8 @@ class InflectorBase */ public static function pluralize($word) { - if (isset(self::$specials[$word])) { - return self::$specials[$word]; + if (isset(static::$specials[$word])) { + return static::$specials[$word]; } foreach (static::$plurals as $rule => $replacement) { if (preg_match($rule, $word)) { @@ -298,7 +298,7 @@ class InflectorBase */ public static function singularize($word) { - $result = array_search($word, self::$specials, true); + $result = array_search($word, static::$specials, true); if ($result !== false) { return $result; } @@ -328,7 +328,7 @@ class InflectorBase * Converts a word like "send_email" to "SendEmail". It * will remove non alphanumeric character from the word, so * "who's online" will be converted to "WhoSOnline" - * @see variablize + * @see variablize() * @param string $word the word to CamelCase * @return string */ @@ -346,11 +346,11 @@ class InflectorBase */ public static function camel2words($name, $ucwords = true) { - $label = trim(strtolower(str_replace(array( + $label = trim(strtolower(str_replace([ '-', '_', '.' - ), ' ', preg_replace('/(?<![A-Z])[A-Z]/', ' \0', $name)))); + ], ' ', preg_replace('/(?<![A-Z])[A-Z]/', ' \0', $name)))); return $ucwords ? ucwords($label) : $label; } @@ -441,12 +441,12 @@ class InflectorBase */ public static function slug($string, $replacement = '-') { - $map = static::$transliteration + array( + $map = static::$transliteration + [ '/[^\w\s]/' => ' ', '/\\s+/' => $replacement, '/(?<=[a-z])([A-Z])/' => $replacement . '\\1', str_replace(':rep', preg_quote($replacement, '/'), '/^[:rep]+|[:rep]+$/') => '' - ); + ]; return preg_replace(array_keys($map), array_values($map), $string); } diff --git a/framework/yii/helpers/JsonBase.php b/framework/yii/helpers/BaseJson.php similarity index 89% rename from framework/yii/helpers/JsonBase.php rename to framework/yii/helpers/BaseJson.php index fa3fb01..35b35ff 100644 --- a/framework/yii/helpers/JsonBase.php +++ b/framework/yii/helpers/BaseJson.php @@ -12,14 +12,14 @@ use yii\base\Arrayable; use yii\web\JsExpression; /** - * JsonBase provides concrete implementation for [[Json]]. + * BaseJson provides concrete implementation for [[Json]]. * - * Do not use JsonBase. Use [[Json]] instead. + * Do not use BaseJson. Use [[Json]] instead. * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ -class JsonBase +class BaseJson { /** * Encodes the given value into a JSON string. @@ -33,7 +33,7 @@ class JsonBase */ public static function encode($value, $options = 0) { - $expressions = array(); + $expressions = []; $value = static::processData($value, $expressions, uniqid()); $json = json_encode($value, $options); return empty($expressions) ? $json : strtr($json, $expressions); @@ -81,32 +81,26 @@ class JsonBase */ protected static function processData($data, &$expressions, $expPrefix) { - if (is_array($data)) { - foreach ($data as $key => $value) { - if (is_array($value) || is_object($value)) { - $data[$key] = static::processData($value, $expressions, $expPrefix); - } - } - return $data; - } elseif (is_object($data)) { + if (is_object($data)) { if ($data instanceof JsExpression) { $token = "!{[$expPrefix=" . count($expressions) . ']}!'; $expressions['"' . $token . '"'] = $data->expression; return $token; - } else { - $data = $data instanceof Arrayable ? $data->toArray() : get_object_vars($data); - $result = array(); - foreach ($data as $key => $value) { - if (is_array($value) || is_object($value)) { - $result[$key] = static::processData($value, $expressions, $expPrefix); - } else { - $result[$key] = $value; - } + } + $data = $data instanceof Arrayable ? $data->toArray() : get_object_vars($data); + if ($data === [] && !$data instanceof Arrayable) { + return new \stdClass(); + } + } + + if (is_array($data)) { + foreach ($data as $key => $value) { + if (is_array($value) || is_object($value)) { + $data[$key] = static::processData($value, $expressions, $expPrefix); } - return $result; } - } else { - return $data; } + + return $data; } } diff --git a/framework/yii/helpers/MarkdownBase.php b/framework/yii/helpers/BaseMarkdown.php similarity index 90% rename from framework/yii/helpers/MarkdownBase.php rename to framework/yii/helpers/BaseMarkdown.php index 9db5b1e..5258534 100644 --- a/framework/yii/helpers/MarkdownBase.php +++ b/framework/yii/helpers/BaseMarkdown.php @@ -10,14 +10,14 @@ namespace yii\helpers; use Michelf\MarkdownExtra; /** - * MarkdownBase provides concrete implementation for [[Markdown]]. + * BaseMarkdown provides concrete implementation for [[Markdown]]. * - * Do not use MarkdownBase. Use [[Markdown]] instead. + * Do not use BaseMarkdown. Use [[Markdown]] instead. * * @author Alexander Makarov <sam@rmcreative.ru> * @since 2.0 */ -class MarkdownBase +class BaseMarkdown { /** * @var MarkdownExtra @@ -31,7 +31,7 @@ class MarkdownBase * @param array $config * @return string */ - public static function process($content, $config = array()) + public static function process($content, $config = []) { if (static::$markdown === null) { static::$markdown = new MarkdownExtra(); diff --git a/framework/yii/helpers/SecurityBase.php b/framework/yii/helpers/BaseSecurity.php similarity index 66% rename from framework/yii/helpers/SecurityBase.php rename to framework/yii/helpers/BaseSecurity.php index 5b192de..8e86654 100644 --- a/framework/yii/helpers/SecurityBase.php +++ b/framework/yii/helpers/BaseSecurity.php @@ -13,31 +13,51 @@ use yii\base\InvalidConfigException; use yii\base\InvalidParamException; /** - * SecurityBase provides concrete implementation for [[Security]]. + * BaseSecurity provides concrete implementation for [[Security]]. * - * Do not use SecurityBase. Use [[Security]] instead. + * Do not use BaseSecurity. Use [[Security]] instead. * * @author Qiang Xue <qiang.xue@gmail.com> * @author Tom Worster <fsb@thefsb.org> * @since 2.0 */ -class SecurityBase +class BaseSecurity { /** + * Uses AES, block size is 128-bit (16 bytes). + */ + const CRYPT_BLOCK_SIZE = 16; + + /** + * Uses AES-192, key size is 192-bit (24 bytes). + */ + const CRYPT_KEY_SIZE = 24; + + /** + * Uses SHA-256. + */ + const DERIVATION_HASH = 'sha256'; + + /** + * Uses 1000 iterations. + */ + const DERIVATION_ITERATIONS = 1000; + + /** * Encrypts data. * @param string $data data to be encrypted. - * @param string $key the encryption secret key + * @param string $password the encryption password * @return string the encrypted data * @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized * @see decrypt() */ - public static function encrypt($data, $key) + public static function encrypt($data, $password) { $module = static::openCryptModule(); - // 192-bit (24 bytes) key size - $key = StringHelper::substr($key, 0, 24); + $data = static::addPadding($data); srand(); $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($module), MCRYPT_RAND); + $key = static::deriveKey($password, $iv); mcrypt_generic_init($module, $key, $iv); $encrypted = $iv . mcrypt_generic($module, $data); mcrypt_generic_deinit($module); @@ -48,23 +68,69 @@ class SecurityBase /** * Decrypts data * @param string $data data to be decrypted. - * @param string $key the decryption secret key + * @param string $password the decryption password * @return string the decrypted data * @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized * @see encrypt() */ - public static function decrypt($data, $key) + public static function decrypt($data, $password) { $module = static::openCryptModule(); - // 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); + $key = static::deriveKey($password, $iv); mcrypt_generic_init($module, $key, $iv); $decrypted = mdecrypt_generic($module, StringHelper::substr($data, $ivSize, StringHelper::strlen($data))); mcrypt_generic_deinit($module); mcrypt_module_close($module); - return rtrim($decrypted, "\0"); + return static::stripPadding($decrypted); + } + + /** + * Adds a padding to the given data (PKCS #7). + * @param string $data the data to pad + * @return string the padded data + */ + protected static function addPadding($data) + { + $pad = self::CRYPT_BLOCK_SIZE - (StringHelper::strlen($data) % self::CRYPT_BLOCK_SIZE); + return $data . str_repeat(chr($pad), $pad); + } + + /** + * Strips the padding from the given data. + * @param string $data the data to trim + * @return string the trimmed data + */ + protected static function stripPadding($data) + { + $end = StringHelper::substr($data, -1, NULL); + $last = ord($end); + $n = StringHelper::strlen($data) - $last; + if (StringHelper::substr($data, $n, NULL) == str_repeat($end, $last)) { + return StringHelper::substr($data, 0, $n); + } + return false; + } + + /** + * Derives a key from the given password (PBKDF2). + * @param string $password the source password + * @param string $salt the random salt + * @return string the derived key + */ + protected static function deriveKey($password, $salt) + { + if (function_exists('hash_pbkdf2')) { + return hash_pbkdf2(self::DERIVATION_HASH, $password, $salt, self::DERIVATION_ITERATIONS, self::CRYPT_KEY_SIZE, true); + } + $hmac = hash_hmac(self::DERIVATION_HASH, $salt . pack('N', 1), $password, true); + $xorsum = $hmac; + for ($i = 1; $i < self::DERIVATION_ITERATIONS; $i++) { + $hmac = hash_hmac(self::DERIVATION_HASH, $hmac, $password, true); + $xorsum ^= $hmac; + } + return substr($xorsum, 0, self::CRYPT_KEY_SIZE); } /** @@ -109,7 +175,7 @@ class SecurityBase /** * Returns a secret key associated with the specified name. * If the secret key does not exist, a random key will be generated - * and saved in the file "keys.php" under the application's runtime directory + * and saved in the file "keys.data" under the application's runtime directory * so that the same secret key can be returned in future requests. * @param string $name the name that is associated with the secret key * @param integer $length the length of the key that should be generated if not exists @@ -118,16 +184,16 @@ class SecurityBase public static function getSecretKey($name, $length = 32) { static $keys; - $keyFile = Yii::$app->getRuntimePath() . '/keys.php'; + $keyFile = Yii::$app->getRuntimePath() . '/keys.json'; if ($keys === null) { - $keys = array(); + $keys = []; if (is_file($keyFile)) { - $keys = require($keyFile); + $keys = json_decode(file_get_contents($keyFile), true); } } if (!isset($keys[$name])) { $keys[$name] = static::generateRandomKey($length); - file_put_contents($keyFile, "<?php\nreturn " . var_export($keys, true) . ";\n"); + file_put_contents($keyFile, json_encode($keys)); } return $keys[$name]; } diff --git a/framework/yii/helpers/StringHelperBase.php b/framework/yii/helpers/BaseStringHelper.php similarity index 97% rename from framework/yii/helpers/StringHelperBase.php rename to framework/yii/helpers/BaseStringHelper.php index cbb696e..e1622b9 100644 --- a/framework/yii/helpers/StringHelperBase.php +++ b/framework/yii/helpers/BaseStringHelper.php @@ -10,15 +10,15 @@ namespace yii\helpers; use yii\base\InvalidParamException; /** - * StringHelperBase provides concrete implementation for [[StringHelper]]. + * BaseStringHelper provides concrete implementation for [[StringHelper]]. * - * Do not use StringHelperBase. Use [[StringHelper]] instead. + * Do not use BaseStringHelper. Use [[StringHelper]] instead. * * @author Qiang Xue <qiang.xue@gmail.com> * @author Alex Makarov <sam@rmcreative.ru> * @since 2.0 */ -class StringHelperBase +class BaseStringHelper { /** * Returns the number of bytes in the given string. diff --git a/framework/yii/helpers/VarDumperBase.php b/framework/yii/helpers/BaseVarDumper.php similarity index 92% rename from framework/yii/helpers/VarDumperBase.php rename to framework/yii/helpers/BaseVarDumper.php index c7da208..b11757b 100644 --- a/framework/yii/helpers/VarDumperBase.php +++ b/framework/yii/helpers/BaseVarDumper.php @@ -9,14 +9,14 @@ namespace yii\helpers; /** - * VarDumperBase provides concrete implementation for [[VarDumper]]. + * BaseVarDumper provides concrete implementation for [[VarDumper]]. * - * Do not use VarDumperBase. Use [[VarDumper]] instead. + * Do not use BaseVarDumper. Use [[VarDumper]] instead. * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ -class VarDumperBase +class BaseVarDumper { private static $_objects; private static $_output; @@ -47,7 +47,7 @@ class VarDumperBase public static function dumpAsString($var, $depth = 10, $highlight = false) { self::$_output = ''; - self::$_objects = array(); + self::$_objects = []; self::$_depth = $depth; self::dumpInternal($var, 0); if ($highlight) { @@ -87,20 +87,20 @@ class VarDumperBase break; case 'array': if (self::$_depth <= $level) { - self::$_output .= 'array(...)'; + self::$_output .= '[...]'; } elseif (empty($var)) { - self::$_output .= 'array()'; + self::$_output .= '[]'; } else { $keys = array_keys($var); $spaces = str_repeat(' ', $level * 4); - self::$_output .= "array\n" . $spaces . '('; + self::$_output .= '['; foreach ($keys as $key) { self::$_output .= "\n" . $spaces . ' '; self::dumpInternal($key, 0); self::$_output .= ' => '; self::dumpInternal($var[$key], $level + 1); } - self::$_output .= "\n" . $spaces . ')'; + self::$_output .= "\n" . $spaces . ']'; } break; case 'object': @@ -115,7 +115,7 @@ class VarDumperBase $spaces = str_repeat(' ', $level * 4); self::$_output .= "$className#$id\n" . $spaces . '('; foreach ($members as $key => $value) { - $keyDisplay = strtr(trim($key), array("\0" => ':')); + $keyDisplay = strtr(trim($key), ["\0" => ':']); self::$_output .= "\n" . $spaces . " [$keyDisplay] => "; self::dumpInternal($value, $level + 1); } diff --git a/framework/yii/helpers/Console.php b/framework/yii/helpers/Console.php index eeadcbc..a34dc96 100644 --- a/framework/yii/helpers/Console.php +++ b/framework/yii/helpers/Console.php @@ -8,15 +8,12 @@ namespace yii\helpers; /** - * TODO adjust phpdoc - * Console View is the base class for console view components - * - * A console view provides functionality to create rich console application by allowing to format output - * by adding color and font style to it. + * Console helper provides useful methods for command line related tasks such as getting input or formatting and coloring + * output. * * @author Carsten Brandt <mail@cebe.cc> * @since 2.0 */ -class Console extends ConsoleBase +class Console extends BaseConsole { } diff --git a/framework/yii/helpers/FileHelper.php b/framework/yii/helpers/FileHelper.php index 9241025..63954a4 100644 --- a/framework/yii/helpers/FileHelper.php +++ b/framework/yii/helpers/FileHelper.php @@ -16,6 +16,6 @@ namespace yii\helpers; * @author Alex Makarov <sam@rmcreative.ru> * @since 2.0 */ -class FileHelper extends FileHelperBase +class FileHelper extends BaseFileHelper { } diff --git a/framework/yii/helpers/Html.php b/framework/yii/helpers/Html.php index 8e4f1c9..f4fbbba 100644 --- a/framework/yii/helpers/Html.php +++ b/framework/yii/helpers/Html.php @@ -13,6 +13,6 @@ namespace yii\helpers; * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ -class Html extends HtmlBase +class Html extends BaseHtml { } diff --git a/framework/yii/helpers/HtmlPurifier.php b/framework/yii/helpers/HtmlPurifier.php index f7203e4..f2852ad 100644 --- a/framework/yii/helpers/HtmlPurifier.php +++ b/framework/yii/helpers/HtmlPurifier.php @@ -19,9 +19,9 @@ namespace yii\helpers; * If you want to configure it: * * ```php - * echo HtmlPurifier::process($html, array( + * echo HtmlPurifier::process($html, [ * 'Attr.EnableID' => true, - * )); + * ]); * ``` * * For more details please refer to HTMLPurifier documentation](http://htmlpurifier.org/). @@ -32,6 +32,6 @@ namespace yii\helpers; * @author Alexander Makarov <sam@rmcreative.ru> * @since 2.0 */ -class HtmlPurifier extends HtmlPurifierBase +class HtmlPurifier extends BaseHtmlPurifier { } diff --git a/framework/yii/helpers/Inflector.php b/framework/yii/helpers/Inflector.php index ba9c069..ab4713e 100644 --- a/framework/yii/helpers/Inflector.php +++ b/framework/yii/helpers/Inflector.php @@ -13,6 +13,6 @@ namespace yii\helpers; * @author Antonio Ramirez <amigo.cobos@gmail.com> * @since 2.0 */ -class Inflector extends InflectorBase +class Inflector extends BaseInflector { } diff --git a/framework/yii/helpers/Json.php b/framework/yii/helpers/Json.php index 424de1f..8ca436a 100644 --- a/framework/yii/helpers/Json.php +++ b/framework/yii/helpers/Json.php @@ -14,6 +14,6 @@ namespace yii\helpers; * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ -class Json extends JsonBase +class Json extends BaseJson { } diff --git a/framework/yii/helpers/Markdown.php b/framework/yii/helpers/Markdown.php index 690df5d..326cd34 100644 --- a/framework/yii/helpers/Markdown.php +++ b/framework/yii/helpers/Markdown.php @@ -19,9 +19,9 @@ namespace yii\helpers; * If you want to configure the parser: * * ```php - * $myHtml = Markdown::process($myText, array( + * $myHtml = Markdown::process($myText, [ * 'fn_id_prefix' => 'footnote_', - * )); + * ]); * ``` * * Note that in order to use this helper you need to install "michelf/php-markdown" Composer package. @@ -30,6 +30,6 @@ namespace yii\helpers; * @author Alexander Makarov <sam@rmcreative.ru> * @since 2.0 */ -class Markdown extends MarkdownBase +class Markdown extends BaseMarkdown { } diff --git a/framework/yii/helpers/Security.php b/framework/yii/helpers/Security.php index d0ca2ed..0e3ee38 100644 --- a/framework/yii/helpers/Security.php +++ b/framework/yii/helpers/Security.php @@ -24,6 +24,6 @@ namespace yii\helpers; * @author Tom Worster <fsb@thefsb.org> * @since 2.0 */ -class Security extends SecurityBase +class Security extends BaseSecurity { } diff --git a/framework/yii/helpers/StringHelper.php b/framework/yii/helpers/StringHelper.php index ef75790..5ecd390 100644 --- a/framework/yii/helpers/StringHelper.php +++ b/framework/yii/helpers/StringHelper.php @@ -14,6 +14,6 @@ namespace yii\helpers; * @author Alex Makarov <sam@rmcreative.ru> * @since 2.0 */ -class StringHelper extends StringHelperBase +class StringHelper extends BaseStringHelper { } diff --git a/framework/yii/helpers/VarDumper.php b/framework/yii/helpers/VarDumper.php index 50e543c..1ac5aa7 100644 --- a/framework/yii/helpers/VarDumper.php +++ b/framework/yii/helpers/VarDumper.php @@ -23,6 +23,6 @@ namespace yii\helpers; * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ -class VarDumper extends VarDumperBase +class VarDumper extends BaseVarDumper { } diff --git a/framework/yii/helpers/mimeTypes.php b/framework/yii/helpers/mimeTypes.php index ffdba4b..4e9f61a 100644 --- a/framework/yii/helpers/mimeTypes.php +++ b/framework/yii/helpers/mimeTypes.php @@ -11,7 +11,7 @@ * @since 2.0 */ -return array( +return [ 'ai' => 'application/postscript', 'aif' => 'audio/x-aiff', 'aifc' => 'audio/x-aiff', @@ -184,4 +184,4 @@ return array( 'xwd' => 'image/x-xwindowdump', 'xyz' => 'chemical/x-pdb', 'zip' => 'application/zip', -); +]; diff --git a/framework/yii/i18n/DbMessageSource.php b/framework/yii/i18n/DbMessageSource.php index 14cf0b9..b660a86 100644 --- a/framework/yii/i18n/DbMessageSource.php +++ b/framework/yii/i18n/DbMessageSource.php @@ -121,11 +121,11 @@ class DbMessageSource extends MessageSource protected function loadMessages($category, $language) { if ($this->enableCaching) { - $key = array( + $key = [ __CLASS__, $category, $language, - ); + ]; $messages = $this->cache->get($key); if ($messages === false) { $messages = $this->loadMessagesFromDb($category, $language); @@ -147,10 +147,10 @@ class DbMessageSource extends MessageSource protected function loadMessagesFromDb($category, $language) { $query = new Query(); - $messages = $query->select(array('t1.message message', 't2.translation translation')) - ->from(array($this->sourceMessageTable . ' t1', $this->messageTable . ' t2')) + $messages = $query->select(['t1.message message', 't2.translation translation']) + ->from([$this->sourceMessageTable . ' t1', $this->messageTable . ' t2']) ->where('t1.id = t2.id AND t1.category = :category AND t2.language = :language') - ->params(array(':category' => $category, ':language' => $language)) + ->params([':category' => $category, ':language' => $language]) ->createCommand($this->db) ->queryAll(); return ArrayHelper::map($messages, 'message', 'translation'); diff --git a/framework/yii/i18n/Formatter.php b/framework/yii/i18n/Formatter.php index 979a46f..2eeb056 100644 --- a/framework/yii/i18n/Formatter.php +++ b/framework/yii/i18n/Formatter.php @@ -19,6 +19,15 @@ use yii\base\InvalidConfigException; * Formatter requires the PHP "intl" extension to be installed. Formatter supports localized * formatting of date, time and numbers, based on the current [[locale]]. * + * This Formatter can replace the `formatter` application component that is configured by default. + * To do so, add the following to your application config under `components`: + * + * ```php + * 'formatter' => [ + * 'class' => 'yii\i18n\Formatter', + * ] + * ``` + * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ @@ -53,7 +62,7 @@ class Formatter extends \yii\base\Formatter * for the possible options. This property is used by [[createNumberFormatter]] when * creating a new number formatter to format decimals, currencies, etc. */ - public $numberFormatOptions = array(); + public $numberFormatOptions = []; /** * @var string the character displayed as the decimal point when formatting a number. * If not set, the decimal separator corresponding to [[locale]] will be used. @@ -93,12 +102,12 @@ class Formatter extends \yii\base\Formatter parent::init(); } - private $_dateFormats = array( + private $_dateFormats = [ 'short' => IntlDateFormatter::SHORT, 'medium' => IntlDateFormatter::MEDIUM, 'long' => IntlDateFormatter::LONG, 'full' => IntlDateFormatter::FULL, - ); + ]; /** * Formats the value as a date. @@ -131,7 +140,12 @@ class Formatter extends \yii\base\Formatter $formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], IntlDateFormatter::NONE); } else { $formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE); - $formatter->setPattern($format); + if ($formatter !== null) { + $formatter->setPattern($format); + } + } + if ($formatter === null) { + throw new InvalidConfigException(intl_get_error_message()); } return $formatter->format($value); } @@ -167,7 +181,12 @@ class Formatter extends \yii\base\Formatter $formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, $this->_dateFormats[$format]); } else { $formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE); - $formatter->setPattern($format); + if ($formatter !== null) { + $formatter->setPattern($format); + } + } + if ($formatter === null) { + throw new InvalidConfigException(intl_get_error_message()); } return $formatter->format($value); } @@ -203,7 +222,12 @@ class Formatter extends \yii\base\Formatter $formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], $this->_dateFormats[$format]); } else { $formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE); - $formatter->setPattern($format); + if ($formatter !== null) { + $formatter->setPattern($format); + } + } + if ($formatter === null) { + throw new InvalidConfigException(intl_get_error_message()); } return $formatter->format($value); } diff --git a/framework/yii/i18n/GettextMessageSource.php b/framework/yii/i18n/GettextMessageSource.php index 745eb38..66704c4 100644 --- a/framework/yii/i18n/GettextMessageSource.php +++ b/framework/yii/i18n/GettextMessageSource.php @@ -68,18 +68,18 @@ class GettextMessageSource extends MessageSource if (is_file($messageFile)) { if ($this->useMoFile) { - $gettextFile = new GettextMoFile(array('useBigEndian' => $this->useBigEndian)); + $gettextFile = new GettextMoFile(['useBigEndian' => $this->useBigEndian]); } else { $gettextFile = new GettextPoFile(); } $messages = $gettextFile->load($messageFile, $category); if (!is_array($messages)) { - $messages = array(); + $messages = []; } return $messages; } else { Yii::error("The message file for category '$category' does not exist: $messageFile", __METHOD__); - return array(); + return []; } } } diff --git a/framework/yii/i18n/GettextMoFile.php b/framework/yii/i18n/GettextMoFile.php index c12aebe..b4a016d 100644 --- a/framework/yii/i18n/GettextMoFile.php +++ b/framework/yii/i18n/GettextMoFile.php @@ -54,6 +54,7 @@ class GettextMoFile extends GettextFile * @param string $context message context * @return array message translations. Array keys are source messages and array values are translated messages: * source message => translated message. + * @throws Exception if unable to read the MO file */ public function load($filePath, $context) { @@ -85,23 +86,23 @@ class GettextMoFile extends GettextFile $sourceOffset = $this->readInteger($fileHandle); $targetOffset = $this->readInteger($fileHandle); - $sourceLengths = array(); - $sourceOffsets = array(); + $sourceLengths = []; + $sourceOffsets = []; fseek($fileHandle, $sourceOffset); for ($i = 0; $i < $count; ++$i) { $sourceLengths[] = $this->readInteger($fileHandle); $sourceOffsets[] = $this->readInteger($fileHandle); } - $targetLengths = array(); - $targetOffsets = array(); + $targetLengths = []; + $targetOffsets = []; fseek($fileHandle, $targetOffset); for ($i = 0; $i < $count; ++$i) { $targetLengths[] = $this->readInteger($fileHandle); $targetOffsets[] = $this->readInteger($fileHandle); } - $messages = array(); + $messages = []; for ($i = 0; $i < $count; ++$i) { $id = $this->readString($fileHandle, $sourceLengths[$i], $sourceOffsets[$i]); $separatorPosition = strpos($id, chr(4)); @@ -128,6 +129,7 @@ class GettextMoFile extends GettextFile * @param array $messages message translations. Array keys are source messages and array values are * translated messages: source message => translated message. Note if the message has a context, * the message ID must be prefixed with the context with chr(4) as the separator. + * @throws Exception if unable to save the MO file */ public function save($filePath, $messages) { @@ -203,6 +205,8 @@ class GettextMoFile extends GettextFile { if ($byteCount > 0) { return fread($fileHandle, $byteCount); + } else { + return null; } } diff --git a/framework/yii/i18n/GettextPoFile.php b/framework/yii/i18n/GettextPoFile.php index cac075b..8fb618c 100644 --- a/framework/yii/i18n/GettextPoFile.php +++ b/framework/yii/i18n/GettextPoFile.php @@ -30,10 +30,10 @@ class GettextPoFile extends GettextFile . 'msgid\s+((?:".*(?<!\\\\)"\s*)+)\s+' // message ID, i.e. original string . 'msgstr\s+((?:".*(?<!\\\\)"\s*)+)/'; // translated string $content = file_get_contents($filePath); - $matches = array(); + $matches = []; $matchCount = preg_match_all($pattern, $content, $matches); - $messages = array(); + $messages = []; for ($i = 0; $i < $matchCount; ++$i) { if ($matches[2][$i] == $context) { $id = $this->decode($matches[3][$i]); @@ -74,8 +74,8 @@ class GettextPoFile extends GettextFile protected function encode($string) { return str_replace( - array('"', "\n", "\t", "\r"), - array('\\"', '\\n', '\\t', '\\r'), + ['"', "\n", "\t", "\r"], + ['\\"', '\\n', '\\t', '\\r'], $string ); } @@ -88,8 +88,8 @@ class GettextPoFile extends GettextFile protected function decode($string) { $string = preg_replace( - array('/"\s+"/', '/\\\\n/', '/\\\\r/', '/\\\\t/', '/\\\\"/'), - array('', "\n", "\r", "\t", '"'), + ['/"\s+"/', '/\\\\n/', '/\\\\r/', '/\\\\t/', '/\\\\"/'], + ['', "\n", "\r", "\t", '"'], $string ); return substr(rtrim($string), 1, -1); diff --git a/framework/yii/i18n/I18N.php b/framework/yii/i18n/I18N.php index 171b5d4..c59a6d2 100644 --- a/framework/yii/i18n/I18N.php +++ b/framework/yii/i18n/I18N.php @@ -10,11 +10,17 @@ namespace yii\i18n; use Yii; use yii\base\Component; use yii\base\InvalidConfigException; -use yii\base\InvalidParamException; /** * I18N provides features related with internationalization (I18N) and localization (L10N). * + * I18N is configured as an application component in [[yii\base\Application]] by default. + * You can access that instance via `Yii::$app->i18n`. + * + * @property MessageFormatter $messageFormatter The message formatter to be used to format message via ICU + * message format. Note that the type of this property differs in getter and setter. See + * [[getMessageFormatter()]] and [[setMessageFormatter()]] for details. + * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ @@ -37,17 +43,6 @@ class I18N extends Component * You may override the configuration of both categories. */ public $translations; - /** - * @var string the path or path alias of the file that contains the plural rules. - * By default, this refers to a file shipped with the Yii distribution. The file is obtained - * by converting from the data file in the CLDR project. - * - * If the default rule file does not contain the expected rules, you may copy and modify it - * for your application, and then configure this property to point to your modified copy. - * - * @see http://www.unicode.org/cldr/charts/supplemental/language_plural_rules.html - */ - public $pluralRuleFile = '@yii/i18n/data/plurals.php'; /** * Initializes the component by configuring the default message categories. @@ -56,48 +51,100 @@ class I18N extends Component { parent::init(); if (!isset($this->translations['yii'])) { - $this->translations['yii'] = array( + $this->translations['yii'] = [ 'class' => 'yii\i18n\PhpMessageSource', - 'sourceLanguage' => 'en_US', + 'sourceLanguage' => 'en-US', 'basePath' => '@yii/messages', - ); + ]; } if (!isset($this->translations['app'])) { - $this->translations['app'] = array( + $this->translations['app'] = [ 'class' => 'yii\i18n\PhpMessageSource', - 'sourceLanguage' => 'en_US', + 'sourceLanguage' => 'en-US', 'basePath' => '@app/messages', - ); + ]; } } /** * Translates a message to the specified language. - * If the first parameter in `$params` is a number and it is indexed by 0, appropriate plural rules - * will be applied to the translated message. + * + * After translation the message will be formatted using [[MessageFormatter]] if it contains + * ICU message format and `$params` are not empty. + * * @param string $category the message category. * @param string $message the message to be translated. * @param array $params the parameters that will be used to replace the corresponding placeholders in the message. - * @param string $language the language code (e.g. `en_US`, `en`). - * @return string the translated message. + * @param string $language the language code (e.g. `en-US`, `en`). + * @return string the translated and formatted message. */ public function translate($category, $message, $params, $language) { $message = $this->getMessageSource($category)->translate($category, $message, $language); + return $this->format($message, $params, $language); + } - if (!is_array($params)) { - $params = array($params); + /** + * Formats a message using [[MessageFormatter]]. + * + * @param string $message the message to be formatted. + * @param array $params the parameters that will be used to replace the corresponding placeholders in the message. + * @param string $language the language code (e.g. `en-US`, `en`). + * @return string the formatted message. + */ + public function format($message, $params, $language) + { + $params = (array)$params; + if ($params === []) { + return $message; } - if (isset($params[0])) { - $message = $this->applyPluralRules($message, $params[0], $language); - if (!isset($params['{n}'])) { - $params['{n}'] = $params[0]; + if (preg_match('~{\s*[\d\w]+\s*,~u', $message)) { + $formatter = $this->getMessageFormatter(); + $result = $formatter->format($message, $params, $language); + if ($result === false) { + $errorMessage = $formatter->getErrorMessage(); + Yii::warning("Formatting message for language '$language' failed with error: $errorMessage. The message being formatted was: $message."); + return $message; + } else { + return $result; } - unset($params[0]); } - return empty($params) ? $message : strtr($message, $params); + $p = []; + foreach($params as $name => $value) { + $p['{' . $name . '}'] = $value; + } + return strtr($message, $p); + } + + /** + * @var string|array|MessageFormatter + */ + private $_messageFormatter; + + /** + * Returns the message formatter instance. + * @return MessageFormatter the message formatter to be used to format message via ICU message format. + */ + public function getMessageFormatter() + { + if ($this->_messageFormatter === null) { + $this->_messageFormatter = new MessageFormatter(); + } elseif (is_array($this->_messageFormatter) || is_string($this->_messageFormatter)) { + $this->_messageFormatter = Yii::createObject($this->_messageFormatter); + } + return $this->_messageFormatter; + } + + /** + * @param string|array|MessageFormatter $value the message formatter to be used to format message via ICU message format. + * Can be given as array or string configuration that will be given to [[Yii::createObject]] to create an instance + * or a [[MessageFormatter]] instance. + */ + public function setMessageFormatter($value) + { + $this->_messageFormatter = $value; } /** @@ -125,62 +172,4 @@ class I18N extends Component throw new InvalidConfigException("Unable to locate message source for category '$category'."); } } - - /** - * Applies appropriate plural rules to the given message. - * @param string $message the message to be applied with plural rules - * @param mixed $number the number by which plural rules will be applied - * @param string $language the language code that determines which set of plural rules to be applied. - * @return string the message that has applied plural rules - */ - protected function applyPluralRules($message, $number, $language) - { - if (strpos($message, '|') === false) { - return $message; - } - $chunks = explode('|', $message); - - $rules = $this->getPluralRules($language); - foreach ($rules as $i => $rule) { - if (isset($chunks[$i]) && $this->evaluate($rule, $number)) { - return $chunks[$i]; - } - } - $n = count($rules); - return isset($chunks[$n]) ? $chunks[$n] : $chunks[0]; - } - - private $_pluralRules = array(); // language => rule set - - /** - * Returns the plural rules for the given language code. - * @param string $language the language code (e.g. `en_US`, `en`). - * @return array the plural rules - * @throws InvalidParamException if the language code is invalid. - */ - protected function getPluralRules($language) - { - if (isset($this->_pluralRules[$language])) { - return $this->_pluralRules[$language]; - } - $allRules = require(Yii::getAlias($this->pluralRuleFile)); - if (isset($allRules[$language])) { - return $this->_pluralRules[$language] = $allRules[$language]; - } elseif (preg_match('/^[a-z]+/', strtolower($language), $matches)) { - return $this->_pluralRules[$language] = isset($allRules[$matches[0]]) ? $allRules[$matches[0]] : array(); - } else { - throw new InvalidParamException("Invalid language code: $language"); - } - } - - /** - * Evaluates a PHP expression with the given number value. - * @param string $expression the PHP expression - * @param mixed $n the number value - * @return boolean the expression result - */ - protected function evaluate($expression, $n) - { - return eval("return $expression;"); - } } diff --git a/framework/yii/i18n/MessageFormatter.php b/framework/yii/i18n/MessageFormatter.php new file mode 100644 index 0000000..43aebdd --- /dev/null +++ b/framework/yii/i18n/MessageFormatter.php @@ -0,0 +1,396 @@ +<?php +/** + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\i18n; + +use yii\base\Component; +use yii\base\NotSupportedException; + +/** + * MessageFormatter allows formatting messages via [ICU message format](http://userguide.icu-project.org/formatparse/messages) + * + * This class enhances the message formatter class provided by the PHP intl extension. + * + * The following enhancements are provided: + * + * - It accepts named arguments and mixed numeric and named arguments. + * - Issues no error when an insufficient number of arguments have been provided. Instead, the placeholders will not be + * substituted. + * - Fixes PHP 5.5 weird placeholder replacement in case no arguments are provided at all (https://bugs.php.net/bug.php?id=65920). + * - Offers limited support for message formatting in case PHP intl extension is not installed. + * However it is highly recommended that you install [PHP intl extension](http://php.net/manual/en/book.intl.php) if you want + * to use MessageFormatter features. + * + * The fallback implementation only supports the following message formats: + * - plural formatting for english ('one' and 'other' selectors) + * - select format + * - simple parameters + * - integer number parameters + * + * The fallback implementation does NOT support the ['apostrophe-friendly' syntax](http://www.php.net/manual/en/messageformatter.formatmessage.php). + * Also messages that are working with the fallback implementation are not necessarily compatible with the + * PHP intl MessageFormatter so do not rely on the fallback if you are able to install intl extension somehow. + * + * @property string $errorCode Code of the last error. This property is read-only. + * @property string $errorMessage Description of the last error. This property is read-only. + * + * @author Alexander Makarov <sam@rmcreative.ru> + * @author Carsten Brandt <mail@cebe.cc> + * @since 2.0 + */ +class MessageFormatter extends Component +{ + private $_errorCode = 0; + private $_errorMessage = ''; + + /** + * Get the error code from the last operation + * @link http://php.net/manual/en/messageformatter.geterrorcode.php + * @return string Code of the last error. + */ + public function getErrorCode() + { + return $this->_errorCode; + } + + /** + * Get the error text from the last operation + * @link http://php.net/manual/en/messageformatter.geterrormessage.php + * @return string Description of the last error. + */ + public function getErrorMessage() + { + return $this->_errorMessage; + } + + /** + * Formats a message via [ICU message format](http://userguide.icu-project.org/formatparse/messages) + * + * It uses the PHP intl extension's [MessageFormatter](http://www.php.net/manual/en/class.messageformatter.php) + * and works around some issues. + * If PHP intl is not installed a fallback will be used that supports a subset of the ICU message format. + * + * @param string $pattern The pattern string to insert parameters into. + * @param array $params The array of name value pairs to insert into the format string. + * @param string $language The locale to use for formatting locale-dependent parts + * @return string|boolean The formatted pattern string or `FALSE` if an error occurred + */ + public function format($pattern, $params, $language) + { + $this->_errorCode = 0; + $this->_errorMessage = ''; + + if ($params === []) { + return $pattern; + } + + if (!class_exists('MessageFormatter', false)) { + return $this->fallbackFormat($pattern, $params, $language); + } + + if (version_compare(PHP_VERSION, '5.5.0', '<') || version_compare(INTL_ICU_VERSION, '4.8', '<')) { + // replace named arguments + $pattern = $this->replaceNamedArguments($pattern, $params, $newParams); + $params = $newParams; + } + + $formatter = new \MessageFormatter($language, $pattern); + if ($formatter === null) { + $this->_errorCode = intl_get_error_code(); + $this->_errorMessage = "Message pattern is invalid: " . intl_get_error_message(); + return false; + } + $result = $formatter->format($params); + if ($result === false) { + $this->_errorCode = $formatter->getErrorCode(); + $this->_errorMessage = $formatter->getErrorMessage(); + return false; + } else { + return $result; + } + } + + /** + * Parses an input string according to an [ICU message format](http://userguide.icu-project.org/formatparse/messages) pattern. + * + * It uses the PHP intl extension's [MessageFormatter::parse()](http://www.php.net/manual/en/messageformatter.parsemessage.php) + * and adds support for named arguments. + * Usage of this method requires PHP intl extension to be installed. + * + * @param string $pattern The pattern to use for parsing the message. + * @param string $message The message to parse, conforming to the pattern. + * @param string $language The locale to use for formatting locale-dependent parts + * @return array|boolean An array containing items extracted, or `FALSE` on error. + * @throws \yii\base\NotSupportedException when PHP intl extension is not installed. + */ + public function parse($pattern, $message, $language) + { + $this->_errorCode = 0; + $this->_errorMessage = ''; + + if (!class_exists('MessageFormatter', false)) { + throw new NotSupportedException('You have to install PHP intl extension to use this feature.'); + } + + // replace named arguments + if (($tokens = $this->tokenizePattern($pattern)) === false) { + $this->_errorCode = -1; + $this->_errorMessage = "Message pattern is invalid."; + return false; + } + $map = []; + foreach($tokens as $i => $token) { + if (is_array($token)) { + $param = trim($token[0]); + if (!isset($map[$param])) { + $map[$param] = count($map); + } + $token[0] = $map[$param]; + $tokens[$i] = '{' . implode(',', $token) . '}'; + } + } + $pattern = implode('', $tokens); + $map = array_flip($map); + + $formatter = new \MessageFormatter($language, $pattern); + if ($formatter === null) { + $this->_errorCode = -1; + $this->_errorMessage = "Message pattern is invalid."; + return false; + } + $result = $formatter->parse($message); + if ($result === false) { + $this->_errorCode = $formatter->getErrorCode(); + $this->_errorMessage = $formatter->getErrorMessage(); + return false; + } else { + $values = []; + foreach($result as $key => $value) { + $values[$map[$key]] = $value; + } + return $values; + } + } + + /** + * Replace named placeholders with numeric placeholders and quote unused. + * + * @param string $pattern The pattern string to replace things into. + * @param array $args The array of values to insert into the format string. + * @return string The pattern string with placeholders replaced. + */ + private function replaceNamedArguments($pattern, $givenParams, &$resultingParams, &$map = []) + { + if (($tokens = $this->tokenizePattern($pattern)) === false) { + return false; + } + foreach($tokens as $i => $token) { + if (!is_array($token)) { + continue; + } + $param = trim($token[0]); + if (isset($givenParams[$param])) { + // if param is given, replace it with a number + if (!isset($map[$param])) { + $map[$param] = count($map); + // make sure only used params are passed to format method + $resultingParams[$map[$param]] = $givenParams[$param]; + } + $token[0] = $map[$param]; + $quote = ""; + } else { + // quote unused token + $quote = "'"; + } + $type = isset($token[1]) ? trim($token[1]) : 'none'; + // replace plural and select format recursively + if ($type == 'plural' || $type == 'select') { + if (!isset($token[2])) { + return false; + } + $subtokens = $this->tokenizePattern($token[2]); + $c = count($subtokens); + for ($k = 0; $k + 1 < $c; $k++) { + if (is_array($subtokens[$k]) || !is_array($subtokens[++$k])) { + return false; + } + $subpattern = $this->replaceNamedArguments(implode(',', $subtokens[$k]), $givenParams, $resultingParams, $map); + $subtokens[$k] = $quote . '{' . $quote . $subpattern . $quote . '}' . $quote; + } + $token[2] = implode('', $subtokens); + } + $tokens[$i] = $quote . '{' . $quote . implode(',', $token) . $quote . '}' . $quote; + } + return implode('', $tokens); + } + + /** + * Fallback implementation for MessageFormatter::formatMessage + * @param string $pattern The pattern string to insert things into. + * @param array $args The array of values to insert into the format string + * @param string $locale The locale to use for formatting locale-dependent parts + * @return string|boolean The formatted pattern string or `FALSE` if an error occurred + */ + protected function fallbackFormat($pattern, $args, $locale) + { + if (($tokens = $this->tokenizePattern($pattern)) === false) { + $this->_errorCode = -1; + $this->_errorMessage = "Message pattern is invalid."; + return false; + } + foreach($tokens as $i => $token) { + if (is_array($token)) { + if (($tokens[$i] = $this->parseToken($token, $args, $locale)) === false) { + $this->_errorCode = -1; + $this->_errorMessage = "Message pattern is invalid."; + return false; + } + } + } + return implode('', $tokens); + } + + /** + * Tokenizes a pattern by separating normal text from replaceable patterns + * @param string $pattern patter to tokenize + * @return array|bool array of tokens or false on failure + */ + private function tokenizePattern($pattern) + { + $depth = 1; + if (($start = $pos = mb_strpos($pattern, '{')) === false) { + return [$pattern]; + } + $tokens = [mb_substr($pattern, 0, $pos)]; + while (true) { + $open = mb_strpos($pattern, '{', $pos + 1); + $close = mb_strpos($pattern, '}', $pos + 1); + if ($open === false && $close === false) { + break; + } + if ($open === false) { + $open = mb_strlen($pattern); + } + if ($close > $open) { + $depth++; + $pos = $open; + } else { + $depth--; + $pos = $close; + } + if ($depth == 0) { + $tokens[] = explode(',', mb_substr($pattern, $start + 1, $pos - $start - 1), 3); + $start = $pos + 1; + $tokens[] = mb_substr($pattern, $start, $open - $start); + $start = $open; + } + } + if ($depth != 0) { + return false; + } + return $tokens; + } + + /** + * Parses a token + * @param array $token the token to parse + * @param array $args arguments to replace + * @param string $locale the locale + * @return bool|string parsed token or false on failure + * @throws \yii\base\NotSupportedException when unsupported formatting is used. + */ + private function parseToken($token, $args, $locale) + { + // parsing pattern based on ICU grammar: + // http://icu-project.org/apiref/icu4c/classMessageFormat.html#details + + $param = trim($token[0]); + if (isset($args[$param])) { + $arg = $args[$param]; + } else { + return '{' . implode(',', $token) . '}'; + } + $type = isset($token[1]) ? trim($token[1]) : 'none'; + switch($type) + { + case 'date': + case 'time': + case 'spellout': + case 'ordinal': + case 'duration': + case 'choice': + case 'selectordinal': + throw new NotSupportedException("Message format '$type' is not supported. You have to install PHP intl extension to use this feature."); + case 'number': + if (is_int($arg) && (!isset($token[2]) || trim($token[2]) == 'integer')) { + return $arg; + } + throw new NotSupportedException("Message format 'number' is only supported for integer values. You have to install PHP intl extension to use this feature."); + case 'none': return $arg; + case 'select': + /* http://icu-project.org/apiref/icu4c/classicu_1_1SelectFormat.html + selectStyle = (selector '{' message '}')+ + */ + if (!isset($token[2])) { + return false; + } + $select = static::tokenizePattern($token[2]); + $c = count($select); + $message = false; + for ($i = 0; $i + 1 < $c; $i++) { + if (is_array($select[$i]) || !is_array($select[$i + 1])) { + return false; + } + $selector = trim($select[$i++]); + if ($message === false && $selector == 'other' || $selector == $arg) { + $message = implode(',', $select[$i]); + } + } + if ($message !== false) { + return $this->fallbackFormat($message, $args, $locale); + } + break; + case 'plural': + /* http://icu-project.org/apiref/icu4c/classicu_1_1PluralFormat.html + pluralStyle = [offsetValue] (selector '{' message '}')+ + offsetValue = "offset:" number + selector = explicitValue | keyword + explicitValue = '=' number // adjacent, no white space in between + keyword = [^[[:Pattern_Syntax:][:Pattern_White_Space:]]]+ + message: see MessageFormat + */ + if (!isset($token[2])) { + return false; + } + $plural = static::tokenizePattern($token[2]); + $c = count($plural); + $message = false; + $offset = 0; + for ($i = 0; $i + 1 < $c; $i++) { + if (is_array($plural[$i]) || !is_array($plural[$i + 1])) { + return false; + } + $selector = trim($plural[$i++]); + if ($i == 1 && substr($selector, 0, 7) == 'offset:') { + $offset = (int) trim(mb_substr($selector, 7, ($pos = mb_strpos(str_replace(["\n", "\r", "\t"], ' ', $selector), ' ', 7)) - 7)); + $selector = trim(mb_substr($selector, $pos + 1)); + } + if ($message === false && $selector == 'other' || + $selector[0] == '=' && (int) mb_substr($selector, 1) == $arg || + $selector == 'one' && $arg - $offset == 1 + ) { + $message = implode(',', str_replace('#', $arg - $offset, $plural[$i])); + } + } + if ($message !== false) { + return $this->fallbackFormat($message, $args, $locale); + } + break; + } + return false; + } +} diff --git a/framework/yii/i18n/MessageSource.php b/framework/yii/i18n/MessageSource.php index 90adbfb..95f907d 100644 --- a/framework/yii/i18n/MessageSource.php +++ b/framework/yii/i18n/MessageSource.php @@ -38,7 +38,7 @@ class MessageSource extends Component */ public $sourceLanguage; - private $_messages = array(); + private $_messages = []; /** * Initializes this component. @@ -62,7 +62,7 @@ class MessageSource extends Component */ protected function loadMessages($category, $language) { - return array(); + return []; } /** @@ -106,11 +106,11 @@ class MessageSource extends Component if (isset($this->_messages[$key][$message]) && $this->_messages[$key][$message] !== '') { return $this->_messages[$key][$message]; } elseif ($this->hasEventHandlers('missingTranslation')) { - $event = new MissingTranslationEvent(array( + $event = new MissingTranslationEvent([ 'category' => $category, 'message' => $message, 'language' => $language, - )); + ]); $this->trigger(self::EVENT_MISSING_TRANSLATION, $event); return $this->_messages[$key] = $event->message; } else { diff --git a/framework/yii/i18n/MissingTranslationEvent.php b/framework/yii/i18n/MissingTranslationEvent.php index 9ac337a..5c8ffd3 100644 --- a/framework/yii/i18n/MissingTranslationEvent.php +++ b/framework/yii/i18n/MissingTranslationEvent.php @@ -27,7 +27,7 @@ class MissingTranslationEvent extends Event */ public $category; /** - * @var string the language ID (e.g. en_US) that the message is to be translated to + * @var string the language ID (e.g. en-US) that the message is to be translated to */ public $language; } diff --git a/framework/yii/i18n/PhpMessageSource.php b/framework/yii/i18n/PhpMessageSource.php index f62939f..e105083 100644 --- a/framework/yii/i18n/PhpMessageSource.php +++ b/framework/yii/i18n/PhpMessageSource.php @@ -20,12 +20,14 @@ use Yii; * - Within each PHP script, the message translations are returned as an array like the following: * * ~~~ - * return array( + * return [ * 'original message 1' => 'translated message 1', * 'original message 2' => 'translated message 2', - * ); + * ]; * ~~~ * + * You may use [[fileMap]] to customize the association between category names and the file names. + * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ @@ -41,10 +43,10 @@ class PhpMessageSource extends MessageSource * The file paths are relative to [[basePath]]. For example, * * ~~~ - * array( + * [ * 'core' => 'core.php', * 'ext' => 'extensions.php', - * ) + * ] * ~~~ */ public $fileMap; @@ -60,20 +62,18 @@ class PhpMessageSource extends MessageSource $messageFile = Yii::getAlias($this->basePath) . "/$language/"; if (isset($this->fileMap[$category])) { $messageFile .= $this->fileMap[$category]; - } elseif (($pos = strrpos($category, '\\')) !== false) { - $messageFile .= (substr($category, $pos) . '.php'); } else { - $messageFile .= "$category.php"; + $messageFile .= str_replace('\\', '/', $category) . '.php'; } if (is_file($messageFile)) { $messages = include($messageFile); if (!is_array($messages)) { - $messages = array(); + $messages = []; } return $messages; } else { Yii::error("The message file for category '$category' does not exist: $messageFile", __METHOD__); - return array(); + return []; } } } diff --git a/framework/yii/i18n/data/plurals.php b/framework/yii/i18n/data/plurals.php deleted file mode 100644 index cb51307..0000000 --- a/framework/yii/i18n/data/plurals.php +++ /dev/null @@ -1,627 +0,0 @@ -<?php -/** - * Plural rules. - * - * This file is automatically generated by the "yii locale/plural" command under the "build" folder. - * Do not modify it directly. - * - * The original plural rule data used for generating this file has the following copyright terms: - * - * Copyright © 1991-2007 Unicode, Inc. All rights reserved. - * Distributed under the Terms of Use in http://www.unicode.org/copyright.html. - * - * @revision 6008 (of the original plural file) - * @link http://www.yiiframework.com/ - * @copyright Copyright (c) 2008 Yii Software LLC - * @license http://www.yiiframework.com/license/ - */ -return array ( - 'ar' => - array ( - 0 => '$n==0', - 1 => '$n==1', - 2 => '$n==2', - 3 => 'in_array(fmod($n,100),range(3,10))', - 4 => 'in_array(fmod($n,100),range(11,99))', - ), - 'asa' => - array ( - 0 => '$n==1', - ), - 'af' => - array ( - 0 => '$n==1', - ), - 'bem' => - array ( - 0 => '$n==1', - ), - 'bez' => - array ( - 0 => '$n==1', - ), - 'bg' => - array ( - 0 => '$n==1', - ), - 'bn' => - array ( - 0 => '$n==1', - ), - 'brx' => - array ( - 0 => '$n==1', - ), - 'ca' => - array ( - 0 => '$n==1', - ), - 'cgg' => - array ( - 0 => '$n==1', - ), - 'chr' => - array ( - 0 => '$n==1', - ), - 'da' => - array ( - 0 => '$n==1', - ), - 'de' => - array ( - 0 => '$n==1', - ), - 'dv' => - array ( - 0 => '$n==1', - ), - 'ee' => - array ( - 0 => '$n==1', - ), - 'el' => - array ( - 0 => '$n==1', - ), - 'en' => - array ( - 0 => '$n==1', - ), - 'eo' => - array ( - 0 => '$n==1', - ), - 'es' => - array ( - 0 => '$n==1', - ), - 'et' => - array ( - 0 => '$n==1', - ), - 'eu' => - array ( - 0 => '$n==1', - ), - 'fi' => - array ( - 0 => '$n==1', - ), - 'fo' => - array ( - 0 => '$n==1', - ), - 'fur' => - array ( - 0 => '$n==1', - ), - 'fy' => - array ( - 0 => '$n==1', - ), - 'gl' => - array ( - 0 => '$n==1', - ), - 'gsw' => - array ( - 0 => '$n==1', - ), - 'gu' => - array ( - 0 => '$n==1', - ), - 'ha' => - array ( - 0 => '$n==1', - ), - 'haw' => - array ( - 0 => '$n==1', - ), - 'he' => - array ( - 0 => '$n==1', - ), - 'is' => - array ( - 0 => '$n==1', - ), - 'it' => - array ( - 0 => '$n==1', - ), - 'jmc' => - array ( - 0 => '$n==1', - ), - 'kaj' => - array ( - 0 => '$n==1', - ), - 'kcg' => - array ( - 0 => '$n==1', - ), - 'kk' => - array ( - 0 => '$n==1', - ), - 'kl' => - array ( - 0 => '$n==1', - ), - 'ksb' => - array ( - 0 => '$n==1', - ), - 'ku' => - array ( - 0 => '$n==1', - ), - 'lb' => - array ( - 0 => '$n==1', - ), - 'lg' => - array ( - 0 => '$n==1', - ), - 'mas' => - array ( - 0 => '$n==1', - ), - 'ml' => - array ( - 0 => '$n==1', - ), - 'mn' => - array ( - 0 => '$n==1', - ), - 'mr' => - array ( - 0 => '$n==1', - ), - 'nah' => - array ( - 0 => '$n==1', - ), - 'nb' => - array ( - 0 => '$n==1', - ), - 'nd' => - array ( - 0 => '$n==1', - ), - 'ne' => - array ( - 0 => '$n==1', - ), - 'nl' => - array ( - 0 => '$n==1', - ), - 'nn' => - array ( - 0 => '$n==1', - ), - 'no' => - array ( - 0 => '$n==1', - ), - 'nr' => - array ( - 0 => '$n==1', - ), - 'ny' => - array ( - 0 => '$n==1', - ), - 'nyn' => - array ( - 0 => '$n==1', - ), - 'om' => - array ( - 0 => '$n==1', - ), - 'or' => - array ( - 0 => '$n==1', - ), - 'pa' => - array ( - 0 => '$n==1', - ), - 'pap' => - array ( - 0 => '$n==1', - ), - 'ps' => - array ( - 0 => '$n==1', - ), - 'pt' => - array ( - 0 => '$n==1', - ), - 'rof' => - array ( - 0 => '$n==1', - ), - 'rm' => - array ( - 0 => '$n==1', - ), - 'rwk' => - array ( - 0 => '$n==1', - ), - 'saq' => - array ( - 0 => '$n==1', - ), - 'seh' => - array ( - 0 => '$n==1', - ), - 'sn' => - array ( - 0 => '$n==1', - ), - 'so' => - array ( - 0 => '$n==1', - ), - 'sq' => - array ( - 0 => '$n==1', - ), - 'ss' => - array ( - 0 => '$n==1', - ), - 'ssy' => - array ( - 0 => '$n==1', - ), - 'st' => - array ( - 0 => '$n==1', - ), - 'sv' => - array ( - 0 => '$n==1', - ), - 'sw' => - array ( - 0 => '$n==1', - ), - 'syr' => - array ( - 0 => '$n==1', - ), - 'ta' => - array ( - 0 => '$n==1', - ), - 'te' => - array ( - 0 => '$n==1', - ), - 'teo' => - array ( - 0 => '$n==1', - ), - 'tig' => - array ( - 0 => '$n==1', - ), - 'tk' => - array ( - 0 => '$n==1', - ), - 'tn' => - array ( - 0 => '$n==1', - ), - 'ts' => - array ( - 0 => '$n==1', - ), - 'ur' => - array ( - 0 => '$n==1', - ), - 'wae' => - array ( - 0 => '$n==1', - ), - 've' => - array ( - 0 => '$n==1', - ), - 'vun' => - array ( - 0 => '$n==1', - ), - 'xh' => - array ( - 0 => '$n==1', - ), - 'xog' => - array ( - 0 => '$n==1', - ), - 'zu' => - array ( - 0 => '$n==1', - ), - 'ak' => - array ( - 0 => '($n==0||$n==1)', - ), - 'am' => - array ( - 0 => '($n==0||$n==1)', - ), - 'bh' => - array ( - 0 => '($n==0||$n==1)', - ), - 'fil' => - array ( - 0 => '($n==0||$n==1)', - ), - 'tl' => - array ( - 0 => '($n==0||$n==1)', - ), - 'guw' => - array ( - 0 => '($n==0||$n==1)', - ), - 'hi' => - array ( - 0 => '($n==0||$n==1)', - ), - 'ln' => - array ( - 0 => '($n==0||$n==1)', - ), - 'mg' => - array ( - 0 => '($n==0||$n==1)', - ), - 'nso' => - array ( - 0 => '($n==0||$n==1)', - ), - 'ti' => - array ( - 0 => '($n==0||$n==1)', - ), - 'wa' => - array ( - 0 => '($n==0||$n==1)', - ), - 'ff' => - array ( - 0 => '($n>=0&&$n<=2)&&$n!=2', - ), - 'fr' => - array ( - 0 => '($n>=0&&$n<=2)&&$n!=2', - ), - 'kab' => - array ( - 0 => '($n>=0&&$n<=2)&&$n!=2', - ), - 'lv' => - array ( - 0 => '$n==0', - 1 => 'fmod($n,10)==1&&fmod($n,100)!=11', - ), - 'iu' => - array ( - 0 => '$n==1', - 1 => '$n==2', - ), - 'kw' => - array ( - 0 => '$n==1', - 1 => '$n==2', - ), - 'naq' => - array ( - 0 => '$n==1', - 1 => '$n==2', - ), - 'se' => - array ( - 0 => '$n==1', - 1 => '$n==2', - ), - 'sma' => - array ( - 0 => '$n==1', - 1 => '$n==2', - ), - 'smi' => - array ( - 0 => '$n==1', - 1 => '$n==2', - ), - 'smj' => - array ( - 0 => '$n==1', - 1 => '$n==2', - ), - 'smn' => - array ( - 0 => '$n==1', - 1 => '$n==2', - ), - 'sms' => - array ( - 0 => '$n==1', - 1 => '$n==2', - ), - 'ga' => - array ( - 0 => '$n==1', - 1 => '$n==2', - 2 => 'in_array($n,array(3,4,5,6))', - 3 => 'in_array($n,array(7,8,9,10))', - ), - 'ro' => - array ( - 0 => '$n==1', - 1 => '$n==0||$n!=1&&in_array(fmod($n,100),range(1,19))', - ), - 'mo' => - array ( - 0 => '$n==1', - 1 => '$n==0||$n!=1&&in_array(fmod($n,100),range(1,19))', - ), - 'lt' => - array ( - 0 => 'fmod($n,10)==1&&!in_array(fmod($n,100),range(11,19))', - 1 => 'in_array(fmod($n,10),range(2,9))&&!in_array(fmod($n,100),range(11,19))', - ), - 'be' => - array ( - 0 => 'fmod($n,10)==1&&fmod($n,100)!=11', - 1 => 'in_array(fmod($n,10),array(2,3,4))&&!in_array(fmod($n,100),array(12,13,14))', - 2 => 'fmod($n,10)==0||in_array(fmod($n,10),array(5,6,7,8,9))||in_array(fmod($n,100),array(11,12,13,14))', - ), - 'bs' => - array ( - 0 => 'fmod($n,10)==1&&fmod($n,100)!=11', - 1 => 'in_array(fmod($n,10),array(2,3,4))&&!in_array(fmod($n,100),array(12,13,14))', - 2 => 'fmod($n,10)==0||in_array(fmod($n,10),array(5,6,7,8,9))||in_array(fmod($n,100),array(11,12,13,14))', - ), - 'hr' => - array ( - 0 => 'fmod($n,10)==1&&fmod($n,100)!=11', - 1 => 'in_array(fmod($n,10),array(2,3,4))&&!in_array(fmod($n,100),array(12,13,14))', - 2 => 'fmod($n,10)==0||in_array(fmod($n,10),array(5,6,7,8,9))||in_array(fmod($n,100),array(11,12,13,14))', - ), - 'ru' => - array ( - 0 => 'fmod($n,10)==1&&fmod($n,100)!=11', - 1 => 'in_array(fmod($n,10),array(2,3,4))&&!in_array(fmod($n,100),array(12,13,14))', - 2 => 'fmod($n,10)==0||in_array(fmod($n,10),array(5,6,7,8,9))||in_array(fmod($n,100),array(11,12,13,14))', - ), - 'sh' => - array ( - 0 => 'fmod($n,10)==1&&fmod($n,100)!=11', - 1 => 'in_array(fmod($n,10),array(2,3,4))&&!in_array(fmod($n,100),array(12,13,14))', - 2 => 'fmod($n,10)==0||in_array(fmod($n,10),array(5,6,7,8,9))||in_array(fmod($n,100),array(11,12,13,14))', - ), - 'sr' => - array ( - 0 => 'fmod($n,10)==1&&fmod($n,100)!=11', - 1 => 'in_array(fmod($n,10),array(2,3,4))&&!in_array(fmod($n,100),array(12,13,14))', - 2 => 'fmod($n,10)==0||in_array(fmod($n,10),array(5,6,7,8,9))||in_array(fmod($n,100),array(11,12,13,14))', - ), - 'uk' => - array ( - 0 => 'fmod($n,10)==1&&fmod($n,100)!=11', - 1 => 'in_array(fmod($n,10),array(2,3,4))&&!in_array(fmod($n,100),array(12,13,14))', - 2 => 'fmod($n,10)==0||in_array(fmod($n,10),array(5,6,7,8,9))||in_array(fmod($n,100),array(11,12,13,14))', - ), - 'cs' => - array ( - 0 => '$n==1', - 1 => 'in_array($n,array(2,3,4))', - ), - 'sk' => - array ( - 0 => '$n==1', - 1 => 'in_array($n,array(2,3,4))', - ), - 'pl' => - array ( - 0 => '$n==1', - 1 => 'in_array(fmod($n,10),array(2,3,4))&&!in_array(fmod($n,100),array(12,13,14))', - 2 => '$n!=1&&in_array(fmod($n,10),array(0,1))||in_array(fmod($n,10),array(5,6,7,8,9))||in_array(fmod($n,100),array(12,13,14))', - ), - 'sl' => - array ( - 0 => 'fmod($n,100)==1', - 1 => 'fmod($n,100)==2', - 2 => 'in_array(fmod($n,100),array(3,4))', - ), - 'mt' => - array ( - 0 => '$n==1', - 1 => '$n==0||in_array(fmod($n,100),range(2,10))', - 2 => 'in_array(fmod($n,100),range(11,19))', - ), - 'mk' => - array ( - 0 => 'fmod($n,10)==1&&$n!=11', - ), - 'cy' => - array ( - 0 => '$n==0', - 1 => '$n==1', - 2 => '$n==2', - 3 => '$n==3', - 4 => '$n==6', - ), - 'lag' => - array ( - 0 => '$n==0', - 1 => '($n>=0&&$n<=2)&&$n!=0&&$n!=2', - ), - 'shi' => - array ( - 0 => '($n>=0&&$n<=1)', - 1 => 'in_array($n,range(2,10))', - ), - 'br' => - array ( - 0 => 'fmod($n,10)==1&&!in_array(fmod($n,100),array(11,71,91))', - 1 => 'fmod($n,10)==2&&!in_array(fmod($n,100),array(12,72,92))', - 2 => 'in_array(fmod($n,10),array(3,4,9))&&!in_array(fmod($n,100),array_merge(range(10,19),range(70,79),range(90,99)))', - 3 => 'fmod($n,1000000)==0&&$n!=0', - ), - 'ksh' => - array ( - 0 => '$n==0', - 1 => '$n==1', - ), - 'tzm' => - array ( - 0 => '($n==0||$n==1)||in_array($n,range(11,99))', - ), - 'gv' => - array ( - 0 => 'in_array(fmod($n,10),array(1,2))||fmod($n,20)==0', - ), -); diff --git a/framework/yii/i18n/data/plurals.xml b/framework/yii/i18n/data/plurals.xml deleted file mode 100644 index 9227dc6..0000000 --- a/framework/yii/i18n/data/plurals.xml +++ /dev/null @@ -1,109 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" ?> -<!DOCTYPE supplementalData SYSTEM "../../common/dtd/ldmlSupplemental.dtd"> -<supplementalData> - <version number="$Revision: 6008 $"/> - <generation date="$Date: 2011-07-12 13:18:01 -0500 (Tue, 12 Jul 2011) $"/> - <plurals> - <!-- if locale is known to have no plurals, there are no rules --> - <pluralRules locales="az bm bo dz fa id ig ii hu ja jv ka kde kea km kn ko lo ms my sah ses sg th to tr vi wo yo zh"/> - <pluralRules locales="ar"> - <pluralRule count="zero">n is 0</pluralRule> - <pluralRule count="one">n is 1</pluralRule> - <pluralRule count="two">n is 2</pluralRule> - <pluralRule count="few">n mod 100 in 3..10</pluralRule> - <pluralRule count="many">n mod 100 in 11..99</pluralRule> - </pluralRules> - <pluralRules locales="asa af bem bez bg bn brx ca cgg chr da de dv ee el en eo es et eu fi fo fur fy gl gsw gu ha haw he is it jmc kaj kcg kk kl ksb ku lb lg mas ml mn mr nah nb nd ne nl nn no nr ny nyn om or pa pap ps pt rof rm rwk saq seh sn so sq ss ssy st sv sw syr ta te teo tig tk tn ts ur wae ve vun xh xog zu"> - <pluralRule count="one">n is 1</pluralRule> - </pluralRules> - <pluralRules locales="ak am bh fil tl guw hi ln mg nso ti wa"> - <pluralRule count="one">n in 0..1</pluralRule> - </pluralRules> - <pluralRules locales="ff fr kab"> - <pluralRule count="one">n within 0..2 and n is not 2</pluralRule> - </pluralRules> - <pluralRules locales="lv"> - <pluralRule count="zero">n is 0</pluralRule> - <pluralRule count="one">n mod 10 is 1 and n mod 100 is not 11</pluralRule> - </pluralRules> - <pluralRules locales="iu kw naq se sma smi smj smn sms"> - <pluralRule count="one">n is 1</pluralRule> - <pluralRule count="two">n is 2</pluralRule> - </pluralRules> - <pluralRules locales="ga"> <!-- http://unicode.org/cldr/trac/ticket/3915 --> - <pluralRule count="one">n is 1</pluralRule> - <pluralRule count="two">n is 2</pluralRule> - <pluralRule count="few">n in 3..6</pluralRule> - <pluralRule count="many">n in 7..10</pluralRule> - </pluralRules> - <pluralRules locales="ro mo"> - <pluralRule count="one">n is 1</pluralRule> - <pluralRule count="few">n is 0 OR n is not 1 AND n mod 100 in 1..19</pluralRule> - </pluralRules> - <pluralRules locales="lt"> - <pluralRule count="one">n mod 10 is 1 and n mod 100 not in 11..19</pluralRule> - <pluralRule count="few">n mod 10 in 2..9 and n mod 100 not in 11..19</pluralRule> - </pluralRules> - <pluralRules locales="be bs hr ru sh sr uk"> - <pluralRule count="one">n mod 10 is 1 and n mod 100 is not 11</pluralRule> - <pluralRule count="few">n mod 10 in 2..4 and n mod 100 not in 12..14</pluralRule> - <pluralRule count="many">n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14</pluralRule> - <!-- others are fractions --> - </pluralRules> - <pluralRules locales="cs sk"> - <pluralRule count="one">n is 1</pluralRule> - <pluralRule count="few">n in 2..4</pluralRule> - </pluralRules> - <pluralRules locales="pl"> - <pluralRule count="one">n is 1</pluralRule> - <pluralRule count="few">n mod 10 in 2..4 and n mod 100 not in 12..14</pluralRule> - <pluralRule count="many">n is not 1 and n mod 10 in 0..1 or n mod 10 in 5..9 or n mod 100 in 12..14</pluralRule> - <!-- others are fractions --> - <!-- and n mod 100 not in 22..24 from Tamplin --> - </pluralRules> - <pluralRules locales="sl"> - <pluralRule count="one">n mod 100 is 1</pluralRule> - <pluralRule count="two">n mod 100 is 2</pluralRule> - <pluralRule count="few">n mod 100 in 3..4</pluralRule> - </pluralRules> - <pluralRules locales="mt"> <!-- from Tamplin's data --> - <pluralRule count="one">n is 1</pluralRule> - <pluralRule count="few">n is 0 or n mod 100 in 2..10</pluralRule> - <pluralRule count="many">n mod 100 in 11..19</pluralRule> - </pluralRules> - <pluralRules locales="mk"> <!-- from Tamplin's data --> - <pluralRule count="one">n mod 10 is 1 and n is not 11</pluralRule> - </pluralRules> - <pluralRules locales="cy"> <!-- from http://www.saltcymru.org/wordpress/?p=99&lang=en --> - <pluralRule count="zero">n is 0</pluralRule> - <pluralRule count="one">n is 1</pluralRule> - <pluralRule count="two">n is 2</pluralRule> - <pluralRule count="few">n is 3</pluralRule> - <pluralRule count="many">n is 6</pluralRule> - </pluralRules> - <pluralRules locales="lag"> - <pluralRule count="zero">n is 0</pluralRule> - <pluralRule count="one">n within 0..2 and n is not 0 and n is not 2</pluralRule> - </pluralRules> - <pluralRules locales="shi"> - <pluralRule count="one">n within 0..1</pluralRule> - <pluralRule count="few">n in 2..10</pluralRule> - </pluralRules> - <pluralRules locales="br"> <!-- from http://unicode.org/cldr/trac/ticket/2886 --> - <pluralRule count="one">n mod 10 is 1 and n mod 100 not in 11,71,91</pluralRule> - <pluralRule count="two">n mod 10 is 2 and n mod 100 not in 12,72,92</pluralRule> - <pluralRule count="few">n mod 10 in 3..4,9 and n mod 100 not in 10..19,70..79,90..99</pluralRule> - <pluralRule count="many">n mod 1000000 is 0 and n is not 0</pluralRule> - </pluralRules> - <pluralRules locales="ksh"> - <pluralRule count="zero">n is 0</pluralRule> - <pluralRule count="one">n is 1</pluralRule> - </pluralRules> - <pluralRules locales="tzm"> - <pluralRule count="one">n in 0..1 or n in 11..99</pluralRule> - </pluralRules> - <pluralRules locales="gv"> - <pluralRule count="one">n mod 10 in 1..2 or n mod 20 is 0</pluralRule> - </pluralRules> - </plurals> -</supplementalData> diff --git a/framework/yii/log/DbTarget.php b/framework/yii/log/DbTarget.php index a828a72..b649e9e 100644 --- a/framework/yii/log/DbTarget.php +++ b/framework/yii/log/DbTarget.php @@ -80,12 +80,12 @@ class DbTarget extends Target VALUES (:level, :category, :log_time, :message)"; $command = $this->db->createCommand($sql); foreach ($this->messages as $message) { - $command->bindValues(array( + $command->bindValues([ ':level' => $message[1], ':category' => $message[2], ':log_time' => $message[3], ':message' => $message[0], - ))->execute(); + ])->execute(); } } } diff --git a/framework/yii/log/EmailTarget.php b/framework/yii/log/EmailTarget.php index 8ae1a88..8ca3d97 100644 --- a/framework/yii/log/EmailTarget.php +++ b/framework/yii/log/EmailTarget.php @@ -22,7 +22,7 @@ class EmailTarget extends Target /** * @var array list of destination email addresses. */ - public $emails = array(); + public $emails = []; /** * @var string email subject */ @@ -34,7 +34,7 @@ class EmailTarget extends Target /** * @var array list of additional headers to use when sending an email. */ - public $headers = array(); + public $headers = []; /** * Sends log messages to specified email addresses. diff --git a/framework/yii/log/FileTarget.php b/framework/yii/log/FileTarget.php index 970c71b..5aa4c12 100644 --- a/framework/yii/log/FileTarget.php +++ b/framework/yii/log/FileTarget.php @@ -78,7 +78,7 @@ class FileTarget extends Target } /** - * Sends log messages to specified email addresses. + * Writes log messages to a file. * @throws InvalidConfigException if unable to open the log file for writing */ public function export() diff --git a/framework/yii/log/Logger.php b/framework/yii/log/Logger.php index 54f3a49..f79c40f 100644 --- a/framework/yii/log/Logger.php +++ b/framework/yii/log/Logger.php @@ -31,24 +31,24 @@ use yii\base\InvalidConfigException; * You may configure the targets in application configuration, like the following: * * ~~~ - * array( - * 'components' => array( - * 'log' => array( - * 'targets' => array( - * 'file' => array( + * [ + * 'components' => [ + * 'log' => [ + * 'targets' => [ + * 'file' => [ * 'class' => 'yii\log\FileTarget', - * 'levels' => array('trace', 'info'), - * 'categories' => array('yii\*'), - * ), - * 'email' => array( + * 'levels' => ['trace', 'info'], + * 'categories' => ['yii\*'], + * ], + * 'email' => [ * 'class' => 'yii\log\EmailTarget', - * 'levels' => array('error', 'warning'), - * 'emails' => array('admin@example.com'), - * ), - * ), - * ), - * ), - * ) + * 'levels' => ['error', 'warning'], + * 'emails' => ['admin@example.com'], + * ], + * ], + * ], + * ], + * ] * ~~~ * * Each log target can have a name and can be referenced via the [[targets]] property @@ -65,8 +65,8 @@ use yii\base\InvalidConfigException; * second element the total time spent in SQL execution. This property is read-only. * @property float $elapsedTime The total elapsed time in seconds for current request. This property is * read-only. - * @property array $profiling The profiling results. Each array element has the following structure: - * `array($token, $category, $time)`. This property is read-only. + * @property array $profiling The profiling results. Each array element has the following structure: `[$token, + * $category, $time]`. This property is read-only. * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 @@ -113,26 +113,26 @@ class Logger extends Component * Each log message is of the following structure: * * ~~~ - * array( + * [ * [0] => message (mixed, can be a string or some complex data, such as an exception object) * [1] => level (integer) * [2] => category (string) * [3] => timestamp (float, obtained by microtime(true)) * [4] => traces (array, debug backtrace, contains the application code call stacks) - * ) + * ] * ~~~ */ - public $messages = array(); + public $messages = []; /** * @var array debug data. This property stores various types of debug data reported at * different instrument places. */ - public $data = array(); + public $data = []; /** * @var array|Target[] the log targets. Each array element represents a single [[Target|log target]] instance * or the configuration for creating the log target instance. */ - public $targets = array(); + public $targets = []; /** * @var integer how many messages should be logged before they are flushed from memory and sent to targets. * Defaults to 1000, meaning the [[flush]] method will be invoked once every 1000 messages logged. @@ -164,7 +164,7 @@ class Logger extends Component $this->targets[$name] = Yii::createObject($target); } } - register_shutdown_function(array($this, 'flush'), true); + register_shutdown_function([$this, 'flush'], true); } /** @@ -180,7 +180,7 @@ class Logger extends Component public function log($message, $level, $category = 'application') { $time = microtime(true); - $traces = array(); + $traces = []; if ($this->traceLevel > 0) { $count = 0; $ts = debug_backtrace(); @@ -195,7 +195,7 @@ class Logger extends Component } } } - $this->messages[] = array($message, $level, $category, $time, $traces); + $this->messages[] = [$message, $level, $category, $time, $traces]; if ($this->flushInterval > 0 && count($this->messages) >= $this->flushInterval) { $this->flush(); } @@ -213,14 +213,14 @@ class Logger extends Component $target->collect($this->messages, $final); } } - $this->messages = array(); + $this->messages = []; } /** * Returns the total elapsed time since the start of the current request. * This method calculates the difference between now and the timestamp * defined by constant `YII_BEGIN_TIME` which is evaluated at the beginning - * of [[YiiBase]] class file. + * of [[BaseYii]] class file. * @return float the total elapsed time in seconds for current request. */ public function getElapsedTime() @@ -241,9 +241,9 @@ class Logger extends Component * such as 'yii\db\Connection'. * @param array $excludeCategories list of categories that you want to exclude * @return array the profiling results. Each array element has the following structure: - * `array($token, $category, $time)`. + * `[$token, $category, $time]`. */ - public function getProfiling($categories = array(), $excludeCategories = array()) + public function getProfiling($categories = [], $excludeCategories = []) { $timings = $this->calculateTimings(); if (empty($categories) && empty($excludeCategories)) { @@ -288,27 +288,26 @@ class Logger extends Component */ public function getDbProfiling() { - $timings = $this->getProfiling(array('yii\db\Command::query', 'yii\db\Command::execute')); + $timings = $this->getProfiling(['yii\db\Command::query', 'yii\db\Command::execute']); $count = count($timings); $time = 0; foreach ($timings as $timing) { $time += $timing[1]; } - return array($count, $time); + return [$count, $time]; } private function calculateTimings() { - $timings = array(); - - $stack = array(); + $timings = []; + $stack = []; foreach ($this->messages as $log) { list($token, $level, $category, $timestamp) = $log; if ($level == self::LEVEL_PROFILE_BEGIN) { $stack[] = $log; } elseif ($level == self::LEVEL_PROFILE_END) { if (($last = array_pop($stack)) !== null && $last[0] === $token) { - $timings[] = array($token, $category, $timestamp - $last[3]); + $timings[] = [$token, $category, $timestamp - $last[3]]; } else { throw new InvalidConfigException("Unmatched profiling block: $token"); } @@ -318,7 +317,7 @@ class Logger extends Component $now = microtime(true); while (($last = array_pop($stack)) !== null) { $delta = $now - $last[3]; - $timings[] = array($last[0], $last[2], $delta); + $timings[] = [$last[0], $last[2], $delta]; } return $timings; @@ -331,14 +330,14 @@ class Logger extends Component */ public static function getLevelName($level) { - static $levels = array( + static $levels = [ self::LEVEL_ERROR => 'error', self::LEVEL_WARNING => 'warning', self::LEVEL_INFO => 'info', self::LEVEL_TRACE => 'trace', self::LEVEL_PROFILE_BEGIN => 'profile begin', self::LEVEL_PROFILE_END => 'profile end', - ); + ]; return isset($levels[$level]) ? $levels[$level] : 'unknown'; } } diff --git a/framework/yii/log/Target.php b/framework/yii/log/Target.php index 0cb72ef..c9d5d97 100644 --- a/framework/yii/log/Target.php +++ b/framework/yii/log/Target.php @@ -41,7 +41,7 @@ abstract class Target extends Component * match those categories sharing the same common prefix. For example, 'yii\db\*' will match * categories starting with 'yii\db\', such as 'yii\db\Connection'. */ - public $categories = array(); + public $categories = []; /** * @var array list of message categories that this target is NOT interested in. Defaults to empty, meaning no uninteresting messages. * If this property is not empty, then any category listed here will be excluded from [[categories]]. @@ -50,7 +50,7 @@ abstract class Target extends Component * categories starting with 'yii\db\', such as 'yii\db\Connection'. * @see categories */ - public $except = array(); + public $except = []; /** * @var boolean whether to log a message containing the current user name and ID. Defaults to false. * @see \yii\web\User @@ -59,9 +59,9 @@ abstract class Target extends Component /** * @var array list of the PHP predefined variables that should be logged in a message. * Note that a variable must be accessible via `$GLOBALS`. Otherwise it won't be logged. - * Defaults to `array('_GET', '_POST', '_FILES', '_COOKIE', '_SESSION', '_SERVER')`. + * Defaults to `['_GET', '_POST', '_FILES', '_COOKIE', '_SESSION', '_SERVER']`. */ - public $logVars = array('_GET', '_POST', '_FILES', '_COOKIE', '_SESSION', '_SERVER'); + public $logVars = ['_GET', '_POST', '_FILES', '_COOKIE', '_SESSION', '_SERVER']; /** * @var integer how many messages should be accumulated before they are exported. * Defaults to 1000. Note that messages will always be exported when the application terminates. @@ -72,7 +72,7 @@ abstract class Target extends Component * @var array the messages that are retrieved from the logger so far by this log target. * Please refer to [[Logger::messages]] for the details about the message structure. */ - public $messages = array(); + public $messages = []; private $_levels = 0; @@ -96,10 +96,10 @@ abstract class Target extends Component $count = count($this->messages); if ($count > 0 && ($final || $this->exportInterval > 0 && $count >= $this->exportInterval)) { if (($context = $this->getContextMessage()) !== '') { - $this->messages[] = array($context, Logger::LEVEL_INFO, 'application', YII_BEGIN_TIME); + $this->messages[] = [$context, Logger::LEVEL_INFO, 'application', YII_BEGIN_TIME]; } $this->export(); - $this->messages = array(); + $this->messages = []; } } @@ -110,9 +110,9 @@ abstract class Target extends Component */ protected function getContextMessage() { - $context = array(); + $context = []; if ($this->logUser && ($user = Yii::$app->getComponent('user', false)) !== null) { - /** @var $user \yii\web\User */ + /** @var \yii\web\User $user */ $context[] = 'User: ' . $user->getId(); } @@ -146,7 +146,7 @@ abstract class Target extends Component * For example, * * ~~~ - * array('error', 'warning') + * ['error', 'warning'] * // which is equivalent to: * Logger::LEVEL_ERROR | Logger::LEVEL_WARNING * ~~~ @@ -156,13 +156,13 @@ abstract class Target extends Component */ public function setLevels($levels) { - static $levelMap = array( + static $levelMap = [ 'error' => Logger::LEVEL_ERROR, 'warning' => Logger::LEVEL_WARNING, 'info' => Logger::LEVEL_INFO, 'trace' => Logger::LEVEL_TRACE, 'profile' => Logger::LEVEL_PROFILE, - ); + ]; if (is_array($levels)) { $this->_levels = 0; foreach ($levels as $level) { @@ -186,7 +186,7 @@ abstract class Target extends Component * @param array $except the message categories to exclude. If empty, it means all categories are allowed. * @return array the filtered messages. */ - public static function filterMessages($messages, $levels = 0, $categories = array(), $except = array()) + public static function filterMessages($messages, $levels = 0, $categories = [], $except = []) { foreach ($messages as $i => $message) { if ($levels && !($levels & $message[1])) { diff --git a/framework/yii/mail/BaseMailer.php b/framework/yii/mail/BaseMailer.php new file mode 100644 index 0000000..90565c9 --- /dev/null +++ b/framework/yii/mail/BaseMailer.php @@ -0,0 +1,300 @@ +<?php +/** + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\mail; + +use Yii; +use yii\base\Component; +use yii\base\InvalidConfigException; +use yii\base\ViewContextInterface; +use yii\web\View; + +/** + * BaseMailer serves as a base class that implements the basic functions required by [[MailerInterface]]. + * + * Concrete child classes should may focus on implementing the [[sendMessage()]] method. + * + * @see BaseMessage + * + * @property View $view View instance. Note that the type of this property differs in getter and setter. See + * [[getView()]] and [[setView()]] for details. + * + * @author Paul Klimov <klimov.paul@gmail.com> + * @since 2.0 + */ +abstract class BaseMailer extends Component implements MailerInterface, ViewContextInterface +{ + /** + * @var string directory containing view files for this email messages. + * This can be specified as an absolute path or path alias. + */ + public $viewPath = '@app/mails'; + /** + * @var string|boolean HTML layout view name. This is the layout used to render HTML mail body. + * The property can take the following values: + * + * - a relative view name: a view file relative to [[viewPath]], e.g., 'layouts/html'. + * - a path alias: an absolute view file path specified as a path alias, e.g., '@app/mails/html'. + * - a boolean false: the layout is disabled. + */ + public $htmlLayout = 'layouts/html'; + /** + * @var string|boolean text layout view name. This is the layout used to render TEXT mail body. + * Please refer to [[htmlLayout]] for possible values that this property can take. + */ + public $textLayout = 'layouts/text'; + /** + * @var array the configuration that should be applied to any newly created + * email message instance by [[createMessage()]] or [[compose()]]. Any valid property defined + * by [[MessageInterface]] can be configured, such as `from`, `to`, `subject`, `textBody`, `htmlBody`, etc. + * + * For example: + * + * ~~~ + * [ + * 'charset' => 'UTF-8', + * 'from' => 'noreply@mydomain.com', + * 'bcc' => 'developer@mydomain.com', + * ] + * ~~~ + */ + public $messageConfig = []; + /** + * @var string the default class name of the new message instances created by [[createMessage()]] + */ + public $messageClass = 'yii\mail\BaseMessage'; + /** + * @var boolean whether to save email messages as files under [[fileTransportPath]] instead of sending them + * to the actual recipients. This is usually used during development for debugging purpose. + * @see fileTransportPath + */ + public $useFileTransport = false; + /** + * @var string the directory where the email messages are saved when [[useFileTransport]] is true. + */ + public $fileTransportPath = '@runtime/mail'; + /** + * @var callback a PHP callback that will be called by [[send()]] when [[useFileTransport]] is true. + * The callback should return a file name which will be used to save the email message. + * If not set, the file name will be generated based on the current timestamp. + * + * The signature of the callback is: + * + * ~~~ + * function ($mailer, $message) + * ~~~ + */ + public $fileTransportCallback; + + /** + * @var \yii\base\View|array view instance or its array configuration. + */ + private $_view = []; + + /** + * @param array|View $view view instance or its array configuration that will be used to + * render message bodies. + * @throws InvalidConfigException on invalid argument. + */ + public function setView($view) + { + if (!is_array($view) && !is_object($view)) { + throw new InvalidConfigException('"' . get_class($this) . '::view" should be either object or configuration array, "' . gettype($view) . '" given.'); + } + $this->_view = $view; + } + + /** + * @return View view instance. + */ + public function getView() + { + if (!is_object($this->_view)) { + $this->_view = $this->createView($this->_view); + } + return $this->_view; + } + + /** + * Creates view instance from given configuration. + * @param array $config view configuration. + * @return View view instance. + */ + protected function createView(array $config) + { + if (!array_key_exists('class', $config)) { + $config['class'] = View::className(); + } + return Yii::createObject($config); + } + + /** + * Creates a new message instance and optionally composes its body content via view rendering. + * + * @param string|array $view the view to be used for rendering the message body. This can be: + * + * - a string, which represents the view name or path alias for rendering the HTML body of the email. + * In this case, the text body will be generated by applying `strip_tags()` to the HTML body. + * - an array with 'html' and/or 'text' elements. The 'html' element refers to the view name or path alias + * for rendering the HTML body, while 'text' element is for rendering the text body. For example, + * `['html' => 'contact-html', 'text' => 'contact-text']`. + * - null, meaning the message instance will be returned without body content. + * + * The view to be rendered can be specified in one of the following formats: + * + * - path alias (e.g. "@app/mails/contact"); + * - a relative view name (e.g. "contact"): the actual view file will be resolved by [[findViewFile()]] + * + * @param array $params the parameters (name-value pairs) that will be extracted and made available in the view file. + * @return MessageInterface message instance. + */ + public function compose($view = null, array $params = []) + { + $message = $this->createMessage(); + if ($view !== null) { + $params['message'] = $message; + if (is_array($view)) { + if (isset($view['html'])) { + $html = $this->render($view['html'], $params, $this->htmlLayout); + } + if (isset($view['text'])) { + $text = $this->render($view['text'], $params, $this->textLayout); + } + } else { + $html = $this->render($view, $params, $this->htmlLayout); + } + if (isset($html)) { + $message->setHtmlBody($html); + } + if (isset($text)) { + $message->setTextBody($text); + } elseif (isset($html)) { + $message->setTextBody(strip_tags($html)); + } + } + return $message; + } + + /** + * Creates a new message instance. + * The newly created instance will be initialized with the configuration specified by [[messageConfig]]. + * If the configuration does not specify a 'class', the [[messageClass]] will be used as the class + * of the new message instance. + * @return MessageInterface message instance. + */ + protected function createMessage() + { + $config = $this->messageConfig; + if (!array_key_exists('class', $config)) { + $config['class'] = $this->messageClass; + } + return Yii::createObject($config); + } + + /** + * Sends the given email message. + * This method will log a message about the email being sent. + * If [[useFileTransport]] is true, it will save the email as a file under [[fileTransportPath]]. + * Otherwise, it will call [[sendMessage()]] to send the email to its recipient(s). + * Child classes should implement [[sendMessage()]] with the actual email sending logic. + * @param MessageInterface $message email message instance to be sent + * @return boolean whether the message has been sent successfully + */ + public function send($message) + { + $address = $message->getTo(); + if (is_array($address)) { + $address = implode(', ', array_keys($address)); + } + Yii::info('Sending email "' . $message->getSubject() . '" to "' . $address . '"', __METHOD__); + + if ($this->useFileTransport) { + return $this->saveMessage($message); + } else { + return $this->sendMessage($message); + } + } + + /** + * Sends multiple messages at once. + * + * The default implementation simply calls [[send()]] multiple times. + * Child classes may override this method to implement more efficient way of + * sending multiple messages. + * + * @param array $messages list of email messages, which should be sent. + * @return integer number of messages that are successfully sent. + */ + public function sendMultiple(array $messages) + { + $successCount = 0; + foreach ($messages as $message) { + if ($this->send($message)) { + $successCount++; + } + } + return $successCount; + } + + /** + * Renders the specified view with optional parameters and layout. + * The view will be rendered using the [[view]] component. + * @param string $view the view name or the path alias of the view file. + * @param array $params the parameters (name-value pairs) that will be extracted and made available in the view file. + * @param string|boolean $layout layout view name or path alias. If false, no layout will be applied. + * @return string the rendering result. + */ + public function render($view, $params = [], $layout = false) + { + $output = $this->getView()->render($view, $params, $this); + if ($layout !== false) { + return $this->getView()->render($layout, ['content' => $output], $this); + } else { + return $output; + } + } + + /** + * Sends the specified message. + * This method should be implemented by child classes with the actual email sending logic. + * @param MessageInterface $message the message to be sent + * @return boolean whether the message is sent successfully + */ + abstract protected function sendMessage($message); + + /** + * Saves the message as a file under [[fileTransportPath]]. + * @param MessageInterface $message + * @return boolean whether the message is saved successfully + */ + protected function saveMessage($message) + { + $path = Yii::getAlias($this->fileTransportPath); + if (!is_dir(($path))) { + mkdir($path, 0777, true); + } + if ($this->fileTransportCallback !== null) { + $file = $path . '/' . call_user_func($this->fileTransportCallback, $this, $message); + } else { + $time = microtime(true); + $file = $path . '/' . date('Ymd-His-', $time) . sprintf('%04d', (int)(($time - (int)$time) * 10000)) . '-' . sprintf('%04d', mt_rand(0, 10000)) . '.eml'; + } + file_put_contents($file, $message->toString()); + return true; + } + + /** + * Finds the view file corresponding to the specified relative view name. + * This method will return the view file by prefixing the view name with [[viewPath]]. + * @param string $view a relative view name. The name does NOT start with a slash. + * @return string the view file path. Note that the file may not exist. + */ + public function findViewFile($view) + { + return Yii::getAlias($this->viewPath) . DIRECTORY_SEPARATOR . $view; + } +} diff --git a/framework/yii/mail/BaseMessage.php b/framework/yii/mail/BaseMessage.php new file mode 100644 index 0000000..01b671c --- /dev/null +++ b/framework/yii/mail/BaseMessage.php @@ -0,0 +1,59 @@ +<?php +/** + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\mail; + +use yii\base\Object; +use Yii; + +/** + * BaseMessage serves as a base class that implements the [[send()]] method required by [[MessageInterface]]. + * + * By default, [[send()]] will use the "mail" application component to send the current message. + * The "mail" application component should be a mailer instance implementing [[MailerInterface]]. + * + * @see BaseMailer + * + * @property MailerInterface $mailer The mailer component. This property is read-only. + * + * @author Paul Klimov <klimov.paul@gmail.com> + * @since 2.0 + */ +abstract class BaseMessage extends Object implements MessageInterface +{ + /** + * @return MailerInterface the mailer component + */ + public function getMailer() + { + return Yii::$app->getComponent('mail'); + } + + /** + * @inheritdoc + */ + public function send() + { + return $this->getMailer()->send($this); + } + + /** + * PHP magic method that returns the string representation of this object. + * @return string the string representation of this object. + */ + public function __toString() + { + // __toString cannot throw exception + // use trigger_error to bypass this limitation + try { + return $this->toString(); + } catch (\Exception $e) { + trigger_error($e->getMessage()); + return ''; + } + } +} diff --git a/framework/yii/mail/MailerInterface.php b/framework/yii/mail/MailerInterface.php new file mode 100644 index 0000000..5ee9ccd --- /dev/null +++ b/framework/yii/mail/MailerInterface.php @@ -0,0 +1,64 @@ +<?php +/** + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\mail; + +/** + * MailerInterface is the interface that should be implemented by mailer classes. + * + * A mailer should mainly support creating and sending [[MessageInterface|mail messages]]. It should + * also support composition of the message body through the view rendering mechanism. For example, + * + * ~~~ + * Yii::$app->mail->compose('contact/html', ['contactForm' => $form]) + * ->setFrom('from@domain.com') + * ->setTo($form->email) + * ->setSubject($form->subject) + * ->send(); + * ~~~ + * + * @see MessageInterface + * + * @author Paul Klimov <klimov.paul@gmail.com> + * @since 2.0 + */ +interface MailerInterface +{ + /** + * Creates a new message instance and optionally composes its body content via view rendering. + * + * @param string|array $view the view to be used for rendering the message body. This can be: + * + * - a string, which represents the view name or path alias for rendering the HTML body of the email. + * In this case, the text body will be generated by applying `strip_tags()` to the HTML body. + * - an array with 'html' and/or 'text' elements. The 'html' element refers to the view name or path alias + * for rendering the HTML body, while 'text' element is for rendering the text body. For example, + * `['html' => 'contact-html', 'text' => 'contact-text']`. + * - null, meaning the message instance will be returned without body content. + * + * @param array $params the parameters (name-value pairs) that will be extracted and made available in the view file. + * @return MessageInterface message instance. + */ + public function compose($view = null, array $params = []); + + /** + * Sends the given email message. + * @param MessageInterface $message email message instance to be sent + * @return boolean whether the message has been sent successfully + */ + public function send($message); + + /** + * Sends multiple messages at once. + * + * This method may be implemented by some mailers which support more efficient way of sending multiple messages in the same batch. + * + * @param array $messages list of email messages, which should be sent. + * @return integer number of messages that are successfully sent. + */ + public function sendMultiple(array $messages); +} diff --git a/framework/yii/mail/MessageInterface.php b/framework/yii/mail/MessageInterface.php new file mode 100644 index 0000000..eba9064 --- /dev/null +++ b/framework/yii/mail/MessageInterface.php @@ -0,0 +1,216 @@ +<?php +/** + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\mail; + +/** + * MessageInterface is the interface that should be implemented by mail message classes. + * + * A message represents the settings and content of an email, such as the sender, recipient, + * subject, body, etc. + * + * Messages are sent by a [[MailerInterface||mailer]], like the following, + * + * ~~~ + * Yii::$app->mail->compose() + * ->setFrom('from@domain.com') + * ->setTo($form->email) + * ->setSubject($form->subject) + * ->setTextBody('Plain text content') + * ->setHtmlBody('<b>HTML content</b>') + * ->send(); + * ~~~ + * + * @see MailerInterface + * + * @author Paul Klimov <klimov.paul@gmail.com> + * @since 2.0 + */ +interface MessageInterface +{ + /** + * Returns the character set of this message. + * @return string the character set of this message. + */ + public function getCharset(); + + /** + * Sets the character set of this message. + * @param string $charset character set name. + * @return static self reference. + */ + public function setCharset($charset); + + /** + * Returns the message sender. + * @return string the sender + */ + public function getFrom(); + + /** + * Sets the message sender. + * @param string|array $from sender email address. + * You may pass an array of addresses if this message is from multiple people. + * You may also specify sender name in addition to email address using format: + * `[email => name]`. + * @return static self reference. + */ + public function setFrom($from); + + /** + * Returns the message recipient(s). + * @return array the message recipients + */ + public function getTo(); + + /** + * Sets the message recipient(s). + * @param string|array $to receiver email address. + * You may pass an array of addresses if multiple recipients should receive this message. + * You may also specify receiver name in addition to email address using format: + * `[email => name]`. + * @return static self reference. + */ + public function setTo($to); + + /** + * Returns the reply-to address of this message. + * @return string the reply-to address of this message. + */ + public function getReplyTo(); + + /** + * Sets the reply-to address of this message. + * @param string|array $replyTo the reply-to address. + * You may pass an array of addresses if this message should be replied to multiple people. + * You may also specify reply-to name in addition to email address using format: + * `[email => name]`. + * @return static self reference. + */ + public function setReplyTo($replyTo); + + /** + * Returns the Cc (additional copy receiver) addresses of this message. + * @return array the Cc (additional copy receiver) addresses of this message. + */ + public function getCc(); + + /** + * Sets the Cc (additional copy receiver) addresses of this message. + * @param string|array $cc copy receiver email address. + * You may pass an array of addresses if multiple recipients should receive this message. + * You may also specify receiver name in addition to email address using format: + * `[email => name]`. + * @return static self reference. + */ + public function setCc($cc); + + /** + * Returns the Bcc (hidden copy receiver) addresses of this message. + * @return array the Bcc (hidden copy receiver) addresses of this message. + */ + public function getBcc(); + + /** + * Sets the Bcc (hidden copy receiver) addresses of this message. + * @param string|array $bcc hidden copy receiver email address. + * You may pass an array of addresses if multiple recipients should receive this message. + * You may also specify receiver name in addition to email address using format: + * `[email => name]`. + * @return static self reference. + */ + public function setBcc($bcc); + + /** + * Returns the message subject. + * @return string the message subject + */ + public function getSubject(); + + /** + * Sets the message subject. + * @param string $subject message subject + * @return static self reference. + */ + public function setSubject($subject); + + /** + * Sets message plain text content. + * @param string $text message plain text content. + * @return static self reference. + */ + public function setTextBody($text); + + /** + * Sets message HTML content. + * @param string $html message HTML content. + * @return static self reference. + */ + public function setHtmlBody($html); + + /** + * Attaches existing file to the email message. + * @param string $fileName full file name + * @param array $options options for embed file. Valid options are: + * + * - fileName: name, which should be used to attach file. + * - contentType: attached file MIME type. + * + * @return static self reference. + */ + public function attach($fileName, array $options = []); + + /** + * Attach specified content as file for the email message. + * @param string $content attachment file content. + * @param array $options options for embed file. Valid options are: + * + * - fileName: name, which should be used to attach file. + * - contentType: attached file MIME type. + * + * @return static self reference. + */ + public function attachContent($content, array $options = []); + + /** + * Attach a file and return it's CID source. + * This method should be used when embedding images or other data in a message. + * @param string $fileName file name. + * @param array $options options for embed file. Valid options are: + * + * - fileName: name, which should be used to attach file. + * - contentType: attached file MIME type. + * + * @return string attachment CID. + */ + public function embed($fileName, array $options = []); + + /** + * Attach a content as file and return it's CID source. + * This method should be used when embedding images or other data in a message. + * @param string $content attachment file content. + * @param array $options options for embed file. Valid options are: + * + * - fileName: name, which should be used to attach file. + * - contentType: attached file MIME type. + * + * @return string attachment CID. + */ + public function embedContent($content, array $options = []); + + /** + * Sends this email message. + * @return boolean whether this message is sent successfully. + */ + public function send(); + + /** + * Returns string representation of this message. + * @return string the string representation of this message. + */ + public function toString(); +} diff --git a/framework/yii/messages/config.php b/framework/yii/messages/config.php index e1c79ff..b707abb 100644 --- a/framework/yii/messages/config.php +++ b/framework/yii/messages/config.php @@ -1,13 +1,13 @@ <?php -return array( +return [ // string, required, root directory of all source files 'sourcePath' => __DIR__ . '/..', // string, required, root directory containing message translations. 'messagePath' => __DIR__, // array, required, list of language codes that the extracted messages - // should be translated to. For example, array('zh_cn', 'de'). - 'languages' => array('de'), + // should be translated to. For example, ['zh-CN', 'de']. + 'languages' => ['de'], // string, the name of the function for translating messages. // Defaults to 'Yii::t'. This is used as a mark to find the messages to be // translated. You may use a string for single function name or an array for @@ -29,11 +29,11 @@ return array( // and the '.svn' will match all files and directories whose name ends with '.svn'. // Note, the '/' characters in a pattern matches both '/' and '\'. // If a file/directory matches both a pattern in "only" and "except", it will NOT be processed. - 'only' => array('.php'), + 'only' => ['.php'], // array, list of patterns that specify which files/directories should NOT be processed. // If empty or not set, all files/directories will be processed. // Please refer to "only" for details about the patterns. - 'except' => array( + 'except' => [ '.svn', '.git', '.gitignore', @@ -41,5 +41,5 @@ return array( '.hgignore', '.hgkeep', '/messages', - ), -); + ], +]; diff --git a/extensions/mutex/yii/mutex/DbMutex.php b/framework/yii/mutex/DbMutex.php similarity index 100% rename from extensions/mutex/yii/mutex/DbMutex.php rename to framework/yii/mutex/DbMutex.php diff --git a/extensions/mutex/yii/mutex/FileMutex.php b/framework/yii/mutex/FileMutex.php similarity index 99% rename from extensions/mutex/yii/mutex/FileMutex.php rename to framework/yii/mutex/FileMutex.php index 5ac9262..fd5cb00 100644 --- a/extensions/mutex/yii/mutex/FileMutex.php +++ b/framework/yii/mutex/FileMutex.php @@ -38,7 +38,7 @@ class FileMutex extends Mutex /** * @var resource[] stores all opened lock files. Keys are lock names and values are file handles. */ - private $_files = array(); + private $_files = []; /** diff --git a/extensions/mutex/yii/mutex/Mutex.php b/framework/yii/mutex/Mutex.php similarity index 96% rename from extensions/mutex/yii/mutex/Mutex.php rename to framework/yii/mutex/Mutex.php index 8f0a560..611e725 100644 --- a/extensions/mutex/yii/mutex/Mutex.php +++ b/framework/yii/mutex/Mutex.php @@ -25,7 +25,7 @@ abstract class Mutex extends Component /** * @var string[] names of the locks acquired in the current PHP process. */ - private $_locks = array(); + private $_locks = []; /** @@ -34,11 +34,10 @@ abstract class Mutex extends Component public function init() { if ($this->autoRelease) { - $mutex = $this; $locks = &$this->_locks; - register_shutdown_function(function () use ($mutex, &$locks) { + register_shutdown_function(function () use (&$locks) { foreach ($locks as $lock) { - $mutex->release($lock); + $this->release($lock); } }); } diff --git a/extensions/mutex/yii/mutex/MysqlMutex.php b/framework/yii/mutex/MysqlMutex.php similarity index 94% rename from extensions/mutex/yii/mutex/MysqlMutex.php rename to framework/yii/mutex/MysqlMutex.php index f1c7cd1..af05b9c 100644 --- a/extensions/mutex/yii/mutex/MysqlMutex.php +++ b/framework/yii/mutex/MysqlMutex.php @@ -38,7 +38,7 @@ class MysqlMutex extends Mutex protected function acquireLock($name, $timeout = 0) { return (boolean)$this->db - ->createCommand('SELECT GET_LOCK(:name, :timeout)', array(':name' => $name, ':timeout' => $timeout)) + ->createCommand('SELECT GET_LOCK(:name, :timeout)', [':name' => $name, ':timeout' => $timeout]) ->queryScalar(); } @@ -51,7 +51,7 @@ class MysqlMutex extends Mutex protected function releaseLock($name) { return (boolean)$this->db - ->createCommand('SELECT RELEASE_LOCK(:name)', array(':name' => $name)) + ->createCommand('SELECT RELEASE_LOCK(:name)', [':name' => $name]) ->queryScalar(); } } diff --git a/framework/yii/rbac/DbManager.php b/framework/yii/rbac/DbManager.php index 46c375f..5bbc7ab 100644 --- a/framework/yii/rbac/DbManager.php +++ b/framework/yii/rbac/DbManager.php @@ -79,7 +79,7 @@ class DbManager extends Manager * which holds the value of `$userId`. * @return boolean whether the operations can be performed by the user. */ - public function checkAccess($userId, $itemName, $params = array()) + public function checkAccess($userId, $itemName, $params = []) { $assignments = $this->getAssignments($userId); return $this->checkAccessRecursive($userId, $itemName, $params, $assignments); @@ -117,9 +117,9 @@ class DbManager extends Manager } } $query = new Query; - $parents = $query->select(array('parent')) + $parents = $query->select(['parent']) ->from($this->itemChildTable) - ->where(array('child' => $itemName)) + ->where(['child' => $itemName]) ->createCommand($this->db) ->queryColumn(); foreach ($parents as $parent) { @@ -146,7 +146,7 @@ class DbManager extends Manager } $query = new Query; $rows = $query->from($this->itemTable) - ->where(array('or', 'name=:name1', 'name=:name2'), array(':name1' => $itemName, ':name2' => $childName)) + ->where(['or', 'name=:name1', 'name=:name2'], [':name1' => $itemName, ':name2' => $childName]) ->createCommand($this->db) ->queryAll(); if (count($rows) == 2) { @@ -162,7 +162,7 @@ class DbManager extends Manager throw new InvalidCallException("Cannot add '$childName' as a child of '$itemName'. A loop has been detected."); } $this->db->createCommand() - ->insert($this->itemChildTable, array('parent' => $itemName, 'child' => $childName)) + ->insert($this->itemChildTable, ['parent' => $itemName, 'child' => $childName]) ->execute(); return true; } else { @@ -180,7 +180,7 @@ class DbManager extends Manager public function removeItemChild($itemName, $childName) { return $this->db->createCommand() - ->delete($this->itemChildTable, array('parent' => $itemName, 'child' => $childName)) + ->delete($this->itemChildTable, ['parent' => $itemName, 'child' => $childName]) ->execute() > 0; } @@ -193,9 +193,9 @@ class DbManager extends Manager public function hasItemChild($itemName, $childName) { $query = new Query; - return $query->select(array('parent')) + return $query->select(['parent']) ->from($this->itemChildTable) - ->where(array('parent' => $itemName, 'child' => $childName)) + ->where(['parent' => $itemName, 'child' => $childName]) ->createCommand($this->db) ->queryScalar() !== false; } @@ -209,24 +209,24 @@ class DbManager extends Manager public function getItemChildren($names) { $query = new Query; - $rows = $query->select(array('name', 'type', 'description', 'biz_rule', 'data')) - ->from(array($this->itemTable, $this->itemChildTable)) - ->where(array('parent' => $names, 'name' => new Expression('child'))) + $rows = $query->select(['name', 'type', 'description', 'biz_rule', 'data']) + ->from([$this->itemTable, $this->itemChildTable]) + ->where(['parent' => $names, 'name' => new Expression('child')]) ->createCommand($this->db) ->queryAll(); - $children = array(); + $children = []; foreach ($rows as $row) { if (($data = @unserialize($row['data'])) === false) { $data = null; } - $children[$row['name']] = new Item(array( + $children[$row['name']] = new Item([ 'manager' => $this, 'name' => $row['name'], 'type' => $row['type'], 'description' => $row['description'], 'bizRule' => $row['biz_rule'], 'data' => $data, - )); + ]); } return $children; } @@ -247,20 +247,20 @@ class DbManager extends Manager throw new InvalidParamException("The item '$itemName' does not exist."); } $this->db->createCommand() - ->insert($this->assignmentTable, array( + ->insert($this->assignmentTable, [ 'user_id' => $userId, 'item_name' => $itemName, 'biz_rule' => $bizRule, 'data' => serialize($data), - )) + ]) ->execute(); - return new Assignment(array( + return new Assignment([ 'manager' => $this, 'userId' => $userId, 'itemName' => $itemName, 'bizRule' => $bizRule, 'data' => $data, - )); + ]); } /** @@ -272,7 +272,7 @@ class DbManager extends Manager public function revoke($userId, $itemName) { return $this->db->createCommand() - ->delete($this->assignmentTable, array('user_id' => $userId, 'item_name' => $itemName)) + ->delete($this->assignmentTable, ['user_id' => $userId, 'item_name' => $itemName]) ->execute() > 0; } @@ -285,9 +285,9 @@ class DbManager extends Manager public function isAssigned($userId, $itemName) { $query = new Query; - return $query->select(array('item_name')) + return $query->select(['item_name']) ->from($this->assignmentTable) - ->where(array('user_id' => $userId, 'item_name' => $itemName)) + ->where(['user_id' => $userId, 'item_name' => $itemName]) ->createCommand($this->db) ->queryScalar() !== false; } @@ -303,20 +303,20 @@ class DbManager extends Manager { $query = new Query; $row = $query->from($this->assignmentTable) - ->where(array('user_id' => $userId, 'item_name' => $itemName)) + ->where(['user_id' => $userId, 'item_name' => $itemName]) ->createCommand($this->db) ->queryOne(); if ($row !== false) { if (($data = @unserialize($row['data'])) === false) { $data = null; } - return new Assignment(array( + return new Assignment([ 'manager' => $this, 'userId' => $row['user_id'], 'itemName' => $row['item_name'], 'bizRule' => $row['biz_rule'], 'data' => $data, - )); + ]); } else { return null; } @@ -332,21 +332,21 @@ class DbManager extends Manager { $query = new Query; $rows = $query->from($this->assignmentTable) - ->where(array('user_id' => $userId)) + ->where(['user_id' => $userId]) ->createCommand($this->db) ->queryAll(); - $assignments = array(); + $assignments = []; foreach ($rows as $row) { if (($data = @unserialize($row['data'])) === false) { $data = null; } - $assignments[$row['item_name']] = new Assignment(array( + $assignments[$row['item_name']] = new Assignment([ 'manager' => $this, 'userId' => $row['user_id'], 'itemName' => $row['item_name'], 'bizRule' => $row['biz_rule'], 'data' => $data, - )); + ]); } return $assignments; } @@ -358,13 +358,13 @@ class DbManager extends Manager public function saveAssignment($assignment) { $this->db->createCommand() - ->update($this->assignmentTable, array( + ->update($this->assignmentTable, [ 'biz_rule' => $assignment->bizRule, 'data' => serialize($assignment->data), - ), array( + ], [ 'user_id' => $assignment->userId, 'item_name' => $assignment->itemName, - )) + ]) ->execute(); } @@ -384,32 +384,32 @@ class DbManager extends Manager ->createCommand($this->db); } elseif ($userId === null) { $command = $query->from($this->itemTable) - ->where(array('type' => $type)) + ->where(['type' => $type]) ->createCommand($this->db); } elseif ($type === null) { - $command = $query->select(array('name', 'type', 'description', 't1.biz_rule', 't1.data')) - ->from(array($this->itemTable . ' t1', $this->assignmentTable . ' t2')) - ->where(array('user_id' => $userId, 'name' => new Expression('item_name'))) + $command = $query->select(['name', 'type', 'description', 't1.biz_rule', 't1.data']) + ->from([$this->itemTable . ' t1', $this->assignmentTable . ' t2']) + ->where(['user_id' => $userId, 'name' => new Expression('item_name')]) ->createCommand($this->db); } else { - $command = $query->select('name', 'type', 'description', 't1.biz_rule', 't1.data') - ->from(array($this->itemTable . ' t1', $this->assignmentTable . ' t2')) - ->where(array('user_id' => $userId, 'type' => $type, 'name' => new Expression('item_name'))) + $command = $query->select(['name', 'type', 'description', 't1.biz_rule', 't1.data']) + ->from([$this->itemTable . ' t1', $this->assignmentTable . ' t2']) + ->where(['user_id' => $userId, 'type' => $type, 'name' => new Expression('item_name')]) ->createCommand($this->db); } - $items = array(); + $items = []; foreach ($command->queryAll() as $row) { if (($data = @unserialize($row['data'])) === false) { $data = null; } - $items[$row['name']] = new Item(array( + $items[$row['name']] = new Item([ 'manager' => $this, 'name' => $row['name'], 'type' => $row['type'], 'description' => $row['description'], 'bizRule' => $row['biz_rule'], 'data' => $data, - )); + ]); } return $items; } @@ -432,22 +432,22 @@ class DbManager extends Manager public function createItem($name, $type, $description = '', $bizRule = null, $data = null) { $this->db->createCommand() - ->insert($this->itemTable, array( + ->insert($this->itemTable, [ 'name' => $name, 'type' => $type, 'description' => $description, 'biz_rule' => $bizRule, 'data' => serialize($data), - )) + ]) ->execute(); - return new Item(array( + return new Item([ 'manager' => $this, 'name' => $name, 'type' => $type, 'description' => $description, 'bizRule' => $bizRule, 'data' => $data, - )); + ]); } /** @@ -459,14 +459,14 @@ class DbManager extends Manager { if ($this->usingSqlite()) { $this->db->createCommand() - ->delete($this->itemChildTable, array('or', 'parent=:name', 'child=:name'), array(':name' => $name)) + ->delete($this->itemChildTable, ['or', 'parent=:name', 'child=:name'], [':name' => $name]) ->execute(); $this->db->createCommand() - ->delete($this->assignmentTable, array('item_name' => $name)) + ->delete($this->assignmentTable, ['item_name' => $name]) ->execute(); } return $this->db->createCommand() - ->delete($this->itemTable, array('name' => $name)) + ->delete($this->itemTable, ['name' => $name]) ->execute() > 0; } @@ -479,7 +479,7 @@ class DbManager extends Manager { $query = new Query; $row = $query->from($this->itemTable) - ->where(array('name' => $name)) + ->where(['name' => $name]) ->createCommand($this->db) ->queryOne(); @@ -487,14 +487,14 @@ class DbManager extends Manager if (($data = @unserialize($row['data'])) === false) { $data = null; } - return new Item(array( + return new Item([ 'manager' => $this, 'name' => $row['name'], 'type' => $row['type'], 'description' => $row['description'], 'bizRule' => $row['biz_rule'], 'data' => $data, - )); + ]); } else { return null; } @@ -509,26 +509,26 @@ class DbManager extends Manager { if ($this->usingSqlite() && $oldName !== null && $item->getName() !== $oldName) { $this->db->createCommand() - ->update($this->itemChildTable, array('parent' => $item->getName()), array('parent' => $oldName)) + ->update($this->itemChildTable, ['parent' => $item->getName()], ['parent' => $oldName]) ->execute(); $this->db->createCommand() - ->update($this->itemChildTable, array('child' => $item->getName()), array('child' => $oldName)) + ->update($this->itemChildTable, ['child' => $item->getName()], ['child' => $oldName]) ->execute(); $this->db->createCommand() - ->update($this->assignmentTable, array('item_name' => $item->getName()), array('item_name' => $oldName)) + ->update($this->assignmentTable, ['item_name' => $item->getName()], ['item_name' => $oldName]) ->execute(); } $this->db->createCommand() - ->update($this->itemTable, array( + ->update($this->itemTable, [ 'name' => $item->getName(), 'type' => $item->type, 'description' => $item->description, 'biz_rule' => $item->bizRule, 'data' => serialize($item->data), - ), array( + ], [ 'name' => $oldName === null ? $item->getName() : $oldName, - )) + ]) ->execute(); } diff --git a/framework/yii/rbac/Item.php b/framework/yii/rbac/Item.php index f7930c1..ae9214e 100644 --- a/framework/yii/rbac/Item.php +++ b/framework/yii/rbac/Item.php @@ -64,7 +64,7 @@ class Item extends Object * @param array $params the parameters to be passed to business rule evaluation * @return boolean whether the specified item is within the hierarchy starting from this item. */ - public function checkAccess($itemName, $params = array()) + public function checkAccess($itemName, $params = []) { Yii::trace('Checking permission: ' . $this->_name, __METHOD__); if ($this->manager->executeBizRule($this->bizRule, $params, $this->data)) { diff --git a/framework/yii/rbac/Manager.php b/framework/yii/rbac/Manager.php index 3d3aafc..1710a77 100644 --- a/framework/yii/rbac/Manager.php +++ b/framework/yii/rbac/Manager.php @@ -58,7 +58,7 @@ abstract class Manager extends Component * And then declare 'authenticated' in this property so that it can be applied to * every authenticated user. */ - public $defaultRoles = array(); + public $defaultRoles = []; /** * Creates a role. @@ -159,7 +159,7 @@ abstract class Manager extends Component */ protected function checkItemChildType($parentType, $childType) { - static $types = array('operation', 'task', 'role'); + static $types = ['operation', 'task', 'role']; if ($parentType < $childType) { throw new InvalidParamException("Cannot add an item of type '{$types[$childType]}' to an item of type '{$types[$parentType]}'."); } @@ -174,7 +174,7 @@ abstract class Manager extends Component * with the tasks and roles assigned to the user. * @return boolean whether the operations can be performed by the user. */ - abstract public function checkAccess($userId, $itemName, $params = array()); + abstract public function checkAccess($userId, $itemName, $params = []); /** * Creates an authorization item. diff --git a/framework/yii/rbac/PhpManager.php b/framework/yii/rbac/PhpManager.php index 203b17e..57ede09 100644 --- a/framework/yii/rbac/PhpManager.php +++ b/framework/yii/rbac/PhpManager.php @@ -36,14 +36,14 @@ class PhpManager extends Manager * If not set, it will be using 'protected/data/rbac.php' as the data file. * Make sure this file is writable by the Web server process if the authorization * needs to be changed. - * @see loadFromFile - * @see saveToFile + * @see loadFromFile() + * @see saveToFile() */ public $authFile; - private $_items = array(); // itemName => item - private $_children = array(); // itemName, childName => child - private $_assignments = array(); // userId, itemName => assignment + private $_items = []; // itemName => item + private $_children = []; // itemName, childName => child + private $_assignments = []; // userId, itemName => assignment /** * Initializes the application component. @@ -69,12 +69,12 @@ class PhpManager extends Manager * this array, which holds the value of `$userId`. * @return boolean whether the operations can be performed by the user. */ - public function checkAccess($userId, $itemName, $params = array()) + public function checkAccess($userId, $itemName, $params = []) { if (!isset($this->_items[$itemName])) { return false; } - /** @var $item Item */ + /** @var Item $item */ $item = $this->_items[$itemName]; Yii::trace('Checking permission: ' . $item->getName(), __METHOD__); if (!isset($params['userId'])) { @@ -85,7 +85,7 @@ class PhpManager extends Manager return true; } if (isset($this->_assignments[$userId][$itemName])) { - /** @var $assignment Assignment */ + /** @var Assignment $assignment */ $assignment = $this->_assignments[$userId][$itemName]; if ($this->executeBizRule($assignment->bizRule, $params, $assignment->data)) { return true; @@ -113,9 +113,9 @@ class PhpManager extends Manager if (!isset($this->_items[$childName], $this->_items[$itemName])) { throw new Exception("Either '$itemName' or '$childName' does not exist."); } - /** @var $child Item */ + /** @var Item $child */ $child = $this->_items[$childName]; - /** @var $item Item */ + /** @var Item $item */ $item = $this->_items[$itemName]; $this->checkItemChildType($item->type, $child->type); if ($this->detectLoop($itemName, $childName)) { @@ -165,10 +165,10 @@ class PhpManager extends Manager public function getItemChildren($names) { if (is_string($names)) { - return isset($this->_children[$names]) ? $this->_children[$names] : array(); + return isset($this->_children[$names]) ? $this->_children[$names] : []; } - $children = array(); + $children = []; foreach ($names as $name) { if (isset($this->_children[$name])) { $children = array_merge($children, $this->_children[$name]); @@ -194,13 +194,13 @@ class PhpManager extends Manager } elseif (isset($this->_assignments[$userId][$itemName])) { throw new InvalidParamException("Authorization item '$itemName' has already been assigned to user '$userId'."); } else { - return $this->_assignments[$userId][$itemName] = new Assignment(array( + return $this->_assignments[$userId][$itemName] = new Assignment([ 'manager' => $this, 'userId' => $userId, 'itemName' => $itemName, 'bizRule' => $bizRule, 'data' => $data, - )); + ]); } } @@ -251,7 +251,7 @@ class PhpManager extends Manager */ public function getAssignments($userId) { - return isset($this->_assignments[$userId]) ? $this->_assignments[$userId] : array(); + return isset($this->_assignments[$userId]) ? $this->_assignments[$userId] : []; } /** @@ -267,17 +267,17 @@ class PhpManager extends Manager if ($userId === null && $type === null) { return $this->_items; } - $items = array(); + $items = []; if ($userId === null) { foreach ($this->_items as $name => $item) { - /** @var $item Item */ + /** @var Item $item */ if ($item->type == $type) { $items[$name] = $item; } } } elseif (isset($this->_assignments[$userId])) { foreach ($this->_assignments[$userId] as $assignment) { - /** @var $assignment Assignment */ + /** @var Assignment $assignment */ $name = $assignment->itemName; if (isset($this->_items[$name]) && ($type === null || $this->_items[$name]->type == $type)) { $items[$name] = $this->_items[$name]; @@ -307,14 +307,14 @@ class PhpManager extends Manager if (isset($this->_items[$name])) { throw new Exception('Unable to add an item whose name is the same as an existing item.'); } - return $this->_items[$name] = new Item(array( + return $this->_items[$name] = new Item([ 'manager' => $this, 'name' => $name, 'type' => $type, 'description' => $description, 'bizRule' => $bizRule, 'data' => $data, - )); + ]); } /** @@ -398,18 +398,18 @@ class PhpManager extends Manager */ public function save() { - $items = array(); + $items = []; foreach ($this->_items as $name => $item) { - /** @var $item Item */ - $items[$name] = array( + /** @var Item $item */ + $items[$name] = [ 'type' => $item->type, 'description' => $item->description, 'bizRule' => $item->bizRule, 'data' => $item->data, - ); + ]; if (isset($this->_children[$name])) { foreach ($this->_children[$name] as $child) { - /** @var $child Item */ + /** @var Item $child */ $items[$name]['children'][] = $child->getName(); } } @@ -417,12 +417,12 @@ class PhpManager extends Manager foreach ($this->_assignments as $userId => $assignments) { foreach ($assignments as $name => $assignment) { - /** @var $assignment Assignment */ + /** @var Assignment $assignment */ if (isset($items[$name])) { - $items[$name]['assignments'][$userId] = array( + $items[$name]['assignments'][$userId] = [ 'bizRule' => $assignment->bizRule, 'data' => $assignment->data, - ); + ]; } } } @@ -440,14 +440,14 @@ class PhpManager extends Manager $items = $this->loadFromFile($this->authFile); foreach ($items as $name => $item) { - $this->_items[$name] = new Item(array( + $this->_items[$name] = new Item([ 'manager' => $this, 'name' => $name, 'type' => $item['type'], 'description' => $item['description'], 'bizRule' => $item['bizRule'], 'data' => $item['data'], - )); + ]); } foreach ($items as $name => $item) { @@ -460,13 +460,13 @@ class PhpManager extends Manager } if (isset($item['assignments'])) { foreach ($item['assignments'] as $userId => $assignment) { - $this->_assignments[$userId][$name] = new Assignment(array( + $this->_assignments[$userId][$name] = new Assignment([ 'manager' => $this, 'userId' => $userId, 'itemName' => $name, 'bizRule' => $assignment['bizRule'], 'data' => $assignment['data'], - )); + ]); } } } @@ -478,8 +478,8 @@ class PhpManager extends Manager public function clearAll() { $this->clearAssignments(); - $this->_children = array(); - $this->_items = array(); + $this->_children = []; + $this->_items = []; } /** @@ -487,7 +487,7 @@ class PhpManager extends Manager */ public function clearAssignments() { - $this->_assignments = array(); + $this->_assignments = []; } /** @@ -505,7 +505,7 @@ class PhpManager extends Manager return false; } foreach ($this->_children[$childName] as $child) { - /** @var $child Item */ + /** @var Item $child */ if ($this->detectLoop($itemName, $child->getName())) { return true; } @@ -517,14 +517,14 @@ class PhpManager extends Manager * Loads the authorization data from a PHP script file. * @param string $file the file path. * @return array the authorization data - * @see saveToFile + * @see saveToFile() */ protected function loadFromFile($file) { if (is_file($file)) { return require($file); } else { - return array(); + return []; } } @@ -532,7 +532,7 @@ class PhpManager extends Manager * Saves the authorization data to a PHP script file. * @param array $data the authorization data * @param string $file the file path. - * @see loadFromFile + * @see loadFromFile() */ protected function saveToFile($data, $file) { diff --git a/framework/yii/redis/Connection.php b/framework/yii/redis/Connection.php index 1a09f7a..8a7a5d4 100644 --- a/framework/yii/redis/Connection.php +++ b/framework/yii/redis/Connection.php @@ -60,7 +60,7 @@ class Connection extends Component /** * @var array List of available redis commands http://redis.io/commands */ - public $redisCommands = array( + public $redisCommands = [ 'BRPOP', // key [key ...] timeout Remove and get the last element in a list, or block until one is available 'BRPOPLPUSH', // source destination timeout Pop a value from a list, push it to another list and return it; or block until one is available 'CLIENT KILL', // ip:port Kill the connection of a client @@ -200,7 +200,7 @@ class Connection extends Component 'ZREVRANK', // key member Determine the index of a member in a sorted set, with scores ordered from high to low 'ZSCORE', // key member Get the score associated with the given member in a sorted set 'ZUNIONSTORE', // destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] Add multiple sorted sets and store the resulting sorted set in a new key - ); + ]; /** * @var Transaction the currently active transaction */ @@ -259,9 +259,9 @@ class Connection extends Component stream_set_timeout($this->_socket, $timeout=(int)$this->dataTimeout, (int) (($this->dataTimeout - $timeout) * 1000000)); } if ($this->password !== null) { - $this->executeCommand('AUTH', array($this->password)); + $this->executeCommand('AUTH', [$this->password]); } - $this->executeCommand('SELECT', array($db)); + $this->executeCommand('SELECT', [$db]); $this->initConnection(); } else { \Yii::error("Failed to open DB connection ({$this->dsn}): " . $errorNumber . ' - ' . $errorDescription, __CLASS__); @@ -312,9 +312,7 @@ class Connection extends Component public function beginTransaction() { $this->open(); - $this->_transaction = new Transaction(array( - 'db' => $this, - )); + $this->_transaction = new Transaction(['db' => $this]); $this->_transaction->begin(); return $this->_transaction; } @@ -375,7 +373,7 @@ class Connection extends Component * for details on the mentioned reply types. * @trows Exception for commands that return [error reply](http://redis.io/topics/protocol#error-reply). */ - public function executeCommand($name, $params=array()) + public function executeCommand($name, $params=[]) { $this->open(); @@ -393,7 +391,7 @@ class Connection extends Component private function parseResponse($command) { - if(($line = fgets($this->_socket)) === false) { + if (($line = fgets($this->_socket)) === false) { throw new Exception("Failed to read from socket.\nRedis command was: " . $command); } $type = $line[0]; @@ -414,7 +412,7 @@ class Connection extends Component $length = $line + 2; $data = ''; while ($length > 0) { - if(($block = fread($this->_socket, $line + 2)) === false) { + if (($block = fread($this->_socket, $line + 2)) === false) { throw new Exception("Failed to read from socket.\nRedis command was: " . $command); } $data .= $block; @@ -423,8 +421,8 @@ class Connection extends Component return mb_substr($data, 0, -2, '8bit'); case '*': // Multi-bulk replies $count = (int) $line; - $data = array(); - for($i = 0; $i < $count; $i++) { + $data = []; + for ($i = 0; $i < $count; $i++) { $data[] = $this->parseResponse($command); } return $data; diff --git a/framework/yii/requirements/YiiRequirementChecker.php b/framework/yii/requirements/YiiRequirementChecker.php index 7d5ae42..586cdce 100644 --- a/framework/yii/requirements/YiiRequirementChecker.php +++ b/framework/yii/requirements/YiiRequirementChecker.php @@ -63,7 +63,7 @@ class YiiRequirementChecker * @param array|string $requirements requirements to be checked. * If an array, it is treated as the set of requirements; * If a string, it is treated as the path of the file, which contains the requirements; - * @return YiiRequirementChecker self instance. + * @return static self instance. */ function check($requirements) { @@ -178,6 +178,9 @@ class YiiRequirementChecker if (empty($extensionVersion)) { return false; } + if (strncasecmp($extensionVersion, 'PECL-', 5) == 0) { + $extensionVersion = substr($extensionVersion, 5); + } return version_compare($extensionVersion, $version, $compare); } diff --git a/framework/yii/requirements/requirements.php b/framework/yii/requirements/requirements.php index 005a205..916d6aa 100644 --- a/framework/yii/requirements/requirements.php +++ b/framework/yii/requirements/requirements.php @@ -3,15 +3,15 @@ * These are the Yii core requirements for the [[YiiRequirementChecker]] instance. * These requirements are mandatory for any Yii application. * - * @var $this YiiRequirementChecker + * @var YiiRequirementChecker $this */ return array( array( 'name' => 'PHP version', 'mandatory' => true, - 'condition' => version_compare(PHP_VERSION, '5.3.7', '>='), + 'condition' => version_compare(PHP_VERSION, '5.4.0', '>='), 'by' => '<a href="http://www.yiiframework.com">Yii Framework</a>', - 'memo' => 'PHP 5.3.7 or higher is required.', + 'memo' => 'PHP 5.4.0 or higher is required.', ), array( 'name' => 'Reflection extension', @@ -43,6 +43,8 @@ return array( 'mandatory' => false, 'condition' => $this->checkPhpExtensionVersion('intl', '1.0.2', '>='), 'by' => '<a href="http://www.php.net/manual/en/book.intl.php">Internationalization</a> support', - 'memo' => 'PHP Intl extension 1.0.2 or higher is required when you want to use <abbr title="Internationalized domain names">IDN</abbr>-feature of EmailValidator or UrlValidator or the <code>yii\i18n\Formatter</code> class.' + 'memo' => 'PHP Intl extension 1.0.2 or higher is required when you want to use advanced parameters formatting + in <code>Yii::t()</code>, <abbr title="Internationalized domain names">IDN</abbr>-feature of + <code>EmailValidator</code> or <code>UrlValidator</code> or the <code>yii\i18n\Formatter</code> class.' ), ); diff --git a/framework/yii/requirements/views/console/index.php b/framework/yii/requirements/views/console/index.php index 6935107..1d87fe9 100644 --- a/framework/yii/requirements/views/console/index.php +++ b/framework/yii/requirements/views/console/index.php @@ -1,7 +1,7 @@ <?php -/* @var $this YiiRequirementChecker */ -/* @var $summary array */ -/* @var $requirements array[] */ +/* @var YiiRequirementChecker $this */ +/* @var array $summary */ +/* @var array[] $requirements */ echo "\nYii Application Requirement Checker\n\n"; diff --git a/framework/yii/requirements/views/web/index.php b/framework/yii/requirements/views/web/index.php index 1887f8b..287d4bb 100644 --- a/framework/yii/requirements/views/web/index.php +++ b/framework/yii/requirements/views/web/index.php @@ -1,7 +1,7 @@ <?php -/* @var $this YiiRequirementChecker */ -/* @var $summary array */ -/* @var $requirements array[] */ +/* @var YiiRequirementChecker $this */ +/* @var array $summary */ +/* @var array[] $requirements */ ?> <!DOCTYPE html> <html lang="en"> @@ -52,18 +52,18 @@ <table class="table table-bordered"> <tr><th>Name</th><th>Result</th><th>Required By</th><th>Memo</th></tr> <?php foreach($requirements as $requirement): ?> - <tr class="<?php echo $requirement['condition'] ? 'success' : ($requirement['mandatory'] ? 'error' : 'warning'); ?>"> + <tr class="<?php echo $requirement['condition'] ? 'success' : ($requirement['mandatory'] ? 'error' : 'warning') ?>"> <td> - <?php echo $requirement['name']; ?> + <?php echo $requirement['name'] ?> </td> <td> - <span class="result"><?php echo $requirement['condition'] ? 'Passed' : ($requirement['mandatory'] ? 'Failed' : 'Warning'); ?></span> + <span class="result"><?php echo $requirement['condition'] ? 'Passed' : ($requirement['mandatory'] ? 'Failed' : 'Warning') ?></span> </td> <td> - <?php echo $requirement['by']; ?> + <?php echo $requirement['by'] ?> </td> <td> - <?php echo $requirement['memo']; ?> + <?php echo $requirement['memo'] ?> </td> </tr> <?php endforeach; ?> @@ -74,7 +74,7 @@ <hr> <div class="footer"> - <p>Server: <?php echo $this->getServerInfo() . ' ' . $this->getNowDate(); ?></p> + <p>Server: <?php echo $this->getServerInfo() . ' ' . $this->getNowDate() ?></p> <p>Powered by <a href="http://www.yiiframework.com/" rel="external">Yii Framework</a></p> </div> </div> diff --git a/framework/yii/test/DbFixtureManager.php b/framework/yii/test/DbFixtureManager.php new file mode 100644 index 0000000..ed90284 --- /dev/null +++ b/framework/yii/test/DbFixtureManager.php @@ -0,0 +1,219 @@ +<?php +/** + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\test; + +use Yii; +use yii\base\Component; +use yii\base\InvalidConfigException; +use yii\db\ActiveRecord; +use yii\db\Connection; + +/** + * DbFixtureManager manages database fixtures during tests. + * + * A fixture represents a list of rows for a specific table. For a test method, + * using a fixture means that at the beginning of the method, the table has and only + * has the rows that are given in the fixture. Therefore, the table's state is + * predictable. + * + * A fixture is represented as a PHP script whose name (without suffix) is the + * same as the table name (if schema name is needed, it should be prefixed to + * the table name). The PHP script returns an array representing a list of table + * rows. Each row is an associative array of column values indexed by column names. + * + * Fixtures must be stored under the [[basePath]] directory. The directory + * may contain a file named `init.php` which will be executed before any fixture is loaded. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @since 2.0 + */ +class DbFixtureManager extends Component +{ + /** + * @var string the init script file that should be executed before running each test. + * This should be a path relative to [[basePath]]. + */ + public $initScript = 'init.php'; + /** + * @var string the base path containing all fixtures. This can be either a directory path or path alias. + */ + public $basePath = '@app/tests/fixtures'; + /** + * @var Connection|string the DB connection object or the application component ID of the DB connection. + * After the DbFixtureManager object is created, if you want to change this property, you should only assign it + * with a DB connection object. + */ + public $db = 'db'; + /** + * @var array list of database schemas that the test tables may reside in. Defaults to + * array(''), meaning using the default schema (an empty string refers to the + * default schema). This property is mainly used when turning on and off integrity checks + * so that fixture data can be populated into the database without causing problem. + */ + public $schemas = ['']; + + private $_rows; // fixture name, row alias => row + private $_models; // fixture name, row alias => record (or class name) + private $_modelClasses; + + + /** + * Loads the specified fixtures. + * + * This method does the following things to load the fixtures: + * + * - Run [[initScript]] if any. + * - Clean up data and models loaded in memory previously. + * - Load each specified fixture by calling [[loadFixture()]]. + * + * @param array $fixtures a list of fixtures (fixture name => table name or AR class name) to be loaded. + * Each array element can be either a table name (with schema prefix if needed), or a fully-qualified + * ActiveRecord class name (e.g. `app\models\Post`). An element can be associated with a key + * which will be treated as the fixture name. + * @return array the loaded fixture data (fixture name => table rows) + * @throws InvalidConfigException if a model class specifying a fixture is not an ActiveRecord class. + */ + public function load(array $fixtures = []) + { + $this->basePath = Yii::getAlias($this->basePath); + + if (is_string($this->db)) { + $this->db = Yii::$app->getComponent($this->db); + } + if (!$this->db instanceof Connection) { + throw new InvalidConfigException("The 'db' property must be either a DB connection instance or the application component ID of a DB connection."); + } + + foreach ($fixtures as $name => $fixture) { + if (strpos($fixture, '\\') !== false) { + $model = new $fixture; + if ($model instanceof ActiveRecord) { + $this->_modelClasses[$name] = $fixture; + $fixtures[$name] = $model->getTableSchema()->name; + } else { + throw new InvalidConfigException("Fixture '$fixture' must be an ActiveRecord class."); + } + } + } + + $this->_modelClasses = $this->_rows = $this->_models = []; + + $this->checkIntegrity(false); + + if (!empty($this->initScript)) { + $initFile = $this->basePath . '/' . $this->initScript; + if (is_file($initFile)) { + require($initFile); + } + } + + foreach ($fixtures as $name => $tableName) { + $rows = $this->loadFixture($tableName); + if (is_array($rows)) { + $this->_rows[$name] = $rows; + } + } + $this->checkIntegrity(true); + return $this->_rows; + } + + /** + * Loads the fixture for the specified table. + * + * This method does the following tasks to load the fixture for a table: + * + * - Remove existing rows in the table. + * - If there is any auto-incremental column, the corresponding sequence will be reset to 0. + * - If a fixture file is found, it will be executed, and its return value will be treated + * as rows which will then be inserted into the table. + * + * @param string $tableName table name + * @return array|boolean the loaded fixture rows indexed by row aliases (if any). + * False is returned if the table does not have a fixture. + * @throws InvalidConfigException if the specified table does not exist + */ + public function loadFixture($tableName) + { + $table = $this->db->getSchema()->getTableSchema($tableName); + if ($table === null) { + throw new InvalidConfigException("Table does not exist: $tableName"); + } + + $this->db->createCommand()->truncateTable($tableName); + + $fileName = $this->basePath . '/' . $tableName . '.php'; + if (!is_file($fileName)) { + return false; + } + + $rows = []; + foreach (require($fileName) as $alias => $row) { + $this->db->createCommand()->insert($tableName, $row)->execute(); + if ($table->sequenceName !== null) { + foreach ($table->primaryKey as $pk) { + if (!isset($row[$pk])) { + $row[$pk] = $this->db->getLastInsertID($table->sequenceName); + break; + } + } + } + $rows[$alias] = $row; + } + + return $rows; + } + + /** + * Returns the fixture data rows. + * The rows will have updated primary key values if the primary key is auto-incremental. + * @param string $fixtureName the fixture name + * @return array the fixture data rows. False is returned if there is no such fixture data. + */ + public function getRows($fixtureName) + { + return isset($this->_rows[$fixtureName]) ? $this->_rows[$fixtureName] : false; + } + + /** + * Returns the specified ActiveRecord instance in the fixture data. + * @param string $fixtureName the fixture name + * @param string $modelName the alias for the fixture data row + * @return \yii\db\ActiveRecord the ActiveRecord instance. Null is returned if there is no such fixture row. + */ + public function getModel($fixtureName, $modelName) + { + if (!isset($this->_modelClasses[$fixtureName]) || !isset($this->_rows[$fixtureName][$modelName])) { + return null; + } + if (isset($this->_models[$fixtureName][$modelName])) { + return $this->_models[$fixtureName][$modelName]; + } + $row = $this->_rows[$fixtureName][$modelName]; + /** @var \yii\db\ActiveRecord $modelClass */ + $modelClass = $this->_models[$fixtureName]; + /** @var \yii\db\ActiveRecord $model */ + $model = new $modelClass; + $keys = []; + foreach ($model->primaryKey() as $key) { + $keys[$key] = isset($row[$key]) ? $row[$key] : null; + } + return $this->_models[$fixtureName][$modelName] = $modelClass::find($keys); + } + + /** + * Enables or disables database integrity check. + * This method may be used to temporarily turn off foreign constraints check. + * @param boolean $check whether to enable database integrity check + */ + public function checkIntegrity($check) + { + foreach ($this->schemas as $schema) { + $this->db->createCommand()->checkIntegrity($check, $schema); + } + } +} diff --git a/framework/yii/test/DbTestTrait.php b/framework/yii/test/DbTestTrait.php new file mode 100644 index 0000000..8a6dc3c --- /dev/null +++ b/framework/yii/test/DbTestTrait.php @@ -0,0 +1,110 @@ +<?php +/** + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\test; + +use Yii; + +/** + * DbTestTrait implements the commonly used methods for setting up and accessing fixture data. + * + * To use DbTestTrait, call the [[loadFixtures()]] method in the setup method in a test case class. + * The specified fixtures will be loaded and accessible through [[getFixtureData()]] and [[getFixtureModel()]]. + * + * For example, + * + * ~~~ + * use yii\test\DbTestTrait; + * use app\models\Post; + * use app\models\User; + * + * class PostTestCase extends \PHPUnit_Framework_TestCase + * { + * use DbTestTrait; + * + * public function setUp() + * { + * $this->loadFixtures([ + * 'posts' => Post::className(), + * 'users' => User::className(), + * ]); + * } + * } + * ~~~ + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @since 2.0 + */ +trait DbTestTrait +{ + /** + * Loads the specified fixtures. + * + * This method should typically be called in the setup method of test cases so that + * the fixtures are loaded before running each test method. + * + * This method does the following things: + * + * - Run [[DbFixtureManager::initScript]] if it is found under [[DbFixtureManager::basePath]]. + * - Clean up data and models loaded in memory previously. + * - Load each specified fixture: + * * Truncate the corresponding table. + * * If a fixture file named `TableName.php` is found under [[DbFixtureManager::basePath]], + * the file will be executed, and the return value will be treated as rows which will + * then be inserted into the table. + * + * @param array $fixtures a list of fixtures (fixture name => table name or AR class name) to be loaded. + * Each array element can be either a table name (with schema prefix if needed), or a fully-qualified + * ActiveRecord class name (e.g. `app\models\Post`). An element can be optionally associated with a key + * which will be treated as the fixture name. For example, + * + * ~~~ + * [ + * 'tbl_comment', + * 'users' => 'tbl_user', // 'users' is the fixture name, 'tbl_user' is a table name + * 'posts' => 'app\models\Post, // 'app\models\Post' is a model class name + * ] + * ~~~ + * + * @return array the loaded fixture data (fixture name => table rows) + */ + public function loadFixtures(array $fixtures = []) + { + return $this->getFixtureManager()->load($fixtures); + } + + /** + * Returns the DB fixture manager. + * @return DbFixtureManager the DB fixture manager + */ + public function getFixtureManager() + { + return Yii::$app->getComponent('fixture'); + } + + /** + * Returns the table rows of the named fixture. + * @param string $fixtureName the fixture name. + * @return array the named fixture table rows. False is returned if there is no such fixture data. + */ + public function getFixtureRows($fixtureName) + { + return $this->getFixtureManager()->getRows($fixtureName); + } + + /** + * Returns the named AR instance corresponding to the named fixture. + * @param string $fixtureName the fixture name. + * @param string $modelName the name of the fixture data row + * @return \yii\db\ActiveRecord the named AR instance corresponding to the named fixture. + * Null is returned if there is no such fixture or the record cannot be found. + */ + public function getFixtureModel($fixtureName, $modelName) + { + return $this->getFixtureManager()->getModel($fixtureName, $modelName); + } +} diff --git a/framework/yii/validators/BooleanValidator.php b/framework/yii/validators/BooleanValidator.php index d1e81b8..9d74705 100644 --- a/framework/yii/validators/BooleanValidator.php +++ b/framework/yii/validators/BooleanValidator.php @@ -57,10 +57,10 @@ class BooleanValidator extends Validator { $value = $object->$attribute; if (!$this->validateValue($value)) { - $this->addError($object, $attribute, $this->message, array( - '{true}' => $this->trueValue, - '{false}' => $this->falseValue, - )); + $this->addError($object, $attribute, $this->message, [ + 'true' => $this->trueValue, + 'false' => $this->falseValue, + ]); } } @@ -79,22 +79,21 @@ class BooleanValidator extends Validator * Returns the JavaScript needed for performing client-side validation. * @param \yii\base\Model $object the data object being validated * @param string $attribute the name of the attribute to be validated. - * @param \yii\base\View $view the view object that is going to be used to render views or view files + * @param \yii\web\View $view the view object that is going to be used to render views or view files * containing a model form with this validator applied. * @return string the client-side validation script. */ public function clientValidateAttribute($object, $attribute, $view) { - $options = array( + $options = [ 'trueValue' => $this->trueValue, 'falseValue' => $this->falseValue, - 'message' => Html::encode(strtr($this->message, array( + 'message' => Html::encode(strtr($this->message, [ '{attribute}' => $object->getAttributeLabel($attribute), - '{value}' => $object->$attribute, '{true}' => $this->trueValue, '{false}' => $this->falseValue, - ))), - ); + ])), + ]; if ($this->skipOnEmpty) { $options['skipOnEmpty'] = 1; } diff --git a/framework/yii/validators/CompareValidator.php b/framework/yii/validators/CompareValidator.php index 8c9e587..08d4809 100644 --- a/framework/yii/validators/CompareValidator.php +++ b/framework/yii/validators/CompareValidator.php @@ -142,10 +142,10 @@ class CompareValidator extends Validator default: $valid = false; break; } if (!$valid) { - $this->addError($object, $attribute, $this->message, array( - '{compareAttribute}' => $compareLabel, - '{compareValue}' => $compareValue, - )); + $this->addError($object, $attribute, $this->message, [ + 'compareAttribute' => $compareLabel, + 'compareValue' => $compareValue, + ]); } } @@ -170,6 +170,7 @@ class CompareValidator extends Validator case '>=': return $value >= $this->compareValue; case '<': return $value < $this->compareValue; case '<=': return $value <= $this->compareValue; + default: return false; } } @@ -178,13 +179,13 @@ class CompareValidator extends Validator * @param \yii\base\Model $object the data object being validated * @param string $attribute the name of the attribute to be validated * @return string the client-side validation script - * @param \yii\base\View $view the view object that is going to be used to render views or view files + * @param \yii\web\View $view the view object that is going to be used to render views or view files * containing a model form with this validator applied. * @throws InvalidConfigException if CompareValidator::operator is invalid */ public function clientValidateAttribute($object, $attribute, $view) { - $options = array('operator' => $this->operator); + $options = ['operator' => $this->operator]; if ($this->compareValue !== null) { $options['compareValue'] = $this->compareValue; @@ -199,11 +200,11 @@ class CompareValidator extends Validator $options['skipOnEmpty'] = 1; } - $options['message'] = Html::encode(strtr($this->message, array( + $options['message'] = Html::encode(strtr($this->message, [ '{attribute}' => $object->getAttributeLabel($attribute), - '{value}' => $object->$attribute, + '{compareAttribute}' => $compareValue, '{compareValue}' => $compareValue, - ))); + ])); ValidationAsset::register($view); return 'yii.validation.compare(value, messages, ' . json_encode($options) . ');'; diff --git a/framework/yii/validators/DateValidator.php b/framework/yii/validators/DateValidator.php index 2f9e18b..5354b71 100644 --- a/framework/yii/validators/DateValidator.php +++ b/framework/yii/validators/DateValidator.php @@ -70,6 +70,8 @@ class DateValidator extends Validator */ public function validateValue($value) { - return DateTime::createFromFormat($this->format, $value) !== false; + DateTime::createFromFormat($this->format, $value); + $errors = DateTime::getLastErrors(); + return $errors['error_count'] === 0 && $errors['warning_count'] === 0; } } diff --git a/framework/yii/validators/EmailValidator.php b/framework/yii/validators/EmailValidator.php index c3d8480..6b5a07f 100644 --- a/framework/yii/validators/EmailValidator.php +++ b/framework/yii/validators/EmailValidator.php @@ -38,16 +38,11 @@ class EmailValidator extends Validator */ public $allowName = false; /** - * @var boolean whether to check the MX record for the email address. - * Defaults to false. To enable it, you need to make sure the PHP function 'checkdnsrr' - * exists in your PHP installation. + * @var boolean whether to check whether the emails domain exists and has either an A or MX record. + * Be aware of the fact that this check can fail due to temporary DNS problems even if the email address is + * valid and an email would be deliverable. Defaults to false. */ - public $checkMX = false; - /** - * @var boolean whether to check port 25 for the email address. - * Defaults to false. - */ - public $checkPort = false; + public $checkDNS = false; /** * @var boolean whether validation process should take into account IDN (internationalized domain * names). Defaults to false meaning that validation of emails containing IDN will always fail. @@ -93,24 +88,19 @@ class EmailValidator extends Validator public function validateValue($value) { // make sure string length is limited to avoid DOS attacks - if (!is_string($value) || strlen($value) >= 255) { + if (!is_string($value) || strlen($value) >= 320) { return false; } - if (($atPosition = strpos($value, '@')) === false) { + if (!preg_match('/^(.*<?)(.*)@(.*)(>?)$/', $value, $matches)) { return false; } - $domain = rtrim(substr($value, $atPosition + 1), '>'); + $domain = $matches[3]; if ($this->enableIDN) { - $value = idn_to_ascii(ltrim(substr($value, 0, $atPosition), '<')) . '@' . idn_to_ascii($domain); + $value = $matches[1] . idn_to_ascii($matches[2]) . '@' . idn_to_ascii($domain) . $matches[4]; } $valid = preg_match($this->pattern, $value) || $this->allowName && preg_match($this->fullPattern, $value); - if ($valid) { - if ($this->checkMX && function_exists('checkdnsrr')) { - $valid = checkdnsrr($domain, 'MX'); - } - if ($valid && $this->checkPort && function_exists('fsockopen')) { - $valid = fsockopen($domain, 25) !== false; - } + if ($valid && $this->checkDNS) { + $valid = checkdnsrr($domain, 'MX') || checkdnsrr($domain, 'A'); } return $valid; } @@ -119,22 +109,21 @@ class EmailValidator extends Validator * Returns the JavaScript needed for performing client-side validation. * @param \yii\base\Model $object the data object being validated * @param string $attribute the name of the attribute to be validated. - * @param \yii\base\View $view the view object that is going to be used to render views or view files + * @param \yii\web\View $view the view object that is going to be used to render views or view files * containing a model form with this validator applied. * @return string the client-side validation script. */ public function clientValidateAttribute($object, $attribute, $view) { - $options = array( + $options = [ 'pattern' => new JsExpression($this->pattern), 'fullPattern' => new JsExpression($this->fullPattern), 'allowName' => $this->allowName, - 'message' => Html::encode(strtr($this->message, array( + 'message' => Html::encode(strtr($this->message, [ '{attribute}' => $object->getAttributeLabel($attribute), - '{value}' => $object->$attribute, - ))), + ])), 'enableIDN' => (boolean)$this->enableIDN, - ); + ]; if ($this->skipOnEmpty) { $options['skipOnEmpty'] = 1; } diff --git a/framework/yii/validators/ExistValidator.php b/framework/yii/validators/ExistValidator.php index 8cbce5f..ba3f332 100644 --- a/framework/yii/validators/ExistValidator.php +++ b/framework/yii/validators/ExistValidator.php @@ -65,11 +65,11 @@ class ExistValidator extends Validator return; } - /** @var $className \yii\db\ActiveRecord */ + /** @var \yii\db\ActiveRecord $className */ $className = $this->className === null ? get_class($object) : $this->className; $attributeName = $this->attributeName === null ? $attribute : $this->attributeName; $query = $className::find(); - $query->where(array($attributeName => $value)); + $query->where([$attributeName => $value]); if (!$query->exists()) { $this->addError($object, $attribute, $this->message); } @@ -92,10 +92,10 @@ class ExistValidator extends Validator if ($this->attributeName === null) { throw new InvalidConfigException('The "attributeName" property must be set.'); } - /** @var $className \yii\db\ActiveRecord */ + /** @var \yii\db\ActiveRecord $className */ $className = $this->className; $query = $className::find(); - $query->where(array($this->attributeName => $value)); + $query->where([$this->attributeName => $value]); return $query->exists(); } } diff --git a/framework/yii/validators/FileValidator.php b/framework/yii/validators/FileValidator.php index e2880af..4726c6c 100644 --- a/framework/yii/validators/FileValidator.php +++ b/framework/yii/validators/FileValidator.php @@ -26,6 +26,7 @@ class FileValidator extends Validator * separated by space or comma (e.g. "gif, jpg"). * Extension names are case-insensitive. Defaults to null, meaning all file name * extensions are allowed. + * @see wrongType */ public $types; /** @@ -46,6 +47,7 @@ class FileValidator extends Validator * @var integer the maximum file count the given attribute can hold. * It defaults to 1, meaning single file upload. By defining a higher number, * multiple uploads become possible. + * @see tooMany */ public $maxFiles = 1; /** @@ -76,9 +78,10 @@ class FileValidator extends Validator public $tooSmall; /** * @var string the error message used when the uploaded file has an extension name - * that is not listed in [[extensions]]. You may use the following tokens in the message: + * that is not listed in [[types]]. You may use the following tokens in the message: * * - {attribute}: the attribute name + * - {file}: the uploaded file name * - {extensions}: the list of the allowed extensions. */ public $wrongType; @@ -87,7 +90,6 @@ class FileValidator extends Validator * You may use the following tokens in the message: * * - {attribute}: the attribute name - * - {file}: the uploaded file name * - {limit}: the value of [[maxFiles]] */ public $tooMany; @@ -144,7 +146,7 @@ class FileValidator extends Validator $this->addError($object, $attribute, $this->uploadRequired); } if (count($files) > $this->maxFiles) { - $this->addError($object, $attribute, $this->tooMany, array('{attribute}' => $attribute, '{limit}' => $this->maxFiles)); + $this->addError($object, $attribute, $this->tooMany, ['limit' => $this->maxFiles]); } else { foreach ($files as $file) { $this->validateFile($object, $attribute, $file); @@ -166,23 +168,23 @@ class FileValidator extends Validator * @param string $attribute the attribute being validated * @param UploadedFile $file uploaded file passed to check against a set of rules */ - protected function validateFile($object, $attribute, $file) + public function validateFile($object, $attribute, $file) { switch ($file->error) { case UPLOAD_ERR_OK: if ($this->maxSize !== null && $file->size > $this->maxSize) { - $this->addError($object, $attribute, $this->tooBig, array('{file}' => $file->name, '{limit}' => $this->getSizeLimit())); + $this->addError($object, $attribute, $this->tooBig, ['file' => $file->name, 'limit' => $this->getSizeLimit()]); } if ($this->minSize !== null && $file->size < $this->minSize) { - $this->addError($object, $attribute, $this->tooSmall, array('{file}' => $file->name, '{limit}' => $this->minSize)); + $this->addError($object, $attribute, $this->tooSmall, ['file' => $file->name, 'limit' => $this->minSize]); } if (!empty($this->types) && !in_array(strtolower(pathinfo($file->name, PATHINFO_EXTENSION)), $this->types, true)) { - $this->addError($object, $attribute, $this->wrongType, array('{file}' => $file->name, '{extensions}' => implode(', ', $this->types))); + $this->addError($object, $attribute, $this->wrongType, ['file' => $file->name, 'extensions' => implode(', ', $this->types)]); } break; case UPLOAD_ERR_INI_SIZE: case UPLOAD_ERR_FORM_SIZE: - $this->addError($object, $attribute, $this->tooBig, array('{file}' => $file->name, '{limit}' => $this->getSizeLimit())); + $this->addError($object, $attribute, $this->tooBig, ['file' => $file->name, 'limit' => $this->getSizeLimit()]); break; case UPLOAD_ERR_PARTIAL: $this->addError($object, $attribute, $this->message); diff --git a/framework/yii/validators/ImageValidator.php b/framework/yii/validators/ImageValidator.php new file mode 100644 index 0000000..a60cc93 --- /dev/null +++ b/framework/yii/validators/ImageValidator.php @@ -0,0 +1,197 @@ +<?php +/** + * Image validator class file. + * + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\validators; + +use Yii; +use yii\web\UploadedFile; +use yii\helpers\FileHelper; + +/** + * ImageValidator verifies if an attribute is receiving a valid image. + * + * @author Taras Gudz <gudz.taras@gmail.com> + * @since 2.0 + */ +class ImageValidator extends FileValidator +{ + /** + * @var string the error message used when the uploaded file is not an image. + * You may use the following tokens in the message: + * + * - {attribute}: the attribute name + * - {file}: the uploaded file name + */ + public $notImage; + /** + * @var integer the minimum width in pixels. + * Defaults to null, meaning no limit. + * @see underWidth + */ + public $minWidth; + /** + * @var integer the maximum width in pixels. + * Defaults to null, meaning no limit. + * @see overWidth + */ + public $maxWidth; + /** + * @var integer the minimum height in pixels. + * Defaults to null, meaning no limit. + * @see underHeight + */ + public $minHeight; + /** + * @var integer the maximum width in pixels. + * Defaults to null, meaning no limit. + * @see overWidth + */ + public $maxHeight; + /** + * @var array|string a list of file mime types that are allowed to be uploaded. + * This can be either an array or a string consisting of file mime types + * separated by space or comma (e.g. "image/jpeg, image/png"). + * Mime type names are case-insensitive. Defaults to null, meaning all mime types + * are allowed. + * @see wrongMimeType + */ + public $mimeTypes; + /** + * @var string the error message used when the image is under [[minWidth]]. + * You may use the following tokens in the message: + * + * - {attribute}: the attribute name + * - {file}: the uploaded file name + * - {limit}: the value of [[minWidth]] + */ + public $underWidth; + /** + * @var string the error message used when the image is over [[maxWidth]]. + * You may use the following tokens in the message: + * + * - {attribute}: the attribute name + * - {file}: the uploaded file name + * - {limit}: the value of [[maxWidth]] + */ + public $overWidth; + /** + * @var string the error message used when the image is under [[minHeight]]. + * You may use the following tokens in the message: + * + * - {attribute}: the attribute name + * - {file}: the uploaded file name + * - {limit}: the value of [[minHeight]] + */ + public $underHeight; + /** + * @var string the error message used when the image is over [[maxHeight]]. + * You may use the following tokens in the message: + * + * - {attribute}: the attribute name + * - {file}: the uploaded file name + * - {limit}: the value of [[maxHeight]] + */ + public $overHeight; + /** + * @var string the error message used when the file has an mime type + * that is not listed in [[mimeTypes]]. + * You may use the following tokens in the message: + * + * - {attribute}: the attribute name + * - {file}: the uploaded file name + * - {mimeTypes}: the value of [[mimeTypes]] + */ + public $wrongMimeType; + + /** + * Initializes the validator. + */ + public function init() + { + parent::init(); + + if ($this->notImage === null) { + $this->notImage = Yii::t('yii', 'The file "{file}" is not an image.'); + } + if ($this->underWidth === null) { + $this->underWidth = Yii::t('yii', 'The file "{file}" is too small. The width cannot be smaller than {limit} pixels.'); + } + if ($this->underHeight === null) { + $this->underHeight = Yii::t('yii', 'The file "{file}" is too small. The height cannot be smaller than {limit} pixels.'); + } + if ($this->overWidth === null) { + $this->overWidth = Yii::t('yii', 'The file "{file}" is too large. The width cannot be larger than {limit} pixels.'); + } + if ($this->overHeight === null) { + $this->overHeight = Yii::t('yii', 'The file "{file}" is too large. The height cannot be larger than {limit} pixels.'); + } + if ($this->wrongMimeType === null) { + $this->wrongMimeType = Yii::t('yii', 'Only files with these mimeTypes are allowed: {mimeTypes}.'); + } + if (!is_array($this->mimeTypes)) { + $this->mimeTypes = preg_split('/[\s,]+/', strtolower($this->mimeTypes), -1, PREG_SPLIT_NO_EMPTY); + } + } + + /** + * Internally validates a file object. + * @param \yii\base\Model $object the object being validated + * @param string $attribute the attribute being validated + * @param UploadedFile $file uploaded file passed to check against a set of rules + */ + public function validateFile($object, $attribute, $file) + { + parent::validateFile($object, $attribute, $file); + + if (!$object->hasErrors($attribute)) { + $this->validateImage($object, $attribute, $file); + } + } + + /** + * Internally validates a file object. + * @param \yii\base\Model $object the object being validated + * @param string $attribute the attribute being validated + * @param UploadedFile $image uploaded file passed to check against a set of rules + */ + public function validateImage($object, $attribute, $image) + { + if (!empty($this->mimeTypes) && !in_array(FileHelper::getMimeType($image->tempName), $this->mimeTypes, true)) { + $this->addError($object, $attribute, $this->wrongMimeType, ['file' => $image->name, 'mimeTypes' => implode(', ', $this->mimeTypes)]); + } + + if (false === ($imageInfo = getimagesize($image->tempName))) { + $this->addError($object, $attribute, $this->notImage, ['file' => $image->name]); + return; + } + + list($width, $height, $type) = $imageInfo; + + if ($width == 0 || $height == 0) { + $this->addError($object, $attribute, $this->notImage, ['file' => $image->name]); + return; + } + + if ($this->minWidth !== null && $width < $this->minWidth) { + $this->addError($object, $attribute, $this->underWidth, ['file' => $image->name, 'limit' => $this->minWidth]); + } + + if ($this->minHeight !== null && $height < $this->minHeight) { + $this->addError($object, $attribute, $this->underHeight, ['file' => $image->name, 'limit' => $this->minHeight]); + } + + if ($this->maxWidth !== null && $width > $this->maxWidth) { + $this->addError($object, $attribute, $this->overWidth, ['file' => $image->name, 'limit' => $this->maxWidth]); + } + + if ($this->maxHeight !== null && $height > $this->maxHeight) { + $this->addError($object, $attribute, $this->overHeight, ['file' => $image->name, 'limit' => $this->maxHeight]); + } + } +} diff --git a/framework/yii/validators/InlineValidator.php b/framework/yii/validators/InlineValidator.php index dd951aa..febf8dc 100644 --- a/framework/yii/validators/InlineValidator.php +++ b/framework/yii/validators/InlineValidator.php @@ -26,8 +26,11 @@ class InlineValidator extends Validator { /** * @var string|\Closure an anonymous function or the name of a model class method that will be - * called to perform the actual validation. Note that if you use anonymous function, you cannot - * use `$this` in it unless you are using PHP 5.4 or above. + * called to perform the actual validation. The signature of the method should be like the following: + * + * ~~~ + * function foo($attribute, $params) + * ~~~ */ public $method; /** @@ -39,7 +42,7 @@ class InlineValidator extends Validator * The signature of the method should be like the following: * * ~~~ - * function foo($attribute) + * function foo($attribute, $params) * { * return "javascript"; * } @@ -60,7 +63,7 @@ class InlineValidator extends Validator { $method = $this->method; if (is_string($method)) { - $method = array($object, $method); + $method = [$object, $method]; } call_user_func($method, $attribute, $this->params); } @@ -79,7 +82,7 @@ class InlineValidator extends Validator * * @param \yii\base\Model $object the data object being validated * @param string $attribute the name of the attribute to be validated. - * @param \yii\base\View $view the view object that is going to be used to render views or view files + * @param \yii\web\View $view the view object that is going to be used to render views or view files * containing a model form with this validator applied. * @return string the client-side validation script. Null if the validator does not support * client-side validation. @@ -91,9 +94,9 @@ class InlineValidator extends Validator if ($this->clientValidate !== null) { $method = $this->clientValidate; if (is_string($method)) { - $method = array($object, $method); + $method = [$object, $method]; } - return call_user_func($method, $attribute); + return call_user_func($method, $attribute, $this->params); } else { return null; } diff --git a/framework/yii/validators/NumberValidator.php b/framework/yii/validators/NumberValidator.php index 2fee133..c114300 100644 --- a/framework/yii/validators/NumberValidator.php +++ b/framework/yii/validators/NumberValidator.php @@ -91,10 +91,10 @@ class NumberValidator extends Validator $this->addError($object, $attribute, $this->message); } if ($this->min !== null && $value < $this->min) { - $this->addError($object, $attribute, $this->tooSmall, array('{min}' => $this->min)); + $this->addError($object, $attribute, $this->tooSmall, ['min' => $this->min]); } if ($this->max !== null && $value > $this->max) { - $this->addError($object, $attribute, $this->tooBig, array('{max}' => $this->max)); + $this->addError($object, $attribute, $this->tooBig, ['max' => $this->max]); } } @@ -114,38 +114,34 @@ class NumberValidator extends Validator * Returns the JavaScript needed for performing client-side validation. * @param \yii\base\Model $object the data object being validated * @param string $attribute the name of the attribute to be validated. - * @param \yii\base\View $view the view object that is going to be used to render views or view files + * @param \yii\web\View $view the view object that is going to be used to render views or view files * containing a model form with this validator applied. * @return string the client-side validation script. */ public function clientValidateAttribute($object, $attribute, $view) { $label = $object->getAttributeLabel($attribute); - $value = $object->$attribute; - $options = array( + $options = [ 'pattern' => new JsExpression($this->integerOnly ? $this->integerPattern : $this->numberPattern), - 'message' => Html::encode(strtr($this->message, array( + 'message' => Html::encode(strtr($this->message, [ '{attribute}' => $label, - '{value}' => $value, - ))), - ); + ])), + ]; if ($this->min !== null) { $options['min'] = $this->min; - $options['tooSmall'] = Html::encode(strtr($this->tooSmall, array( + $options['tooSmall'] = Html::encode(strtr($this->tooSmall, [ '{attribute}' => $label, - '{value}' => $value, '{min}' => $this->min, - ))); + ])); } if ($this->max !== null) { $options['max'] = $this->max; - $options['tooBig'] = Html::encode(strtr($this->tooBig, array( + $options['tooBig'] = Html::encode(strtr($this->tooBig, [ '{attribute}' => $label, - '{value}' => $value, '{max}' => $this->max, - ))); + ])); } if ($this->skipOnEmpty) { $options['skipOnEmpty'] = 1; diff --git a/framework/yii/validators/PunycodeAsset.php b/framework/yii/validators/PunycodeAsset.php index 08d3fb4..c0c1e2b 100644 --- a/framework/yii/validators/PunycodeAsset.php +++ b/framework/yii/validators/PunycodeAsset.php @@ -9,13 +9,15 @@ namespace yii\validators; use yii\web\AssetBundle; /** + * This asset bundle provides the javascript files needed for the [[EmailValidator]]s client validation. + * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ class PunycodeAsset extends AssetBundle { public $sourcePath = '@yii/assets'; - public $js = array( + public $js = [ 'punycode/punycode.js', - ); + ]; } diff --git a/framework/yii/validators/RangeValidator.php b/framework/yii/validators/RangeValidator.php index f89d420..6668969 100644 --- a/framework/yii/validators/RangeValidator.php +++ b/framework/yii/validators/RangeValidator.php @@ -81,24 +81,23 @@ class RangeValidator extends Validator * Returns the JavaScript needed for performing client-side validation. * @param \yii\base\Model $object the data object being validated * @param string $attribute the name of the attribute to be validated. - * @param \yii\base\View $view the view object that is going to be used to render views or view files + * @param \yii\web\View $view the view object that is going to be used to render views or view files * containing a model form with this validator applied. * @return string the client-side validation script. */ public function clientValidateAttribute($object, $attribute, $view) { - $range = array(); + $range = []; foreach ($this->range as $value) { $range[] = (string)$value; } - $options = array( + $options = [ 'range' => $range, 'not' => $this->not, - 'message' => Html::encode(strtr($this->message, array( + 'message' => Html::encode(strtr($this->message, [ '{attribute}' => $object->getAttributeLabel($attribute), - '{value}' => $object->$attribute, - ))), - ); + ])), + ]; if ($this->skipOnEmpty) { $options['skipOnEmpty'] = 1; } diff --git a/framework/yii/validators/RegularExpressionValidator.php b/framework/yii/validators/RegularExpressionValidator.php index df57e7d..efa9e22 100644 --- a/framework/yii/validators/RegularExpressionValidator.php +++ b/framework/yii/validators/RegularExpressionValidator.php @@ -78,7 +78,7 @@ class RegularExpressionValidator extends Validator * Returns the JavaScript needed for performing client-side validation. * @param \yii\base\Model $object the data object being validated * @param string $attribute the name of the attribute to be validated. - * @param \yii\base\View $view the view object that is going to be used to render views or view files + * @param \yii\web\View $view the view object that is going to be used to render views or view files * containing a model form with this validator applied. * @return string the client-side validation script. */ @@ -98,14 +98,13 @@ class RegularExpressionValidator extends Validator $pattern .= preg_replace('/[^igm]/', '', $flag); } - $options = array( + $options = [ 'pattern' => new JsExpression($pattern), 'not' => $this->not, - 'message' => Html::encode(strtr($this->message, array( + 'message' => Html::encode(strtr($this->message, [ '{attribute}' => $object->getAttributeLabel($attribute), - '{value}' => $object->$attribute, - ))), - ); + ])), + ]; if ($this->skipOnEmpty) { $options['skipOnEmpty'] = 1; } diff --git a/framework/yii/validators/RequiredValidator.php b/framework/yii/validators/RequiredValidator.php index 9ca0ecb..2b5e333 100644 --- a/framework/yii/validators/RequiredValidator.php +++ b/framework/yii/validators/RequiredValidator.php @@ -74,9 +74,9 @@ class RequiredValidator extends Validator if ($this->requiredValue === null) { $this->addError($object, $attribute, $this->message); } else { - $this->addError($object, $attribute, $this->message, array( - '{requiredValue}' => $this->requiredValue, - )); + $this->addError($object, $attribute, $this->message, [ + 'requiredValue' => $this->requiredValue, + ]); } } } @@ -102,17 +102,17 @@ class RequiredValidator extends Validator * Returns the JavaScript needed for performing client-side validation. * @param \yii\base\Model $object the data object being validated * @param string $attribute the name of the attribute to be validated. - * @param \yii\base\View $view the view object that is going to be used to render views or view files + * @param \yii\web\View $view the view object that is going to be used to render views or view files * containing a model form with this validator applied. * @return string the client-side validation script. */ public function clientValidateAttribute($object, $attribute, $view) { - $options = array(); + $options = []; if ($this->requiredValue !== null) { - $options['message'] = strtr($this->message, array( + $options['message'] = strtr($this->message, [ '{requiredValue}' => $this->requiredValue, - )); + ]); $options['requiredValue'] = $this->requiredValue; } else { $options['message'] = $this->message; @@ -121,10 +121,9 @@ class RequiredValidator extends Validator $options['strict'] = 1; } - $options['message'] = Html::encode(strtr($options['message'], array( + $options['message'] = Html::encode(strtr($options['message'], [ '{attribute}' => $object->getAttributeLabel($attribute), - '{value}' => $object->$attribute, - ))); + ])); ValidationAsset::register($view); return 'yii.validation.required(value, messages, ' . json_encode($options) . ');'; diff --git a/framework/yii/validators/StringValidator.php b/framework/yii/validators/StringValidator.php index 946ca6e..92aad56 100644 --- a/framework/yii/validators/StringValidator.php +++ b/framework/yii/validators/StringValidator.php @@ -25,10 +25,10 @@ class StringValidator extends Validator * This can be specified in one of the following forms: * * - an integer: the exact length that the value should be of; - * - an array of one element: the minimum length that the value should be of. For example, `array(8)`. + * - an array of one element: the minimum length that the value should be of. For example, `[8]`. * This will overwrite [[min]]. * - an array of two elements: the minimum and maximum lengths that the value should be of. - * For example, `array(8, 128)`. This will overwrite both [[min]] and [[max]]. + * For example, `[8, 128]`. This will overwrite both [[min]] and [[max]]. */ public $length; /** @@ -112,13 +112,13 @@ class StringValidator extends Validator $length = mb_strlen($value, $this->encoding); if ($this->min !== null && $length < $this->min) { - $this->addError($object, $attribute, $this->tooShort, array('{min}' => $this->min)); + $this->addError($object, $attribute, $this->tooShort, ['min' => $this->min]); } if ($this->max !== null && $length > $this->max) { - $this->addError($object, $attribute, $this->tooLong, array('{max}' => $this->max)); + $this->addError($object, $attribute, $this->tooLong, ['max' => $this->max]); } if ($this->length !== null && $length !== $this->length) { - $this->addError($object, $attribute, $this->notEqual, array('{length}' => $this->length)); + $this->addError($object, $attribute, $this->notEqual, ['length' => $this->length]); } } @@ -142,45 +142,40 @@ class StringValidator extends Validator * Returns the JavaScript needed for performing client-side validation. * @param \yii\base\Model $object the data object being validated * @param string $attribute the name of the attribute to be validated. - * @param \yii\base\View $view the view object that is going to be used to render views or view files + * @param \yii\web\View $view the view object that is going to be used to render views or view files * containing a model form with this validator applied. * @return string the client-side validation script. */ public function clientValidateAttribute($object, $attribute, $view) { $label = $object->getAttributeLabel($attribute); - $value = $object->$attribute; - $options = array( - 'message' => Html::encode(strtr($this->message, array( + $options = [ + 'message' => Html::encode(strtr($this->message, [ '{attribute}' => $label, - '{value}' => $value, - ))), - ); + ])), + ]; if ($this->min !== null) { $options['min'] = $this->min; - $options['tooShort'] = Html::encode(strtr($this->tooShort, array( + $options['tooShort'] = Html::encode(strtr($this->tooShort, [ '{attribute}' => $label, - '{value}' => $value, '{min}' => $this->min, - ))); + ])); } if ($this->max !== null) { $options['max'] = $this->max; - $options['tooLong'] = Html::encode(strtr($this->tooLong, array( + $options['tooLong'] = Html::encode(strtr($this->tooLong, [ '{attribute}' => $label, - '{value}' => $value, '{max}' => $this->max, - ))); + ])); } if ($this->length !== null) { $options['is'] = $this->length; - $options['notEqual'] = Html::encode(strtr($this->notEqual, array( + $options['notEqual'] = Html::encode(strtr($this->notEqual, [ '{attribute}' => $label, - '{value}' => $value, '{length}' => $this->is, - ))); + ])); } if ($this->skipOnEmpty) { $options['skipOnEmpty'] = 1; diff --git a/framework/yii/validators/UniqueValidator.php b/framework/yii/validators/UniqueValidator.php index c3876e8..7006cc4 100644 --- a/framework/yii/validators/UniqueValidator.php +++ b/framework/yii/validators/UniqueValidator.php @@ -12,7 +12,7 @@ use yii\base\InvalidConfigException; use yii\db\ActiveRecord; /** - * CUniqueValidator validates that the attribute value is unique in the corresponding database table. + * UniqueValidator validates that the attribute value is unique in the corresponding database table. * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 @@ -60,7 +60,7 @@ class UniqueValidator extends Validator return; } - /** @var $className \yii\db\ActiveRecord */ + /** @var \yii\db\ActiveRecord $className */ $className = $this->className === null ? get_class($object) : $this->className; $attributeName = $this->attributeName === null ? $attribute : $this->attributeName; @@ -70,7 +70,7 @@ class UniqueValidator extends Validator } $query = $className::find(); - $query->where(array($column->name => $value)); + $query->where([$column->name => $value]); if (!$object instanceof ActiveRecord || $object->getIsNewRecord()) { // if current $object isn't in the database yet then it's OK just to call exists() diff --git a/framework/yii/validators/UrlValidator.php b/framework/yii/validators/UrlValidator.php index aa3dad1..5400c32 100644 --- a/framework/yii/validators/UrlValidator.php +++ b/framework/yii/validators/UrlValidator.php @@ -31,7 +31,7 @@ class UrlValidator extends Validator * @var array list of URI schemes which should be considered valid. By default, http and https * are considered to be valid schemes. **/ - public $validSchemes = array('http', 'https'); + public $validSchemes = ['http', 'https']; /** * @var string the default URI scheme. If the input doesn't contain the scheme part, the default * scheme will be prepended to it (thus changing the input). Defaults to null, meaning a URL must @@ -115,7 +115,7 @@ class UrlValidator extends Validator * Returns the JavaScript needed for performing client-side validation. * @param \yii\base\Model $object the data object being validated * @param string $attribute the name of the attribute to be validated. - * @param \yii\base\View $view the view object that is going to be used to render views or view files + * @param \yii\web\View $view the view object that is going to be used to render views or view files * containing a model form with this validator applied. * @return string the client-side validation script. * @see \yii\Web\ActiveForm::enableClientValidation @@ -128,14 +128,13 @@ class UrlValidator extends Validator $pattern = $this->pattern; } - $options = array( + $options = [ 'pattern' => new JsExpression($pattern), - 'message' => Html::encode(strtr($this->message, array( + 'message' => Html::encode(strtr($this->message, [ '{attribute}' => $object->getAttributeLabel($attribute), - '{value}' => $object->$attribute, - ))), + ])), 'enableIDN' => (boolean)$this->enableIDN, - ); + ]; if ($this->skipOnEmpty) { $options['skipOnEmpty'] = 1; } diff --git a/framework/yii/validators/ValidationAsset.php b/framework/yii/validators/ValidationAsset.php index 625e580..14d7ad0 100644 --- a/framework/yii/validators/ValidationAsset.php +++ b/framework/yii/validators/ValidationAsset.php @@ -9,16 +9,18 @@ namespace yii\validators; use yii\web\AssetBundle; /** + * This asset bundle provides the javascript files for client validation. + * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ class ValidationAsset extends AssetBundle { public $sourcePath = '@yii/assets'; - public $js = array( + public $js = [ 'yii.validation.js', - ); - public $depends = array( + ]; + public $depends = [ 'yii\web\YiiAsset', - ); + ]; } diff --git a/framework/yii/validators/Validator.php b/framework/yii/validators/Validator.php index ec9afc4..2cd611b 100644 --- a/framework/yii/validators/Validator.php +++ b/framework/yii/validators/Validator.php @@ -31,6 +31,7 @@ use yii\base\NotSupportedException; * - `exist`: [[ExistValidator]] * - `file`: [[FileValidator]] * - `filter`: [[FilterValidator]] + * - `image`: [[ImageValidator]] * - `in`: [[RangeValidator]] * - `integer`: [[NumberValidator]] * - `match`: [[RegularExpressionValidator]] @@ -48,7 +49,7 @@ abstract class Validator extends Component /** * @var array list of built-in validators (name => class or configuration) */ - public static $builtInValidators = array( + public static $builtInValidators = [ 'boolean' => 'yii\validators\BooleanValidator', 'captcha' => 'yii\captcha\CaptchaValidator', 'compare' => 'yii\validators\CompareValidator', @@ -59,11 +60,12 @@ abstract class Validator extends Component 'exist' => 'yii\validators\ExistValidator', 'file' => 'yii\validators\FileValidator', 'filter' => 'yii\validators\FilterValidator', + 'image' => 'yii\validators\ImageValidator', 'in' => 'yii\validators\RangeValidator', - 'integer' => array( + 'integer' => [ 'class' => 'yii\validators\NumberValidator', 'integerOnly' => true, - ), + ], 'match' => 'yii\validators\RegularExpressionValidator', 'number' => 'yii\validators\NumberValidator', 'required' => 'yii\validators\RequiredValidator', @@ -71,12 +73,13 @@ abstract class Validator extends Component 'string' => 'yii\validators\StringValidator', 'unique' => 'yii\validators\UniqueValidator', 'url' => 'yii\validators\UrlValidator', - ); + ]; /** - * @var array list of attributes to be validated. + * @var array|string attributes to be validated by this validator. For multiple attributes, + * please specify them as an array; for single attribute, you may use either a string or an array. */ - public $attributes = array(); + public $attributes = []; /** * @var string the user-defined error message. It may contain the following placeholders which * will be replaced accordingly by the validator: @@ -86,13 +89,15 @@ abstract class Validator extends Component */ public $message; /** - * @var array list of scenarios that the validator can be applied to. + * @var array|string scenarios that the validator can be applied to. For multiple scenarios, + * please specify them as an array; for single scenario, you may use either a string or an array. */ - public $on = array(); + public $on = []; /** - * @var array list of scenarios that the validator should not be applied to. + * @var array|string scenarios that the validator should not be applied to. For multiple scenarios, + * please specify them as an array; for single scenario, you may use either a string or an array. */ - public $except = array(); + public $except = []; /** * @var boolean whether this validation rule should be skipped if the attribute being validated * already has some validation error according to some previous rules. Defaults to true. @@ -129,21 +134,10 @@ abstract class Validator extends Component * @param array $params initial values to be applied to the validator properties * @return Validator the validator */ - public static function createValidator($type, $object, $attributes, $params = array()) + public static function createValidator($type, $object, $attributes, $params = []) { - if (!is_array($attributes)) { - $attributes = preg_split('/[\s,]+/', $attributes, -1, PREG_SPLIT_NO_EMPTY); - } $params['attributes'] = $attributes; - if (isset($params['on']) && !is_array($params['on'])) { - $params['on'] = preg_split('/[\s,]+/', $params['on'], -1, PREG_SPLIT_NO_EMPTY); - } - - if (isset($params['except']) && !is_array($params['except'])) { - $params['except'] = preg_split('/[\s,]+/', $params['except'], -1, PREG_SPLIT_NO_EMPTY); - } - if (method_exists($object, $type)) { // method-based validator $params['class'] = __NAMESPACE__ . '\InlineValidator'; @@ -165,6 +159,17 @@ abstract class Validator extends Component } /** + * @inheritdoc + */ + public function init() + { + parent::init(); + $this->attributes = (array)$this->attributes; + $this->on = (array)$this->on; + $this->except = (array)$this->except; + } + + /** * Validates the specified object. * @param \yii\base\Model $object the data object being validated * @param array|null $attributes the list of attributes to be validated. @@ -213,7 +218,7 @@ abstract class Validator extends Component * * @param \yii\base\Model $object the data object being validated * @param string $attribute the name of the attribute to be validated. - * @param \yii\base\View $view the view object that is going to be used to render views or view files + * @param \yii\web\View $view the view object that is going to be used to render views or view files * containing a model form with this validator applied. * @return string the client-side validation script. Null if the validator does not support * client-side validation. @@ -248,12 +253,12 @@ abstract class Validator extends Component * @param string $message the error message * @param array $params values for the placeholders in the error message */ - public function addError($object, $attribute, $message, $params = array()) + public function addError($object, $attribute, $message, $params = []) { $value = $object->$attribute; - $params['{attribute}'] = $object->getAttributeLabel($attribute); - $params['{value}'] = is_array($value) ? 'array()' : $value; - $object->addError($attribute, strtr($message, $params)); + $params['attribute'] = $object->getAttributeLabel($attribute); + $params['value'] = is_array($value) ? 'array()' : $value; + $object->addError($attribute, Yii::$app->getI18n()->format($message, $params, Yii::$app->language)); } /** @@ -266,7 +271,7 @@ abstract class Validator extends Component */ public function isEmpty($value, $trim = false) { - return $value === null || $value === array() || $value === '' + return $value === null || $value === [] || $value === '' || $trim && is_scalar($value) && trim($value) === ''; } } diff --git a/framework/yii/views/errorHandler/callStackItem.php b/framework/yii/views/errorHandler/callStackItem.php index 20ad398..566e1d3 100644 --- a/framework/yii/views/errorHandler/callStackItem.php +++ b/framework/yii/views/errorHandler/callStackItem.php @@ -12,15 +12,15 @@ */ ?> <li class="<?php if (!$handler->isCoreFile($file) || $index === 1) echo 'application'; ?> call-stack-item" - data-line="<?php echo (int)($line - $begin); ?>"> + data-line="<?= (int)($line - $begin) ?>"> <div class="element-wrap"> <div class="element"> - <span class="item-number"><?php echo (int)$index; ?>.</span> + <span class="item-number"><?= (int)$index ?>.</span> <span class="text"><?php if ($file !== null) echo 'in ' . $handler->htmlEncode($file); ?></span> <?php if ($method !== null): ?> <span class="call"> <?php if ($file !== null) echo '–' ?> - <?php if ($class !== null) echo $handler->addTypeLinks($class) . '::'; ?><?php echo $handler->addTypeLinks($method . '()'); ?> + <?php if ($class !== null) echo $handler->addTypeLinks($class) . '::'; ?><?= $handler->addTypeLinks($method . '()') ?> </span> <?php endif; ?> <span class="at"><?php if ($line !== null) echo 'at line'; ?></span> @@ -32,7 +32,7 @@ <div class="error-line"></div> <?php for ($i = $begin; $i <= $end; ++$i): ?><div class="hover-line"></div><?php endfor; ?> <div class="code"> - <?php for ($i = $begin; $i <= $end; ++$i): ?><span class="lines-item"><?php echo (int)($i + 1); ?></span><?php endfor; ?> + <?php for ($i = $begin; $i <= $end; ++$i): ?><span class="lines-item"><?= (int)($i + 1) ?></span><?php endfor; ?> <pre><?php // fill empty lines with a whitespace to avoid rendering problems in opera for ($i = $begin; $i <= $end; ++$i) { diff --git a/framework/yii/views/errorHandler/error.php b/framework/yii/views/errorHandler/error.php index 8f2e509..066d7e4 100644 --- a/framework/yii/views/errorHandler/error.php +++ b/framework/yii/views/errorHandler/error.php @@ -28,7 +28,7 @@ if ($exception instanceof \yii\base\UserException) { <html> <head> <meta charset="utf-8" /> - <title><?php echo $handler->htmlEncode($name); ?></title> + <title><?= $handler->htmlEncode($name) ?></title> <style> body { @@ -69,8 +69,8 @@ if ($exception instanceof \yii\base\UserException) { </head> <body> - <h1><?php echo $handler->htmlEncode($name); ?></h1> - <h2><?php echo nl2br($handler->htmlEncode($message)); ?></h2> + <h1><?= $handler->htmlEncode($name) ?></h1> + <h2><?= nl2br($handler->htmlEncode($message)) ?></h2> <p> The above error occurred while the Web server was processing your request. </p> @@ -78,7 +78,7 @@ if ($exception instanceof \yii\base\UserException) { Please contact us if you think this is a server error. Thank you. </p> <div class="version"> - <?php echo date('Y-m-d H:i:s', time()); ?> + <?= date('Y-m-d H:i:s', time()) ?> </div> <?php if (method_exists($this, 'endBody')) $this->endBody(); // to allow injecting code into body (mostly by Yii Debug Toolbar) ?> </body> diff --git a/framework/yii/views/errorHandler/exception.php b/framework/yii/views/errorHandler/exception.php index f0d3d6e..426447a 100644 --- a/framework/yii/views/errorHandler/exception.php +++ b/framework/yii/views/errorHandler/exception.php @@ -342,8 +342,8 @@ pre .diff .change{ <?php if ($exception instanceof \yii\base\ErrorException): ?> <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEwAAABACAIAAAA/NwYHAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyBpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBXaW5kb3dzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjU4QjIxQTgwQzU1MDExRTJBNEMxRURGMUEzMjA1M0UxIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjU4QjIxQTgxQzU1MDExRTJBNEMxRURGMUEzMjA1M0UxIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6NThCMjFBN0VDNTUwMTFFMkE0QzFFREYxQTMyMDUzRTEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NThCMjFBN0ZDNTUwMTFFMkE0QzFFREYxQTMyMDUzRTEiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz74UrDRAAAOWUlEQVR42txb2W8T1xf2LF5mvIzjOJvjxFmbQMJOSGJqFpVAKX0rSOWhf1sfqlbqS4UoS9WWlkJYhQQJtJANsoeEJF7j2Wd+Z+YGM7bHWxKk8LuqKseeuXO/c8/yfecOmKqqlk9hyOm0RZIIl8uC45XeS34aCGU5dvUq/+QJ2dmJ4ThGUbjV6j53zur1/v+AFFIpfn0dPkgTE5kvrQMDJMNgGPaJgVQkKXHjhsJx7vPnrW43+hICip+ft7x+bbxSam6WJAl++sRACu/exW7ckJ4/1zCk01XffINwKorCr67mX4/7/Z9STMKGsG/eJG/dksfGNjfqxYuoxQI4SZeLm52V5udz9gv+JAgis40wQ+r+fWFmhjp8mOrqytneXQFS4vnk7dsZhBmc64oCmVQWBGxyMv8u/uFDR18f4fEIKyupv/7io1FwaSmVwlwuR2OjESe2G0oIz/Nrz54JDx7YIPYqGURXl62rSxgbMxqI2Lu36uJFm8ezu0CKopiA8ccf9idPdmRCa1+f7+JF8OfN6N0N7kqSpNPptPX18aHQDiSwYFBkGCitH1LUbgAJ8WOz2Vx+P1ZXt82puEBADoWsR44YY3JXgEQ48fV1cmVlmwjV1lZXOOxyuTK+uovqJBRDbmQEn542z0x+v+TzKVYrznFkLGZfWzO3lNdLDw56PB673Y4bKC65nWUlh4eF7LyPhvvsWXtzc3EuAjGz/v33lvdpT1UUUZLyb+Crq2WfTz5xwuZwwLrhLuB3yr171NycSb5Jp2lVzUG4LZDwPC4Wk81Asn191mDQ6DAm1ywtiYJgJGumJlEZxvrll36GgaWD1cCyLMPEKYq/edP+9m0uB5qelpeWLHmBva2dlDjOdGV8LFa8MsG9Isfl0FHTgff2+nw+yL2ZzYEUBZ+jLS2WPJBaWM7OUoGAo75+Z0BqMFjW/CdRLA4SfpVisdL1s6aG6uykQFgZA4wkaZreCATMZx4eji8vJ3U5Zq2pcezfb6uvJ4vbO37jBkaSnrNn8TypqmqMkzX3sVTKFKQYi6Xu3iXcbkc4rIngktuI45TTme/28I0VKmGhOJqYQD8JsJL6emtdHVkE4fovv0g6/Zd//tkD2idboWowJMncnByXAxJR8NTff0svX+IdHZIoEvv3p774Ql5ZIZJJDFzXYqHM3E9ZXra811zGemMpwxE0AkxR8GiyCEIgvihseIslmk67Tp+mWlszOVNeW4OUaA6S540g4fPG6OjGo0fy+Lg2+eQkNzlJiKLn6FFgrZI+LKOj+TFGLC8Lk5NKW1vOZsLy5JmZ0t7e1OTweGDBZEmEmyZ5+TIpy0p/P71vHzcxwY6OimtrxOys+fSpVPyHH9imJvrAAfAWUBjs+LiSrSSk168dDod/cHCTu4ZCIJRsi4u5C/3vPwg/9/79RlW18fSpurRUUizj4NVOpwlIU4SbWzc+npCk9LNnCssqZtonK5VDkRgbg3XjVVUi+HzebNj0tIBhsEWeSAQSptzennj40KS+Ly2Bkytra+5wGLfbwbLgEdz4OHyff3E6GJSqq5E5IJ4dXi9kKROQkiCIPF8wuYNgq6iWvs8B5u4kSXg8DvUWlkLyPF6ors7Pp+fnOfBnWL2qKoXlGBYKgWhGvg3YIC3DxCYlRMUwdXAQBKi9AMPaSSoHDDMcBveDrJu+fp2Ymip2cSmpybe0UIcOMX4/Aobpwxykpnqqq4V9+3hRtC8sfDyEbChkGxoCYyupVOLKFfnly+1S/MZGl8+XU1TNyQDsNZRaqbc3lkhAUv1IODkgfeEwAzGDYfGrV6X//tsBUbqwgEejWF69MZdasJkgVdz9/ZaGho/S7GhstBw8yLS1gTXBqJAqQOZuf1rIdul79xSzqoYXkrBut5s8cuSjSEeGcff0OHUqo4VNf7+abf50S8va2bMbnZ1cAe5WMJNFo8kHD/LJFllIwlphzM0pZW5OTQ36YH/3rnT3KZUiFxdJPdcjr+FPnGDTaWpmBlBJwML6+yG2pK4ufmmJf/AgJ2RY8C/QXG63arNZV1cdhtKKvXnDkiSkH7vTWRZB11jF5CRWCptM00JHB9HUBEVMSSaFqSlyZsaUoH0AOTsrADfo6cH1ARrKU1sbPXYsDZaC5NHeDk4EX0JpSdE0+/hxbv5vbrYADKsVVqjeumUxgIQEaxsYUPNKEVlIJXBTU6ogFAEp1NbKLS1EX181TQN3Ad+TamrS9fUb3d3pu3fpojJKWlmBskHqLXC4EVzX0t3Nt7aC90B6RGIK1qBJ1rx77XV1TG0tXAM8KXbyJKuq1NgYuIDidmPhMB0IoLJhDlIBVi0IEsvKq6uaNn3xAi9cKjWEoZAzEkFWR8QC7qL1ET19GkhpkcwMfpV8+FBpadGYlx54ToqCG9HeIsqidX3gc3Y9gBRF1dXBlfBEsIilvn4tEuGgtoNpIhGGYZxmqoUEg6VfvoS8BGzbeGZUInnY7VQk4vV6jb0G+AAGRs9YP3xYEEVb4caUdPt23LgO/UzO8/XXtmy9m5snbTbYSfREVO3Aw1MXLiB3QA5lUi+04xSOE826GMVGczPaw/zKi54n7t2b1o9uyj0p0O3LDw1Zsw+q1Koq2ChFVXGIHUhUPh9yZmNVh2WgyocXOJ/VdlKUpErLAOH3Gx+WX2khtNI+n6VCbghhlmMv+9CQJq8hxwB44NwuV07IEfoowRNQp6xiesEwRaZGtYGo/Nw7p8RB1EFEgMtkvif1UfFqt1jRNzbKaRhvl8ToQb4DZGiLXSz9cLvEiEZ3hCGpHLddWrvFB8/MIB1YsMxCIimjVVXSe9nJyY3btzW3gCpls1Vdvoxv4e0Pjal2dKx/950AjEwQ8EQCwp+MxSyyTBeWcFBUk3/+aT1/3vSRQiLB3buH5/UyctiZAjrL6QR2BnXcAoXO76dra8E6hN1uC4U2W8nLyx8yf1sbZCaUSysDCat0ut1ES4sQCMj60Dq/oii9emUpDBIACDieoCjPqVM5OMVkMn7tWvH+iMYlBgetDQ2EYYBfSHfupO/cIbu73efOOYJBWIlgeFtAY5qyvBV3BYOBbSCPgW8oeqbWOr+SFMdxfmLCbnbkkOlKsBgmLSxox/QdHcBBpXgcaiP7/LlcVONro6qqqrMzI0RQYYxfuSLoJAnsm4CE/9VX3MyMsrKSSV9aFdALgfaGwOPH4tyco7c3/w2BgjGZ4RCZ3K1UV4sg/IqAhIfNzQnw38hIKhRCDZhyOoWwjURHB4gPICiZ9QmCIMIuvfcdeXw8qvcvsWxmn/zpJ+nQIfH1a1BV4Czwf6K62v5eA1WWeDYJWk+PxayDZtKwKgPbh8oChL63F9HdrH78gQOwkzazNpyxY5jSwW/+OTYWv3Wr+tKl4nygYKbSgqRo5ti6aOZ52JOcZWkkqb5ebWmpaCrg6zJFlQxUvGDufvVKqZTQlpkGdCnMz88b+Q1gBu+18nwFjaJAQIXUpbenK3ZXrcotLKTv3y/naG2LBXB6OnHzJnH5stXtBqmNsp24uIgtL+eLcj4YJBIJ+9u3uW0HhnGcOKG1aUoRPZOfoQYkfv9dNrj+xxjyxETs11+9ly7FfvxRep+Nc7Kk0tCAf/45Y7NpB8yvXmEjI0btZmNZFygvMyVUAqQsivGbN+Wd6BGW1hwrK9Fr1wqd5AoNDbbjx701NTYdZIqiksmkxQASND3/9CkdDltAVVYEUtKZAAS06ctRQFNUqGyyTBXNgVnX2+2QaUyvxxYW5IWFgjUOHLG6Gp0xo7chBShpOYb47bfVqSlgSPauLtfAQKGCSearJPzMGfX6ddN+qRIKqb292NQUS1EWli0CVcsKDofc3o61teH374PwdWTnatSYgwnJ8XEMtkiScmYD6Z1xRdQ9hNhTzNwecqvi9zskSWuIlAMSJgILimfOsIpCGbIrFwyq+/Z5envhwWJVVXrvXs6sX7i5gR0dUjhMeb2M3oxJDw2xjx7xQK3eX6w1VBsaHMePw7KkUAh8RwS29Pgxbei/YKurKssCp/1QSAs3jWSHo8gBPplPA0DUe32+tUgERASt42Sbmy39/Ux7O9AUq94LhA+m/cLNSWm6KhiEeeBiRBuJcDjl8Wj7OT8PCLFAwHHyJMMwaDYIOZZhksCuDCDxubnk9ev2b78ldMrJ/vuv8uYNlh0LmVM6u8NRWXaFe2iaVgOB9UiEBRMSBD4w4K2vRwgx/VARLY4r4B6400nrfUr0J6JvxIEDcZeLGxnBPB6E0KG/mpOpk5zPl7s/IyOrqZQNmHoyCSIJy9nJhga5r09r71mtlMdTJMeShegORDzW2Ji8cAH+BHiwaONLtChO1AL2w/TzXeOVKLqIzz5jm5pQ19yIUJMEsI1mjS/g+mwBum/ds4eprUV217othZkdWYTWZRphps0iTD8zMQeZzUszmcPj8cCcaE1GhGlgpP/8g5XULtnD4XbnvEO3lc5A8UaYBqOAfsVdrvxsjvw8f0JNNLKsCHq9UjoxMmJpbf2YjSxUbGha0KVtjmkcFIVV0sXCW1vlxUVRP/ZNB4N8ZyfwOHJ5mS56uqy8fbsxOuo+eLBcPbm1Vpqjv1/q6VGz27Yg0qjy/rnGh3kcDuro0Y2NjXRtLXbsmJemId9Kw8PG1kT8yBEoNta5OdvCAiq5IPAlHAcTl9V33dqAqaGiank4u0Ch8Cu/3ZQ5DsVOnUKnKZAIQENHDQEPPITas4dqaBCbm/nhYSi5CmSaSIT82O66U03RTPpFYgJsl+FxHwxqt7tDIUgzkiQlTp9OLy5anU53TY3pGwI7CXJnBzrPyvrG72e7u1WOI0SR9PlQIwpM4PV6EaEFE5fZTcd257+6gx1L6QP8VusnOp1AHjKOg9ZcftjvUpCoYwjpBy0PNSy30FZG438CDAABlQXh2BuRRgAAAABJRU5ErkJggg==" alt="Gears"/> <h1> - <span><?php echo $handler->htmlEncode($exception->getName()); ?></span> - – <?php echo $handler->addTypeLinks(get_class($exception)); ?> + <span><?= $handler->htmlEncode($exception->getName()) ?></span> + – <?= $handler->addTypeLinks(get_class($exception)) ?> </h1> <?php else: ?> <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAAA6CAIAAACPssguAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyBpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBXaW5kb3dzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjcwRURBOUQxQzQ3RDExRTJCRjVFODJDQkFGODFDN0VBIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjcwRURBOUQyQzQ3RDExRTJCRjVFODJDQkFGODFDN0VBIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6NzBFREE5Q0ZDNDdEMTFFMkJGNUU4MkNCQUY4MUM3RUEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NzBFREE5RDBDNDdEMTFFMkJGNUU4MkNCQUY4MUM3RUEiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4L6mYsAAAE9klEQVR42tRa227aTBDGiwkhJtD8EanSQ6RKqKpSBbWqql5U7QP0TfsSvWhvStMEO6cqDUpjRylSQ1QC+IQN/mdrhBIb413vgsheIDDr9XwzOzPfzFrwPC/Fe8Cauiybe3uersNPQZJylYr04oUgCNyfJXJfcTAYXH/5YoH0qjq66LbbTrtdfPcOITTXALDuf/40FSWlabeuq6rpeZkHD6Ryma8dOOvDdV19Zycg/XBoml6r9ft9vk9EnNV/dOT8+RM1oXdxof/4wdfrEF/1G7Va+uIiagL8BfaBafMIAHy38/17/+/fydNgAkyDyXMHwLEsS1EmqH9kBOvgACbPFwDQaPfr11S9TjT7+JijEfgAsFst8/CQYv7BQe/6el4AQGTsfPuGbqSt2CGcnrarVS4hlRUAxETz9+/eyQntjXAL3MgeUhG7+vXtbSr1Dx+sqoYss3sCYlS/cXLinJ8nu713emqoKqMRmAD4xAElBQA3GpA62DwBsagfiIN7ecnyeHZyIbKoP4o46KurY2+Rrq7CeQ08YenZs0wmM1MAI+KQDv3VKpd7r1+L2WwQsGH0q9VCyN3dZrOrKPdevUpGsxMCmEAcMqVSaWNjcXExcN00zRbQ7BAAWMSs1ZY2N7NLSzMCMJk4ZBFaXl4OAxBF0Yza68fH3Z2dzNu3Ceo1lMB3raurScQhgqjBDhGiibS1v98zzVlEIax+CJ0TM9fYqAIXB9HRBsiFXq0myGuIVv2xxGGQNCaCVcG2tCGVDkBi4jBU80ThYFlwLVojICr1kxAHFLHRsWoNgzu5oABASBw8247ygVScZJhcyDIVuUDk6tf39oiIg+exUAOwMNiZfAVSAI7jGPv7sSUvhwLl/Jyqc0EEwA+dsR2H+EFWy4OdwdqERiACQNhxGIaaaCcWyDY3Zni7u2BzPgDoOg64Yo92YvJ43e2CzUlCKmIlDsSZ2FcG4Qq4faQoJO0jRKJ+uszFq+tWr5PkNRRPHM7O2IWBpdxGYxrkAk2DODihbAqKdJpNkRIDJhdxniDGEgdavgpSWp8+XX7+HADg9vsJCnDcPnr+XHr0KKpeE6fScdA0l1P7AIwAu2BxfR3qIYotREEcpj8mkwsxkjjs7iYmDuRdCXJykSuXx3YuxEji0O2mE0lvlEpWpZK6vWsxv2s0BCjeE1kVk4ujo2KlEvYEkZE4jMlBCK1sbUFdf7NCx22YlRVHURKuCeSiVpM2N8NGEFmJwzgAIH2hUAhYAD6vGQ6J/bOpe2/eBDoXiJ04jNm1CAVsDT8Zj7ijyAViJQ6zHPV6Z3s7kNfQrcylqlCVpuZ42IeHgbMpdJM4QD2auFc+mxE+m0JUHQfSqHd2Fsg7mMxxWjxwNiX43yBzNT9+9GSZj6I2NsKZH5e5Y9+hSODQ79+vfvjgP0K8SRzSvCw9jgvxJBe/fpmaln/yBAe3WXYcOHYuRmdTCFctmsah4zBjhtdo+D08BGHVhs1zd9Q/MgKIDcJjCwzyefv+/dRdG86/zItzvri+7knS3ZLefPgQPX7sMxSUy+W8ly+NtbW7Ir2+tjaoVHL5fDqdFgFENpstlMtQ/bdlOc3pJZLpjX6x6G1t/ff0KegdH1v96yV7EEk7nU6r1bJtm+PrVPx9FyFQd7FYBMa+sLAwBODnMgirAAM+p/EqLDcuJAiwbaCsgU+fsf8vwADjle3ME1OGEwAAAABJRU5ErkJggg==" alt="Attention"/> @@ -359,31 +359,31 @@ pre .diff .change{ } ?></h1> <?php endif; ?> - <h2><?php echo $handler->htmlEncode($exception->getMessage()); ?></h2> - <?php echo $handler->renderPreviousExceptions($exception); ?> + <h2><?= nl2br($handler->htmlEncode($exception->getMessage())) ?></h2> + <?= $handler->renderPreviousExceptions($exception) ?> </div> <div class="call-stack"> <ul> - <?php echo $handler->renderCallStackItem($exception->getFile(), $exception->getLine(), null, null, 1); ?> + <?= $handler->renderCallStackItem($exception->getFile(), $exception->getLine(), null, null, 1) ?> <?php for ($i = 0, $trace = $exception->getTrace(), $length = count($trace); $i < $length; ++$i): ?> - <?php echo $handler->renderCallStackItem(@$trace[$i]['file'] ?: null, @$trace[$i]['line'] ?: null, - @$trace[$i]['class'] ?: null, @$trace[$i]['function'] ?: null, $i + 2); ?> + <?= $handler->renderCallStackItem(@$trace[$i]['file'] ?: null, @$trace[$i]['line'] ?: null, + @$trace[$i]['class'] ?: null, @$trace[$i]['function'] ?: null, $i + 2) ?> <?php endfor; ?> </ul> </div> <div class="request"> <div class="code"> - <?php echo $handler->renderRequest(); ?> + <?= $handler->renderRequest() ?> </div> </div> <div class="footer"> <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAS4AAADeCAIAAAAMx3q5AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyBpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBXaW5kb3dzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkE5NkEyOTAzQzQ3RDExRTI4NjIzOEE2RkU5QTc4RTU0IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkE5NkEyOTA0QzQ3RDExRTI4NjIzOEE2RkU5QTc4RTU0Ij4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6QTk2QTI5MDFDNDdEMTFFMjg2MjM4QTZGRTlBNzhFNTQiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6QTk2QTI5MDJDNDdEMTFFMjg2MjM4QTZGRTlBNzhFNTQiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4DYCAjAAAx4UlEQVR42ux9aZvjuJEmgqTOvCrrrmr3jO3xjD37/3/FftiP3me965nxbl/VVdWVl1IXyVhSJ0EEgABJSZQEtLoyUwJBisSLN24AIgohlv+W2vLN6f/47/kfkP0nlr9s/6V/gfJA8hsAxJvEgUQH5Q/QdgD+UeQ75BexXk/2I+oGly/h+g0EkWA0sJ6l9c0wee6m/xjPv9hHaOpKTMMhsq8Dqwyf/9OLrt5d/EX3ZM3POjLfSjGbCd+cZkM8S+5+gcfPwdUbDiCX9/l4AWlaxJP7cfw1X9HQAkAor4VYDaVFtigfiNtbjLpR1z0AyVPDaiDy2hbvTpPHBGchdJc3ofRY1XeKLbDc6Nk0O3b5EuRrex2Lv1Vmgy3HSOOU+AeAPIp6B1bPDrT3E/hHgYafDdgAamT5I0yT5P6X5Me/pnc/YxpXm9BHjUMUyd3kv2zAAWF4kPrnw4QlVB7TMsVM7W7yY7WbGZzSzGhdS5M0A+RPf03v7YDERTsZYfVu8o9UJAToOC8FpcVlvAIglSFhQw1MQAK1gpDXM5p/wfUXVx+o4REHDsSvuxNg1LjMfQAsKiKTD4ufgMKM0AQfqsOWnhVoZtQGkI+/WsF2RIA0XGcmmk6Su7NdgZ9mXyocFXgC3B9Dfvsx+fmv6fM9B5BHLZo+TH+AClxIiq5QXEm3XASyxsOnRygvpFB8GY6kTlS8nm2Hx9mnCg83sHc1kIkkQQiTlqiyEJgUS4bGqOdDIZsDgKcrWPlQpxmSCipxyOIVz9Ov/5l+/jvOxma8HZ28urna0fzXBKfnvOrG6XSaPLquqgF3eLNqXV1Brteg4mcEDpsYhjtrp0/Jp/+Fdz9hmlgBeVyiaYKzp9nPlR+mhTAJniSp0vLQCIYsTGXtCEUuJrRHab6MjDIqeQMtUITBkJ6Oxe9h4EPZviwqA0dPoTqxwY0PWVfF40OD4i93SJ8+J7/8T3z+dgL0uLnCyjjcwVp8iHHWgz3Hd67rqY0Vw5ASR2t/T3DssMNnyceh8dqqXXCuQP6/9MvfMZ4dCz0armSWPo3j3ypriE6EKUxUCWbfGT2yk4mVWO2l4xHj0fyrm9mm4eUE7JqXXYIAmwpnZemafAgWHKq2dp34Y5sDS3l1lP76N3z8ZCbA1tLjQSgRGu/Y5PqfjzSef3Na0aJ937D6fNiYTALO38HOh8C6VOXUudL48AnGD8Ht99jpG2IyzBEbB6TEefo0T56g4hTB1f+CDmUxPxjUDrn9uxiBg9arKYywJkbqmy/XZU1ozsZyw3xqQZNIO8gMoS26VS/MHKOx64k+Hyef/4ajL1Z6bCEljue/NbpyO0iwoNfTTQul+8rtdHyKyTh2cK7aoQiX18R0N7jmtZdsM24RrggwG4QAbBhy8luYjS6ksQw0diRdcJ9O3i4ckt7/lH79D5HM2yasGs6Y4mycfBUVnImEw5DUDFlAADsAy2qkaSZSegiw9MZVm8aPhrtXeic42HLKUaaO0RbXSJs+pZ//JiYPLbflFHyJn9sxaZx40+VkZjRqJtE4/tYoK4YhtVZo74+uV1n6MH1pOx/S4swu+LD0KHXeLsPD5gYPFO9eHlGefvsHPvxs9j3uDY3mE03irzuFX+Xo0/orMDier9g/TmcJzpqD4tK16NtBaGf0Jf36d6uwelhKnMS/oUigjhOD1w+EKeCXRYfUSmqNC5fZxejnUEKeJwUZ1fzs3KNtrCI2Jweq0mpGRXszAuisfAh2PtQMa8ul0V0bkHpv3ohD5pP0y/8W04cDqo7mwafJvV80tTcnfmDeRrszAy6u/A09cEuT9Ld/wNU7cflW6POM9+nn2MyqFGcLKDqfVz6ACi7XLdyojeBCxY9Bng43C2WhL5ivRu/noBOR136OokvD3BhQDCNt9FqBKukuVu+82dYMBh4FniAPPOcEcSZjyJ7u7ODkCwHdXaL64dMnEY/h5nsUQRvQ6CmR05J0tpDeQ68rnpbqOHlIf/sPEU/2qTqaB5ylT03ZZsjkKHOii8EdYlAjNQlTW9XAoD1KfaU0K+2ZJjyXBktXhG6vrCwBn0A0zGDNCa4f3aa7n6CpPcWKYtednXp8lPJs0QxNNoe16rh3NOrGz9b7aexZ0dLmyTOnGw+KnZ7pU/cAsr00l8uqH+ENTr3q3Ys0Tb/8HzG+M1hr9mNWXUa61Xk5EaaWJ4U9YMDMkKSlrZJllThgnjYIxdxyA25z0bzes6cy7aVkRnu7n5UVDQQVgQhWfVJLhoQNP334AUe/GlDXCBr3Ip3ufLndI01AZVaMMjHJ+sxydbFOZGazQeF1SJgZkQgufMaKGXCz0HBx8vSrSOZw/Z3OWrMLK05xtmSsWHMWy8UJyV/NByFlGi29tVrMC+HmtIkVS2dAajDN1RQsq3l8RrFnzPPyB5TAqXBFf0gTBxiyhJi3H3TTFoxGR8vZgO27ZMU4QhUSVTsBMC+KVk2pDjj+hg8/7pobdS3G8aHpDpiPTujz7rVjATfThlxxi51LWRrkM+JVsO728hziJGHdL2j0IYC7VsqjKfpDNz50+foAO5mKCzSKeAK3f9A5OXbk4ZivpFPzyFj5S4NytJUqgaxlXEimkinO4oSU3Y9IkCd5CEg/Vto92mvhcqNtgv5wV6vfAeOwYRfQcP+OdarnLisgx2P89p8C02a50ZxMEKdjMKZTwD4fEFPZ3+LU8YYAOChPCjfO03FjUCzH3ICmtKk1JttM//zOTrYiXb22ynFthog21Z7HqmSukUgtctn2PBhP8K55NBqBmjKQAeRrx+s2cCxwsPuFoXCvkp1BsdrlQY3VsXL9m8bMrU3PnaYL0ON8r2ic1zCfOuATKoWBK6548u4BcQL9nS7gl5vruP4xYxhR2QLqxTVh7CAyfdlmSWOuLwjHbEYnPrSjwp0PVWoE4ZbQbA8u16ZpHQqNTS9c1Qlzp9pD40Kztvgic/epwMeFH0Pbm6Q6341TkV+u22ZdLtOZIVVqqXEvrD5gMZQX9AI+N06TJ6ta7pDFD8Mr0zLCKiJe5E89H1TIgXIXTbUFxU1xbdaEZlv9eKvvnvFgdVNzO3kzNN7/V000Om294lv95gDF4PrW36+jafMxPvxfsUt/IzT3ctIqDddhHFNT1QbIyDUONwpHbmwOitAfQhhqItGqxrhVsJpalTdhiTU31S91ivMm+JCvMdjmjpoCQKqWOmtr9sH8GZ9+2hEaN/uW+cZscTplQZGpLsLVbXOmwEoSpvNR0Gi3pi6VGf5W+6omdztCY5JONH4Kw4tZKcONJ4W7vE+zGS8EqzI3pjhvjBXz3lcv6mqJ5uh4p5hve8Q58AQG/W038q3Eh1YZzn46E2Wro9FMqCZeTe7E5DdxoMi4ystVK/fC2O3p3KqD1yuuYcu7b5ZYBLOaziH4EOoO6nROfPoFIBS9G37UOAef1aogoul4qc420B8TJFnogOr1ATH29hPYFiYv9JYix4n4ODkyTpQuu9ra5saKEEZwfbsrLdHwmdN+lrtfGCuF0dhwaFMLNXKGvtJxgUjx6UeRTBvkxii42BVvHCIQEhroAjW/QWBQDmnv4s0rEw4bID1w/OIu3aDRiB9wl7mg+jShJVL2pefuDR4ady2y6jVDIIxR2nBCrRrpmO5DaS3a/fvMeqMJjWgjS+fq4MHVrQgid9IDwdeM7DgE13lviWG1xu7U2nXDVgHEVhhSQ7FMBXXdDdPchLMIHDWg8SDOQzBpxs5LMVTJGbJ47bnlF6A6N1Yp1E8bb5wojF84nDkF1aLqfEmoctrEToUrqL8YKZceT5YG1WYuEMJdAJJWTvSb/doYkrsqltgNKDuZnRvZooq65FWC4st34swb1OTJ3ShFnHjK+aMYf2lECo1gsAchlq2CawHZ4MpYgRsrsiLXu9gfQv/CQYQDHu/ZnfXOkxEMqgCnkLluzeQalqzhb1oXm76ivOEu2dx2y/V4/FnMn+qjMYDuYRY9fciNExphj0LMrgTU/LDbt8K3o225mJrOa6IxgA64u/mhajmwsgWHWpX08od8aipWTiumsvV3VXx2+r4VoQiZuhiEDIONtjwImdZlUyl5tAmOBVeLD0szTkGpZ/tVAPTXpteJDL57gxXSHMCiXltuwvnBbMKxthD6flFrsAVWiZSWUcNI8mr4dnQtnornT7V0xWBQLfpbpkhntlRWofKq52bLMQXKSYUS+JFx5DreC692woqS8YZZUMO1ggsnxs0a813b/ShHt3GMt0CrJwb+V6dCSaazqIVWvxsQdsiMGmf3dZTGTFfMZFS/pu2KFTX2FIoYOz1PjEevNI5qKY0RXNawxZTNU+uVx02xlAhKqu2t32TQzc8BuloaumxjYld4iKpAkV+oL3j9UU+JYHHrO+VyuRitHHOgRHnvR372E6kVW/RD6+PX68O2cejrA2PRqkxdHP24+tV9s/FO1fC3c2udYGBFWVDnBDkxXr/eybXvLRCx0XLdDXSGpu4VN34Qk4mYfOGgkZph140mDYNa+amoUjIVSEM8QA1uFOVQmm16FIIS1FZSN4JqrMg33iyI8YNdSxR71RLZJwIBNQiZcwFm8ybpNuQQb2n2GQQ/+7RdIDCDYjKtgEaAMAwGos0NWnEVHcZdCup+01xjfO18O2D3984aAQui7Q32N8NwtPJtuKKxG7yo+RVtScPblcWJIcvB5Q1xoy512Oxp5IRDuBTU0BHjqw8cxUW4WjXtc1SfmAzu05auQMWjKX62l3DhQyEcUts5xc8MKmUab8RUJzR2g9u9rT8lCDU4KtROTjXHxEVBDSjyjTc5MTKCb2BvzwqaOi3U6gXVNThTQY0Kdi5gZSvg9JuIn4to5KRNZTJqJ7gBTatWtp/DkMJWeNcQpa2hO4eIV6NeBRQUh9Wh6EiMH6XgG992Ef9tgFND8ZQ4/kUIqfw+J22qG75whBj3chlrCAijvOqUMwHcWHM3bgxEGDCyWIIKwCN6hlGYoZGzbPP3zLBEZoN9gmq9C2BxYNhtRVYc0hF/hj1uLZYew7QGlm3GZN3ZqFTpvCimsm0S14HouquCh926aKfnRvn+sLZ+Cpo6eXD7DnpD4ZuLjFGbD6uVjSfG3P6aiamJ866JUXDFNMsocUQsnjQuKZi9DLYcg1pOiqnA2kZTSWvcfoClzp3wsgEo8r0aWQvf/97BpVFZ56FKHFSPF4BqYNCY2PRGN7bfwrq/pm2rKUPgWzEuRQ+CePq1JKZaWy+sGHcFnFoLlo+hTsWcPdTa6fD8PY2xYn7NvWHw8qNnu6Nn63SWzB/dZCLohtUyiQmqBKZ4TS1QPL1R4UaLh8MSE1e6rPLFc0JtsnfsUHQixuDFW+h0tct98Zs2YhUUNbRE+jZar83Mh0R8dkU+BP5+HuTUJRRC0GRuSde2uLxkfo+2Erpl403w0q9ihKgIvTDo7ZAVdWjM7Tcf/sU/gKNuwcLXn0x/czXe1N8ho7QmAHDscg7cCC7caFc0LEpJ/rPDjkYK6gBPL6Z+qGVpcNk9qn5UeXMGOah6kIt+yNUMLfKBZH0o3/DcAJim0zR2EFMXDka/wZGyQoWXTULRFZ/hq4/QHdaY04dD0/677f6CwfFDWDsS49m9k/1mYUe18yLI1Zetpe1WmVMADJNUmRvBWnHIFE/ATGiWizvIo3UbhyI4Rk6HH/4oghAYG7FUYZ7mtMTtYGD1e+lygrVhayYt0RYpzuJDodUMTWqhuaC4hMY0mX1zklF9MrGsKHY5TsUluOolSRmeZacXvv19FX6A3XcQJA4bH7T+YPp1Z8eUu8n6SeIR2nYjk4gRrlxukxtPummPtnzT8rKtVQWZ1lRidJIStSFrNYFnsqZevoCXtkhx6z6N+9ASnePFLQo7J9rbPr4Gh7a9yIweANuVbJl1Gy+SzO74t7IbvmpoLdiHdM7vB+xIPfluOFQ5aNKvSBD0y49w+cK+xnOpqSorQINj7VFtbcr9DJzN6kufb6G4sN+MuPMJumHQ122pCKzVyJLAo3ggmWualUidrak6ati0XvhiV1B0Jcb8BG/+WWwC4qDO1Kyc5gv2aC/rw2Sa05nVIik2q8SH8uykp0eFMgXlt+JcY+Tab8wORpdgP+amDTyMG5kNak9Q9ftFwSBw2csgcJ/YbmhceBr/TbvjTeNcAtxHvxMtkRO7ZZMNnPmw8u6R+tC5AEvAS/nxNxFc0zxn2SdKi0be7iG2XXVZu7SbZHhebOq2DcKXHKRs3gn2AZAwDD/+6+lnUUHLTtro9STzO8SYt1iHCzRaUeZg2oGqmg00ekOcNEYn6bQiFCuIqdAbVkGja9nvhqeji2PdSmaG6Da300GR0U3bgPO3YSXKrqo5ipDM75k3Lk8mVqtZGLW09pc3cWqdYBAG3Z1DsTIac73Rt6Ml+DR3bLACU6MgY8WwSCZyaVO6jDLY7OXV/Bw67z+jmy2LSm826ofOEbnBPh9wcPFii0apcDjwNzAGvhm7Qn4wML2CNhcC021Qhw+ZwQZCY8RkOPoJMXXGDUzVZjCKusYTaMGqZG2kdGrOrGg48M0alBNcvfLceLwtTadMj38neGnb6ZviScW0w5DyWdwo91tmGxtWSOD7NtRPO+Glq3QqdhFtw0Ljuz+y9UarEqgPmK+5AIJj2Ui+lgiu37seH1YiT9DsHc/0+IfQB9E9zwVrEFYJi69dB7UaGi9ehO//1Vemare6iDpiTOMnlowKl2XPBZj33SF2QTRvYmHjRt2iBNb6VGY2JK9zNbch7LpLpzvUFa1ozG2qORoj5ryQngrP9avnJ6tqyivqR0anE3HnhsACcKjXVoHiBEMzBGMRX5IY5w9cGfX8Wje8cfLsNwlFbRoxB42/+2+CTKeq4y6DSnmDVbtBzVM3fT1u/cB9sLwiY8wJhVvs+tYT5BYYpo3OBWgq8ZDrjZP7UpH60WhNLXfn3OUKttMmWbE6GsMofP8nGN7s0EbWlNOq4UFcsp90RlrOfo/GbbAsmDARI8vHGMFNxeW0sZsO+1z6OsGAmY3R8E5STqgzoDF484fg5n3LNac9QZcZHgJ7zUvQEKNdYwzhyrxCSiRJhz/oFyMjN8pbLdb44i5L+SB6U3mKNVrxrZIJZ9kHXrwP3v5BNuRU2R2qGQ61Rvxraar0iF0iSuwbZjpOGgDeV6RqWzHOx9EYcxlV9A8gfZTvQe1IYw5mc4PNTSugWBeNg5vw419Ed+D2xPYUNAXMZ+4wGjOMG2rNQYfv5XgiJjFGwQ17DVH2V6zNjcJhCyKWxih3kOyo/fCWNNgwaybuKdqGi8aoG374c9uF1f0Kxtx0gJrqTiXAc4gxZOf1ExsP1I8nZ65LtiwqlnTaeVvn8GBvqGMqkzkgb96H7/6UwZISumy3HMDEneBegrX5iuPAKuKqdXtpN4rT3oQyzyjqGeh71ybGADqZjNrkzj2tjBzvhy9D6LYLimY0MulR9C6D9/8GV6/FCbRdi9C7GTxlmlKTZ56M6lLlTRtyzfFwuOviLndZd1KdwcZhA+/9S6RcYTWMgtvvwrc5PVbcIxUagQ2Rqq8tXQPWR8bjOb43H2xCgWSfMWcVWOuHS51x8UqTaZpM2DJqG9T65ls3uIxqb4S+Q12xJhrX9HgRfPj3TGT1UXKtbUn8YJdRoVdaHhhbJOuC4Az2FO2izYiwWqwtBmDrr3UQva8zz/dhtmkEjXm7fhe8/7MYvmxS8CPSlBqrM6Y9H1NLZJY5MmeZlb8bUGogEfuZK4HGuL/SnM2I0ZrgH8LNqa5EGSXyK7uZKiTuXFqojcZVz7ATvPw+ePOnTI30RLTrho7rkNWUmsmoyg5RwKmXoYYLlQyths0PHRV3EJqC4ubQn0YoUeypto3eWuOExpW8+uZfIANkZLNWlTjAMUdWq8JZhZ5abn3dOxUvXtL8GHXHgQhxLwuSSEl+SfxsJsZMRs0e3umtWZ2GKFHsM4u/pllVklczQL7/C9x+L8IzzYjbPSuWi6JuXnqN8dkqoxKkKPOYpSAPsAN02cqEozu2TMXD6F1T9zza5wPOQISIrh/pOsPwNnuJ2QgfPuFsVFejg6pvNfDY9T4za50R/XYdesGKiPUpxYctHoVL6CZkUHyKOtdGKA5jPKkFqxve6DaKctqVdN+s2KDqKHFp9wJe/zF4/UcYvKhtWWng++kx08jqwO9Rq9T60jCjYUIQKmMuzTfGzKkgz+vvlCVxym3ILPFTJfiiqh2VvJSLqMk9toMDzFYjGqsD8vb74N2f4eotOEutzWY5EI/XvHU0V7EkLBo8IY5puSiYUhCrBMgltiTGEC73/Dh2tyz3wpe6AjbVspQicYi2vNZGhFVptLALV+/E1TuYPOD4G04euA+kpVU4wQGHrAE0fyvpCymH0xXEpzhPk2kQ9vTEeCXwrmj9WRVdLddezZ4oMQOW3xnLPA1b3RbV60P9fUHDgqSeCFYH5D8BwmHno+sUbSMUOaqjAavmb5sf1b+G7JUmOL4T428iHrsBr2KhAGgSdpbAmkpXzzsq5ZxJo94mybMJimKQbwKQb78BtOSL9B+Mm4c1b77L8dAPX+uqZlRO3D0kFK0EWAGQ0lFBCBevRPZKZjh9zDE5n+xM0Gl0A8a9ES6Vzpfa4bf9W342kCTjCG8AAt2jCeAywYcSNVG/ceFR7EaSXV2YEtze6bukCDPBGR1+Ttjw5iqvEiSZCa7DV2KYY1LMnnH6IGYjgYk47QYVF4AUay0VGTFG0aWeGIeJeOB/A6zbDZbyb4NovIg+Nk6JrYDi7uiRxKQYdFe21vkoVybnzxRV1lPogP8nsEawysZAD0wqlgY+XN2oDC8g116UO6PxWpN4ZIBiJqjMcTdS5l5aJ7hwStXngzNqz5fk0GNlQJYxmd/UC+hcLObOTEwfcT7K/j1FMnRryE6P0o6AidF4EwbQQ5yiHY3ajXTWP1FrfGFo+uhOltkBF53v66Ou7VBkiqM1AUlgMqPK4SsQi52rl5icPYpkXn7ue4UFVBoGGHRpORoxWM1PFyZUZNSx0XhzmYhpLYnzQJOzH711yg92AmfroMgEW31AEpjMWu8KeldCvF9olU95EM/8JKiSr+nRIqvTwrA03lyTxps8ETWDIn5dU57ix9jYb9DJEgMbngQkLxg5yxjq+TaAbj98syNKbCkU9wzI0n3cUuXgJQwWaVmzjCqfF1Q5q0uEjRc1BiP3maLb6MCgJGPFQgc2CBWNMZlE0VDTtbfY8u3IzGbD6INT2W9XcEYt//4c8ylBbs1SZfcKulfi4l0utWYkmWFy/ixOsS0T8xuRtBd21KFWRoVBik+1HpMg7EpYdxht6wY3BmtNhYjT44OiE/U1RZJ6quyI8KXov8wdIUuejJ9FvvvnXioWg42U9N4Lq9V02RZujGbKlqTpPMVERyMBXNaE4n5nYDjs/G53ounRQLEaIBvEJDFmNsMynuwuSrbEkxyQ86ccnLuoxl0x5LvK6pAsYpIrC6Vld388CjS5GoEYymwma2osO2oTZNcO0fTIoFiB93aKye2wUT9/LakyHheokpGcsdOi1W5BqrBQFEOsBWbpwCSZdjq6HhFAB3He/inXCS57+h1pmqLE44NiNUF0F5ikqbJzmb/yaThZwPJeJNMd3ofGSHERzL3NDnIwPRkoNHcwpvMg6OiIMYF7E3nZ6XBrNbWSoGuU6fLcgVE0bRacRwnFygDbEyZzrbKfv3q3C63yaSHBjhq2GTplMTO2jshtp444tLY4fu52NZtJwUDg/X4kzMpj9sN3Bkdig5R43FCsY63ZNSZlrfJGLKdjxpPzTHx9EmncKA7BHSMUbDB0quOvpdCiaySdkncJ80iCYcvnVTe86UevnXB4CjGoByFJGjy7u55okL/E21yTzAA5f4JkXDygcSnV1ANU6XQXCSNpkkzCsE+ri6KDoqXqIojwItqTaHpqUKzPePujykx96t5mL8wtPSPIJdin2sJgtQGgQIl8JjTzoZzBmM5IKK7URXHfzlk0jL4DvdW0WdH0ZKHYICb3Yum5xqXFP0NjBsvsX0x3gTlzWyuKzbeMFYXGpdHacoy98JWTQ1+cduBbGzC5V0tPdJm9ULzLAJnBEuKCmQfq+SFtR8cYrKVTcKNBYU62Wv1MkmlIRYcHueWGvqQDZkuF0B+E7/Z/3rOAYrOYFDsNHsifyUX2yv9YYjIZ5arcLhslnTZKjCkNxQUrBtpvdyA4DqPfuYqmjcir5wXFprhuHwE9RUwmC8E1w+RadnXM3DJ1y3eDyvEA7kxI/EEem6Ra20wA/RRbFNA7CD+47gl1doFvu8Zke6iSin29wDDPcs4ZMlcpm8zbmpt8GG641mHY4OsHkc37ylB0SB3m9OoE1wbvxY6sNR6Kx0SVmwFzQGav7pucIeNHSMdVYCPtGCVJp1w+RFDHMR8YJ5MORKoHOIBhgl/b8PQD6A6N3ovdiaYeijunysZ5cm13DTC6EtEV4hySZ4jvRdVgzsSJEhkhO3p1cdahbgiIrkpt6K4yYm01cxj900FURA/Fo7T0KJjsYHSTvSCdiPgB0meXMnawkE4jefoCSy102zlkyYRp9lLz+kGEC0f/7LBPeRB9qL9tsIfiOVJlaTQM+qLbz+d6OlqQJGtmJyJIuXWO61fZyO2oUTggJcMEnaGIzfTNP+qGt73QWUVsXG/0UDwYVTaIya3gGl5lL5FOIXmA5NFmsIkYWKqTxyVXOk5jEZKd+kIcLI04hMEg/HBwHHooHpIqGxRfy/AOehi8wegVJPeQPgG1CWlGiYsIm2peew5Ky06OOJ12xRXFisNtNIByJuTSHMr/MO9baA5w8wKqp8qGSDK6RXELyROkj7k+WaZEt6XdJasYdMYbdfclEF0dDmuLpjZTTfi70Kgi7o0SPRRPE5OKC+Qye4l0HCQL084i4jQpRtjoi+K4ll00U2iSzikohvoCcBY1rw4u++GbTnjdEhx6KB6B+FqnGrp0bDBIMwbAOEi+xclhLJYp0omageilhKMfLbxYFYfZXVl489+1B4fiIFudni0sXTdyLR5Yfy1YvxWN4fWX+O0U+yuuWu7vsqr1BsVy4LJEarDrQKkDripHFV6Lf9I0Idkvjwun8MbEmlWexbKppp+piG2bIR6Kx4HJOkguvfM4wyQNn5Kru/jlNO3v+evHVAWDhWuxuADQOETNm/wOSyRaTTX7p0QvoB6fSlmtos+m8zTB6RoLicgBOU6Hg+C5F0zZmqGjZVWOlctdGkFPucJOCUb274YVTTkX0T+1x1TjWfF0eNIVwPcT3BLPYgInuGTI21na3cOXTagYvbzODQuHuNqWGyn7DdpJdBB+FwUXLcShZ8Wj50mnElvPc5ylSNLaApDXEc4HwTgCQ0Qrz7gKxT2Epc+1EXC7L4vaCW674W1rn75nxVPgSU7PDBr3q00NcWFEzF9F5sh+ztPOQ3w9Si5Tur6Gy2bp+ksiiTFTF7dXxdYS0dihuF6EcGE11RyQEj0UTweT1j6PU4xT5GhV07T3kLyYpMM1sEC1oGLpIyiVkIPiTxmmkFCWm2CXdW5CGAyj79uMQy+gnpTsath1KwPh4yxdQggKO5mufsUy56UI43SQYXIYPnUgZptHgGNqSan0kYWAaqC1skPfGOcmGWAhN5l+H0DUZhx6VjxWnnQ96ts4TVFrCCkZctaYz4NyMgXyKbnE8l6LGiakvY9lXs2gqLLzjlhxUc7094Fxq+A9Q85D8TRJkvP+JMZxjGUVC8tBn0gSH2YKZPc+fpExJCmprt5BYajdhkrAgEqMC12RVAUJqykW3kQjVw+i78JKiYj7x6eH4qmhUW1fnxNV9lMAKaMRZSAhPCcXT8lV6jZhQL/7YqwKqMxBke1FHITfdYLrlquIHorn0u4naZyuBE77JDcyTIzRY3w9x44ULkeYdQBXL3U0WO8eR8fc0CqiTfkkWzd4ZXVdtAeHHoon3jIQ3k2SLQViGZBY8GwUVEfCz7H8IEUYJZej+BKx1nxNUtpyQ5tqtn9Lrg7Uq76d4HYQfTgiHHoonnj78hzzKIT4XKuDZdojdjJ6zEhy67jfMiGgzWyzBFWq7EewtNzoTKZ8Qozgoh9+bJsq6KF4vu1hmk7miAq9rRvBkIotZ92tPERGj8FTcjlJqkeTI2G5CQ2uC7QsEis+DGAwiL43I83J6OWh6Fu1+Y1b0XSc7Pp0k7Q/ii+U0BwwBAZs8qcSFYorAZWVE0x+vnDl/7PZhdhanvQu/qNHHdk+j+IEl0aVwtyWKgkvtUcQhRi1jcIomWDWb5RjAxZ/ZsJqguEweg7BDfkEFPPZqPIh6dcnm92V30IV0bPiCeJw82Yumsbp3i4mY8WMG+erxA7QkBhgKTVZMaKCqFPuye7KbzMOPRRPsGUY/PYcr93fskpIesuLCqEq/hX8j6vRkNDiMpg9J4NxMqB1OBLAyoISQF9rLzV583MNMcOh1ZXfZhx6AfUEKTETTVPKoV/ObgJjH3nS08nCxT0zFr1naTfDZD+cBKs3NLMcVv+nmITQwAwc2rKBRStNpp4VTxmH95N0PE/LnIYrTpPYTqXLlXFVYyRRD1VIKnt3nnSe42FqqoWzhWiq7KwYwBA3fLj5zcSH2LdlA4u2mkw9FE+2zRL8OoppsVDjGKfzAJFV0IIOIl9kIa/RKKNv9YKCkllXoc1wWC2kpoU86aF4IpSYUeGvT7HxYCoym65DQWzMhFyILlI60nA0v0gkJ8fWZrO5BhWKkE9Ia5w3bvjwZHDooXg6oum353i6TA1WAtYEZYMxcmPBlkPCVLLl0NyYR5DHwwRNE0z1Z+SWGzYfdoIXJ4NDD8UTweFoli5iTWm4seLAUSusImO7Q1TIdEF6SzSG2dyXCXnFkKghVRsf5iGmGQ6rIaq19hsPxaNvcZqJpvOy4V/+VRVKN34O1dVhVCRVeVVGmXxEHj4eD5I00BNjLAuo9lSpDIf98KMVUS13XXgoniAl/vwwS1I2AXIYsmpfpBRNXKAxTcOCxigbfyQBtWMgbjxdHHooHj0Ov4wyFZHwg0ukJ3OjTGZKuLco+Q9MEeQk4W4JczNEjsb+Go0yK4qUgffVeN089em7k8Shh+Lxq4jPyZF8kRyNqhWHX/F1icPjVQU9FE+2ZWT46XGOatiaZINRUoEp4+qmBz9KDpWsYqQsqxInIzzPy2gseflDuBAyfxf5sDIOjwKfHopHSYkpigyHSYrH9o1gMu8VEz8QTfvNYBN8eCw86aF4lKLpr0/zQu7FmpaKpKZEjaFKj0i6+1HVMzVRcubyHEiW58hY8XnWx0IhRqFRP5djd84Dhx6KR4nDu3HyMEmO99tlaJzErK1yMj4cngcOPRSPr43n6a+P8y1ZKblNhJ1Tdhiq+1JgqQgj6TAkdrTYGlephYOqELV+c55EGzTGMjEGK3XxjPjQQ/H4KDFO8ce7GbmjWVnRovzzxK6ghl0NjTEvKvA4ptAi4GdJJwOkrmcIg0H4/nxwKHy+4nGZan74NksK2IMiGkGulLH6tTAn1wcCKIDCbaWNctZiYVeNUnkOUPazQQQQcsUOsanGUb42QBjPe0GQ7/Em5D2AQ+hfRL83bwx8Yjj0rHhMKuKnx/nUUCZDH0SqpSbujtk2iuMcq+HY51k/kd+PguFF9Idzw6GH4tG0+0lyv9z4QhvvqfME6uLTJBRzIsiRqpTKUVZ1oy0TOIpiagakfvjuDHHooXgclJh78x/mZSShYnHRDUxyY9nqg6gMr6Y1opq0qElStpUhl4w7TkA6SRx6KB6HivjT/SzF6rtH6CRV0ximLXxR1Iw7p6B75jj0ZpsjUBF/eZhN5qlYm0mKIiSgUth0nQW4nuDLf9afFGs/gWTyWb6Ba6sNyB9tjyju/FYYCFE93Qb8ki1ne63u2DlhHHpWbHt7nGYqos2bz2Uo5NNURdLjOT/UjRzr7HB+Gjj0rNhqSpwn+NPCiwiKhLlmSJTICgq/CoL0Sn4OUXRIrMcqdYPSwQUyI/wcy3Ekqhb2MuQM3J88Dj0rtlo0zVTEBP1NOgscelZsb/ttFD9NE7HeU1RinjUjydrjWhdcMk1Rx2PvmSH5K0BRSoWkPcrEuz01yA6UYkRB+XSLdzuhx6FnxRaLpnmg6Xm0QA+q88GhZ8WWiqY/3k0TVF0HWyosaY9m42qBKwvdCtbOzRbCkqlTsqCWSY8kXtWyKijjKhauAJqzo3pW9K1hcH4dxaNZej5fvB85UN+p4tCzYosocdly0fRhVlQFhcRSK4UQ5SlJ9S+GjUt2SoXnZNotD70lQFgz9PYvIL9dkSiBOl1ZezxzPvSs2EbR9Ge21dS814UhGs4eDm4NpdvYd+yns+Rd9aNzRJ2HYtvbw0RKz0fU5wEqBTRIfBZKZJjKehMh3RRk6SBvHf55pwvYkDt5cHoBtS2UuIg1ncrehK1wB9QMhxUorcLqeozSDt7KhopyxiMVr4ZFj8jyL81wgvJzKMIq6ck4Q0r0rNgicH5+ms/i0uYzUjkKvaxJSJREfURz4BtqhV6SeQ0iLvKqASypOgpqRYF7VvSteWvNl6cZhZFy+nw5omxlHyEMLuRoIJRAOGOUnCbmTbXlFPwc5MUSoeh5p24AfmJ4VmyRtSZ3JKZGm4wwqXtaXRHtxh6yR1M5UNZxuoonQ11PzkRe9ax4eHCOFukXK699gXm2vxZCrjl+DqEGbUuh2ChkX0NZ/6QcGJu3wMSNaxUXldSt4sWsv47Oo+hZ0bcDiKZikZHoPmAtsqr3XXjnYXTrhh6KnhVbA87fnuPHaSEjscA8hTjvLTeuuQtkY2YRA1utTIogF8XIOTmbWOij5Mjk5CIzU8F0q88LUQVEQAFgx0PRs2KLKPF+erZ3ZtCpnjHsWdG3RilxsTtiKXxabJKJSpQi2Se3Md+oGCaFVDSqXNGCSrCiI8iFqTxH+SJV7XGrpVIR5B0A1ZNxnh5Fz4qHp8SfH86XEvsdL516VmwNJc7icgqUUJx1AkvaXVEBW5kiFQokdDs6glxITCuHjWvLc6hlyEFxdxb9oVveLhBvP/I04Fnx6CiRjPM0lg+2DUO/g7wDzcE9ghpQPeSi61nRs2ILwPl1NJ/OUU0ykotfIBTZTo5lAUGbQdHIjUJTngMILZEwrhZpExnlOWR63Kq43QgC8FD0rNgCSvzpfkYWOSyRHsoSpZnBikfYQ0M19GXl1Ur3onz2yy4x8c42zsZD8ZCUOMs3oqFL6AstGjXCKrmLBmqj56jEKELItMfZsffMUIE58DYbD8U2UOKP9zNUpnNpY1GK3VDI+3LrJjoWvf064l1lPFrCVkvbd2suitj5Bin9cvleFEDP22w8FA8OzsdpsqBESyq9kGFpkDbRKlI2VUy1oWi7i66fdd5s0wZKvJtuzCRrw8i2DhqA7LVf/q4EXm92qiiOo40gF1Y/x1o1U4iRKkMOJQOPoK+wsN2qkGIArnpeUfSseGhwzhJ8XJTMQJtBBSvthYHVaIwXwW33nqDBnrRqvchLp54VW0CJP9xNUJTDwCSywkLyEpDbn21pCDaFRxVvBJQ8GJsIciEFkRdVPlhxqak8ByuIvBT8Lfs5rnqhnySeFQ8MziQV357jM78bXjr1rHj49vlpFifFeDOJHose81UsNUI5n1jRzaSNnwrx2avRlAhyUQgiJ7RHLIRxK2XIy/tA6cpzlATZwkVedMPQV9DwrHgQ6bT4zs/uKcLnQIm+eVbcaxvN0nGcgiiTFcgZT7IWKfFLUWajIsiFUCo+SYFz28FN5TlKgW/FKDnJsrpVZe3lOZbn6wRwSSmK4CPgPCvu02BzzvlQy/ZiwDXYnCc4PSvuA5xJil9G83Sh3IEcV13KOdrSImwMpBuFrUxhUODBwvaJUmEoKs67yI3LkSlbLtFZomVATaUNNYI8rwIurvvedupZ8dDty3M8T1dQQlLILLxpcfJpYtk2P5h5T+RgTu5N5FW7Wo5w3aMNNl469VDcq8Emo8RVfOk67FP3Wk/f4j6E2g1nUIPG0ttIRpCSG1wYy5Ajrww56em/HXLlr7MFp4fizluc4ufnFRQX6RiYLn8pxXzLcdVYyHLA8kTHZRNKBDkVKU5tR0MCjUKXgl57TKu0z9Ti500/9MXdPBTbIJ3ORYn6CikRGlbcorHEdjKZaTtgOZtq/Q5qubEEQD0gCTQiqty7HffVRcQkwHOWV73ZZg/SaSw0Fo615aRsLYF1fW3Vz1E0h6wiysjpW4g0lw0o1F4YcoS5beMNws9RRHypDPnNwFOiZ8V2SKeZoijKdFd4IWszDIOJBVGbua+PYTWcD5u9A68vOl4n9KzYAuk0xyGRQYTbGDepHE2gEFcZHnL58KKfA9bbVdBoXEepITWO2NAjyNu/qcVOhZSTRfg5Cj6X20HEp8QzB6dnxR1Lp89zYWQmkvo2ihzqeIpKWOJkCTN3khJGQrZosGtABgCvLyOPOs+K7WFFRXvb8k3e0gKbQJEbFzS1VQuB1gKLKmShMqliESlXaqPLkENZmbRtYoWF0eQy5LdDh+BvD07PirvEYYkSz6llcumby46fA54VWyGd3o1jk/yHUsnsJTcGi8PTJROumUINIhfKnhkbjpW0x0J5DiXwrcBmmzLk8gaP29GUCHJhK8/x9qrLJ0BPiZ4V9yadnle76IY+4tSzYlvaJE4nccqwkEDRwppKNLhSvgLqIIJjNRHkIDsmVW6UsqnW+cqq43GzwaO5DHkI8OGm4ynRs2KbpVMDILd/lLyOZPJVybKKawsLGsoQK28Wo234EeSidIRiPH05jLph4G0znhXb0u4m/DI25Q1+ixiDFT2uZnGwpqmSirglt0KZC1GyrIK85ZOQTiNVglS26BDlTaxkPXPdtx8Fb68crDUenJ4Vdw/FcaWKUkqdbVzzLRmtqnamq4yvO5GpIeo4vDLkxL4A373oetR5VjwuRZELTLBVFhVk2KjKaVadFYlOkua5+V0z+JvLTr/jsLh7cHootkdR1OCL3ou76MAvSosCCxuOlsLWiln8qp+jDHssRZAL069yLPlF1ySaetRZ2/8XYADH/GSQm72TNQAAAABJRU5ErkJggg==" alt="Yii Framework"/> - <p class="timestamp"><?php echo date('Y-m-d, H:i:s'); ?></p> - <p><?php echo $handler->createServerInformationLink(); ?></p> - <p><a href="http://yiiframework.com/">Yii Framework</a>/<?php echo $handler->createFrameworkVersionLink(); ?></p> + <p class="timestamp"><?= date('Y-m-d, H:i:s') ?></p> + <p><?= $handler->createServerInformationLink() ?></p> + <p><a href="http://yiiframework.com/">Yii Framework</a>/<?= $handler->createFrameworkVersionLink() ?></p> </div> <script type="text/javascript"> diff --git a/framework/yii/views/errorHandler/previousException.php b/framework/yii/views/errorHandler/previousException.php index e6dcf87..766c0a7 100644 --- a/framework/yii/views/errorHandler/previousException.php +++ b/framework/yii/views/errorHandler/previousException.php @@ -9,13 +9,13 @@ <h2> <span>Caused by:</span> <?php if ($exception instanceof \yii\base\Exception): ?> - <span><?php echo $handler->htmlEncode($exception->getName()); ?></span> – - <?php echo $handler->addTypeLinks(get_class($exception)); ?> + <span><?= $handler->htmlEncode($exception->getName()) ?></span> – + <?= $handler->addTypeLinks(get_class($exception)) ?> <?php else: ?> - <span><?php echo $handler->htmlEncode(get_class($exception)); ?></span> + <span><?= $handler->htmlEncode(get_class($exception)) ?></span> <?php endif; ?> </h2> - <h3><?php echo $handler->htmlEncode($exception->getMessage()); ?></h3> - <p>in <span class="file"><?php echo $exception->getFile(); ?></span> at line <span class="line"><?php echo $exception->getLine(); ?></span></p> - <?php echo $handler->renderPreviousExceptions($exception); ?> + <h3><?= nl2br($handler->htmlEncode($exception->getMessage())) ?></h3> + <p>in <span class="file"><?= $exception->getFile() ?></span> at line <span class="line"><?= $exception->getLine() ?></span></p> + <?= $handler->renderPreviousExceptions($exception) ?> </div> diff --git a/framework/yii/views/messageConfig.php b/framework/yii/views/messageConfig.php index d0d515a..9babb0c 100644 --- a/framework/yii/views/messageConfig.php +++ b/framework/yii/views/messageConfig.php @@ -1,13 +1,13 @@ <?php -return array( +return [ // string, required, root directory of all source files 'sourcePath' => __DIR__, // string, required, root directory containing message translations. 'messagePath' => __DIR__ . DIRECTORY_SEPARATOR . 'messages', // array, required, list of language codes that the extracted messages - // should be translated to. For example, array('zh_cn', 'de'). - 'languages' => array('de'), + // should be translated to. For example, ['zh_cn', 'de']. + 'languages' => ['de'], // string, the name of the function for translating messages. // Defaults to 'Yii::t'. This is used as a mark to find the messages to be // translated. You may use a string for single function name or an array for @@ -29,11 +29,11 @@ return array( // and the '.svn' will match all files and directories whose name ends with '.svn'. // Note, the '/' characters in a pattern matches both '/' and '\'. // If a file/directory matches both a pattern in "only" and "except", it will NOT be processed. - 'only' => array('.php'), + 'only' => ['.php'], // array, list of patterns that specify which files/directories should NOT be processed. // If empty or not set, all files/directories will be processed. // Please refer to "only" for details about the patterns. - 'except' => array( + 'except' => [ '.svn', '.git', '.gitignore', @@ -41,5 +41,5 @@ return array( '.hgignore', '.hgkeep', '/messages', - ), -); + ], +]; diff --git a/framework/yii/views/migration.php b/framework/yii/views/migration.php index a75e22f..f6add4d 100644 --- a/framework/yii/views/migration.php +++ b/framework/yii/views/migration.php @@ -8,7 +8,9 @@ echo "<?php\n"; ?> -class <?php echo $className; ?> extends \yii\db\Migration +use yii\db\Schema; + +class <?= $className ?> extends \yii\db\Migration { public function up() { @@ -17,7 +19,7 @@ class <?php echo $className; ?> extends \yii\db\Migration public function down() { - echo "<?php echo $className; ?> cannot be reverted.\n"; + echo "<?= $className ?> cannot be reverted.\n"; return false; } } diff --git a/framework/yii/web/AccessControl.php b/framework/yii/web/AccessControl.php index 7f791b8..549f087 100644 --- a/framework/yii/web/AccessControl.php +++ b/framework/yii/web/AccessControl.php @@ -26,25 +26,25 @@ use yii\base\ActionFilter; * ~~~ * public function behaviors() * { - * return array( - * 'access' => array( + * return [ + * 'access' => [ * 'class' => \yii\web\AccessControl::className(), - * 'only' => array('create', 'update'), - * 'rules' => array( + * 'only' => ['create', 'update'], + * 'rules' => [ * // deny all POST requests - * array( + * [ * 'allow' => false, - * 'verbs' => array('POST') - * ), + * 'verbs' => ['POST'] + * ], * // allow authenticated users - * array( + * [ * 'allow' => true, - * 'roles' => array('@'), - * ), + * 'roles' => ['@'], + * ], * // everything else is denied - * ), - * ), - * ); + * ], + * ], + * ]; * } * ~~~ * @@ -70,16 +70,14 @@ class AccessControl extends ActionFilter * @var array the default configuration of access rules. Individual rule configurations * specified via [[rules]] will take precedence when the same property of the rule is configured. */ - public $ruleConfig = array( - 'class' => 'yii\web\AccessRule', - ); + public $ruleConfig = ['class' => 'yii\web\AccessRule']; /** * @var array a list of access rule objects or configuration arrays for creating the rule objects. * If a rule is specified via a configuration array, it will be merged with [[ruleConfig]] first * before it is used for creating the rule object. * @see ruleConfig */ - public $rules = array(); + public $rules = []; /** * Initializes the [[rules]] array by instantiating rule objects from configurations. @@ -104,7 +102,7 @@ class AccessControl extends ActionFilter { $user = Yii::$app->getUser(); $request = Yii::$app->getRequest(); - /** @var $rule AccessRule */ + /** @var AccessRule $rule */ foreach ($this->rules as $rule) { if ($allow = $rule->allows($action, $user, $request)) { return true; @@ -119,6 +117,12 @@ class AccessControl extends ActionFilter return false; } } + if (isset($this->denyCallback)) { + call_user_func($this->denyCallback, $rule); + } + else { + $this->denyAccess($user); + } return false; } diff --git a/framework/yii/web/Application.php b/framework/yii/web/Application.php index 5fd2310..13b5588 100644 --- a/framework/yii/web/Application.php +++ b/framework/yii/web/Application.php @@ -38,14 +38,14 @@ class Application extends \yii\base\Application * to the action. For example, * * ~~~ - * array( + * [ * 'offline/notice', * 'param1' => 'value1', * 'param2' => 'value2', - * ) + * ] * ~~~ * - * Defaults to null, meaning catch-all is not effective. + * Defaults to null, meaning catch-all is not used. */ public $catchAll; /** @@ -167,22 +167,12 @@ class Application extends \yii\base\Application public function registerCoreComponents() { parent::registerCoreComponents(); - $this->setComponents(array( - 'request' => array( - 'class' => 'yii\web\Request', - ), - 'response' => array( - 'class' => 'yii\web\Response', - ), - 'session' => array( - 'class' => 'yii\web\Session', - ), - 'user' => array( - 'class' => 'yii\web\User', - ), - 'assetManager' => array( - 'class' => 'yii\web\AssetManager', - ), - )); + $this->setComponents([ + 'request' => ['class' => 'yii\web\Request'], + 'response' => ['class' => 'yii\web\Response'], + 'session' => ['class' => 'yii\web\Session'], + 'user' => ['class' => 'yii\web\User'], + 'assetManager' => ['class' => 'yii\web\AssetManager'], + ]); } } diff --git a/framework/yii/web/AssetBundle.php b/framework/yii/web/AssetBundle.php index d324ef3..964fe98 100644 --- a/framework/yii/web/AssetBundle.php +++ b/framework/yii/web/AssetBundle.php @@ -10,13 +10,14 @@ namespace yii\web; use Yii; use yii\base\InvalidConfigException; use yii\base\Object; -use yii\base\View; +use yii\web\View; /** * AssetBundle represents a collection of asset files, such as CSS, JS, images. * - * Each asset bundle has a unique name that globally identifies it among all asset bundles - * used in an application. + * Each asset bundle has a unique name that globally identifies it among all asset bundles used in an application. + * The name is the [fully qualified class name](http://php.net/manual/en/language.namespaces.rules.php) + * of the class representing it. * * An asset bundle can depend on other asset bundles. When registering an asset bundle * with a view, all its dependent asset bundles will be automatically registered. @@ -67,9 +68,18 @@ class AssetBundle extends Object */ public $baseUrl; /** - * @var array list of the bundle names that this bundle depends on + * @var array list of bundle class names that this bundle depends on. + * + * For example: + * + * ```php + * public $depends = [ + * 'yii\web\YiiAsset', + * 'yii\bootstrap\BootstrapAsset', + * ]; + * ``` */ - public $depends = array(); + public $depends = []; /** * @var array list of JavaScript files that this bundle contains. Each JavaScript file can * be either a file path (without leading slash) relative to [[basePath]] or a URL representing @@ -77,7 +87,7 @@ class AssetBundle extends Object * * Note that only forward slash "/" can be used as directory separator. */ - public $js = array(); + public $js = []; /** * @var array list of CSS files that this bundle contains. Each CSS file can * be either a file path (without leading slash) relative to [[basePath]] or a URL representing @@ -85,22 +95,22 @@ class AssetBundle extends Object * * Note that only forward slash "/" can be used as directory separator. */ - public $css = array(); + public $css = []; /** - * @var array the options that will be passed to [[\yii\base\View::registerJsFile()]] + * @var array the options that will be passed to [[\yii\web\View::registerJsFile()]] * when registering the JS files in this bundle. */ - public $jsOptions = array(); + public $jsOptions = []; /** - * @var array the options that will be passed to [[\yii\base\View::registerCssFile()]] + * @var array the options that will be passed to [[\yii\web\View::registerCssFile()]] * when registering the CSS files in this bundle. */ - public $cssOptions = array(); + public $cssOptions = []; /** * @var array the options to be passed to [[AssetManager::publish()]] when the asset bundle * is being published. */ - public $publishOptions = array(); + public $publishOptions = []; /** * @param View $view @@ -130,31 +140,30 @@ class AssetBundle extends Object /** * Registers the CSS and JS files with the given view. - * This method will first register all dependent asset bundles. - * It will then try to convert non-CSS or JS files (e.g. LESS, Sass) into the corresponding - * CSS or JS files using [[AssetManager::converter|asset converter]]. - * @param \yii\base\View $view the view that the asset files to be registered with. - * @throws InvalidConfigException if [[baseUrl]] or [[basePath]] is not set when the bundle - * contains internal CSS or JS files. + * @param \yii\web\View $view the view that the asset files are to be registered with. */ - public function registerAssets($view) + public function registerAssetFiles($view) { - foreach ($this->depends as $name) { - $view->registerAssetBundle($name); - } - - $this->publish($view->getAssetManager()); - foreach ($this->js as $js) { - $view->registerJsFile($this->baseUrl . '/' . $js, $this->jsOptions); + if (strpos($js, '/') !== 0 && strpos($js, '://') === false) { + $view->registerJsFile($this->baseUrl . '/' . $js, $this->jsOptions); + } else { + $view->registerJsFile($js, $this->jsOptions); + } } foreach ($this->css as $css) { - $view->registerCssFile($this->baseUrl . '/' . $css, $this->cssOptions); + if (strpos($css, '/') !== 0 && strpos($css, '://') === false) { + $view->registerCssFile($this->baseUrl . '/' . $css, $this->cssOptions); + } else { + $view->registerCssFile($css, $this->cssOptions); + } } } /** * Publishes the asset bundle if its source code is not under Web-accessible directory. + * It will also try to convert non-CSS or JS files (e.g. LESS, Sass) into the corresponding + * CSS or JS files using [[AssetManager::converter|asset converter]]. * @param AssetManager $am the asset manager to perform the asset publishing * @throws InvalidConfigException if [[baseUrl]] or [[basePath]] is not set when the bundle * contains internal CSS or JS files. diff --git a/framework/yii/web/AssetConverter.php b/framework/yii/web/AssetConverter.php index 420a5bc..1b7d1c8 100644 --- a/framework/yii/web/AssetConverter.php +++ b/framework/yii/web/AssetConverter.php @@ -9,10 +9,13 @@ namespace yii\web; use Yii; use yii\base\Component; +use yii\base\Exception; /** * AssetConverter supports conversion of several popular script formats into JS or CSS scripts. * + * It is used by [[AssetManager]] to convert files after they have been published. + * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ @@ -23,12 +26,14 @@ class AssetConverter extends Component implements AssetConverterInterface * The keys are the asset file extension names, and the values are the corresponding * target script types (either "css" or "js") and the commands used for the conversion. */ - public $commands = array( - 'less' => array('css', 'lessc {from} {to}'), - 'scss' => array('css', 'sass {from} {to}'), - 'sass' => array('css', 'sass {from} {to}'), - 'styl' => array('js', 'stylus < {from} > {to}'), - ); + public $commands = [ + 'less' => ['css', 'lessc {from} {to} --no-color'], + 'scss' => ['css', 'sass {from} {to}'], + 'sass' => ['css', 'sass {from} {to}'], + 'styl' => ['js', 'stylus < {from} > {to}'], + 'coffee' => ['js', 'coffee -p {from} > {to}'], + 'ts' => ['js', 'tsc --out {to} {from}'], + ]; /** * Converts a given asset file into a CSS or JS file. @@ -45,17 +50,50 @@ class AssetConverter extends Component implements AssetConverterInterface list ($ext, $command) = $this->commands[$ext]; $result = substr($asset, 0, $pos + 1) . $ext; if (@filemtime("$basePath/$result") < filemtime("$basePath/$asset")) { - $output = array(); - $command = strtr($command, array( - '{from}' => escapeshellarg("$basePath/$asset"), - '{to}' => escapeshellarg("$basePath/$result"), - )); - exec($command, $output); - Yii::trace("Converted $asset into $result: " . implode("\n", $output), __METHOD__); + $this->runCommand($command, $basePath, $asset, $result); } return $result; } } return $asset; } + + /** + * Runs a command to convert asset files. + * @param string $command the command to run + * @param string $basePath asset base path and command working directory + * @param string $asset the name of the asset file + * @param string $result the name of the file to be generated by the converter command + * @return bool true on success, false on failure. Failures will be logged. + * @throws \yii\base\Exception when the command fails and YII_DEBUG is true. + * In production mode the error will be logged. + */ + protected function runCommand($command, $basePath, $asset, $result) + { + $command = strtr($command, [ + '{from}' => escapeshellarg("$basePath/$asset"), + '{to}' => escapeshellarg("$basePath/$result"), + ]); + $descriptor = [ + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'], + ]; + $pipes = []; + $proc = proc_open($command, $descriptor, $pipes, $basePath); + $stdout = stream_get_contents($pipes[1]); + $stderr = stream_get_contents($pipes[2]); + foreach($pipes as $pipe) { + fclose($pipe); + } + $status = proc_close($proc); + + if ($status === 0) { + Yii::trace("Converted $asset into $result:\nSTDOUT:\n$stdout\nSTDERR:\n$stderr", __METHOD__); + } elseif (YII_DEBUG) { + throw new Exception("AssetConverter command '$command' failed with exit code $status:\nSTDOUT:\n$stdout\nSTDERR:\n$stderr"); + } else { + Yii::error("AssetConverter command '$command' failed with exit code $status:\nSTDOUT:\n$stdout\nSTDERR:\n$stderr"); + } + return $status === 0; + } } diff --git a/framework/yii/web/AssetManager.php b/framework/yii/web/AssetManager.php index 500848b..0a8b0b8 100644 --- a/framework/yii/web/AssetManager.php +++ b/framework/yii/web/AssetManager.php @@ -16,8 +16,24 @@ use yii\helpers\FileHelper; /** * AssetManager manages asset bundles and asset publishing. * - * @property AssetConverterInterface $converter The asset converter. Note that the type of this property differs in - * getter and setter. See [[getConverter()]] and [[setConverter()]] for details. + * AssetManager is configured as an application component in [[yii\web\Application]] by default. + * You can access that instance via `Yii::$app->assetManager`. + * + * You can modify its configuration by adding an array to your application config under `components` + * as it is shown in the following example: + * + * ~~~ + * 'assetManager' => [ + * 'bundles' => [ + * // you can override AssetBundle configs here + * ], + * //'linkAssets' => true, + * // ... + * ] + * ~~~ + * + * @property AssetConverterInterface $converter The asset converter. Note that the type of this property + * differs in getter and setter. See [[getConverter()]] and [[setConverter()]] for details. * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 @@ -25,11 +41,20 @@ use yii\helpers\FileHelper; class AssetManager extends Component { /** - * @var array list of available asset bundles. The keys are the class names of the asset bundles, - * and the values are either the configuration arrays for creating the [[AssetBundle]] objects - * or the corresponding asset bundle instances. + * @var array list of available asset bundles. The keys are the class names (without leading backslash) + * of the asset bundles, and the values are either the configuration arrays for creating the [[AssetBundle]] + * objects or the corresponding asset bundle instances. For example, the following code disables + * the bootstrap css file used by Bootstrap widgets (because you want to use your own styles): + * + * ~~~ + * [ + * 'yii\bootstrap\BootstrapAsset' => [ + * 'css' => [], + * ], + * ] + * ~~~ */ - public $bundles = array(); + public $bundles = []; /** * @return string the root directory storing the published asset files. */ @@ -95,21 +120,29 @@ class AssetManager extends Component * it will treat `$name` as the class of the asset bundle and create a new instance of it. * * @param string $name the class name of the asset bundle + * @param boolean $publish whether to publish the asset files in the asset bundle before it is returned. + * If you set this false, you must manually call `AssetBundle::publish()` to publish the asset files. * @return AssetBundle the asset bundle instance * @throws InvalidConfigException if $name does not refer to a valid asset bundle */ - public function getBundle($name) + public function getBundle($name, $publish = true) { if (isset($this->bundles[$name])) { - if (is_array($this->bundles[$name])) { - $this->bundles[$name] = Yii::createObject(array_merge(array('class' => $name), $this->bundles[$name])); - } elseif (!$this->bundles[$name] instanceof AssetBundle) { + if ($this->bundles[$name] instanceof AssetBundle) { + return $this->bundles[$name]; + } elseif (is_array($this->bundles[$name])) { + $bundle = Yii::createObject(array_merge(array('class' => $name), $this->bundles[$name])); + } else { throw new InvalidConfigException("Invalid asset bundle: $name"); } } else { - $this->bundles[$name] = Yii::createObject($name); + $bundle = Yii::createObject($name); + } + if ($publish) { + /** @var AssetBundle $bundle */ + $bundle->publish($this); } - return $this->bundles[$name]; + return $this->bundles[$name] = $bundle; } private $_converter; @@ -142,7 +175,7 @@ class AssetManager extends Component /** * @var array published assets */ - private $_published = array(); + private $_published = []; /** * Publishes a file or a directory. @@ -188,7 +221,7 @@ class AssetManager extends Component * @return array the path (directory or file path) and the URL that the asset is published as. * @throws InvalidParamException if the asset to be published does not exist. */ - public function publish($path, $options = array()) + public function publish($path, $options = []) { if (isset($this->_published[$path])) { return $this->_published[$path]; @@ -220,7 +253,7 @@ class AssetManager extends Component } } - return $this->_published[$path] = array($dstFile, $this->baseUrl . "/$dir/$fileName"); + return $this->_published[$path] = [$dstFile, $this->baseUrl . "/$dir/$fileName"]; } else { $dir = $this->hash($src . filemtime($src)); $dstDir = $this->basePath . DIRECTORY_SEPARATOR . $dir; @@ -229,10 +262,10 @@ class AssetManager extends Component symlink($src, $dstDir); } } elseif (!is_dir($dstDir) || !empty($options['forceCopy'])) { - $opts = array( + $opts = [ 'dirMode' => $this->dirMode, 'fileMode' => $this->fileMode, - ); + ]; if (isset($options['beforeCopy'])) { $opts['beforeCopy'] = $options['beforeCopy']; } else { @@ -246,7 +279,7 @@ class AssetManager extends Component FileHelper::copyDirectory($src, $dstDir, $opts); } - return $this->_published[$path] = array($dstDir, $this->baseUrl . '/' . $dir); + return $this->_published[$path] = [$dstDir, $this->baseUrl . '/' . $dir]; } } diff --git a/framework/yii/web/CacheSession.php b/framework/yii/web/CacheSession.php index b4ce2ae..7b4a98d 100644 --- a/framework/yii/web/CacheSession.php +++ b/framework/yii/web/CacheSession.php @@ -19,7 +19,17 @@ use yii\base\InvalidConfigException; * * Beware, by definition cache storage are volatile, which means the data stored on them * may be swapped out and get lost. Therefore, you must make sure the cache used by this component - * is NOT volatile. If you want to use database as storage medium, use [[DbSession]] is a better choice. + * is NOT volatile. If you want to use database as storage medium, [[DbSession]] is a better choice. + * + * The following example shows how you can configure the application to use CacheSession: + * Add the following to your application config under `components`: + * + * ~~~ + * 'session' => [ + * 'class' => 'yii\web\CacheSession', + * // 'cache' => 'mycache', + * ] + * ~~~ * * @property boolean $useCustomStorage Whether to use custom storage. This property is read-only. * @@ -103,6 +113,6 @@ class CacheSession extends Session */ protected function calculateKey($id) { - return array(__CLASS__, $id); + return [__CLASS__, $id]; } } diff --git a/framework/yii/web/Controller.php b/framework/yii/web/Controller.php index 6b8afa4..71e7793 100644 --- a/framework/yii/web/Controller.php +++ b/framework/yii/web/Controller.php @@ -14,6 +14,9 @@ use yii\helpers\Html; /** * Controller is the base class of web controllers. * + * @property string $canonicalUrl The canonical URL of the currently requested page. This property is + * read-only. + * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ @@ -24,6 +27,10 @@ class Controller extends \yii\base\Controller * CSRF validation is enabled only when both this property and [[Request::enableCsrfValidation]] are true. */ public $enableCsrfValidation = true; + /** + * @var array the parameters bound to the current action. This is mainly used by [[getCanonicalUrl()]]. + */ + public $actionParams = []; /** * Binds the parameters to the action. @@ -34,7 +41,7 @@ class Controller extends \yii\base\Controller * @param \yii\base\Action $action the action to be bound with parameters * @param array $params the parameters to be bound to the action * @return array the valid parameters that the action can run with. - * @throws HttpException if there are missing parameters. + * @throws HttpException if there are missing or invalid parameters. */ public function bindActionParams($action, $params) { @@ -44,25 +51,36 @@ class Controller extends \yii\base\Controller $method = new \ReflectionMethod($action, 'run'); } - $args = array(); - $missing = array(); + $args = []; + $missing = []; + $actionParams = []; foreach ($method->getParameters() as $param) { $name = $param->getName(); if (array_key_exists($name, $params)) { - $args[] = $params[$name]; + if ($param->isArray()) { + $args[] = $actionParams[$name] = is_array($params[$name]) ? $params[$name] : [$params[$name]]; + } elseif (!is_array($params[$name])) { + $args[] = $actionParams[$name] = $params[$name]; + } else { + throw new HttpException(400, Yii::t('yii', 'Invalid data received for parameter "{param}".', [ + 'param' => $name, + ])); + } unset($params[$name]); } elseif ($param->isDefaultValueAvailable()) { - $args[] = $param->getDefaultValue(); + $args[] = $actionParams[$name] = $param->getDefaultValue(); } else { $missing[] = $name; } } if (!empty($missing)) { - throw new HttpException(400, Yii::t('yii', 'Missing required parameters: {params}', array( - '{params}' => implode(', ', $missing), - ))); + throw new HttpException(400, Yii::t('yii', 'Missing required parameters: {params}', [ + 'params' => implode(', ', $missing), + ])); } + + $this->actionParams = $actionParams; return $args; } @@ -100,7 +118,7 @@ class Controller extends \yii\base\Controller * @param array $params the parameters (name-value pairs) to be included in the generated URL * @return string the created URL */ - public function createUrl($route, $params = array()) + public function createUrl($route, $params = []) { if (strpos($route, '/') === false) { // empty or an action ID @@ -113,31 +131,62 @@ class Controller extends \yii\base\Controller } /** + * Returns the canonical URL of the currently requested page. + * The canonical URL is constructed using [[route]] and [[actionParams]]. You may use the following code + * in the layout view to add a link tag about canonical URL: + * + * ~~~ + * $this->registerLinkTag(['rel' => 'canonical', 'href' => Yii::$app->controller->canonicalUrl]); + * ~~~ + * + * @return string the canonical URL of the currently requested page + */ + public function getCanonicalUrl() + { + return Yii::$app->getUrlManager()->createAbsoluteUrl($this->getRoute(), $this->actionParams); + } + + /** * Redirects the browser to the specified URL. * This method is a shortcut to [[Response::redirect()]]. * + * You can use it in an action by returning the [[Response]] directly: + * + * ```php + * // stop executing this action and redirect to login page + * return $this->redirect(['login']); + * ``` + * * @param string|array $url the URL to be redirected to. This can be in one of the following formats: * * - a string representing a URL (e.g. "http://example.com") * - a string representing a URL alias (e.g. "@example.com") - * - an array in the format of `array($route, ...name-value pairs...)` (e.g. `array('site/index', 'ref' => 1)`) + * - an array in the format of `[$route, ...name-value pairs...]` (e.g. `['site/index', 'ref' => 1]`) * [[Html::url()]] will be used to convert the array into a URL. * * Any relative URL will be converted into an absolute one by prepending it with the host info * of the current request. * - * @param integer $statusCode the HTTP status code. If null, it will use 302. + * @param integer $statusCode the HTTP status code. Defaults to 302. * See [[http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html]] * for details about HTTP status code * @return Response the current response object */ - public function redirect($url, $statusCode = null) + public function redirect($url, $statusCode = 302) { return Yii::$app->getResponse()->redirect(Html::url($url), $statusCode); } /** * Redirects the browser to the home page. + * + * You can use this method in an action by returning the [[Response]] directly: + * + * ```php + * // stop executing this action and redirect to home page + * return $this->goHome(); + * ``` + * * @return Response the current response object */ public function goHome() @@ -146,8 +195,37 @@ class Controller extends \yii\base\Controller } /** + * Redirects the browser to the last visited page. + * + * You can use this method in an action by returning the [[Response]] directly: + * + * ```php + * // stop executing this action and redirect to last visited page + * return $this->goBack(); + * ``` + * + * @param string|array $defaultUrl the default return URL in case it was not set previously. + * If this is null and the return URL was not set previously, [[Application::homeUrl]] will be redirected to. + * Please refer to [[User::setReturnUrl()]] on accepted format of the URL. + * @return Response the current response object + * @see User::getReturnUrl() + */ + public function goBack($defaultUrl = null) + { + return Yii::$app->getResponse()->redirect(Yii::$app->getUser()->getReturnUrl($defaultUrl)); + } + + /** * Refreshes the current page. * This method is a shortcut to [[Response::refresh()]]. + * + * You can use it in an action by returning the [[Response]] directly: + * + * ```php + * // stop executing this action and refresh the current page + * return $this->refresh(); + * ``` + * * @param string $anchor the anchor that should be appended to the redirection URL. * Defaults to empty. Make sure the anchor starts with '#' if you want to specify it. * @return Response the response object itself diff --git a/framework/yii/web/CookieCollection.php b/framework/yii/web/CookieCollection.php index 6940493..3cf80ff 100644 --- a/framework/yii/web/CookieCollection.php +++ b/framework/yii/web/CookieCollection.php @@ -32,7 +32,7 @@ class CookieCollection extends Object implements \IteratorAggregate, \ArrayAcces /** * @var Cookie[] the cookies in this collection (indexed by the cookie names) */ - private $_cookies = array(); + private $_cookies = []; /** * Constructor. @@ -40,7 +40,7 @@ class CookieCollection extends Object implements \IteratorAggregate, \ArrayAcces * an array of name-value pairs.s * @param array $config name-value pairs that will be used to initialize the object properties */ - public function __construct($cookies = array(), $config = array()) + public function __construct($cookies = [], $config = []) { $this->_cookies = $cookies; parent::__construct($config); @@ -141,10 +141,10 @@ class CookieCollection extends Object implements \IteratorAggregate, \ArrayAcces $cookie->expire = 1; $cookie->value = ''; } else { - $cookie = new Cookie(array( + $cookie = new Cookie([ 'name' => $cookie, 'expire' => 1, - )); + ]); } if ($removeFromBrowser) { $this->_cookies[$cookie->name] = $cookie; @@ -162,7 +162,7 @@ class CookieCollection extends Object implements \IteratorAggregate, \ArrayAcces if ($this->readOnly) { throw new InvalidCallException('The cookie collection is read only.'); } - $this->_cookies = array(); + $this->_cookies = []; } /** diff --git a/framework/yii/web/DbSession.php b/framework/yii/web/DbSession.php index c508bb8..d5d1742 100644 --- a/framework/yii/web/DbSession.php +++ b/framework/yii/web/DbSession.php @@ -19,13 +19,14 @@ use yii\base\InvalidConfigException; * must be pre-created. The table name can be changed by setting [[sessionTable]]. * * The following example shows how you can configure the application to use DbSession: + * Add the following to your application config under `components`: * * ~~~ - * 'session' => array( + * 'session' => [ * 'class' => 'yii\web\DbSession', * // 'db' => 'mydb', * // 'sessionTable' => 'my_session', - * ) + * ] * ~~~ * * @property boolean $useCustomStorage Whether to use custom storage. This property is read-only. @@ -111,13 +112,13 @@ class DbSession extends Session $query = new Query; $row = $query->from($this->sessionTable) - ->where(array('id' => $oldID)) + ->where(['id' => $oldID]) ->createCommand($this->db) ->queryOne(); if ($row !== false) { if ($deleteOldSession) { $this->db->createCommand() - ->update($this->sessionTable, array('id' => $newID), array('id' => $oldID)) + ->update($this->sessionTable, ['id' => $newID], ['id' => $oldID]) ->execute(); } else { $row['id'] = $newID; @@ -128,10 +129,10 @@ class DbSession extends Session } else { // shouldn't reach here normally $this->db->createCommand() - ->insert($this->sessionTable, array( + ->insert($this->sessionTable, [ 'id' => $newID, 'expire' => time() + $this->getTimeout(), - ))->execute(); + ])->execute(); } } @@ -144,9 +145,9 @@ class DbSession extends Session public function readSession($id) { $query = new Query; - $data = $query->select(array('data')) + $data = $query->select(['data']) ->from($this->sessionTable) - ->where('[[expire]]>:expire AND [[id]]=:id', array(':expire' => time(), ':id' => $id)) + ->where('[[expire]]>:expire AND [[id]]=:id', [':expire' => time(), ':id' => $id]) ->createCommand($this->db) ->queryScalar(); return $data === false ? '' : $data; @@ -166,21 +167,21 @@ class DbSession extends Session try { $expire = time() + $this->getTimeout(); $query = new Query; - $exists = $query->select(array('id')) + $exists = $query->select(['id']) ->from($this->sessionTable) - ->where(array('id' => $id)) + ->where(['id' => $id]) ->createCommand($this->db) ->queryScalar(); if ($exists === false) { $this->db->createCommand() - ->insert($this->sessionTable, array( + ->insert($this->sessionTable, [ 'id' => $id, 'data' => $data, 'expire' => $expire, - ))->execute(); + ])->execute(); } else { $this->db->createCommand() - ->update($this->sessionTable, array('data' => $data, 'expire' => $expire), array('id' => $id)) + ->update($this->sessionTable, ['data' => $data, 'expire' => $expire], ['id' => $id]) ->execute(); } } catch (\Exception $e) { @@ -202,7 +203,7 @@ class DbSession extends Session public function destroySession($id) { $this->db->createCommand() - ->delete($this->sessionTable, array('id' => $id)) + ->delete($this->sessionTable, ['id' => $id]) ->execute(); return true; } @@ -216,7 +217,7 @@ class DbSession extends Session public function gcSession($maxLifetime) { $this->db->createCommand() - ->delete($this->sessionTable, '[[expire]]<:expire', array(':expire' => time())) + ->delete($this->sessionTable, '[[expire]]<:expire', [':expire' => time()]) ->execute(); return true; } diff --git a/framework/yii/web/ErrorAction.php b/framework/yii/web/ErrorAction.php index 3dd2823..bdbb4c5 100644 --- a/framework/yii/web/ErrorAction.php +++ b/framework/yii/web/ErrorAction.php @@ -23,11 +23,9 @@ use yii\base\UserException; * ```php * public function actions() * { - * return array( - * 'error' => array( - * 'class' => 'yii\web\ErrorAction', - * ), - * ); + * return [ + * 'error' => ['class' => 'yii\web\ErrorAction'], + * ]; * } * ``` * @@ -41,9 +39,9 @@ use yii\base\UserException; * Finally, configure the "errorHandler" application component as follows, * * ```php - * 'errorHandler' => array( + * 'errorHandler' => [ * 'errorAction' => 'site/error', - * ) + * ] * ``` * * @author Qiang Xue <qiang.xue@gmail.com> @@ -98,11 +96,11 @@ class ErrorAction extends Action if (Yii::$app->getRequest()->getIsAjax()) { return "$name: $message"; } else { - return $this->controller->render($this->view ?: $this->id, array( + return $this->controller->render($this->view ?: $this->id, [ 'name' => $name, 'message' => $message, 'exception' => $exception, - )); + ]); } } } diff --git a/framework/yii/web/HeaderCollection.php b/framework/yii/web/HeaderCollection.php index 609058b..e8e4f9c 100644 --- a/framework/yii/web/HeaderCollection.php +++ b/framework/yii/web/HeaderCollection.php @@ -26,7 +26,7 @@ class HeaderCollection extends Object implements \IteratorAggregate, \ArrayAcces /** * @var array the headers in this collection (indexed by the header names) */ - private $_headers = array(); + private $_headers = []; /** * Returns an iterator for traversing the headers in the collection. @@ -83,7 +83,7 @@ class HeaderCollection extends Object implements \IteratorAggregate, \ArrayAcces * If there is already a header with the same name, it will be replaced. * @param string $name the name of the header * @param string $value the value of the header - * @return HeaderCollection the collection object itself + * @return static the collection object itself */ public function set($name, $value = '') { @@ -98,7 +98,7 @@ class HeaderCollection extends Object implements \IteratorAggregate, \ArrayAcces * be appended to it instead of replacing it. * @param string $name the name of the header * @param string $value the value of the header - * @return HeaderCollection the collection object itself + * @return static the collection object itself */ public function add($name, $value) { @@ -112,7 +112,7 @@ class HeaderCollection extends Object implements \IteratorAggregate, \ArrayAcces * If there is already a header with the same name, the new one will be ignored. * @param string $name the name of the header * @param string $value the value of the header - * @return HeaderCollection the collection object itself + * @return static the collection object itself */ public function setDefault($name, $value) { @@ -156,7 +156,7 @@ class HeaderCollection extends Object implements \IteratorAggregate, \ArrayAcces */ public function removeAll() { - $this->_headers = array(); + $this->_headers = []; } /** diff --git a/framework/yii/web/HttpCache.php b/framework/yii/web/HttpCache.php index d2f3923..134df71 100644 --- a/framework/yii/web/HttpCache.php +++ b/framework/yii/web/HttpCache.php @@ -12,7 +12,32 @@ use yii\base\ActionFilter; use yii\base\Action; /** - * The HttpCache provides functionality for caching via HTTP Last-Modified and Etag headers + * The HttpCache provides functionality for caching via HTTP Last-Modified and Etag headers. + * + * It is an action filter that can be added to a controller and handles the `beforeAction` event. + * + * To use AccessControl, declare it in the `behaviors()` method of your controller class. + * In the following example the filter will be applied to the `list`-action and + * the Last-Modified header will contain the date of the last update to the user table in the database. + * + * ~~~ + * public function behaviors() + * { + * return [ + * 'httpCache' => [ + * 'class' => \yii\web\HttpCache::className(), + * 'only' => ['list'], + * 'lastModified' => function ($action, $params) { + * $q = new Query(); + * return strtotime($q->from('users')->max('updated_timestamp')); + * }, + * // 'etagSeed' => function ($action, $params) { + * // return // generate etag seed here + * // } + * ], + * ]; + * } + * ~~~ * * @author Da:Sourcerer <webmaster@dasourcerer.net> * @author Qiang Xue <qiang.xue@gmail.com> diff --git a/framework/yii/web/HttpException.php b/framework/yii/web/HttpException.php index 2e677d5..2398437 100644 --- a/framework/yii/web/HttpException.php +++ b/framework/yii/web/HttpException.php @@ -16,6 +16,14 @@ use yii\base\UserException; * keeps a standard HTTP status code (e.g. 404, 500). Error handlers may use this status code * to decide how to format the error page. * + * Throwing an HttpException like in the following example will result in the 404 page to be displayed. + * + * ```php + * if ($item === null) { // item does not exist + * throw new \yii\web\HttpException(404, 'The requested Item could not be found.'); + * } + * ``` + * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ diff --git a/framework/yii/web/JqueryAsset.php b/framework/yii/web/JqueryAsset.php index 9991eb4..90d2df6 100644 --- a/framework/yii/web/JqueryAsset.php +++ b/framework/yii/web/JqueryAsset.php @@ -15,8 +15,8 @@ namespace yii\web; */ class JqueryAsset extends AssetBundle { - public $sourcePath = '@yii/assets'; - public $js = array( + public $sourcePath = '@vendor/yiisoft/jquery'; + public $js = [ 'jquery.js', - ); + ]; } diff --git a/framework/yii/web/JsExpression.php b/framework/yii/web/JsExpression.php index 7daac08..1d05b57 100644 --- a/framework/yii/web/JsExpression.php +++ b/framework/yii/web/JsExpression.php @@ -30,7 +30,7 @@ class JsExpression extends Object * @param string $expression the JavaScript expression represented by this object * @param array $config additional configurations for this object */ - public function __construct($expression, $config = array()) + public function __construct($expression, $config = []) { $this->expression = $expression; parent::__construct($config); diff --git a/framework/yii/web/PageCache.php b/framework/yii/web/PageCache.php index 9bc8981..4c8cc50 100644 --- a/framework/yii/web/PageCache.php +++ b/framework/yii/web/PageCache.php @@ -10,12 +10,40 @@ namespace yii\web; use Yii; use yii\base\ActionFilter; use yii\base\Action; -use yii\base\View; use yii\caching\Dependency; /** * The PageCache provides functionality for whole page caching * + * It is an action filter that can be added to a controller and handles the `beforeAction` event. + * + * To use PageCache, declare it in the `behaviors()` method of your controller class. + * In the following example the filter will be applied to the `list`-action and + * cache the whole page for maximum 60 seconds or until the count of entries in the post table changes. + * It also stores different versions of the page depended on the route ([[varyByRoute]] is true by default), + * the application language and user id. + * + * ~~~ + * public function behaviors() + * { + * return [ + * 'pageCache' => [ + * 'class' => \yii\web\PageCache::className(), + * 'only' => ['list'], + * 'duration' => 60, + * 'dependecy' => [ + * 'class' => 'yii\caching\DbDependency', + * 'sql' => 'SELECT COUNT(*) FROM post', + * ], + * 'variations' => [ + * Yii::$app->language, + * Yii::$app->user->id + * ] + * ], + * ]; + * } + * ~~~ + * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ @@ -41,10 +69,10 @@ class PageCache extends ActionFilter * For example, * * ~~~ - * array( + * [ * 'class' => 'yii\caching\DbDependency', * 'sql' => 'SELECT MAX(lastModified) FROM Post', - * ) + * ] * ~~~ * * would make the output cache depends on the last modified time of all posts. @@ -58,9 +86,10 @@ class PageCache extends ActionFilter * according to the current application language: * * ~~~ - * array( + * [ * Yii::$app->language, - * ) + * ] + * ~~~ */ public $variations; /** @@ -69,7 +98,7 @@ class PageCache extends ActionFilter */ public $enabled = true; /** - * @var View the view component to use for caching. If not set, the default application view component + * @var \yii\base\View the view component to use for caching. If not set, the default application view component * [[Application::view]] will be used. */ public $view; @@ -91,8 +120,8 @@ class PageCache extends ActionFilter */ public function beforeAction($action) { - $properties = array(); - foreach (array('cache', 'duration', 'dependency', 'variations', 'enabled') as $name) { + $properties = []; + foreach (['cache', 'duration', 'dependency', 'variations', 'enabled'] as $name) { $properties[$name] = $this->$name; } $id = $this->varyByRoute ? $action->getUniqueId() : __CLASS__; diff --git a/framework/yii/web/Request.php b/framework/yii/web/Request.php index 4fb6257..7a7cedf 100644 --- a/framework/yii/web/Request.php +++ b/framework/yii/web/Request.php @@ -18,6 +18,9 @@ use yii\helpers\Security; * Also it provides an interface to retrieve request parameters from $_POST, $_GET, $_COOKIES and REST * parameters sent via other HTTP methods like PUT or DELETE. * + * Request is configured as an application component in [[yii\web\Application]] by default. + * You can access that instance via `Yii::$app->request`. + * * @property string $absoluteUrl The currently requested absolute URL. This property is read-only. * @property string $acceptTypes User browser accept types, null if not present. This property is read-only. * @property array $acceptedContentTypes The content types ordered by the preference level. The first element @@ -29,6 +32,9 @@ use yii\helpers\Security; * previously, a random key will be generated and used. * @property CookieCollection $cookies The cookie collection. This property is read-only. * @property string $csrfToken The random token for CSRF validation. This property is read-only. + * @property string $csrfTokenFromHeader The CSRF token sent via [[CSRF_HEADER]] by browser. Null is returned + * if no such header is sent. This property is read-only. + * @property array $delete The DELETE request parameter values. This property is read-only. * @property string $hostInfo Schema and hostname part (with port number if needed) of the request URL (e.g. * `http://www.yiiframework.com`). * @property boolean $isAjax Whether this is an AJAX (XMLHttpRequest) request. This property is read-only. @@ -45,11 +51,14 @@ use yii\helpers\Security; * read-only. * @property string $method Request method, such as GET, POST, HEAD, PUT, PATCH, DELETE. The value returned is * turned into upper case. This property is read-only. + * @property array $patch The PATCH request parameter values. This property is read-only. * @property string $pathInfo Part of the request URL that is after the entry script and before the question * mark. Note, the returned path info is already URL-decoded. * @property integer $port Port number for insecure requests. + * @property array $post The POST request parameter values. This property is read-only. * @property string $preferredLanguage The language that the application should use. Null is returned if both * [[getAcceptedLanguages()]] and `$languages` are empty. This property is read-only. + * @property array $put The PUT request parameter values. This property is read-only. * @property string $queryString Part of the request URL that is after the question mark. This property is * read-only. * @property string $rawBody The request body. This property is read-only. @@ -75,6 +84,7 @@ class Request extends \yii\base\Request */ const CSRF_HEADER = 'X-CSRF-Token'; + /** * @var boolean whether to enable CSRF (Cross-Site Request Forgery) validation. Defaults to true. * When CSRF validation is enabled, forms submitted to an Yii Web application must be originated @@ -100,7 +110,7 @@ class Request extends \yii\base\Request * @var array the configuration of the CSRF cookie. This property is used only when [[enableCsrfValidation]] is true. * @see Cookie */ - public $csrfCookie = array('httpOnly' => true); + public $csrfCookie = ['httpOnly' => true]; /** * @var boolean whether cookies should be validated to ensure they are not tampered. Defaults to true. */ @@ -108,8 +118,8 @@ class Request extends \yii\base\Request /** * @var string|boolean the name of the POST parameter that is used to indicate if a request is a PUT, PATCH or DELETE * request tunneled through POST. Default to '_method'. - * @see getMethod - * @see getRestParams + * @see getMethod() + * @see getRestParams() */ public $restVar = '_method'; @@ -127,7 +137,7 @@ class Request extends \yii\base\Request if ($result !== false) { list ($route, $params) = $result; $_GET = array_merge($_GET, $params); - return array($route, $_GET); + return [$route, $_GET]; } else { throw new HttpException(404, Yii::t('yii', 'Page not found.')); } @@ -234,7 +244,7 @@ class Request extends \yii\base\Request /** * Returns the request parameters for the RESTful request. * @return array the RESTful request parameters - * @see getMethod + * @see getMethod() */ public function getRestParams() { @@ -242,12 +252,8 @@ class Request extends \yii\base\Request if (isset($_POST[$this->restVar])) { $this->_restParams = $_POST; } else { - $this->_restParams = array(); - if (function_exists('mb_parse_str')) { - mb_parse_str($this->getRawBody(), $this->_restParams); - } else { - parse_str($this->getRawBody(), $this->_restParams); - } + $this->_restParams = []; + mb_parse_str($this->getRawBody(), $this->_restParams); } } return $this->_restParams; @@ -291,59 +297,78 @@ class Request extends \yii\base\Request /** * Returns the named GET parameter value. * If the GET parameter does not exist, the second parameter to this method will be returned. - * @param string $name the GET parameter name + * @param string $name the GET parameter name. If not specified, whole $_GET is returned. * @param mixed $defaultValue the default parameter value if the GET parameter does not exist. * @return mixed the GET parameter value - * @see getPost + * @see getPost() */ - public function get($name, $defaultValue = null) + public function get($name = null, $defaultValue = null) { + if ($name === null) { + return $_GET; + } return isset($_GET[$name]) ? $_GET[$name] : $defaultValue; } /** * Returns the named POST parameter value. * If the POST parameter does not exist, the second parameter to this method will be returned. - * @param string $name the POST parameter name + * @param string $name the POST parameter name. If not specified, whole $_POST is returned. * @param mixed $defaultValue the default parameter value if the POST parameter does not exist. + * @property array the POST request parameter values * @return mixed the POST parameter value - * @see getParam + * @see get() */ - public function getPost($name, $defaultValue = null) + public function getPost($name = null, $defaultValue = null) { + if ($name === null) { + return $_POST; + } return isset($_POST[$name]) ? $_POST[$name] : $defaultValue; } /** * Returns the named DELETE parameter value. - * @param string $name the DELETE parameter name + * @param string $name the DELETE parameter name. If not specified, an array of DELETE parameters is returned. * @param mixed $defaultValue the default parameter value if the DELETE parameter does not exist. + * @property array the DELETE request parameter values * @return mixed the DELETE parameter value */ - public function getDelete($name, $defaultValue = null) + public function getDelete($name = null, $defaultValue = null) { + if ($name === null) { + return $this->getRestParams(); + } return $this->getIsDelete() ? $this->getRestParam($name, $defaultValue) : null; } /** * Returns the named PUT parameter value. - * @param string $name the PUT parameter name + * @param string $name the PUT parameter name. If not specified, an array of PUT parameters is returned. * @param mixed $defaultValue the default parameter value if the PUT parameter does not exist. + * @property array the PUT request parameter values * @return mixed the PUT parameter value */ - public function getPut($name, $defaultValue = null) + public function getPut($name = null, $defaultValue = null) { + if ($name === null) { + return $this->getRestParams(); + } return $this->getIsPut() ? $this->getRestParam($name, $defaultValue) : null; } /** * Returns the named PATCH parameter value. - * @param string $name the PATCH parameter name + * @param string $name the PATCH parameter name. If not specified, an array of PATCH parameters is returned. * @param mixed $defaultValue the default parameter value if the PATCH parameter does not exist. + * @property array the PATCH request parameter values * @return mixed the PATCH parameter value */ - public function getPatch($name, $defaultValue = null) + public function getPatch($name = null, $defaultValue = null) { + if ($name === null) { + return $this->getRestParams(); + } return $this->getIsPatch() ? $this->getRestParam($name, $defaultValue) : null; } @@ -355,7 +380,7 @@ class Request extends \yii\base\Request * By default this is determined based on the user request information. * You may explicitly specify it by setting the [[setHostInfo()|hostInfo]] property. * @return string schema and hostname part (with port number if needed) of the request URL (e.g. `http://www.yiiframework.com`) - * @see setHostInfo + * @see setHostInfo() */ public function getHostInfo() { @@ -394,7 +419,7 @@ class Request extends \yii\base\Request * This is similar to [[scriptUrl]] except that it does not include the script file name, * and the ending slashes are removed. * @return string the relative URL for the application - * @see setScriptUrl + * @see setScriptUrl() */ public function getBaseUrl() { @@ -711,7 +736,7 @@ class Request extends \yii\base\Request * Defaults to 80, or the port specified by the server if the current * request is insecure. * @return integer port number for insecure requests. - * @see setPort + * @see setPort() */ public function getPort() { @@ -742,7 +767,7 @@ class Request extends \yii\base\Request * Defaults to 443, or the port specified by the server if the current * request is secure. * @return integer port number for secure requests. - * @see setSecurePort + * @see setSecurePort() */ public function getSecurePort() { @@ -780,7 +805,7 @@ class Request extends \yii\base\Request if (isset($_SERVER['HTTP_ACCEPT'])) { $this->_contentTypes = $this->parseAcceptHeader($_SERVER['HTTP_ACCEPT']); } else { - $this->_contentTypes = array(); + $this->_contentTypes = []; } } return $this->_contentTypes; @@ -809,7 +834,7 @@ class Request extends \yii\base\Request if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { $this->_languages = $this->parseAcceptHeader($_SERVER['HTTP_ACCEPT_LANGUAGE']); } else { - $this->_languages = array(); + $this->_languages = []; } } return $this->_languages; @@ -832,11 +857,11 @@ class Request extends \yii\base\Request */ protected function parseAcceptHeader($header) { - $accepts = array(); + $accepts = []; $n = preg_match_all('/\s*([\w\/\-\*]+)\s*(?:;\s*q\s*=\s*([\d\.]+))?[^,]*/', $header, $matches, PREG_SET_ORDER); for ($i = 0; $i < $n; ++$i) { if (!empty($matches[$i][1])) { - $accepts[] = array($matches[$i][1], isset($matches[$i][2]) ? (float)$matches[$i][2] : 1, $i); + $accepts[] = [$matches[$i][1], isset($matches[$i][2]) ? (float)$matches[$i][2] : 1, $i]; } } usort($accepts, function ($a, $b) { @@ -860,7 +885,7 @@ class Request extends \yii\base\Request } } }); - $result = array(); + $result = []; foreach ($accepts as $accept) { $result[] = $accept[0]; } @@ -876,18 +901,18 @@ class Request extends \yii\base\Request * @return string the language that the application should use. Null is returned if both [[getAcceptedLanguages()]] * and `$languages` are empty. */ - public function getPreferredLanguage($languages = array()) + public function getPreferredLanguage($languages = []) { $acceptedLanguages = $this->getAcceptedLanguages(); if (empty($languages)) { return isset($acceptedLanguages[0]) ? $acceptedLanguages[0] : null; } foreach ($acceptedLanguages as $acceptedLanguage) { - $acceptedLanguage = str_replace('-', '_', strtolower($acceptedLanguage)); + $acceptedLanguage = str_replace('_', '-', strtolower($acceptedLanguage)); foreach ($languages as $language) { - $language = str_replace('-', '_', strtolower($language)); - // en_us==en_us, en==en_us, en_us==en - if ($language === $acceptedLanguage || strpos($acceptedLanguage, $language . '_') === 0 || strpos($language, $acceptedLanguage . '_') === 0) { + $language = str_replace('_', '-', strtolower($language)); + // en-us==en-us, en==en-us, en-us==en + if ($language === $acceptedLanguage || strpos($acceptedLanguage, $language . '-') === 0 || strpos($language, $acceptedLanguage . '-') === 0) { return $language; } } @@ -914,9 +939,9 @@ class Request extends \yii\base\Request public function getCookies() { if ($this->_cookies === null) { - $this->_cookies = new CookieCollection($this->loadCookies(), array( + $this->_cookies = new CookieCollection($this->loadCookies(), [ 'readOnly' => true, - )); + ]); } return $this->_cookies; } @@ -927,23 +952,23 @@ class Request extends \yii\base\Request */ protected function loadCookies() { - $cookies = array(); + $cookies = []; if ($this->enableCookieValidation) { $key = $this->getCookieValidationKey(); foreach ($_COOKIE as $name => $value) { if (is_string($value) && ($value = Security::validateData($value, $key)) !== false) { - $cookies[$name] = new Cookie(array( + $cookies[$name] = new Cookie([ 'name' => $name, 'value' => @unserialize($value), - )); + ]); } } } else { foreach ($_COOKIE as $name => $value) { - $cookies[$name] = new Cookie(array( + $cookies[$name] = new Cookie([ 'name' => $name, 'value' => $value, - )); + ]); } } return $cookies; @@ -1029,7 +1054,7 @@ class Request extends \yii\base\Request public function validateCsrfToken() { $method = $this->getMethod(); - if (!$this->enableCsrfValidation || !in_array($method, array('POST', 'PUT', 'PATCH', 'DELETE'), true)) { + if (!$this->enableCsrfValidation || !in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE'], true)) { return true; } $trueToken = $this->getCookies()->getValue($this->csrfVar); diff --git a/framework/yii/web/Response.php b/framework/yii/web/Response.php index e6505fd..3ebd358 100644 --- a/framework/yii/web/Response.php +++ b/framework/yii/web/Response.php @@ -22,6 +22,20 @@ use yii\helpers\StringHelper; * It holds the [[headers]], [[cookies]] and [[content]] that is to be sent to the client. * It also controls the HTTP [[statusCode|status code]]. * + * Response is configured as an application component in [[yii\web\Application]] by default. + * You can access that instance via `Yii::$app->response`. + * + * You can modify its configuration by adding an array to your application config under `components` + * as it is shown in the following example: + * + * ~~~ + * 'response' => [ + * 'format' => yii\web\Response::FORMAT_JSON, + * 'charset' => 'UTF-8', + * // ... + * ] + * ~~~ + * * @property CookieCollection $cookies The cookie collection. This property is read-only. * @property HeaderCollection $headers The header collection. This property is read-only. * @property boolean $isClientError Whether this response indicates a client error. This property is @@ -112,9 +126,10 @@ class Response extends \yii\base\Response */ public $charset; /** - * @var string + * @var string the HTTP status description that comes together with the status code. + * @see [[httpStatuses]] */ - public $statusText; + public $statusText = 'OK'; /** * @var string the version of the HTTP protocol to use. If not set, it will be determined via `$_SERVER['SERVER_PROTOCOL']`, * or '1.1' if that is not available. @@ -123,7 +138,7 @@ class Response extends \yii\base\Response /** * @var array list of HTTP status codes and the corresponding texts */ - public static $httpStatuses = array( + public static $httpStatuses = [ 100 => 'Continue', 101 => 'Switching Protocols', 102 => 'Processing', @@ -189,12 +204,12 @@ class Response extends \yii\base\Response 509 => 'Bandwidth Limit Exceeded', 510 => 'Not Extended', 511 => 'Network Authentication Required', - ); + ]; /** * @var integer the HTTP status code to send with the response. */ - private $_statusCode; + private $_statusCode = 200; /** * @var HeaderCollection */ @@ -235,16 +250,14 @@ class Response extends \yii\base\Response public function setStatusCode($value, $text = null) { if ($value === null) { - $this->_statusCode = null; - $this->statusText = null; - return; + $value = 200; } $this->_statusCode = (int)$value; if ($this->getIsInvalid()) { throw new InvalidParamException("The HTTP status code is invalid: $value"); } if ($text === null) { - $this->statusText = isset(self::$httpStatuses[$this->_statusCode]) ? self::$httpStatuses[$this->_statusCode] : ''; + $this->statusText = isset(static::$httpStatuses[$this->_statusCode]) ? static::$httpStatuses[$this->_statusCode] : ''; } else { $this->statusText = $text; } @@ -283,10 +296,10 @@ class Response extends \yii\base\Response { $this->_headers = null; $this->_cookies = null; - $this->_statusCode = null; + $this->_statusCode = 200; + $this->statusText = 'OK'; $this->data = null; $this->content = null; - $this->statusText = null; } /** @@ -298,9 +311,7 @@ class Response extends \yii\base\Response return; } $statusCode = $this->getStatusCode(); - if ($statusCode !== null) { - header("HTTP/{$this->version} $statusCode {$this->statusText}"); - } + header("HTTP/{$this->version} $statusCode {$this->statusText}"); if ($this->_headers) { $headers = $this->getHeaders(); foreach ($headers as $name => $values) { @@ -463,7 +474,7 @@ class Response extends \yii\base\Response protected function getHttpRange($fileSize) { if (!isset($_SERVER['HTTP_RANGE']) || $_SERVER['HTTP_RANGE'] === '-') { - return array(0, $fileSize - 1); + return [0, $fileSize - 1]; } if (!preg_match('/^bytes=(\d*)-(\d*)$/', $_SERVER['HTTP_RANGE'], $matches)) { return false; @@ -484,7 +495,7 @@ class Response extends \yii\base\Response if ($start < 0 || $start > $end) { return false; } else { - return array($start, $end); + return [$start, $end]; } } @@ -559,7 +570,20 @@ class Response extends \yii\base\Response /** * Redirects the browser to the specified URL. * - * This method will send out a "Location" header to achieve the redirection. + * This method adds a "Location" header to the current response. Note that it does not send out + * the header until [[send()]] is called. In a controller action you may use this method as follows: + * + * ~~~ + * return Yii::$app->getResponse()->redirect($url); + * ~~~ + * + * In other places, if you want to send out the "Location" header immediately, you should use + * the following code: + * + * ~~~ + * Yii::$app->getResponse()->redirect($url)->send(); + * return; + * ~~~ * * In AJAX mode, this normally will not work as expected unless there are some * client-side JavaScript code handling the redirection. To help achieve this goal, @@ -578,29 +602,23 @@ class Response extends \yii\base\Response * }); * ~~~ * - * In a controller action you may use this method like this: - * - * ~~~ - * return Yii::$app->getResponse()->redirect($url); - * ~~~ - * * @param string|array $url the URL to be redirected to. This can be in one of the following formats: * * - a string representing a URL (e.g. "http://example.com") * - a string representing a URL alias (e.g. "@example.com") - * - an array in the format of `array($route, ...name-value pairs...)` (e.g. `array('site/index', 'ref' => 1)`). + * - an array in the format of `[$route, ...name-value pairs...]` (e.g. `['site/index', 'ref' => 1]`). * Note that the route is with respect to the whole application, instead of relative to a controller or module. * [[Html::url()]] will be used to convert the array into a URL. * * Any relative URL will be converted into an absolute one by prepending it with the host info * of the current request. * - * @param integer $statusCode the HTTP status code. If null, it will use 302. + * @param integer $statusCode the HTTP status code. Defaults to 302. * See [[http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html]] * for details about HTTP status code - * @return Response the response object itself + * @return static the response object itself */ - public function redirect($url, $statusCode = null) + public function redirect($url, $statusCode = 302) { if (is_array($url) && isset($url[0])) { // ensure the route is absolute @@ -649,10 +667,10 @@ class Response extends \yii\base\Response * * ~~~ * // add a cookie - * $response->cookies->add(new Cookie(array( + * $response->cookies->add(new Cookie([ * 'name' => $name, * 'value' => $value, - * )); + * ]); * * // remove a cookie * $response->cookies->remove('name'); @@ -747,7 +765,7 @@ class Response extends \yii\base\Response */ public function getIsEmpty() { - return in_array($this->getStatusCode(), array(201, 204, 304)); + return in_array($this->getStatusCode(), [201, 204, 304]); } /** diff --git a/framework/yii/web/ResponseEvent.php b/framework/yii/web/ResponseEvent.php index e5d4210..dabaf2f 100644 --- a/framework/yii/web/ResponseEvent.php +++ b/framework/yii/web/ResponseEvent.php @@ -31,7 +31,7 @@ class ResponseEvent extends Event * @param Response $response the response object associated with this event. * @param array $config the configuration array for initializing the newly created object. */ - public function __construct($response, $config = array()) + public function __construct($response, $config = []) { $this->response = $response; parent::__construct($config); diff --git a/framework/yii/web/Session.php b/framework/yii/web/Session.php index 92ec3ad..894d75d 100644 --- a/framework/yii/web/Session.php +++ b/framework/yii/web/Session.php @@ -15,7 +15,7 @@ use yii\base\InvalidParamException; * Session provides session data management and the related configurations. * * Session is a Web application component that can be accessed via `Yii::$app->session`. - + * * To start the session, call [[open()]]; To complete and send out session data, call [[close()]]; * To destroy the session, call [[destroy()]]. * @@ -46,7 +46,7 @@ use yii\base\InvalidParamException; * call methods such as [[setFlash()]], [[getFlash()]]. * * @property array $allFlashes Flash messages (key => message). This property is read-only. - * @property array $cookieParams The session cookie parameters. + * @property array $cookieParams The session cookie parameters. This property is read-only. * @property integer $count The number of session variables. This property is read-only. * @property string $flash The key identifying the flash message. Note that flash messages and normal session * variables share the same name space. If you have a normal session variable using the same name, its value will @@ -80,13 +80,12 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co * @var string the name of the session variable that stores the flash message data. */ public $flashVar = '__flash'; - /** - * @var array parameter-value pairs to override default session cookie parameters + * @var array parameter-value pairs to override default session cookie parameters that are used for session_set_cookie_params() function + * Array may have the following possible keys: 'lifetime', 'path', 'domain', 'secure', 'httpOnly' + * @see http://www.php.net/manual/en/function.session-set-cookie-params.php */ - public $cookieParams = array( - 'httpOnly' => true - ); + private $_cookieParams = ['httpOnly' => true]; /** * Initializes the application component. @@ -98,7 +97,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co if ($this->autoStart) { $this->open(); } - register_shutdown_function(array($this, 'close')); + register_shutdown_function([$this, 'close']); } /** @@ -120,27 +119,24 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co */ public function open() { - // this is available in PHP 5.4.0+ - if (function_exists('session_status')) { - if (session_status() == PHP_SESSION_ACTIVE) { - $this->_opened = true; - return; - } + if (session_status() == PHP_SESSION_ACTIVE) { + $this->_opened = true; + return; } if (!$this->_opened) { if ($this->getUseCustomStorage()) { @session_set_save_handler( - array($this, 'openSession'), - array($this, 'closeSession'), - array($this, 'readSession'), - array($this, 'writeSession'), - array($this, 'destroySession'), - array($this, 'gcSession') + [$this, 'openSession'], + [$this, 'closeSession'], + [$this, 'readSession'], + [$this, 'writeSession'], + [$this, 'destroySession'], + [$this, 'gcSession'] ); } - $this->setCookieParams($this->cookieParams); + $this->setCookieParamsInternal(); @session_start(); @@ -268,26 +264,36 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co $params['httpOnly'] = $params['httponly']; unset($params['httponly']); } - return $params; + return array_merge($params, $this->_cookieParams); } /** * Sets the session cookie parameters. - * The effect of this method only lasts for the duration of the script. - * Call this method before the session starts. + * The cookie parameters passed to this method will be merged with the result + * of `session_get_cookie_params()`. * @param array $value cookie parameters, valid keys include: `lifetime`, `path`, `domain`, `secure` and `httpOnly`. * @throws InvalidParamException if the parameters are incomplete. * @see http://us2.php.net/manual/en/function.session-set-cookie-params.php */ - public function setCookieParams($value) + public function setCookieParams(array $value) + { + $this->_cookieParams = $value; + } + + /** + * Sets the session cookie parameters. + * This method is called by [[open()]] when it is about to open the session. + * @throws InvalidParamException if the parameters are incomplete. + * @see http://us2.php.net/manual/en/function.session-set-cookie-params.php + */ + private function setCookieParamsInternal() { $data = $this->getCookieParams(); extract($data); - extract($value); if (isset($lifetime, $path, $domain, $secure, $httpOnly)) { session_set_cookie_params($lifetime, $path, $domain, $secure, $httpOnly); } else { - throw new InvalidParamException('Please make sure these parameters are provided: lifetime, path, domain, secure and httpOnly.'); + throw new InvalidParamException('Please make sure cookieParams contains these elements: lifetime, path, domain, secure and httpOnly.'); } } @@ -561,7 +567,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co */ protected function updateFlashCounters() { - $counters = $this->get($this->flashVar, array()); + $counters = $this->get($this->flashVar, []); if (is_array($counters)) { foreach ($counters as $key => $count) { if ($count) { @@ -588,7 +594,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co */ public function getFlash($key, $defaultValue = null, $delete = false) { - $counters = $this->get($this->flashVar, array()); + $counters = $this->get($this->flashVar, []); if (isset($counters[$key])) { $value = $this->get($key, $defaultValue); if ($delete) { @@ -606,8 +612,8 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co */ public function getAllFlashes() { - $counters = $this->get($this->flashVar, array()); - $flashes = array(); + $counters = $this->get($this->flashVar, []); + $flashes = []; foreach (array_keys($counters) as $key) { if (isset($_SESSION[$key])) { $flashes[$key] = $_SESSION[$key]; @@ -626,7 +632,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co */ public function setFlash($key, $value = true) { - $counters = $this->get($this->flashVar, array()); + $counters = $this->get($this->flashVar, []); $counters[$key] = 0; $_SESSION[$key] = $value; $_SESSION[$this->flashVar] = $counters; @@ -642,7 +648,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co */ public function removeFlash($key) { - $counters = $this->get($this->flashVar, array()); + $counters = $this->get($this->flashVar, []); $value = isset($_SESSION[$key], $counters[$key]) ? $_SESSION[$key] : null; unset($counters[$key], $_SESSION[$key]); $_SESSION[$this->flashVar] = $counters; @@ -657,7 +663,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co */ public function removeAllFlashes() { - $counters = $this->get($this->flashVar, array()); + $counters = $this->get($this->flashVar, []); foreach (array_keys($counters) as $key) { unset($_SESSION[$key]); } diff --git a/framework/yii/web/UploadedFile.php b/framework/yii/web/UploadedFile.php index c4e2f0f..1de4d46 100644 --- a/framework/yii/web/UploadedFile.php +++ b/framework/yii/web/UploadedFile.php @@ -74,7 +74,7 @@ class UploadedFile extends Object * For example, '[1]file' for tabular file uploading; and 'file[1]' for an element in a file array. * @return UploadedFile the instance of the uploaded file. * Null is returned if no file is uploaded for the specified model attribute. - * @see getInstanceByName + * @see getInstanceByName() */ public static function getInstance($model, $attribute) { @@ -122,9 +122,9 @@ class UploadedFile extends Object { $files = static::loadFiles(); if (isset($files[$name])) { - return array($files[$name]); + return [$files[$name]]; } - $results = array(); + $results = []; foreach ($files as $key => $file) { if (strpos($key, "{$name}[") === 0) { $results[] = self::$_files[$key]; @@ -180,7 +180,7 @@ class UploadedFile extends Object private static function loadFiles() { if (self::$_files === null) { - self::$_files = array(); + self::$_files = []; if (isset($_FILES) && is_array($_FILES)) { foreach ($_FILES as $class => $info) { self::loadFilesRecursive($class, $info['name'], $info['tmp_name'], $info['type'], $info['size'], $info['error']); @@ -206,13 +206,13 @@ class UploadedFile extends Object self::loadFilesRecursive($key . '[' . $i . ']', $name, $tempNames[$i], $types[$i], $sizes[$i], $errors[$i]); } } else { - self::$_files[$key] = new static(array( + self::$_files[$key] = new static([ 'name' => $names, 'tempName' => $tempNames, 'type' => $types, 'size' => $sizes, 'error' => $errors, - )); + ]); } } } diff --git a/framework/yii/web/UrlManager.php b/framework/yii/web/UrlManager.php index 0d9547f..a2044cb 100644 --- a/framework/yii/web/UrlManager.php +++ b/framework/yii/web/UrlManager.php @@ -14,6 +14,22 @@ use yii\caching\Cache; /** * UrlManager handles HTTP request parsing and creation of URLs based on a set of rules. * + * UrlManager is configured as an application component in [[yii\base\Application]] by default. + * You can access that instance via `Yii::$app->urlManager`. + * + * You can modify its configuration by adding an array to your application config under `components` + * as it is shown in the following example: + * + * ~~~ + * 'urlManager' => [ + * 'enablePrettyUrl' => true, + * 'rules' => [ + * // your rules go here + * ], + * // ... + * ] + * ~~~ + * * @property string $baseUrl The base URL that is used by [[createUrl()]] to prepend URLs it creates. * @property string $hostInfo The host info (e.g. "http://www.example.com") that is used by * [[createAbsoluteUrl()]] to prepend URLs it creates. @@ -61,7 +77,7 @@ class UrlManager extends Component * Here is an example configuration for RESTful CRUD controller: * * ~~~php - * array( + * [ * 'dashboard' => 'site/index', * * 'POST <controller:\w+>s' => '<controller>/create', @@ -70,13 +86,13 @@ class UrlManager extends Component * 'PUT <controller:\w+>/<id:\d+>' => '<controller>/update', * 'DELETE <controller:\w+>/<id:\d+>' => '<controller>/delete', * '<controller:\w+>/<id:\d+>' => '<controller>/view', - * ); + * ]; * ~~~ * * Note that if you modify this property after the UrlManager object is created, make sure * you populate the array with rule objects instead of rule configurations. */ - public $rules = array(); + public $rules = []; /** * @var string the URL suffix used when in 'path' format. * For example, ".html" can be used so that the URL looks like pointing to a static HTML page. @@ -105,9 +121,7 @@ class UrlManager extends Component * @var array the default configuration of URL rules. Individual rule configurations * specified via [[rules]] will take precedence when the same property of the rule is configured. */ - public $ruleConfig = array( - 'class' => 'yii\web\UrlRule', - ); + public $ruleConfig = ['class' => 'yii\web\UrlRule']; private $_baseUrl; private $_hostInfo; @@ -141,12 +155,10 @@ class UrlManager extends Component } } - $rules = array(); + $rules = []; foreach ($this->rules as $key => $rule) { if (!is_array($rule)) { - $rule = array( - 'route' => $rule, - ); + $rule = ['route' => $rule]; if (preg_match('/^((?:(GET|HEAD|POST|PUT|PATCH|DELETE),)*(GET|HEAD|POST|PUT|PATCH|DELETE))\s+(.*)$/', $key, $matches)) { $rule['verb'] = explode(',', $matches[1]); $rule['mode'] = UrlRule::PARSING_ONLY; @@ -159,7 +171,7 @@ class UrlManager extends Component $this->rules = $rules; if (isset($key, $hash)) { - $this->cache->set($key, array($this->rules, $hash)); + $this->cache->set($key, [$this->rules, $hash]); } } @@ -173,7 +185,7 @@ class UrlManager extends Component { if ($this->enablePrettyUrl) { $pathInfo = $request->getPathInfo(); - /** @var $rule UrlRule */ + /** @var UrlRule $rule */ foreach ($this->rules as $rule) { if (($result = $rule->parseRequest($this, $request)) !== false) { Yii::trace("Request parsed with URL rule: {$rule->name}", __METHOD__); @@ -185,6 +197,8 @@ class UrlManager extends Component return false; } + Yii::trace('No matching URL rules. Using default URL parsing logic.', __METHOD__); + $suffix = (string)$this->suffix; if ($suffix !== '' && $pathInfo !== '') { $n = strlen($this->suffix); @@ -200,15 +214,14 @@ class UrlManager extends Component } } - Yii::trace('No matching URL rules. Using default URL parsing logic.', __METHOD__); - return array($pathInfo, array()); + return [$pathInfo, []]; } else { + Yii::trace('Pretty URL not enabled. Using default URL parsing logic.', __METHOD__); $route = $request->get($this->routeVar); if (is_array($route)) { $route = ''; } - Yii::trace('Pretty URL not enabled. Using default URL parsing logic.', __METHOD__); - return array((string)$route, array()); + return [(string)$route, []]; } } @@ -219,7 +232,7 @@ class UrlManager extends Component * @param array $params the parameters (name-value pairs) * @return string the created URL */ - public function createUrl($route, $params = array()) + public function createUrl($route, $params = []) { $anchor = isset($params['#']) ? '#' . $params['#'] : ''; unset($params['#'], $params[$this->routeVar]); @@ -228,7 +241,7 @@ class UrlManager extends Component $baseUrl = $this->getBaseUrl(); if ($this->enablePrettyUrl) { - /** @var $rule UrlRule */ + /** @var UrlRule $rule */ foreach ($this->rules as $rule) { if (($url = $rule->createUrl($this, $route, $params)) !== false) { if ($rule->host !== null) { @@ -255,7 +268,7 @@ class UrlManager extends Component if (!empty($params)) { $url .= '&' . http_build_query($params); } - return $url; + return $url . $anchor; } } @@ -267,7 +280,7 @@ class UrlManager extends Component * @return string the created URL * @see createUrl() */ - public function createAbsoluteUrl($route, $params = array()) + public function createAbsoluteUrl($route, $params = []) { $url = $this->createUrl($route, $params); if (strpos($url, '://') !== false) { @@ -286,7 +299,7 @@ class UrlManager extends Component public function getBaseUrl() { if ($this->_baseUrl === null) { - /** @var $request \yii\web\Request */ + /** @var \yii\web\Request $request */ $request = Yii::$app->getRequest(); $this->_baseUrl = $this->showScriptName || !$this->enablePrettyUrl ? $request->getScriptUrl() : $request->getBaseUrl(); } diff --git a/framework/yii/web/UrlRule.php b/framework/yii/web/UrlRule.php index ec73c92..af227cd 100644 --- a/framework/yii/web/UrlRule.php +++ b/framework/yii/web/UrlRule.php @@ -11,7 +11,17 @@ use yii\base\Object; use yii\base\InvalidConfigException; /** - * UrlRule represents a rule used for parsing and generating URLs. + * UrlRule represents a rule used by [[UrlManager]] for parsing and generating URLs. + * + * To define your own URL parsing and creation logic you can extend from this class + * and add it to [[UrlManager::rules]] like this: + * + * ~~~ + * 'rules' => [ + * ['class' => 'MyUrlRule', 'pattern' => '...', 'route' => 'site/index', ...], + * // ... + * ] + * ~~~ * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 @@ -50,7 +60,7 @@ class UrlRule extends Object * When this rule is used to parse the incoming request, the values declared in this property * will be injected into $_GET. */ - public $defaults = array(); + public $defaults = []; /** * @var string the URL suffix used for this rule. * For example, ".html" can be used so that the URL looks like pointing to a static HTML page. @@ -84,11 +94,11 @@ class UrlRule extends Object /** * @var array list of regex for matching parameters. This is used in generating URL. */ - private $_paramRules = array(); + private $_paramRules = []; /** * @var array list of parameters used in the route. */ - private $_routeParams = array(); + private $_routeParams = []; /** * Initializes this rule. @@ -107,7 +117,7 @@ class UrlRule extends Object $this->verb[$i] = strtoupper($verb); } } else { - $this->verb = array(strtoupper($this->verb)); + $this->verb = [strtoupper($this->verb)]; } } if ($this->name === null) { @@ -133,7 +143,7 @@ class UrlRule extends Object } } - $tr = $tr2 = array(); + $tr = $tr2 = []; if (preg_match_all('/<(\w+):?([^>]+)?>/', $this->pattern, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) { foreach ($matches as $match) { $name = $match[1][0]; @@ -156,6 +166,7 @@ class UrlRule extends Object } } } + $tr['.'] = '\\.'; $this->_template = preg_replace('/<(\w+):?([^>]+)?>/', '<$1>', $this->pattern); $this->pattern = '#^' . trim(strtr($this->_template, $tr), '/') . '$#u'; @@ -210,7 +221,7 @@ class UrlRule extends Object } } $params = $this->defaults; - $tr = array(); + $tr = []; foreach ($matches as $name => $value) { if (isset($this->_routeParams[$name])) { $tr[$this->_routeParams[$name]] = $value; @@ -224,7 +235,7 @@ class UrlRule extends Object } else { $route = $this->route; } - return array($route, $params); + return [$route, $params]; } /** @@ -240,7 +251,7 @@ class UrlRule extends Object return false; } - $tr = array(); + $tr = []; // match the route part first if ($route !== $this->route) { diff --git a/framework/yii/web/User.php b/framework/yii/web/User.php index f6a9bc8..682d78e 100644 --- a/framework/yii/web/User.php +++ b/framework/yii/web/User.php @@ -10,7 +10,6 @@ namespace yii\web; use Yii; use yii\base\Component; use yii\base\InvalidConfigException; -use yii\base\InvalidParamException; /** * User is the class for the "user" application component that manages the user authentication status. @@ -21,10 +20,25 @@ use yii\base\InvalidParamException; * User works with a class implementing the [[IdentityInterface]]. This class implements * the actual user authentication logic and is often backed by a user database table. * + * User is configured as an application component in [[yii\web\Application]] by default. + * You can access that instance via `Yii::$app->user`. + * + * You can modify its configuration by adding an array to your application config under `components` + * as it is shown in the following example: + * + * ~~~ + * 'user' => [ + * 'identityClass' => 'app\models\User', // User must implement the IdentityInterface + * 'enableAutoLogin' => true, + * // 'loginUrl' => ['user/login'], + * // ... + * ] + * ~~~ + * * @property string|integer $id The unique identifier for the user. If null, it means the user is a guest. * This property is read-only. - * @property IdentityInterface $identity The identity object associated with the currently logged user. Null is - * returned if the user is not logged in (not authenticated). + * @property IdentityInterface $identity The identity object associated with the currently logged user. Null + * is returned if the user is not logged in (not authenticated). * @property boolean $isGuest Whether the current user is a guest. This property is read-only. * @property string $returnUrl The URL that the user should be redirected to after login. Note that the type * of this property differs in getter and setter. See [[getReturnUrl()]] and [[setReturnUrl()]] for details. @@ -54,17 +68,17 @@ class User extends Component * the name-value pairs are GET parameters used to construct the login URL. For example, * * ~~~ - * array('site/login', 'ref' => 1) + * ['site/login', 'ref' => 1] * ~~~ * * If this property is null, a 403 HTTP exception will be raised when [[loginRequired()]] is called. */ - public $loginUrl = array('site/login'); + public $loginUrl = ['site/login']; /** * @var array the configuration of the identity cookie. This property is used only when [[enableAutoLogin]] is true. * @see Cookie */ - public $identityCookie = array('name' => '_identity', 'httpOnly' => true); + public $identityCookie = ['name' => '_identity', 'httpOnly' => true]; /** * @var integer the number of seconds in which the user will be logged out automatically if he * remains inactive. If this property is not set, the user will be logged out after @@ -94,7 +108,7 @@ class User extends Component */ public $returnUrlVar = '__returnUrl'; - private $_access = array(); + private $_access = []; /** @@ -130,8 +144,8 @@ class User extends Component * Returns the identity object associated with the currently logged user. * @return IdentityInterface the identity object associated with the currently logged user. * Null is returned if the user is not logged in (not authenticated). - * @see login - * @see logout + * @see login() + * @see logout() */ public function getIdentity() { @@ -140,7 +154,7 @@ class User extends Component if ($id === null) { $this->_identity = null; } else { - /** @var $class IdentityInterface */ + /** @var IdentityInterface $class */ $class = $this->identityClass; $this->_identity = $class::findIdentity($id); } @@ -181,6 +195,9 @@ class User extends Component { if ($this->beforeLogin($identity, false)) { $this->switchIdentity($identity, $duration); + $id = $identity->getId(); + $ip = Yii::$app->getRequest()->getUserIP(); + Yii::info("User '$id' logged in from $ip.", __METHOD__); $this->afterLogin($identity, false); } return !$this->getIsGuest(); @@ -200,12 +217,14 @@ class User extends Component $data = json_decode($value, true); if (count($data) === 3 && isset($data[0], $data[1], $data[2])) { list ($id, $authKey, $duration) = $data; - /** @var $class IdentityInterface */ + /** @var IdentityInterface $class */ $class = $this->identityClass; $identity = $class::findIdentity($id); if ($identity !== null && $identity->validateAuthKey($authKey)) { if ($this->beforeLogin($identity, true)) { $this->switchIdentity($identity, $this->autoRenewCookie ? $duration : 0); + $ip = Yii::$app->getRequest()->getUserIP(); + Yii::info("User '$id' logged in from $ip via cookie.", __METHOD__); $this->afterLogin($identity, true); } } elseif ($identity !== null) { @@ -226,6 +245,9 @@ class User extends Component $identity = $this->getIdentity(); if ($identity !== null && $this->beforeLogout($identity)) { $this->switchIdentity(null); + $id = $identity->getId(); + $ip = Yii::$app->getRequest()->getUserIP(); + Yii::info("User '$id' logged out from $ip.", __METHOD__); if ($destroySession) { Yii::$app->getSession()->destroy(); } @@ -259,7 +281,7 @@ class User extends Component * If this is null and the return URL was not set previously, [[Application::homeUrl]] will be redirected to. * Please refer to [[setReturnUrl()]] on accepted format of the URL. * @return string the URL that the user should be redirected to after login. - * @see loginRequired + * @see loginRequired() */ public function getReturnUrl($defaultUrl = null) { @@ -282,7 +304,7 @@ class User extends Component * the name-value pairs are GET parameters used to construct the URL. For example, * * ~~~ - * array('admin/index', 'ref' => 1) + * ['admin/index', 'ref' => 1] * ~~~ */ public function setReturnUrl($url) @@ -324,10 +346,10 @@ class User extends Component */ protected function beforeLogin($identity, $cookieBased) { - $event = new UserEvent(array( + $event = new UserEvent([ 'identity' => $identity, 'cookieBased' => $cookieBased, - )); + ]); $this->trigger(self::EVENT_BEFORE_LOGIN, $event); return $event->isValid; } @@ -342,10 +364,10 @@ class User extends Component */ protected function afterLogin($identity, $cookieBased) { - $this->trigger(self::EVENT_AFTER_LOGIN, new UserEvent(array( + $this->trigger(self::EVENT_AFTER_LOGIN, new UserEvent([ 'identity' => $identity, 'cookieBased' => $cookieBased, - ))); + ])); } /** @@ -358,9 +380,9 @@ class User extends Component */ protected function beforeLogout($identity) { - $event = new UserEvent(array( + $event = new UserEvent([ 'identity' => $identity, - )); + ]); $this->trigger(self::EVENT_BEFORE_LOGOUT, $event); return $event->isValid; } @@ -374,9 +396,9 @@ class User extends Component */ protected function afterLogout($identity) { - $this->trigger(self::EVENT_AFTER_LOGOUT, new UserEvent(array( + $this->trigger(self::EVENT_AFTER_LOGOUT, new UserEvent([ 'identity' => $identity, - ))); + ])); } /** @@ -406,16 +428,16 @@ class User extends Component * information in the cookie. * @param IdentityInterface $identity * @param integer $duration number of seconds that the user can remain in logged-in status. - * @see loginByCookie + * @see loginByCookie() */ protected function sendIdentityCookie($identity, $duration) { $cookie = new Cookie($this->identityCookie); - $cookie->value = json_encode(array( + $cookie->value = json_encode([ $identity->getId(), $identity->getAuthKey(), $duration, - )); + ]); $cookie->expire = time() + $duration; Yii::$app->getResponse()->getCookies()->add($cookie); } @@ -487,10 +509,10 @@ class User extends Component * before, its result will be directly returned when calling this method to check the same * operation. If this parameter is false, this method will always call * [[AuthManager::checkAccess()]] to obtain the up-to-date access result. Note that this - * caching is effective only within the same request and only works when `$params = array()`. + * caching is effective only within the same request and only works when `$params = []`. * @return boolean whether the operations can be performed by this user. */ - public function checkAccess($operation, $params = array(), $allowCaching = true) + public function checkAccess($operation, $params = [], $allowCaching = true) { $auth = Yii::$app->getAuthManager(); if ($auth === null) { diff --git a/framework/yii/web/VerbFilter.php b/framework/yii/web/VerbFilter.php index 4f45190..c3235a6 100644 --- a/framework/yii/web/VerbFilter.php +++ b/framework/yii/web/VerbFilter.php @@ -24,18 +24,18 @@ use yii\base\Behavior; * ~~~ * public function behaviors() * { - * return array( - * 'verbs' => array( + * return [ + * 'verbs' => [ * 'class' => \yii\web\VerbFilter::className(), - * 'actions' => array( - * 'index' => array('get'), - * 'view' => array('get'), - * 'create' => array('get', 'post'), - * 'update' => array('get', 'put', 'post'), - * 'delete' => array('post', 'delete'), - * ), - * ), - * ); + * 'actions' => [ + * 'index' => ['get'], + * 'view' => ['get'], + * 'create' => ['get', 'post'], + * 'update' => ['get', 'put', 'post'], + * 'delete' => ['post', 'delete'], + * ], + * ], + * ]; * } * ~~~ * @@ -52,7 +52,7 @@ class VerbFilter extends Behavior * allowed methods (e.g. GET, HEAD, PUT) as the value. * If an action is not listed all request methods are considered allowed. */ - public $actions = array(); + public $actions = []; /** @@ -61,9 +61,7 @@ class VerbFilter extends Behavior */ public function events() { - return array( - Controller::EVENT_BEFORE_ACTION => 'beforeAction', - ); + return [Controller::EVENT_BEFORE_ACTION => 'beforeAction']; } /** diff --git a/framework/yii/web/View.php b/framework/yii/web/View.php new file mode 100644 index 0000000..db0c500 --- /dev/null +++ b/framework/yii/web/View.php @@ -0,0 +1,447 @@ +<?php +/** + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\web; + +use Yii; +use yii\helpers\FileHelper; +use yii\helpers\Html; +use yii\web\JqueryAsset; +use yii\web\AssetBundle; +use yii\widgets\Block; +use yii\widgets\ContentDecorator; +use yii\widgets\FragmentCache; +use yii\base\InvalidConfigException; + +/** + * View represents a view object in the MVC pattern. + * + * View provides a set of methods (e.g. [[render()]]) for rendering purpose. + * + * View is configured as an application component in [[yii\base\Application]] by default. + * You can access that instance via `Yii::$app->view`. + * + * You can modify its configuration by adding an array to your application config under `components` + * as it is shown in the following example: + * + * ~~~ + * 'view' => [ + * 'theme' => 'app\themes\MyTheme', + * 'renderers' => [ + * // you may add Smarty or Twig renderer here + * ] + * // ... + * ] + * ~~~ + * + * @property \yii\web\AssetManager $assetManager The asset manager. Defaults to the "assetManager" application + * component. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @since 2.0 + */ +class View extends \yii\base\View +{ + const EVENT_BEGIN_BODY = 'beginBody'; + /** + * @event Event an event that is triggered by [[endBody()]]. + */ + const EVENT_END_BODY = 'endBody'; + + /** + * The location of registered JavaScript code block or files. + * This means the location is in the head section. + */ + const POS_HEAD = 1; + /** + * The location of registered JavaScript code block or files. + * This means the location is at the beginning of the body section. + */ + const POS_BEGIN = 2; + /** + * The location of registered JavaScript code block or files. + * This means the location is at the end of the body section. + */ + const POS_END = 3; + /** + * The location of registered JavaScript code block. + * This means the JavaScript code block will be enclosed within `jQuery(document).ready()`. + */ + const POS_READY = 4; + /** + * This is internally used as the placeholder for receiving the content registered for the head section. + */ + const PH_HEAD = '<![CDATA[YII-BLOCK-HEAD]]>'; + /** + * This is internally used as the placeholder for receiving the content registered for the beginning of the body section. + */ + const PH_BODY_BEGIN = '<![CDATA[YII-BLOCK-BODY-BEGIN]]>'; + /** + * This is internally used as the placeholder for receiving the content registered for the end of the body section. + */ + const PH_BODY_END = '<![CDATA[YII-BLOCK-BODY-END]]>'; + + /** + * @var AssetBundle[] list of the registered asset bundles. The keys are the bundle names, and the values + * are the registered [[AssetBundle]] objects. + * @see registerAssetBundle() + */ + public $assetBundles = []; + /** + * @var string the page title + */ + public $title; + /** + * @var array the registered meta tags. + * @see registerMetaTag() + */ + public $metaTags; + /** + * @var array the registered link tags. + * @see registerLinkTag() + */ + public $linkTags; + /** + * @var array the registered CSS code blocks. + * @see registerCss() + */ + public $css; + /** + * @var array the registered CSS files. + * @see registerCssFile() + */ + public $cssFiles; + /** + * @var array the registered JS code blocks + * @see registerJs() + */ + public $js; + /** + * @var array the registered JS files. + * @see registerJsFile() + */ + public $jsFiles; + + private $_assetManager; + + /** + * Registers the asset manager being used by this view object. + * @return \yii\web\AssetManager the asset manager. Defaults to the "assetManager" application component. + */ + public function getAssetManager() + { + return $this->_assetManager ?: Yii::$app->getAssetManager(); + } + + /** + * Sets the asset manager. + * @param \yii\web\AssetManager $value the asset manager + */ + public function setAssetManager($value) + { + $this->_assetManager = $value; + } + + /** + * Marks the ending of an HTML page. + */ + public function endPage() + { + $this->trigger(self::EVENT_END_PAGE); + + $content = ob_get_clean(); + foreach (array_keys($this->assetBundles) as $bundle) { + $this->registerAssetFiles($bundle); + } + echo strtr($content, [ + self::PH_HEAD => $this->renderHeadHtml(), + self::PH_BODY_BEGIN => $this->renderBodyBeginHtml(), + self::PH_BODY_END => $this->renderBodyEndHtml(), + ]); + + unset( + $this->metaTags, + $this->linkTags, + $this->css, + $this->cssFiles, + $this->js, + $this->jsFiles + ); + } + + /** + * Registers all files provided by an asset bundle including depending bundles files. + * Removes a bundle from [[assetBundles]] once files are registered. + * @param string $name name of the bundle to register + */ + private function registerAssetFiles($name) + { + if (!isset($this->assetBundles[$name])) { + return; + } + $bundle = $this->assetBundles[$name]; + foreach ($bundle->depends as $dep) { + $this->registerAssetFiles($dep); + } + $bundle->registerAssetFiles($this); + unset($this->assetBundles[$name]); + } + + /** + * Marks the beginning of an HTML body section. + */ + public function beginBody() + { + echo self::PH_BODY_BEGIN; + $this->trigger(self::EVENT_BEGIN_BODY); + } + + /** + * Marks the ending of an HTML body section. + */ + public function endBody() + { + $this->trigger(self::EVENT_END_BODY); + echo self::PH_BODY_END; + } + + /** + * Marks the position of an HTML head section. + */ + public function head() + { + echo self::PH_HEAD; + } + + /** + * Registers the named asset bundle. + * All dependent asset bundles will be registered. + * @param string $name the name of the asset bundle. + * @param integer|null $position if set, this forces a minimum position for javascript files. + * This will adjust depending assets javascript file position or fail if requirement can not be met. + * If this is null, asset bundles position settings will not be changed. + * See [[registerJsFile]] for more details on javascript position. + * @return AssetBundle the registered asset bundle instance + * @throws InvalidConfigException if the asset bundle does not exist or a circular dependency is detected + */ + public function registerAssetBundle($name, $position = null) + { + if (!isset($this->assetBundles[$name])) { + $am = $this->getAssetManager(); + $bundle = $am->getBundle($name); + $this->assetBundles[$name] = false; + // register dependencies + $pos = isset($bundle->jsOptions['position']) ? $bundle->jsOptions['position'] : null; + foreach ($bundle->depends as $dep) { + $this->registerAssetBundle($dep, $pos); + } + $this->assetBundles[$name] = $bundle; + } elseif ($this->assetBundles[$name] === false) { + throw new InvalidConfigException("A circular dependency is detected for bundle '$name'."); + } else { + $bundle = $this->assetBundles[$name]; + } + + if ($position !== null) { + $pos = isset($bundle->jsOptions['position']) ? $bundle->jsOptions['position'] : null; + if ($pos === null) { + $bundle->jsOptions['position'] = $pos = $position; + } elseif ($pos > $position) { + throw new InvalidConfigException("An asset bundle that depends on '$name' has a higher javascript file position configured than '$name'."); + } + // update position for all dependencies + foreach ($bundle->depends as $dep) { + $this->registerAssetBundle($dep, $pos); + } + } + return $bundle; + } + + /** + * Registers a meta tag. + * @param array $options the HTML attributes for the meta tag. + * @param string $key the key that identifies the meta tag. If two meta tags are registered + * with the same key, the latter will overwrite the former. If this is null, the new meta tag + * will be appended to the existing ones. + */ + public function registerMetaTag($options, $key = null) + { + if ($key === null) { + $this->metaTags[] = Html::tag('meta', '', $options); + } else { + $this->metaTags[$key] = Html::tag('meta', '', $options); + } + } + + /** + * Registers a link tag. + * @param array $options the HTML attributes for the link tag. + * @param string $key the key that identifies the link tag. If two link tags are registered + * with the same key, the latter will overwrite the former. If this is null, the new link tag + * will be appended to the existing ones. + */ + public function registerLinkTag($options, $key = null) + { + if ($key === null) { + $this->linkTags[] = Html::tag('link', '', $options); + } else { + $this->linkTags[$key] = Html::tag('link', '', $options); + } + } + + /** + * Registers a CSS code block. + * @param string $css the CSS code block to be registered + * @param array $options the HTML attributes for the style tag. + * @param string $key the key that identifies the CSS code block. If null, it will use + * $css as the key. If two CSS code blocks are registered with the same key, the latter + * will overwrite the former. + */ + public function registerCss($css, $options = [], $key = null) + { + $key = $key ?: md5($css); + $this->css[$key] = Html::style($css, $options); + } + + /** + * Registers a CSS file. + * @param string $url the CSS file to be registered. + * @param array $options the HTML attributes for the link tag. + * @param string $key the key that identifies the CSS script file. If null, it will use + * $url as the key. If two CSS files are registered with the same key, the latter + * will overwrite the former. + */ + public function registerCssFile($url, $options = [], $key = null) + { + $key = $key ?: $url; + $this->cssFiles[$key] = Html::cssFile($url, $options); + } + + /** + * Registers a JS code block. + * @param string $js the JS code block to be registered + * @param integer $position the position at which the JS script tag should be inserted + * in a page. The possible values are: + * + * - [[POS_HEAD]]: in the head section + * - [[POS_BEGIN]]: at the beginning of the body section + * - [[POS_END]]: at the end of the body section + * - [[POS_READY]]: enclosed within jQuery(document).ready(). This is the default value. + * Note that by using this position, the method will automatically register the jQuery js file. + * + * @param string $key the key that identifies the JS code block. If null, it will use + * $js as the key. If two JS code blocks are registered with the same key, the latter + * will overwrite the former. + */ + public function registerJs($js, $position = self::POS_READY, $key = null) + { + $key = $key ?: md5($js); + $this->js[$position][$key] = $js; + if ($position === self::POS_READY) { + JqueryAsset::register($this); + } + } + + /** + * Registers a JS file. + * Please note that when this file depends on other JS files to be registered before, + * for example jQuery, you should use [[registerAssetBundle]] instead. + * @param string $url the JS file to be registered. + * @param array $options the HTML attributes for the script tag. A special option + * named "position" is supported which specifies where the JS script tag should be inserted + * in a page. The possible values of "position" are: + * + * - [[POS_HEAD]]: in the head section + * - [[POS_BEGIN]]: at the beginning of the body section + * - [[POS_END]]: at the end of the body section. This is the default value. + * + * @param string $key the key that identifies the JS script file. If null, it will use + * $url as the key. If two JS files are registered with the same key, the latter + * will overwrite the former. + */ + public function registerJsFile($url, $options = [], $key = null) + { + $position = isset($options['position']) ? $options['position'] : self::POS_END; + unset($options['position']); + $key = $key ?: $url; + $this->jsFiles[$position][$key] = Html::jsFile($url, $options); + } + + /** + * Renders the content to be inserted in the head section. + * The content is rendered using the registered meta tags, link tags, CSS/JS code blocks and files. + * @return string the rendered content + */ + protected function renderHeadHtml() + { + $lines = []; + if (!empty($this->metaTags)) { + $lines[] = implode("\n", $this->metaTags); + } + + $request = Yii::$app->getRequest(); + if ($request instanceof \yii\web\Request && $request->enableCsrfValidation) { + $lines[] = Html::tag('meta', '', ['name' => 'csrf-var', 'content' => $request->csrfVar]); + $lines[] = Html::tag('meta', '', ['name' => 'csrf-token', 'content' => $request->getCsrfToken()]); + } + + if (!empty($this->linkTags)) { + $lines[] = implode("\n", $this->linkTags); + } + if (!empty($this->cssFiles)) { + $lines[] = implode("\n", $this->cssFiles); + } + if (!empty($this->css)) { + $lines[] = implode("\n", $this->css); + } + if (!empty($this->jsFiles[self::POS_HEAD])) { + $lines[] = implode("\n", $this->jsFiles[self::POS_HEAD]); + } + if (!empty($this->js[self::POS_HEAD])) { + $lines[] = Html::script(implode("\n", $this->js[self::POS_HEAD]), ['type' => 'text/javascript']); + } + return empty($lines) ? '' : implode("\n", $lines); + } + + /** + * Renders the content to be inserted at the beginning of the body section. + * The content is rendered using the registered JS code blocks and files. + * @return string the rendered content + */ + protected function renderBodyBeginHtml() + { + $lines = []; + if (!empty($this->jsFiles[self::POS_BEGIN])) { + $lines[] = implode("\n", $this->jsFiles[self::POS_BEGIN]); + } + if (!empty($this->js[self::POS_BEGIN])) { + $lines[] = Html::script(implode("\n", $this->js[self::POS_BEGIN]), ['type' => 'text/javascript']); + } + return empty($lines) ? '' : implode("\n", $lines); + } + + /** + * Renders the content to be inserted at the end of the body section. + * The content is rendered using the registered JS code blocks and files. + * @return string the rendered content + */ + protected function renderBodyEndHtml() + { + $lines = []; + if (!empty($this->jsFiles[self::POS_END])) { + $lines[] = implode("\n", $this->jsFiles[self::POS_END]); + } + if (!empty($this->js[self::POS_END])) { + $lines[] = Html::script(implode("\n", $this->js[self::POS_END]), ['type' => 'text/javascript']); + } + if (!empty($this->js[self::POS_READY])) { + $js = "jQuery(document).ready(function(){\n" . implode("\n", $this->js[self::POS_READY]) . "\n});"; + $lines[] = Html::script($js, ['type' => 'text/javascript']); + } + return empty($lines) ? '' : implode("\n", $lines); + } +} diff --git a/framework/yii/web/XmlResponseFormatter.php b/framework/yii/web/XmlResponseFormatter.php index 737011d..292424a 100644 --- a/framework/yii/web/XmlResponseFormatter.php +++ b/framework/yii/web/XmlResponseFormatter.php @@ -17,6 +17,8 @@ use yii\helpers\StringHelper; /** * XmlResponseFormatter formats the given data into an XML response content. * + * It is used by [[Response]] to format response data. + * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ @@ -69,7 +71,7 @@ class XmlResponseFormatter extends Component implements ResponseFormatterInterfa if ($data instanceof Arrayable) { $this->buildXml($child, $data->toArray()); } else { - $array = array(); + $array = []; foreach ($data as $name => $value) { $array[$name] = $value; } diff --git a/framework/yii/web/YiiAsset.php b/framework/yii/web/YiiAsset.php index 2ad5384..d38b711 100644 --- a/framework/yii/web/YiiAsset.php +++ b/framework/yii/web/YiiAsset.php @@ -7,20 +7,19 @@ namespace yii\web; -use Yii; -use yii\base\View; - /** + * This asset bundle provides the base javascript files for the Yii Framework. + * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ class YiiAsset extends AssetBundle { public $sourcePath = '@yii/assets'; - public $js = array( + public $js = [ 'yii.js', - ); - public $depends = array( + ]; + public $depends = [ 'yii\web\JqueryAsset', - ); + ]; } diff --git a/framework/yii/widgets/ActiveField.php b/framework/yii/widgets/ActiveField.php index ea8aa1b..0320516 100644 --- a/framework/yii/widgets/ActiveField.php +++ b/framework/yii/widgets/ActiveField.php @@ -8,7 +8,6 @@ namespace yii\widgets; use Yii; use yii\base\Component; -use yii\db\ActiveRecord; use yii\helpers\ArrayHelper; use yii\helpers\Html; use yii\base\Model; @@ -40,9 +39,7 @@ class ActiveField extends Component * * - tag: the tag name of the container element. Defaults to "div". */ - public $options = array( - 'class' => 'form-group', - ); + public $options = ['class' => 'form-group']; /** * @var string the template that is used to arrange the label, the input field, the error message and the hint text. * The following tokens will be replaced when [[render()]] is called: `{label}`, `{input}`, `{error}` and `{hint}`. @@ -52,7 +49,7 @@ class ActiveField extends Component * @var array the default options for the input tags. The parameter passed to individual input methods * (e.g. [[textInput()]]) will be merged with this property when rendering the input tag. */ - public $inputOptions = array('class' => 'form-control'); + public $inputOptions = ['class' => 'form-control']; /** * @var array the default options for the error tags. The parameter passed to [[error()]] will be * merged with this property when rendering the error tag. @@ -60,12 +57,12 @@ class ActiveField extends Component * * - tag: the tag name of the container element. Defaults to "div". */ - public $errorOptions = array('class' => 'help-block'); + public $errorOptions = ['class' => 'help-block']; /** * @var array the default options for the label tags. The parameter passed to [[label()]] will be * merged with this property when rendering the label tag. */ - public $labelOptions = array('class' => 'control-label'); + public $labelOptions = ['class' => 'control-label']; /** * @var array the default options for the hint tags. The parameter passed to [[hint()]] will be * merged with this property when rendering the hint tag. @@ -73,7 +70,7 @@ class ActiveField extends Component * * - tag: the tag name of the container element. Defaults to "div". */ - public $hintOptions = array('class' => 'hint-block'); + public $hintOptions = ['class' => 'hint-block']; /** * @var boolean whether to enable client-side data validation. * If not set, it will take the value of [[ActiveForm::enableClientValidation]]. @@ -104,7 +101,7 @@ class ActiveField extends Component /** * @var array the jQuery selectors for selecting the container, input and error tags. * The array keys should be "container", "input", and/or "error", and the array values - * are the corresponding selectors. For example, `array('input' => '#my-input')`. + * are the corresponding selectors. For example, `['input' => '#my-input']`. * * The container selector is used under the context of the form, while the input and the error * selectors are used under the context of the container. @@ -119,7 +116,7 @@ class ActiveField extends Component * `{error}`, and `{error}`. Note that you normally don't need to access this property directly as * it is maintained by various methods of this class. */ - public $parts = array(); + public $parts = []; /** @@ -191,7 +188,7 @@ class ActiveField extends Component $inputID = Html::getInputId($this->model, $this->attribute); $attribute = Html::getAttributeName($this->attribute); $options = $this->options; - $class = isset($options['class']) ? array($options['class']) : array(); + $class = isset($options['class']) ? [$options['class']] : []; $class[] = "field-$inputID"; if ($this->model->isAttributeRequired($attribute)) { $class[] = $this->form->requiredCssClass; @@ -221,9 +218,9 @@ class ActiveField extends Component * @param array $options the tag options in terms of name-value pairs. It will be merged with [[labelOptions]]. * The options will be rendered as the attributes of the resulting tag. The values will be HTML-encoded * using [[Html::encode()]]. If a value is null, the corresponding attribute will not be rendered. - * @return ActiveField the field object itself + * @return static the field object itself */ - public function label($label = null, $options = array()) + public function label($label = null, $options = []) { $options = array_merge($this->labelOptions, $options); if ($label !== null) { @@ -244,9 +241,9 @@ class ActiveField extends Component * * - tag: this specifies the tag name. If not set, "div" will be used. * - * @return ActiveField the field object itself + * @return static the field object itself */ - public function error($options = array()) + public function error($options = []) { $options = array_merge($this->errorOptions, $options); $this->parts['{error}'] = Html::error($this->model, $this->attribute, $options); @@ -263,9 +260,9 @@ class ActiveField extends Component * * - tag: this specifies the tag name. If not set, "div" will be used. * - * @return ActiveField the field object itself + * @return static the field object itself */ - public function hint($content, $options = array()) + public function hint($content, $options = []) { $options = array_merge($this->hintOptions, $options); $tag = ArrayHelper::remove($options, 'tag', 'div'); @@ -278,9 +275,9 @@ class ActiveField extends Component * @param string $type the input type (e.g. 'text', 'password') * @param array $options the tag options in terms of name-value pairs. These will be rendered as * the attributes of the resulting tag. The values will be HTML-encoded using [[Html::encode()]]. - * @return ActiveField the field object itself + * @return static the field object itself */ - public function input($type, $options = array()) + public function input($type, $options = []) { $options = array_merge($this->inputOptions, $options); $this->parts['{input}'] = Html::activeInput($type, $this->model, $this->attribute, $options); @@ -293,9 +290,9 @@ class ActiveField extends Component * unless they are explicitly specified in `$options`. * @param array $options the tag options in terms of name-value pairs. These will be rendered as * the attributes of the resulting tag. The values will be HTML-encoded using [[Html::encode()]]. - * @return ActiveField the field object itself + * @return static the field object itself */ - public function textInput($options = array()) + public function textInput($options = []) { $options = array_merge($this->inputOptions, $options); $this->parts['{input}'] = Html::activeTextInput($this->model, $this->attribute, $options); @@ -308,9 +305,9 @@ class ActiveField extends Component * unless they are explicitly specified in `$options`. * @param array $options the tag options in terms of name-value pairs. These will be rendered as * the attributes of the resulting tag. The values will be HTML-encoded using [[Html::encode()]]. - * @return ActiveField the field object itself + * @return static the field object itself */ - public function passwordInput($options = array()) + public function passwordInput($options = []) { $options = array_merge($this->inputOptions, $options); $this->parts['{input}'] = Html::activePasswordInput($this->model, $this->attribute, $options); @@ -323,11 +320,12 @@ class ActiveField extends Component * unless they are explicitly specified in `$options`. * @param array $options the tag options in terms of name-value pairs. These will be rendered as * the attributes of the resulting tag. The values will be HTML-encoded using [[Html::encode()]]. - * @return ActiveField the field object itself + * @return static the field object itself */ - public function fileInput($options = array()) + public function fileInput($options = []) { - if ($this->inputOptions !== array('class' => 'form-control')) { + // https://github.com/yiisoft/yii2/pull/795 + if ($this->inputOptions !== ['class' => 'form-control']) { $options = array_merge($this->inputOptions, $options); } $this->parts['{input}'] = Html::activeFileInput($this->model, $this->attribute, $options); @@ -339,9 +337,9 @@ class ActiveField extends Component * The model attribute value will be used as the content in the textarea. * @param array $options the tag options in terms of name-value pairs. These will be rendered as * the attributes of the resulting tag. The values will be HTML-encoded using [[Html::encode()]]. - * @return ActiveField the field object itself + * @return static the field object itself */ - public function textarea($options = array()) + public function textarea($options = []) { $options = array_merge($this->inputOptions, $options); $this->parts['{input}'] = Html::activeTextarea($this->model, $this->attribute, $options); @@ -367,9 +365,9 @@ class ActiveField extends Component * @param boolean $enclosedByLabel whether to enclose the radio within the label. * If true, the method will still use [[template]] to layout the checkbox and the error message * except that the radio is enclosed by the label tag. - * @return ActiveField the field object itself + * @return static the field object itself */ - public function radio($options = array(), $enclosedByLabel = true) + public function radio($options = [], $enclosedByLabel = true) { if ($enclosedByLabel) { if (!isset($options['label'])) { @@ -402,9 +400,9 @@ class ActiveField extends Component * @param boolean $enclosedByLabel whether to enclose the checkbox within the label. * If true, the method will still use [[template]] to layout the checkbox and the error message * except that the checkbox is enclosed by the label tag. - * @return ActiveField the field object itself + * @return static the field object itself */ - public function checkbox($options = array(), $enclosedByLabel = true) + public function checkbox($options = [], $enclosedByLabel = true) { if ($enclosedByLabel) { if (!isset($options['label'])) { @@ -436,10 +434,10 @@ class ActiveField extends Component * and the array values are the extra attributes for the corresponding option tags. For example, * * ~~~ - * array( - * 'value1' => array('disabled' => true), - * 'value2' => array('label' => 'value 2'), - * ); + * [ + * 'value1' => ['disabled' => true], + * 'value2' => ['label' => 'value 2'], + * ]; * ~~~ * * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', @@ -448,9 +446,9 @@ class ActiveField extends Component * The rest of the options will be rendered as the attributes of the resulting tag. The values will * be HTML-encoded using [[Html::encode()]]. If a value is null, the corresponding attribute will not be rendered. * - * @return ActiveField the field object itself + * @return static the field object itself */ - public function dropDownList($items, $options = array()) + public function dropDownList($items, $options = []) { $options = array_merge($this->inputOptions, $options); $this->parts['{input}'] = Html::activeDropDownList($this->model, $this->attribute, $items, $options); @@ -475,10 +473,10 @@ class ActiveField extends Component * and the array values are the extra attributes for the corresponding option tags. For example, * * ~~~ - * array( - * 'value1' => array('disabled' => true), - * 'value2' => array('label' => 'value 2'), - * ); + * [ + * 'value1' => ['disabled' => true], + * 'value2' => ['label' => 'value 2'], + * ]; * ~~~ * * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', @@ -490,9 +488,9 @@ class ActiveField extends Component * The rest of the options will be rendered as the attributes of the resulting tag. The values will * be HTML-encoded using [[Html::encode()]]. If a value is null, the corresponding attribute will not be rendered. * - * @return ActiveField the field object itself + * @return static the field object itself */ - public function listBox($items, $options = array()) + public function listBox($items, $options = []) { $options = array_merge($this->inputOptions, $options); $this->parts['{input}'] = Html::activeListBox($this->model, $this->attribute, $items, $options); @@ -505,7 +503,7 @@ class ActiveField extends Component * As a result, the corresponding submitted value is an array. * The selection of the checkbox list is taken from the value of the model attribute. * @param array $items the data item used to generate the checkboxes. - * The array keys are the labels, while the array values are the corresponding checkbox values. + * The array values are the labels, while the array keys are the corresponding checkbox values. * Note that the labels will NOT be HTML-encoded, while the values will. * @param array $options options (name => config) for the checkbox list. The following options are specially handled: * @@ -522,9 +520,9 @@ class ActiveField extends Component * where $index is the zero-based index of the checkbox in the whole list; $label * is the label for the checkbox; and $name, $value and $checked represent the name, * value and the checked status of the checkbox input. - * @return ActiveField the field object itself + * @return static the field object itself */ - public function checkboxList($items, $options = array()) + public function checkboxList($items, $options = []) { $this->parts['{input}'] = Html::activeCheckboxList($this->model, $this->attribute, $items, $options); return $this; @@ -552,9 +550,9 @@ class ActiveField extends Component * where $index is the zero-based index of the radio button in the whole list; $label * is the label for the radio button; and $name, $value and $checked represent the name, * value and the checked status of the radio button input. - * @return ActiveField the field object itself + * @return static the field object itself */ - public function radioList($items, $options = array()) + public function radioList($items, $options = []) { $this->parts['{input}'] = Html::activeRadioList($this->model, $this->attribute, $items, $options); return $this; @@ -571,9 +569,9 @@ class ActiveField extends Component * * @param string $class the widget class name * @param array $config name-value pairs that will be used to initialize the widget - * @return ActiveField the field object itself + * @return static the field object itself */ - public function widget($class, $config = array()) + public function widget($class, $config = []) { /** @var \yii\base\Widget $class */ $config['model'] = $this->model; @@ -589,10 +587,14 @@ class ActiveField extends Component */ protected function getClientOptions() { + $attribute = Html::getAttributeName($this->attribute); + if (!in_array($attribute, $this->model->activeAttributes(), true)) { + return []; + } + $enableClientValidation = $this->enableClientValidation || $this->enableClientValidation === null && $this->form->enableClientValidation; if ($enableClientValidation) { - $attribute = Html::getAttributeName($this->attribute); - $validators = array(); + $validators = []; foreach ($this->model->getActiveValidators($attribute) as $validator) { /** @var \yii\validators\Validator $validator */ $js = $validator->clientValidateAttribute($this->model, $attribute, $this->form->getView()); @@ -613,7 +615,7 @@ class ActiveField extends Component if ($enableClientValidation && !empty($options['validate']) || $enableAjaxValidation) { $inputID = Html::getInputId($this->model, $this->attribute); $options['name'] = $inputID; - foreach (array('validateOnChange', 'validateOnType', 'validationDelay') as $name) { + foreach (['validateOnChange', 'validateOnType', 'validationDelay'] as $name) { $options[$name] = $this->$name === null ? $this->form->$name : $this->$name; } $options['container'] = isset($this->selectors['container']) ? $this->selectors['container'] : ".field-$inputID"; @@ -625,7 +627,7 @@ class ActiveField extends Component } return $options; } else { - return array(); + return []; } } } diff --git a/framework/yii/widgets/ActiveForm.php b/framework/yii/widgets/ActiveForm.php index b46ad46..b218a2e 100644 --- a/framework/yii/widgets/ActiveForm.php +++ b/framework/yii/widgets/ActiveForm.php @@ -36,7 +36,7 @@ class ActiveForm extends Widget * The values will be HTML-encoded using [[Html::encode()]]. * If a value is null, the corresponding attribute will not be rendered. */ - public $options = array(); + public $options = []; /** * @var array the default configuration used by [[field()]] when creating a new field object. */ @@ -140,7 +140,7 @@ class ActiveForm extends Widget * represents the validation options for a particular attribute. * @internal */ - public $attributes = array(); + public $attributes = []; /** * Initializes the widget. @@ -180,18 +180,18 @@ class ActiveForm extends Widget */ protected function getClientOptions() { - $options = array( + $options = [ 'errorSummary' => '.' . $this->errorSummaryCssClass, 'validateOnSubmit' => $this->validateOnSubmit, 'errorCssClass' => $this->errorCssClass, 'successCssClass' => $this->successCssClass, 'validatingCssClass' => $this->validatingCssClass, 'ajaxVar' => $this->ajaxVar, - ); + ]; if ($this->validationUrl !== null) { $options['validationUrl'] = Html::url($this->validationUrl); } - foreach (array('beforeSubmit', 'beforeValidate', 'afterValidate') as $name) { + foreach (['beforeSubmit', 'beforeValidate', 'afterValidate'] as $name) { if (($value = $this->$name) !== null) { $options[$name] = $value instanceof JsExpression ? $value : new JsExpression($value); } @@ -212,15 +212,15 @@ class ActiveForm extends Widget * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. * @return string the generated error summary */ - public function errorSummary($models, $options = array()) + public function errorSummary($models, $options = []) { if (!is_array($models)) { - $models = array($models); + $models = [$models]; } - $lines = array(); + $lines = []; foreach ($models as $model) { - /** @var $model Model */ + /** @var Model $model */ foreach ($model->getFirstErrors() as $error) { $lines[] = Html::encode($error); } @@ -257,12 +257,101 @@ class ActiveForm extends Widget * @return ActiveField the created ActiveField object * @see fieldConfig */ - public function field($model, $attribute, $options = array()) + public function field($model, $attribute, $options = []) { - return Yii::createObject(array_merge($this->fieldConfig, $options, array( + return Yii::createObject(array_merge($this->fieldConfig, $options, [ 'model' => $model, 'attribute' => $attribute, 'form' => $this, - ))); + ])); + } + + /** + * Validates one or several models and returns an error message array indexed by the attribute IDs. + * This is a helper method that simplifies the way of writing AJAX validation code. + * + * For example, you may use the following code in a controller action to respond + * to an AJAX validation request: + * + * ~~~ + * $model = new Post; + * $model->load($_POST); + * if (Yii::$app->request->isAjax) { + * Yii::$app->response->format = Response::FORMAT_JSON; + * return ActiveForm::validate($model); + * } + * // ... respond to non-AJAX request ... + * ~~~ + * + * To validate multiple models, simply pass each model as a parameter to this method, like + * the following: + * + * ~~~ + * ActiveForm::validate($model1, $model2, ...); + * ~~~ + * + * @param Model $model the model to be validated + * @param mixed $attributes list of attributes that should be validated. + * If this parameter is empty, it means any attribute listed in the applicable + * validation rules should be validated. + * + * When this method is used to validate multiple models, this parameter will be interpreted + * as a model. + * + * @return array the error message array indexed by the attribute IDs. + */ + public static function validate($model, $attributes = null) + { + $result = []; + if ($attributes instanceof Model) { + // validating multiple models + $models = func_get_args(); + $attributes = null; + } else { + $models = [$model]; + } + /** @var Model $model */ + foreach ($models as $model) { + $model->validate($attributes); + foreach ($model->getErrors() as $attribute => $errors) { + $result[Html::getInputId($model, $attribute)] = $errors; + } + } + return $result; + } + + /** + * Validates an array of model instances and returns an error message array indexed by the attribute IDs. + * This is a helper method that simplifies the way of writing AJAX validation code for tabular input. + * + * For example, you may use the following code in a controller action to respond + * to an AJAX validation request: + * + * ~~~ + * // ... load $models ... + * if (Yii::$app->request->isAjax) { + * Yii::$app->response->format = Response::FORMAT_JSON; + * return ActiveForm::validateMultiple($models); + * } + * // ... respond to non-AJAX request ... + * ~~~ + * + * @param array $models an array of models to be validated. + * @param mixed $attributes list of attributes that should be validated. + * If this parameter is empty, it means any attribute listed in the applicable + * validation rules should be validated. + * @return array the error message array indexed by the attribute IDs. + */ + public static function validateMultiple($models, $attributes = null) + { + $result = []; + /** @var Model $model */ + foreach ($models as $i => $model) { + $model->validate($attributes); + foreach ($model->getErrors() as $attribute => $errors) { + $result[Html::getInputId($model, "[$i]" . $attribute)] = $errors; + } + } + return $result; } } diff --git a/framework/yii/widgets/ActiveFormAsset.php b/framework/yii/widgets/ActiveFormAsset.php index cbd3f9a..5acb5e1 100644 --- a/framework/yii/widgets/ActiveFormAsset.php +++ b/framework/yii/widgets/ActiveFormAsset.php @@ -15,10 +15,10 @@ use yii\web\AssetBundle; class ActiveFormAsset extends AssetBundle { public $sourcePath = '@yii/assets'; - public $js = array( + public $js = [ 'yii.activeForm.js', - ); - public $depends = array( + ]; + public $depends = [ 'yii\web\YiiAsset', - ); + ]; } diff --git a/framework/yii/widgets/ListViewBase.php b/framework/yii/widgets/BaseListView.php similarity index 76% rename from framework/yii/widgets/ListViewBase.php rename to framework/yii/widgets/BaseListView.php index 33186ae..4c4e5a4 100644 --- a/framework/yii/widgets/ListViewBase.php +++ b/framework/yii/widgets/BaseListView.php @@ -17,13 +17,13 @@ use yii\helpers\Html; * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ -abstract class ListViewBase extends Widget +abstract class BaseListView extends Widget { /** * @var array the HTML attributes for the container tag of the list view. * The "tag" element specifies the tag name of the container element and defaults to "div". */ - public $options = array(); + public $options = []; /** * @var \yii\data\DataProviderInterface the data provider for the view. This property is required. */ @@ -32,12 +32,12 @@ abstract class ListViewBase extends Widget * @var array the configuration for the pager widget. By default, [[LinkPager]] will be * used to render the pager. You can use a different widget class by configuring the "class" element. */ - public $pager = array(); + public $pager = []; /** * @var array the configuration for the sorter widget. By default, [[LinkSorter]] will be * used to render the sorter. You can use a different widget class by configuring the "class" element. */ - public $sorter = array(); + public $sorter = []; /** * @var string the HTML content to be displayed as the summary of the list view. * If you do not want to show the summary, you may set it with an empty string. @@ -53,10 +53,13 @@ abstract class ListViewBase extends Widget */ public $summary; /** - * @var string|boolean the HTML content to be displayed when [[dataProvider]] does not have any data. - * If false, the list view will still be displayed (without body content though). + * @var boolean whether to show the list view if [[dataProvider]] returns no data. */ - public $empty; + public $showOnEmpty = false; + /** + * @var string the HTML content to be displayed when [[dataProvider]] does not have any data. + */ + public $emptyText; /** * @var string the layout that determines how different sections of the list view should be organized. * The following tokens will be replaced with the corresponding section contents: @@ -83,6 +86,10 @@ abstract class ListViewBase extends Widget if ($this->dataProvider === null) { throw new InvalidConfigException('The "dataProvider" property must be set.'); } + if ($this->emptyText === null) { + $this->emptyText = Yii::t('yii', 'No results found.'); + } + $this->dataProvider->prepare(); } /** @@ -90,14 +97,13 @@ abstract class ListViewBase extends Widget */ public function run() { - if ($this->dataProvider->getCount() > 0 || $this->empty === false) { - $widget = $this; - $content = preg_replace_callback("/{\\w+}/", function ($matches) use ($widget) { - $content = $widget->renderSection($matches[0]); + if ($this->dataProvider->getCount() > 0 || $this->showOnEmpty) { + $content = preg_replace_callback("/{\\w+}/", function ($matches) { + $content = $this->renderSection($matches[0]); return $content === false ? $matches[0] : $content; }, $this->layout); } else { - $content = '<div class="empty">' . ($this->empty === null ? Yii::t('yii', 'No results found.') : $this->empty) . '</div>'; + $content = $this->renderEmpty(); } $tag = ArrayHelper::remove($this->options, 'tag', 'div'); echo Html::tag($tag, $content, $this->options); @@ -126,35 +132,53 @@ abstract class ListViewBase extends Widget } /** + * Renders the HTML content indicating that the list view has no data. + * @return string the rendering result + * @see emptyText + */ + public function renderEmpty() + { + return '<div class="empty">' . ($this->emptyText === null ? Yii::t('yii', 'No results found.') : $this->emptyText) . '</div>'; + } + + /** * Renders the summary text. */ public function renderSummary() { $count = $this->dataProvider->getCount(); + if ($count <= 0) { + return ''; + } if (($pagination = $this->dataProvider->getPagination()) !== false) { $totalCount = $this->dataProvider->getTotalCount(); $begin = $pagination->getPage() * $pagination->pageSize + 1; $end = $begin + $count - 1; + if ($begin > $end) { + $begin = $end; + } $page = $pagination->getPage() + 1; $pageCount = $pagination->pageCount; if (($summaryContent = $this->summary) === null) { - $summaryContent = '<div class="summary">' . Yii::t('yii', 'Total <b>1</b> item.|Showing <b>{begin}-{end}</b> of <b>{totalCount}</b> items.', $totalCount) . '</div>'; + $summaryContent = '<div class="summary">' + . Yii::t('yii', 'Showing <b>{begin, number}-{end, number}</b> of <b>{totalCount, number}</b> {totalCount, plural, one{item} other{items}}.') + . '</div>'; } } else { $begin = $page = $pageCount = 1; $end = $totalCount = $count; if (($summaryContent = $this->summary) === null) { - $summaryContent = '<div class="summary">' . Yii::t('yii', 'Total <b>1</b> item.|Total <b>{count}</b> items.', $count) . '</div>'; + $summaryContent = '<div class="summary">' . Yii::t('yii', 'Total <b>{count, number}</b> {count, plural, one{item} other{items}}.') . '</div>'; } } - return strtr($summaryContent, array( - '{begin}' => $begin, - '{end}' => $end, - '{count}' => $count, - '{totalCount}' => $totalCount, - '{page}' => $page, - '{pageCount}' => $pageCount, - )); + return Yii::$app->getI18n()->format($summaryContent, [ + 'begin' => $begin, + 'end' => $end, + 'count' => $count, + 'totalCount' => $totalCount, + 'page' => $page, + 'pageCount' => $pageCount, + ], Yii::$app->language); } /** diff --git a/framework/yii/widgets/Breadcrumbs.php b/framework/yii/widgets/Breadcrumbs.php index f1427aa..2353845 100644 --- a/framework/yii/widgets/Breadcrumbs.php +++ b/framework/yii/widgets/Breadcrumbs.php @@ -23,12 +23,12 @@ use yii\helpers\Html; * * ~~~ * // $this is the view object currently being used - * echo Breadcrumbs::widget(array( - * 'links' => array( - * array('label' => 'Sample Post', 'url' => array('post/edit', 'id' => 1)), + * echo Breadcrumbs::widget([ + * 'links' => [ + * ['label' => 'Sample Post', 'url' => ['post/edit', 'id' => 1]], * 'Edit', - * ), - * )); + * ], + * ]); * ~~~ * * Because breadcrumbs usually appears in nearly every page of a website, you may consider placing it in a layout view. @@ -37,9 +37,9 @@ use yii\helpers\Html; * * ~~~ * // $this is the view object currently being used - * echo Breadcrumbs::widget(array( - * 'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : array(), - * )); + * echo Breadcrumbs::widget([ + * 'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [], + * ]); * ~~~ * * @author Qiang Xue <qiang.xue@gmail.com> @@ -54,7 +54,7 @@ class Breadcrumbs extends Widget /** * @var array the HTML attributes for the breadcrumb container tag. */ - public $options = array('class' => 'breadcrumb'); + public $options = ['class' => 'breadcrumb']; /** * @var boolean whether to HTML-encode the link labels. */ @@ -71,16 +71,16 @@ class Breadcrumbs extends Widget * with the following structure: * * ~~~ - * array( + * [ * 'label' => 'label of the link', // required * 'url' => 'url of the link', // optional, will be processed by Html::url() - * ) + * ] * ~~~ * - * If a link is active, you only need to specify its "label", and instead of writing `array('label' => $label)`, + * If a link is active, you only need to specify its "label", and instead of writing `['label' => $label]`, * you should simply use `$label`. */ - public $links = array(); + public $links = []; /** * @var string the template used to render each inactive item in the breadcrumbs. The token `{link}` * will be replaced with the actual HTML link for each inactive item. @@ -100,18 +100,18 @@ class Breadcrumbs extends Widget if (empty($this->links)) { return; } - $links = array(); + $links = []; if ($this->homeLink === null) { - $links[] = $this->renderItem(array( + $links[] = $this->renderItem([ 'label' => Yii::t('yii', 'Home'), 'url' => Yii::$app->homeUrl, - ), $this->itemTemplate); + ], $this->itemTemplate); } elseif ($this->homeLink !== false) { $links[] = $this->renderItem($this->homeLink, $this->itemTemplate); } foreach ($this->links as $link) { if (!is_array($link)) { - $link = array('label' => $link); + $link = ['label' => $link]; } $links[] = $this->renderItem($link, isset($link['url']) ? $this->itemTemplate : $this->activeItemTemplate); } @@ -133,9 +133,9 @@ class Breadcrumbs extends Widget throw new InvalidConfigException('The "label" element is required for each link.'); } if (isset($link['url'])) { - return strtr($template, array('{link}' => Html::a($label, $link['url']))); + return strtr($template, ['{link}' => Html::a($label, $link['url'])]); } else { - return strtr($template, array('{link}' => $label)); + return strtr($template, ['{link}' => $label]); } } } diff --git a/framework/yii/widgets/ContentDecorator.php b/framework/yii/widgets/ContentDecorator.php index f91117a..9224f35 100644 --- a/framework/yii/widgets/ContentDecorator.php +++ b/framework/yii/widgets/ContentDecorator.php @@ -24,7 +24,7 @@ class ContentDecorator extends Widget /** * @var array the parameters (name => value) to be extracted and made available in the decorative view. */ - public $params = array(); + public $params = []; /** * Starts recording a clip. diff --git a/framework/yii/widgets/DetailView.php b/framework/yii/widgets/DetailView.php index c3bf864..43d5a3e 100644 --- a/framework/yii/widgets/DetailView.php +++ b/framework/yii/widgets/DetailView.php @@ -30,17 +30,17 @@ use yii\helpers\Inflector; * A typical usage of DetailView is as follows: * * ~~~ - * echo DetailView::widget(array( + * echo DetailView::widget([ * 'model' => $model, - * 'attributes' => array( + * 'attributes' => [ * 'title', // title attribute (in plain text) * 'description:html', // description attribute in HTML - * array( // the owner name of the model + * [ // the owner name of the model * 'label' => 'Owner', * 'value' => $model->owner->name, - * ), - * ), - * )); + * ], + * ], + * ]); * ~~~ * * @author Qiang Xue <qiang.xue@gmail.com> @@ -57,8 +57,8 @@ class DetailView extends Widget * @var array a list of attributes to be displayed in the detail view. Each array element * represents the specification for displaying one particular attribute. * - * An attribute can be specified as a string in the format of "Name" or "Name:Type", where "Name" refers to - * the attribute name, and "Type" represents the type of the attribute. The "Type" is passed to the [[Formatter::format()]] + * An attribute can be specified as a string in the format of "Name" or "Name:Format", where "Name" refers to + * the attribute name, and "Format" represents the format of the attribute. The "Format" is passed to the [[Formatter::format()]] * method to format an attribute value into a displayable text. Please refer to [[Formatter]] for the supported types. * * An attribute can also be specified in terms of an array with the following elements: @@ -67,8 +67,8 @@ class DetailView extends Widget * - label: the label associated with the attribute. If this is not specified, it will be generated from the attribute name. * - value: the value to be displayed. If this is not specified, it will be retrieved from [[model]] using the attribute name * by calling [[ArrayHelper::getValue()]]. Note that this value will be formatted into a displayable text - * according to the "type" option. - * - type: the type of the value that determines how the value would be formatted into a displayable text. + * according to the "format" option. + * - format: the type of the value that determines how the value would be formatted into a displayable text. * Please refer to [[Formatter]] for supported types. * - visible: whether the attribute is visible. If set to `false`, the attribute will be displayed. */ @@ -90,7 +90,7 @@ class DetailView extends Widget * @var array the HTML attributes for the container tag of this widget. The "tag" option specifies * what container tag should be used. It defaults to "table" if not set. */ - public $options = array('class' => 'table table-striped table-bordered'); + public $options = ['class' => 'table table-striped table-bordered']; /** * @var array|Formatter the formatter used to format model attribute values into displayable texts. * This can be either an instance of [[Formatter]] or an configuration array for creating the [[Formatter]] @@ -124,7 +124,7 @@ class DetailView extends Widget */ public function run() { - $rows = array(); + $rows = []; $i = 0; foreach ($this->attributes as $attribute) { $rows[] = $this->renderAttribute($attribute, $i++); @@ -143,10 +143,10 @@ class DetailView extends Widget protected function renderAttribute($attribute, $index) { if (is_string($this->template)) { - return strtr($this->template, array( + return strtr($this->template, [ '{label}' => $attribute['label'], - '{value}' => $this->formatter->format($attribute['value'], $attribute['type']), - )); + '{value}' => $this->formatter->format($attribute['value'], $attribute['format']), + ]); } else { return call_user_func($this->template, $attribute, $index, $this); } @@ -174,20 +174,20 @@ class DetailView extends Widget foreach ($this->attributes as $i => $attribute) { if (is_string($attribute)) { if (!preg_match('/^(\w+)(\s*:\s*(\w+))?$/', $attribute, $matches)) { - throw new InvalidConfigException('The attribute must be specified in the format of "Name" or "Name:Type"'); + throw new InvalidConfigException('The attribute must be specified in the format of "Name" or "Name:Format"'); } - $attribute = array( + $attribute = [ 'name' => $matches[1], - 'type' => isset($matches[3]) ? $matches[3] : 'text', - ); + 'format' => isset($matches[3]) ? $matches[3] : 'text', + ]; } if (!is_array($attribute)) { throw new InvalidConfigException('The attribute configuration must be an array.'); } - if (!isset($attribute['type'])) { - $attribute['type'] = 'text'; + if (!isset($attribute['format'])) { + $attribute['format'] = 'text'; } if (isset($attribute['name'])) { $name = $attribute['name']; diff --git a/framework/yii/widgets/FragmentCache.php b/framework/yii/widgets/FragmentCache.php index 3f4027b..3005df5 100644 --- a/framework/yii/widgets/FragmentCache.php +++ b/framework/yii/widgets/FragmentCache.php @@ -39,10 +39,10 @@ class FragmentCache extends Widget * For example, * * ~~~ - * array( + * [ * 'class' => 'yii\caching\DbDependency', * 'sql' => 'SELECT MAX(lastModified) FROM Post', - * ) + * ] * ~~~ * * would make the output cache depends on the last modified time of all posts. @@ -56,9 +56,9 @@ class FragmentCache extends Widget * according to the current application language: * * ~~~ - * array( + * [ * Yii::$app->language, - * ) + * ] */ public $variations; /** @@ -108,7 +108,7 @@ class FragmentCache extends Widget if (is_array($this->dependency)) { $this->dependency = Yii::createObject($this->dependency); } - $data = array($content, $this->dynamicPlaceholders); + $data = [$content, $this->dynamicPlaceholders]; $this->cache->set($this->calculateKey(), $data, $this->duration, $this->dependency); if (empty($this->getView()->cacheStack) && !empty($this->dynamicPlaceholders)) { @@ -167,7 +167,7 @@ class FragmentCache extends Widget */ protected function calculateKey() { - $factors = array(__CLASS__, $this->getId()); + $factors = [__CLASS__, $this->getId()]; if (is_array($this->variations)) { foreach ($this->variations as $factor) { $factors[] = $factor; diff --git a/framework/yii/widgets/LinkPager.php b/framework/yii/widgets/LinkPager.php index d375f46..807a4b8 100644 --- a/framework/yii/widgets/LinkPager.php +++ b/framework/yii/widgets/LinkPager.php @@ -36,7 +36,7 @@ class LinkPager extends Widget /** * @var array HTML attributes for the pager container tag. */ - public $options = array('class' => 'pagination'); + public $options = ['class' => 'pagination']; /** * @var string the CSS class for the "first" page button. */ @@ -112,7 +112,7 @@ class LinkPager extends Widget */ protected function renderPageButtons() { - $buttons = array(); + $buttons = []; $pageCount = $this->pagination->getPageCount(); $currentPage = $this->pagination->getPage(); @@ -164,15 +164,15 @@ class LinkPager extends Widget */ protected function renderPageButton($label, $page, $class, $disabled, $active) { + $options = ['class' => $class === '' ? null : $class]; if ($active) { - $class .= ' ' . $this->activePageCssClass; + Html::addCssClass($options, $this->activePageCssClass); } if ($disabled) { - $class .= ' ' . $this->disabledPageCssClass; + Html::addCssClass($options, $this->disabledPageCssClass); + return Html::tag('li', Html::tag('span', $label), $options); } - $class = trim($class); - $options = array('class' => $class === '' ? null : $class); - return Html::tag('li', Html::a($label, $this->pagination->createUrl($page), array('data-page' => $page)), $options); + return Html::tag('li', Html::a($label, $this->pagination->createUrl($page), ['data-page' => $page]), $options); } /** @@ -188,6 +188,6 @@ class LinkPager extends Widget $endPage = $pageCount - 1; $beginPage = max(0, $endPage - $this->maxButtonCount + 1); } - return array($beginPage, $endPage); + return [$beginPage, $endPage]; } } diff --git a/framework/yii/widgets/LinkSorter.php b/framework/yii/widgets/LinkSorter.php index c8b30e5..4ed8cc0 100644 --- a/framework/yii/widgets/LinkSorter.php +++ b/framework/yii/widgets/LinkSorter.php @@ -35,7 +35,7 @@ class LinkSorter extends Widget /** * @var array HTML attributes for the sorter container tag. */ - public $options = array('class' => 'sorter'); + public $options = ['class' => 'sorter']; /** @@ -64,10 +64,10 @@ class LinkSorter extends Widget protected function renderSortLinks() { $attributes = empty($this->atttributes) ? array_keys($this->sort->attributes) : $this->attributes; - $links = array(); + $links = []; foreach ($attributes as $name) { $links[] = $this->sort->link($name); } - return Html::ul($links, array('encode' => false)); + return Html::ul($links, ['encode' => false]); } } diff --git a/framework/yii/widgets/ListView.php b/framework/yii/widgets/ListView.php index c191389..ad13420 100644 --- a/framework/yii/widgets/ListView.php +++ b/framework/yii/widgets/ListView.php @@ -16,14 +16,14 @@ use yii\helpers\Html; * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ -class ListView extends ListViewBase +class ListView extends BaseListView { /** * @var array the HTML attributes for the container of the rendering result of each data model. * The "tag" element specifies the tag name of the container element and defaults to "div". * If "tag" is false, it means no container element will be rendered. */ - public $itemOptions = array(); + public $itemOptions = []; /** * @var string|callback the name of the view for rendering each data item, or a callback (e.g. an anonymous function) * for rendering each data item. If it specifies a view name, the following variables will @@ -57,7 +57,7 @@ class ListView extends ListViewBase { $models = $this->dataProvider->getModels(); $keys = $this->dataProvider->getKeys(); - $rows = array(); + $rows = []; foreach (array_values($models) as $index => $model) { $rows[] = $this->renderItem($model, $keys[$index], $index); } @@ -76,12 +76,12 @@ class ListView extends ListViewBase if ($this->itemView === null) { $content = $key; } elseif (is_string($this->itemView)) { - $content = $this->getView()->render($this->itemView, array( + $content = $this->getView()->render($this->itemView, [ 'model' => $model, 'key' => $key, 'index' => $index, 'widget' => $this, - )); + ]); } else { $content = call_user_func($this->itemView, $model, $key, $index, $this); } diff --git a/framework/yii/widgets/MaskedInput.php b/framework/yii/widgets/MaskedInput.php index 8b8cbc6..fc21cef 100644 --- a/framework/yii/widgets/MaskedInput.php +++ b/framework/yii/widgets/MaskedInput.php @@ -23,10 +23,10 @@ use yii\web\JsExpression; * shows how to use MaskedInput to collect phone numbers: * * ~~~ - * echo MaskedInput::widget(array( + * echo MaskedInput::widget([ * 'name' => 'phone', * 'mask' => '999-999-9999', - * )); + * ]); * ~~~ * * The masked text field is implemented based on the [jQuery masked input plugin](http://digitalbush.com/projects/masked-input-plugin). @@ -49,7 +49,7 @@ class MaskedInput extends InputWidget public $mask; /** * @var array the mapping between mask characters and the corresponding patterns. - * For example, `array('~' => '[+-]')` specifies that the '~' character expects '+' or '-' input. + * For example, `['~' => '[+-]']` specifies that the '~' character expects '+' or '-' input. * Defaults to null, meaning using the map as described in [[mask]]. */ public $charMap; @@ -64,7 +64,7 @@ class MaskedInput extends InputWidget /** * @var array the HTML attributes for the input tag. */ - public $options = array(); + public $options = []; /** @@ -119,7 +119,7 @@ class MaskedInput extends InputWidget */ protected function getClientOptions() { - $options = array(); + $options = []; if ($this->placeholder !== null) { $options['placeholder'] = $this->placeholder; } diff --git a/framework/yii/widgets/MaskedInputAsset.php b/framework/yii/widgets/MaskedInputAsset.php index d091505..52fa2da 100644 --- a/framework/yii/widgets/MaskedInputAsset.php +++ b/framework/yii/widgets/MaskedInputAsset.php @@ -15,10 +15,10 @@ use yii\web\AssetBundle; class MaskedInputAsset extends AssetBundle { public $sourcePath = '@yii/assets'; - public $js = array( + public $js = [ 'jquery.maskedinput.js', - ); - public $depends = array( + ]; + public $depends = [ 'yii\web\YiiAsset', - ); + ]; } diff --git a/framework/yii/widgets/Menu.php b/framework/yii/widgets/Menu.php index 8bf845e..d5ff8ef 100644 --- a/framework/yii/widgets/Menu.php +++ b/framework/yii/widgets/Menu.php @@ -27,19 +27,19 @@ use yii\helpers\Html; * The following example shows how to use Menu: * * ~~~ - * echo Menu::widget(array( - * 'items' => array( + * echo Menu::widget([ + * 'items' => [ * // Important: you need to specify url as 'controller/action', * // not just as 'controller' even if default action is used. - * array('label' => 'Home', 'url' => array('site/index')), + * ['label' => 'Home', 'url' => ['site/index']], * // 'Products' menu item will be selected as long as the route is 'product/index' - * array('label' => 'Products', 'url' => array('product/index'), 'items' => array( - * array('label' => 'New Arrivals', 'url' => array('product/index', 'tag' => 'new')), - * array('label' => 'Most Popular', 'url' => array('product/index', 'tag' => 'popular')), - * )), - * array('label' => 'Login', 'url' => array('site/login'), 'visible' => Yii::$app->user->isGuest), - * ), - * )); + * ['label' => 'Products', 'url' => ['product/index'], 'items' => [ + * ['label' => 'New Arrivals', 'url' => ['product/index', 'tag' => 'new']], + * ['label' => 'Most Popular', 'url' => ['product/index', 'tag' => 'popular']], + * ]], + * ['label' => 'Login', 'url' => ['site/login'], 'visible' => Yii::$app->user->isGuest], + * ], + * ]); * ~~~ * * @author Qiang Xue <qiang.xue@gmail.com> @@ -67,14 +67,14 @@ class Menu extends Widget * If this option is not set, [[linkTemplate]] or [[labelTemplate]] will be used instead. * - options: array, optional, the HTML attributes for the menu container tag. */ - public $items = array(); + public $items = []; /** * @var array list of HTML attributes for the menu container tag. This will be overwritten * by the "options" set in individual [[items]]. The following special options are recognized: * * - tag: string, defaults to "li", the tag name of the item container tags. */ - public $itemOptions = array(); + public $itemOptions = []; /** * @var string the template used to render the body of a menu which is a link. * In this template, the token `{url}` will be replaced with the corresponding link URL; @@ -104,7 +104,7 @@ class Menu extends Widget /** * @var boolean whether to automatically activate items according to whether their route setting * matches the currently requested route. - * @see isItemActive + * @see isItemActive() */ public $activateItems = true; /** @@ -122,7 +122,7 @@ class Menu extends Widget * * - tag: string, defaults to "ul", the tag name of the item container tags. */ - public $options = array(); + public $options = []; /** * @var string the CSS class that will be assigned to the first item in the main menu or each submenu. * Defaults to null, meaning no such CSS class will be assigned. @@ -137,14 +137,14 @@ class Menu extends Widget * @var string the route used to determine if a menu item is active or not. * If not set, it will use the route of the current request. * @see params - * @see isItemActive + * @see isItemActive() */ public $route; /** * @var array the parameters used to determine if a menu item is active or not. * If not set, it will use `$_GET`. * @see route - * @see isItemActive + * @see isItemActive() */ public $params; @@ -174,11 +174,11 @@ class Menu extends Widget protected function renderItems($items) { $n = count($items); - $lines = array(); + $lines = []; foreach ($items as $i => $item) { - $options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', array())); + $options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', [])); $tag = ArrayHelper::remove($options, 'tag', 'li'); - $class = array(); + $class = []; if ($item['active']) { $class[] = $this->activeCssClass; } @@ -198,9 +198,9 @@ class Menu extends Widget $menu = $this->renderItem($item); if (!empty($item['items'])) { - $menu .= strtr($this->submenuTemplate, array( + $menu .= strtr($this->submenuTemplate, [ '{items}' => $this->renderItems($item['items']), - )); + ]); } $lines[] = Html::tag($tag, $menu, $options); } @@ -217,15 +217,15 @@ class Menu extends Widget { if (isset($item['url'])) { $template = ArrayHelper::getValue($item, 'template', $this->linkTemplate); - return strtr($template, array( + return strtr($template, [ '{url}' => Html::url($item['url']), '{label}' => $item['label'], - )); + ]); } else { $template = ArrayHelper::getValue($item, 'template', $this->labelTemplate); - return strtr($template, array( + return strtr($template, [ '{label}' => $item['label'], - )); + ]); } } diff --git a/framework/yii/yii b/framework/yii/yii index ebba1a6..b34cfc8 100755 --- a/framework/yii/yii +++ b/framework/yii/yii @@ -15,10 +15,10 @@ defined('STDIN') or define('STDIN', fopen('php://stdin', 'r')); require(__DIR__ . '/Yii.php'); -$application = new yii\console\Application(array( +$application = new yii\console\Application([ 'id' => 'yii-console', 'basePath' => __DIR__ . '/console', 'controllerPath' => '@yii/console/controllers', -)); +]); $exitCode = $application->run(); exit($exitCode); diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..fa6b3a4 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,41 @@ +Yii 2.0 Unit tests +================== + +DIRECTORY STRUCTURE +------------------- + + unit/ Unit tests to run with PHPUnit + data/ models, config and other test data + config.php this file contains configuration for database and caching backends + framework/ the framework unit tests + runtime/ the application runtime dir for the yii test app + web/ webapp for functional testing + + +HOW TO RUN THE TESTS +-------------------- + +Make sure you have PHPUnit installed. + +Run PHPUnit in the yii repo base directory. + +```php +phpunit +``` + +You can run tests for specific groups only: + +```php +phpunit --group=mysql,base,i18n +``` + +You can get a list of available groups via `phpunit --list-groups`. + +TEST CONFIGURATION +------------------ + +PHPUnit configuration is in `phpunit.xml.dist` in repository root folder. +You can create your own phpunit.xml to override dist config. + +Database and other backend system configuration can be found in `unit/data/config.php` +adjust them to your needs to allow testing databases and caching in your environment. \ No newline at end of file diff --git a/tests/unit/TestCase.php b/tests/unit/TestCase.php index bd95465..c8e5731 100644 --- a/tests/unit/TestCase.php +++ b/tests/unit/TestCase.php @@ -2,10 +2,15 @@ namespace yiiunit; +require_once('PHPUnit/Runner/Version.php'); +spl_autoload_unregister(['Yii', 'autoload']); +require_once('PHPUnit/Autoload.php'); +spl_autoload_register(['Yii', 'autoload']); // put yii's autoloader at the end + /** * This is the base class for all yii framework unit tests. */ -abstract class TestCase extends \yii\test\TestCase +abstract class TestCase extends \PHPUnit_Framework_TestCase { public static $params; @@ -27,10 +32,10 @@ abstract class TestCase extends \yii\test\TestCase */ public function getParam($name, $default = null) { - if (self::$params === null) { - self::$params = require(__DIR__ . '/data/config.php'); + if (static::$params === null) { + static::$params = require(__DIR__ . '/data/config.php'); } - return isset(self::$params[$name]) ? self::$params[$name] : $default; + return isset(static::$params[$name]) ? static::$params[$name] : $default; } /** @@ -39,12 +44,12 @@ abstract class TestCase extends \yii\test\TestCase * @param array $config The application configuration, if needed * @param string $appClass name of the application class to create */ - protected function mockApplication($config = array(), $appClass = '\yii\console\Application') + protected function mockApplication($config = [], $appClass = '\yii\console\Application') { - static $defaultConfig = array( + static $defaultConfig = [ 'id' => 'testapp', 'basePath' => __DIR__, - ); + ]; new $appClass(array_merge($defaultConfig, $config)); } diff --git a/tests/unit/VendorTestCase.php b/tests/unit/VendorTestCase.php new file mode 100644 index 0000000..d633d02 --- /dev/null +++ b/tests/unit/VendorTestCase.php @@ -0,0 +1,30 @@ +<?php + +namespace yiiunit; + +use yii\base\NotSupportedException; +use Yii; + +/** + * This is the base class for all yii framework unit tests, which requires + * external vendor libraries to function. + */ +class VendorTestCase extends TestCase +{ + /** + * This method is called before the first test of this test class is run. + * Attempts to load vendor autoloader. + * @throws \yii\base\NotSupportedException + */ + public static function setUpBeforeClass() + { + $vendorDir = __DIR__ . '/vendor'; + Yii::setAlias('@vendor', $vendorDir); + $vendorAutoload = $vendorDir . '/autoload.php'; + if (file_exists($vendorAutoload)) { + require_once($vendorAutoload); + } else { + throw new NotSupportedException("Vendor autoload file '{$vendorAutoload}' is missing."); + } + } +} \ No newline at end of file diff --git a/tests/unit/bootstrap.php b/tests/unit/bootstrap.php index 0580db6..c63e002 100644 --- a/tests/unit/bootstrap.php +++ b/tests/unit/bootstrap.php @@ -1,5 +1,8 @@ <?php +// ensure we get report on all possible php errors +error_reporting(-1); + define('YII_ENABLE_ERROR_HANDLER', false); define('YII_DEBUG', true); $_SERVER['SCRIPT_NAME'] = '/' . __DIR__; diff --git a/tests/unit/data/ar/ActiveRecord.php b/tests/unit/data/ar/ActiveRecord.php index f1194ea..bcb5b48 100644 --- a/tests/unit/data/ar/ActiveRecord.php +++ b/tests/unit/data/ar/ActiveRecord.php @@ -7,8 +7,6 @@ namespace yiiunit\data\ar; -use yii\db\Connection; - /** * ActiveRecord is ... * diff --git a/tests/unit/data/ar/Customer.php b/tests/unit/data/ar/Customer.php index bbc0182..0d2add1 100644 --- a/tests/unit/data/ar/Customer.php +++ b/tests/unit/data/ar/Customer.php @@ -1,8 +1,6 @@ <?php namespace yiiunit\data\ar; -use yii\db\ActiveQuery; - /** * Class Customer * @@ -19,6 +17,9 @@ class Customer extends ActiveRecord public $status2; + public static $afterSaveInsert = null; + public static $afterSaveNewRecord = null; + public static function tableName() { return 'tbl_customer'; @@ -26,11 +27,18 @@ class Customer extends ActiveRecord public function getOrders() { - return $this->hasMany('Order', array('customer_id' => 'id'))->orderBy('id'); + return $this->hasMany(Order::className(), ['customer_id' => 'id'])->orderBy('id'); } public static function active($query) { $query->andWhere('status=1'); } + + public function afterSave($insert) + { + static::$afterSaveInsert = $insert; + static::$afterSaveNewRecord = $this->isNewRecord; + parent::afterSave($insert); + } } diff --git a/tests/unit/data/ar/NullValues.php b/tests/unit/data/ar/NullValues.php new file mode 100644 index 0000000..e6aa3b9 --- /dev/null +++ b/tests/unit/data/ar/NullValues.php @@ -0,0 +1,20 @@ +<?php + +namespace yiiunit\data\ar; + +/** + * Class NullValues + * + * @property integer $id + * @property integer $var1 + * @property integer $var2 + * @property integer $var3 + * @property string $stringcol + */ +class NullValues extends ActiveRecord +{ + public static function tableName() + { + return 'tbl_null_values'; + } +} diff --git a/tests/unit/data/ar/Order.php b/tests/unit/data/ar/Order.php index 063bb67..6d5e926 100644 --- a/tests/unit/data/ar/Order.php +++ b/tests/unit/data/ar/Order.php @@ -19,17 +19,17 @@ class Order extends ActiveRecord public function getCustomer() { - return $this->hasOne('Customer', array('id' => 'customer_id')); + return $this->hasOne(Customer::className(), ['id' => 'customer_id']); } public function getOrderItems() { - return $this->hasMany('OrderItem', array('order_id' => 'id')); + return $this->hasMany(OrderItem::className(), ['order_id' => 'id']); } public function getItems() { - return $this->hasMany('Item', array('id' => 'item_id')) + return $this->hasMany(Item::className(), ['id' => 'item_id']) ->via('orderItems', function ($q) { // additional query configuration })->orderBy('id'); @@ -37,9 +37,9 @@ class Order extends ActiveRecord public function getBooks() { - return $this->hasMany('Item', array('id' => 'item_id')) - ->viaTable('tbl_order_item', array('order_id' => 'id')) - ->where(array('category_id' => 1)); + return $this->hasMany(Item::className(), ['id' => 'item_id']) + ->viaTable('tbl_order_item', ['order_id' => 'id']) + ->where(['category_id' => 1]); } public function beforeSave($insert) diff --git a/tests/unit/data/ar/OrderItem.php b/tests/unit/data/ar/OrderItem.php index 297432b..b340a46 100644 --- a/tests/unit/data/ar/OrderItem.php +++ b/tests/unit/data/ar/OrderItem.php @@ -19,11 +19,11 @@ class OrderItem extends ActiveRecord public function getOrder() { - return $this->hasOne('Order', array('id' => 'order_id')); + return $this->hasOne(Order::className(), ['id' => 'order_id']); } public function getItem() { - return $this->hasOne('Item', array('id' => 'item_id')); + return $this->hasOne(Item::className(), ['id' => 'item_id']); } } diff --git a/tests/unit/data/base/InvalidRulesModel.php b/tests/unit/data/base/InvalidRulesModel.php index 13f428d..3c865e5 100644 --- a/tests/unit/data/base/InvalidRulesModel.php +++ b/tests/unit/data/base/InvalidRulesModel.php @@ -10,8 +10,8 @@ class InvalidRulesModel extends Model { public function rules() { - return array( - array('test'), - ); + return [ + ['test'], + ]; } } diff --git a/tests/unit/data/base/Singer.php b/tests/unit/data/base/Singer.php index 96c4850..547cee8 100644 --- a/tests/unit/data/base/Singer.php +++ b/tests/unit/data/base/Singer.php @@ -13,10 +13,10 @@ class Singer extends Model public function rules() { - return array( - array('lastName', 'default', 'value' => 'Lennon'), - array('lastName', 'required'), - array('underscore_style', 'yii\captcha\CaptchaValidator'), - ); + return [ + [['lastName'], 'default', 'value' => 'Lennon'], + [['lastName'], 'required'], + [['underscore_style'], 'yii\captcha\CaptchaValidator'], + ]; } } diff --git a/tests/unit/data/base/Speaker.php b/tests/unit/data/base/Speaker.php index b0acc6b..7585df3 100644 --- a/tests/unit/data/base/Speaker.php +++ b/tests/unit/data/base/Speaker.php @@ -26,22 +26,20 @@ class Speaker extends Model public function attributeLabels() { - return array( + return [ 'customLabel' => 'This is the custom label', - ); + ]; } public function rules() { - return array( - - ); + return []; } public function scenarios() { - return array( - 'test' => array('firstName', 'lastName', '!underscore_style'), - ); + return [ + 'test' => ['firstName', 'lastName', '!underscore_style'], + ]; } } diff --git a/tests/unit/data/config.php b/tests/unit/data/config.php index beba900..cb3306f 100644 --- a/tests/unit/data/config.php +++ b/tests/unit/data/config.php @@ -1,37 +1,37 @@ <?php -return array( - 'databases' => array( - 'cubrid' => array( +return [ + 'databases' => [ + 'cubrid' => [ 'dsn' => 'cubrid:dbname=demodb;host=localhost;port=33000', 'username' => 'dba', 'password' => '', 'fixture' => __DIR__ . '/cubrid.sql', - ), - 'mysql' => array( + ], + 'mysql' => [ 'dsn' => 'mysql:host=127.0.0.1;dbname=yiitest', 'username' => 'travis', 'password' => '', 'fixture' => __DIR__ . '/mysql.sql', - ), - 'sqlite' => array( + ], + 'sqlite' => [ 'dsn' => 'sqlite::memory:', 'fixture' => __DIR__ . '/sqlite.sql', - ), - 'sqlsrv' => array( + ], + 'sqlsrv' => [ 'dsn' => 'sqlsrv:Server=localhost;Database=test', 'username' => '', 'password' => '', 'fixture' => __DIR__ . '/mssql.sql', - ), - 'pgsql' => array( + ], + 'pgsql' => [ 'dsn' => 'pgsql:host=localhost;dbname=yiitest;port=5432;', 'username' => 'postgres', 'password' => 'postgres', 'fixture' => __DIR__ . '/postgres.sql', - ), - 'redis' => array( + ], + 'redis' => [ 'dsn' => 'redis://localhost:6379/0', 'password' => null, - ), - ), -); + ], + ], +]; diff --git a/tests/unit/data/cubrid.sql b/tests/unit/data/cubrid.sql index 3dcfa37..905ebd2 100644 --- a/tests/unit/data/cubrid.sql +++ b/tests/unit/data/cubrid.sql @@ -9,6 +9,7 @@ DROP TABLE IF EXISTS tbl_item; DROP TABLE IF EXISTS tbl_order; DROP TABLE IF EXISTS tbl_category; DROP TABLE IF EXISTS tbl_customer; +DROP TABLE IF EXISTS tbl_null_values; DROP TABLE IF EXISTS tbl_type; DROP TABLE IF EXISTS tbl_constraints; @@ -61,6 +62,16 @@ CREATE TABLE `tbl_order_item` ( CONSTRAINT `FK_order_item_item_id` FOREIGN KEY (`item_id`) REFERENCES `tbl_item` (`id`) ON DELETE CASCADE ); +CREATE TABLE tbl_null_values ( + `id` INT(11) NOT NULL AUTO_INCREMENT, + `var1` INT NULL, + `var2` INT NULL, + `var3` INT DEFAULT NULL, + `stringcol` VARCHAR (32) DEFAULT NULL, + PRIMARY KEY (id) +); + + CREATE TABLE `tbl_type` ( `int_col` int(11) NOT NULL, `int_col2` int(11) DEFAULT '1', diff --git a/tests/unit/data/i18n/messages/de-DE/test.php b/tests/unit/data/i18n/messages/de-DE/test.php new file mode 100644 index 0000000..d8c5290 --- /dev/null +++ b/tests/unit/data/i18n/messages/de-DE/test.php @@ -0,0 +1,9 @@ +<?php +/** + * + */ +return [ + 'The dog runs fast.' => 'Der Hund rennt schnell.', + 'His speed is about {n} km/h.' => 'Seine Geschwindigkeit beträgt {n} km/h.', + 'His name is {name} and his speed is about {n, number} km/h.' => 'Er heißt {name} und ist {n, number} km/h schnell.', +]; \ No newline at end of file diff --git a/tests/unit/data/i18n/messages/en-US/test.php b/tests/unit/data/i18n/messages/en-US/test.php new file mode 100644 index 0000000..83342b0 --- /dev/null +++ b/tests/unit/data/i18n/messages/en-US/test.php @@ -0,0 +1,7 @@ +<?php +/** + * + */ +return [ + 'The dog runs fast.' => 'Der Hund rennt schell.', +]; \ No newline at end of file diff --git a/tests/unit/data/mssql.sql b/tests/unit/data/mssql.sql index 959c6c6..2c29fa4 100644 --- a/tests/unit/data/mssql.sql +++ b/tests/unit/data/mssql.sql @@ -4,6 +4,7 @@ IF OBJECT_ID('[dbo].[tbl_order]', 'U') IS NOT NULL DROP TABLE [dbo].[tbl_order]; IF OBJECT_ID('[dbo].[tbl_category]', 'U') IS NOT NULL DROP TABLE [dbo].[tbl_category]; IF OBJECT_ID('[dbo].[tbl_customer]', 'U') IS NOT NULL DROP TABLE [dbo].[tbl_customer]; IF OBJECT_ID('[dbo].[tbl_type]', 'U') IS NOT NULL DROP TABLE [dbo].[tbl_type]; +IF OBJECT_ID('[dbo].[tbl_null_values]', 'U') IS NOT NULL DROP TABLE [dbo].[tbl_null_values]; CREATE TABLE [dbo].[tbl_customer] ( [id] [int] IDENTITY(1,1) NOT NULL, @@ -54,6 +55,15 @@ CREATE TABLE [dbo].[tbl_order_item] ( ) ON [PRIMARY] ); +CREATE TABLE [dbo].[tbl_null_values] ( + id [int] UNSIGNED NOT NULL, + var1 [int] UNSIGNED NULL, + var2 [int] NULL, + var3 [int] DEFAULT NULL, + stringcol [varchar](32) DEFAULT NULL, + PRIMARY KEY (id) +); + CREATE TABLE [dbo].[tbl_type] ( [int_col] [int] NOT NULL, [int_col2] [int] DEFAULT '1', diff --git a/tests/unit/data/mysql.sql b/tests/unit/data/mysql.sql index 133348d..43322ad 100644 --- a/tests/unit/data/mysql.sql +++ b/tests/unit/data/mysql.sql @@ -9,6 +9,7 @@ DROP TABLE IF EXISTS tbl_item CASCADE; DROP TABLE IF EXISTS tbl_order CASCADE; DROP TABLE IF EXISTS tbl_category CASCADE; DROP TABLE IF EXISTS tbl_customer CASCADE; +DROP TABLE IF EXISTS tbl_null_values CASCADE; DROP TABLE IF EXISTS tbl_type CASCADE; DROP TABLE IF EXISTS tbl_constraints CASCADE; @@ -71,6 +72,15 @@ CREATE TABLE `tbl_composite_fk` ( CONSTRAINT `FK_composite_fk_order_item` FOREIGN KEY (`order_id`,`item_id`) REFERENCES `tbl_order_item` (`order_id`,`item_id`) ON DELETE CASCADE ); +CREATE TABLE tbl_null_values ( + `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `var1` INT UNSIGNED NULL, + `var2` INT NULL, + `var3` INT DEFAULT NULL, + `stringcol` VARCHAR (32) DEFAULT NULL, + PRIMARY KEY (id) +); + CREATE TABLE `tbl_type` ( `int_col` int(11) NOT NULL, `int_col2` int(11) DEFAULT '1', @@ -109,3 +119,35 @@ INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (2, 4, INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (2, 5, 1, 15.0); INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (2, 3, 1, 8.0); INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (3, 2, 1, 40.0); + + +/** + * (MySQL-)Database Schema for validator tests + */ + +DROP TABLE IF EXISTS tbl_validator_main CASCADE; +DROP TABLE IF EXISTS tbl_validator_ref CASCADE; + +CREATE TABLE tbl_validator_main ( + `id` INT(11) NOT NULL AUTO_INCREMENT, + `field1` VARCHAR(255), + PRIMARY KEY (`id`) +) ENGINE =InnoDB DEFAULT CHARSET =utf8; + +CREATE TABLE tbl_validator_ref ( + `id` INT(11) NOT NULL AUTO_INCREMENT, + `a_field` VARCHAR(255), + `ref` INT(11), + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +INSERT INTO tbl_validator_main (id, field1) VALUES (1, 'just a string1'); +INSERT INTO tbl_validator_main (id, field1) VALUES (2, 'just a string2'); +INSERT INTO tbl_validator_main (id, field1) VALUES (3, 'just a string3'); +INSERT INTO tbl_validator_main (id, field1) VALUES (4, 'just a string4'); +INSERT INTO tbl_validator_ref (a_field, ref) VALUES ('ref_to_2', 2); +INSERT INTO tbl_validator_ref (a_field, ref) VALUES ('ref_to_2', 2); +INSERT INTO tbl_validator_ref (a_field, ref) VALUES ('ref_to_3', 3); +INSERT INTO tbl_validator_ref (a_field, ref) VALUES ('ref_to_4', 4); +INSERT INTO tbl_validator_ref (a_field, ref) VALUES ('ref_to_4', 4); +INSERT INTO tbl_validator_ref (a_field, ref) VALUES ('ref_to_5', 5); diff --git a/tests/unit/data/postgres.sql b/tests/unit/data/postgres.sql index f8fb0eb..f9ee192 100644 --- a/tests/unit/data/postgres.sql +++ b/tests/unit/data/postgres.sql @@ -10,6 +10,7 @@ DROP TABLE IF EXISTS tbl_order CASCADE; DROP TABLE IF EXISTS tbl_category CASCADE; DROP TABLE IF EXISTS tbl_customer CASCADE; DROP TABLE IF EXISTS tbl_type CASCADE; +DROP TABLE IF EXISTS tbl_null_values CASCADE; DROP TABLE IF EXISTS tbl_constraints CASCADE; CREATE TABLE tbl_constraints @@ -54,6 +55,15 @@ CREATE TABLE tbl_order_item ( PRIMARY KEY (order_id,item_id) ); +CREATE TABLE tbl_null_values ( + id INT NOT NULL, + var1 INT NULL, + var2 INT NULL, + var3 INT DEFAULT NULL, + stringcol VARCHAR(32) DEFAULT NULL, + PRIMARY KEY (id) +); + CREATE TABLE tbl_type ( int_col integer NOT NULL, int_col2 integer DEFAULT '1', @@ -92,3 +102,32 @@ INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (2, 4, INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (2, 5, 1, 15.0); INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (2, 3, 1, 8.0); INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (3, 2, 1, 40.0); + +/** + * (Postgres-)Database Schema for validator tests + */ + +DROP TABLE IF EXISTS tbl_validator_main CASCADE; +DROP TABLE IF EXISTS tbl_validator_ref CASCADE; + +CREATE TABLE tbl_validator_main ( + id integer not null primary key, + field1 VARCHAR(255) +); + +CREATE TABLE tbl_validator_ref ( + id integer not null primary key, + a_field VARCHAR(255), + ref integer +); + +INSERT INTO tbl_validator_main (id, field1) VALUES (1, 'just a string1'); +INSERT INTO tbl_validator_main (id, field1) VALUES (2, 'just a string2'); +INSERT INTO tbl_validator_main (id, field1) VALUES (3, 'just a string3'); +INSERT INTO tbl_validator_main (id, field1) VALUES (4, 'just a string4'); +INSERT INTO tbl_validator_ref (id, a_field, ref) VALUES (1, 'ref_to_2', 2); +INSERT INTO tbl_validator_ref (id, a_field, ref) VALUES (2, 'ref_to_2', 2); +INSERT INTO tbl_validator_ref (id, a_field, ref) VALUES (3, 'ref_to_3', 3); +INSERT INTO tbl_validator_ref (id, a_field, ref) VALUES (4, 'ref_to_4', 4); +INSERT INTO tbl_validator_ref (id, a_field, ref) VALUES (5, 'ref_to_4', 4); +INSERT INTO tbl_validator_ref (id, a_field, ref) VALUES (6, 'ref_to_5', 5); \ No newline at end of file diff --git a/tests/unit/data/sqlite.sql b/tests/unit/data/sqlite.sql index f031ac3..ff79c66 100644 --- a/tests/unit/data/sqlite.sql +++ b/tests/unit/data/sqlite.sql @@ -10,6 +10,7 @@ DROP TABLE IF EXISTS tbl_order; DROP TABLE IF EXISTS tbl_category; DROP TABLE IF EXISTS tbl_customer; DROP TABLE IF EXISTS tbl_type; +DROP TABLE IF EXISTS tbl_null_values; CREATE TABLE tbl_customer ( id INTEGER NOT NULL, @@ -57,6 +58,14 @@ CREATE TABLE `tbl_composite_fk` ( CONSTRAINT `FK_composite_fk_order_item` FOREIGN KEY (`order_id`,`item_id`) REFERENCES `tbl_order_item` (`order_id`,`item_id`) ON DELETE CASCADE ); +CREATE TABLE tbl_null_values ( + id INTEGER UNSIGNED PRIMARY KEY NOT NULL, + var1 INTEGER UNSIGNED, + var2 INTEGER, + var3 INTEGER DEFAULT NULL, + stringcol VARCHAR(32) DEFAULT NULL +); + CREATE TABLE tbl_type ( int_col INTEGER NOT NULL, int_col2 INTEGER DEFAULT '1', @@ -94,4 +103,33 @@ INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (1, 2, INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (2, 4, 1, 10.0); INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (2, 5, 1, 15.0); INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (2, 3, 1, 8.0); -INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (3, 2, 1, 40.0); \ No newline at end of file +INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (3, 2, 1, 40.0); + +/** + * (SqLite-)Database Schema for validator tests + */ + +DROP TABLE IF EXISTS tbl_validator_main; +DROP TABLE IF EXISTS tbl_validator_ref; + +CREATE TABLE tbl_validator_main ( + id INTEGER PRIMARY KEY , + field1 VARCHAR(255) +); + +CREATE TABLE tbl_validator_ref ( + id INTEGER PRIMARY KEY , + a_field VARCHAR(255), + ref INT(11) +); + +INSERT INTO tbl_validator_main (id, field1) VALUES (1, 'just a string1'); +INSERT INTO tbl_validator_main (id, field1) VALUES (2, 'just a string2'); +INSERT INTO tbl_validator_main (id, field1) VALUES (3, 'just a string3'); +INSERT INTO tbl_validator_main (id, field1) VALUES (4, 'just a string4'); +INSERT INTO tbl_validator_ref (id, a_field, ref) VALUES (1, 'ref_to_2', 2); +INSERT INTO tbl_validator_ref (id, a_field, ref) VALUES (2, 'ref_to_2', 2); +INSERT INTO tbl_validator_ref (id, a_field, ref) VALUES (3, 'ref_to_3', 3); +INSERT INTO tbl_validator_ref (id, a_field, ref) VALUES (4, 'ref_to_4', 4); +INSERT INTO tbl_validator_ref (id, a_field, ref) VALUES (5, 'ref_to_4', 4); +INSERT INTO tbl_validator_ref (id, a_field, ref) VALUES (6, 'ref_to_5', 5); diff --git a/tests/unit/data/travis/apc-setup.sh b/tests/unit/data/travis/apc-setup.sh index e5e8734..3925267 100755 --- a/tests/unit/data/travis/apc-setup.sh +++ b/tests/unit/data/travis/apc-setup.sh @@ -1,2 +1,8 @@ -echo "extension = apc.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini -echo "apc.enable_cli = 1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini \ No newline at end of file +#!/bin/sh + +if [ "$(expr "$TRAVIS_PHP_VERSION" "<" "5.5")" -eq 1 ]; then + echo "extension = apc.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini + echo "apc.enable_cli = 1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini +else + echo "Not installing APC as it is not available in PHP 5.5 anymore." +fi \ No newline at end of file diff --git a/tests/unit/data/travis/cubrid-setup.sh b/tests/unit/data/travis/cubrid-setup.sh index af007ff..9c3bb74 100755 --- a/tests/unit/data/travis/cubrid-setup.sh +++ b/tests/unit/data/travis/cubrid-setup.sh @@ -1,32 +1,25 @@ #!/bin/sh # -# install CUBRID DBMS https://github.com/CUBRID/node-cubrid/blob/056e734ce36bb3fd25f100c983beb3947e899c1c/.travis.yml +# install CUBRID DBMS -sudo hostname localhost -# Update OS before installing prerequisites. +# cubrid dbms +echo 'yes' | sudo add-apt-repository ppa:cubrid/cubrid sudo apt-get update -# Install Chef Solo prerequisites. -sudo apt-get install ruby ruby-dev libopenssl-ruby rdoc ri irb build-essential ssl-cert -# Install Chef Solo. -# Chef Solo 11.4.4 is broken, so install a previous version. -# The bug is planned to be fixed in 11.4.5 which haven't been released yet. -sudo gem install --version '<11.4.4' chef --no-rdoc --no-ri -# Make sure the target directory for cookbooks exists. -mkdir -p /tmp/chef-solo -# Prepare a file with runlist for Chef Solo. -echo '{"cubrid":{"version":"'$CUBRID_VERSION'"},"run_list":["cubrid::demodb"]}' > cubrid_chef.json -# Install CUBRID via Chef Solo. Download all cookbooks from a remote URL. -sudo chef-solo -c tests/unit/data/travis/cubrid-solo.rb -j cubrid_chef.json -r http://sourceforge.net/projects/cubrid/files/CUBRID-Demo-Virtual-Machines/Vagrant/chef-cookbooks.tar.gz/download - +sudo apt-get install cubrid +/etc/profile.d/cubrid.sh +sudo apt-get install cubrid-demodb +# cubrid pdo install_pdo_cubrid() { - wget "http://pecl.php.net/get/PDO_CUBRID-9.1.0.0003.tgz" && - tar -zxf "PDO_CUBRID-9.1.0.0003.tgz" && - sh -c "cd PDO_CUBRID-9.1.0.0003 && phpize && ./configure && make && sudo make install" + wget "http://pecl.php.net/get/PDO_CUBRID-9.2.0.0001.tgz" && + tar -zxf "PDO_CUBRID-9.2.0.0001.tgz" && + sh -c "cd PDO_CUBRID-9.2.0.0001 && phpize && ./configure && make && sudo make install" echo "extension=pdo_cubrid.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini return $? } -install_pdo_cubrid > ~/pdo_cubrid.log || ( echo "=== PDO CUBRID BUILD FAILED ==="; cat ~/pdo_cubrid.log ) \ No newline at end of file +install_pdo_cubrid > ~/pdo_cubrid.log || ( echo "=== PDO CUBRID BUILD FAILED ==="; cat ~/pdo_cubrid.log ) + +echo "Installed CUBRID `dpkg -s cubrid |grep Version`" diff --git a/tests/unit/data/validators/TestValidator.php b/tests/unit/data/validators/TestValidator.php new file mode 100644 index 0000000..de6fb07 --- /dev/null +++ b/tests/unit/data/validators/TestValidator.php @@ -0,0 +1,44 @@ +<?php + +namespace yiiunit\data\validators; + + +use yii\validators\Validator; + +class TestValidator extends Validator +{ + private $_validatedAttributes = []; + private $_setErrorOnValidateAttribute = false; + + public function validateAttribute($object, $attribute) + { + $this->markAttributeValidated($attribute); + if ($this->_setErrorOnValidateAttribute == true) { + $this->addError($object, $attribute, sprintf('%s##%s', $attribute, get_class($object))); + } + } + + protected function markAttributeValidated($attr, $increaseBy = 1) + { + if (!isset($this->_validatedAttributes[$attr])) { + $this->_validatedAttributes[$attr] = 1; + } else { + $this->_validatedAttributes[$attr] = $this->_validatedAttributes[$attr] + $increaseBy; + } + } + + public function countAttributeValidations($attr) + { + return isset($this->_validatedAttributes[$attr]) ? $this->_validatedAttributes[$attr] : 0; + } + + public function isAttributeValidated($attr) + { + return isset($this->_validatedAttributes[$attr]); + } + + public function enableErrorOnValidateAttribute() + { + $this->_setErrorOnValidateAttribute = true; + } +} \ No newline at end of file diff --git a/tests/unit/data/validators/models/FakedValidationModel.php b/tests/unit/data/validators/models/FakedValidationModel.php new file mode 100644 index 0000000..e4de44b --- /dev/null +++ b/tests/unit/data/validators/models/FakedValidationModel.php @@ -0,0 +1,63 @@ +<?php + +namespace yiiunit\data\validators\models; + +use yii\base\Model; + +class FakedValidationModel extends Model +{ + public $val_attr_a; + public $val_attr_b; + public $val_attr_c; + public $val_attr_d; + private $attr = []; + + /** + * @param array $attributes + * @return self + */ + public static function createWithAttributes($attributes = []) + { + $m = new static(); + foreach ($attributes as $attribute => $value) { + $m->$attribute = $value; + } + return $m; + } + + public function rules() + { + return [ + [['val_attr_a', 'val_attr_b'], 'required', 'on' => 'reqTest'], + ['val_attr_c', 'integer'], + ]; + } + + public function inlineVal($attribute, $params = []) + { + return true; + } + + public function __get($name) + { + if (stripos($name, 'attr') === 0) { + return isset($this->attr[$name]) ? $this->attr[$name] : null; + } + + return parent::__get($name); + } + + public function __set($name, $value) + { + if (stripos($name, 'attr') === 0) { + $this->attr[$name] = $value; + } else { + parent::__set($name, $value); + } + } + + public function getAttributeLabel($attr) + { + return $attr; + } +} diff --git a/tests/unit/data/validators/models/ValidatorTestMainModel.php b/tests/unit/data/validators/models/ValidatorTestMainModel.php new file mode 100644 index 0000000..dd6696d --- /dev/null +++ b/tests/unit/data/validators/models/ValidatorTestMainModel.php @@ -0,0 +1,21 @@ +<?php + +namespace yiiunit\data\validators\models; + + +use yiiunit\data\ar\ActiveRecord; + +class ValidatorTestMainModel extends ActiveRecord +{ + public $testMainVal = 1; + + public static function tableName() + { + return 'tbl_validator_main'; + } + + public function getReferences() + { + return $this->hasMany(ValidatorTestRefModel::className(), ['ref' => 'id']); + } +} \ No newline at end of file diff --git a/tests/unit/data/validators/models/ValidatorTestRefModel.php b/tests/unit/data/validators/models/ValidatorTestRefModel.php new file mode 100644 index 0000000..ea86fc7 --- /dev/null +++ b/tests/unit/data/validators/models/ValidatorTestRefModel.php @@ -0,0 +1,23 @@ +<?php + +namespace yiiunit\data\validators\models; + + +use yiiunit\data\ar\ActiveRecord; + +class ValidatorTestRefModel extends ActiveRecord +{ + + public $test_val = 2; + public $test_val_fail = 99; + + public static function tableName() + { + return 'tbl_validator_ref'; + } + + public function getMain() + { + return $this->hasOne(ValidatorTestMainModel::className(), ['id' => 'ref']); + } +} \ No newline at end of file diff --git a/tests/unit/data/views/layout.php b/tests/unit/data/views/layout.php new file mode 100644 index 0000000..97a0888 --- /dev/null +++ b/tests/unit/data/views/layout.php @@ -0,0 +1,22 @@ +<?php +/** + * @var \yii\web\View $this + * @var string $content + */ +?> +<?php $this->beginPage(); ?> +<!DOCTYPE html> +<html> +<head> + <title>Test</title> + <?php $this->head(); ?> +</head> +<body> +<?php $this->beginBody(); ?> + +<?= $content ?> + +<?php $this->endBody(); ?> +</body> +</html> +<?php $this->endPage(); ?> diff --git a/tests/unit/data/views/rawlayout.php b/tests/unit/data/views/rawlayout.php new file mode 100644 index 0000000..aaa489f --- /dev/null +++ b/tests/unit/data/views/rawlayout.php @@ -0,0 +1,5 @@ +<?php +/** + * @var \yii\web\View $this + */ +?><?php $this->beginPage(); ?>1<?php $this->head(); ?>2<?php $this->beginBody(); ?>3<?php $this->endBody(); ?>4<?php $this->endPage(); ?> diff --git a/tests/unit/data/views/simple.php b/tests/unit/data/views/simple.php new file mode 100644 index 0000000..437ba90 --- /dev/null +++ b/tests/unit/data/views/simple.php @@ -0,0 +1 @@ +This is a damn simple view file. \ No newline at end of file diff --git a/apps/advanced/frontend/assets/.gitkeep b/tests/unit/data/web/assets/.gitignore similarity index 100% rename from apps/advanced/frontend/assets/.gitkeep rename to tests/unit/data/web/assets/.gitignore diff --git a/tests/unit/extensions/swiftmailer/MailerTest.php b/tests/unit/extensions/swiftmailer/MailerTest.php new file mode 100644 index 0000000..8398efe --- /dev/null +++ b/tests/unit/extensions/swiftmailer/MailerTest.php @@ -0,0 +1,121 @@ +<?php + +namespace yiiunit\extensions\swiftmailer; + +use Yii; +use yii\swiftmailer\Mailer; +use yii\swiftmailer\Message; +use yiiunit\VendorTestCase; + +/** + * @group vendor + * @group mail + * @group swiftmailer + */ +class MailerTest extends VendorTestCase +{ + public function setUp() + { + $this->mockApplication([ + 'components' => [ + 'email' => $this->createTestEmailComponent() + ] + ]); + } + + /** + * @return Mailer test email component instance. + */ + protected function createTestEmailComponent() + { + $component = new Mailer(); + return $component; + } + + // Tests : + + public function testSetupTransport() + { + $mailer = new Mailer(); + + $transport = \Swift_MailTransport::newInstance(); + $mailer->setTransport($transport); + $this->assertEquals($transport, $mailer->getTransport(), 'Unable to setup transport!'); + } + + /** + * @depends testSetupTransport + */ + public function testConfigureTransport() + { + $mailer = new Mailer(); + + $transportConfig = [ + 'class' => 'Swift_SmtpTransport', + 'host' => 'localhost', + 'username' => 'username', + 'password' => 'password', + ]; + $mailer->setTransport($transportConfig); + $transport = $mailer->getTransport(); + $this->assertTrue(is_object($transport), 'Unable to setup transport via config!'); + $this->assertEquals($transportConfig['class'], get_class($transport), 'Invalid transport class!'); + $this->assertEquals($transportConfig['host'], $transport->getHost(), 'Invalid transport host!'); + } + + /** + * @depends testConfigureTransport + */ + public function testConfigureTransportConstruct() + { + $mailer = new Mailer(); + + $host = 'some.test.host'; + $port = 999; + $transportConfig = [ + 'class' => 'Swift_SmtpTransport', + 'constructArgs' => [ + $host, + $port, + ], + ]; + $mailer->setTransport($transportConfig); + $transport = $mailer->getTransport(); + $this->assertTrue(is_object($transport), 'Unable to setup transport via config!'); + $this->assertEquals($host, $transport->getHost(), 'Invalid transport host!'); + $this->assertEquals($port, $transport->getPort(), 'Invalid transport host!'); + } + + /** + * @depends testConfigureTransportConstruct + */ + public function testConfigureTransportWithPlugins() + { + $mailer = new Mailer(); + + $pluginClass = 'Swift_Plugins_ThrottlerPlugin'; + $rate = 10; + + $transportConfig = [ + 'class' => 'Swift_SmtpTransport', + 'plugins' => [ + [ + 'class' => $pluginClass, + 'constructArgs' => [ + $rate, + ], + ], + ], + ]; + $mailer->setTransport($transportConfig); + $transport = $mailer->getTransport(); + $this->assertTrue(is_object($transport), 'Unable to setup transport via config!'); + $this->assertContains(':' . $pluginClass . ':', print_r($transport, true), 'Plugin not added'); + } + + public function testGetSwiftMailer() + { + $mailer = new Mailer(); + $this->assertTrue(is_object($mailer->getSwiftMailer()), 'Unable to get Swift mailer instance!'); + } +} \ No newline at end of file diff --git a/tests/unit/extensions/swiftmailer/MessageTest.php b/tests/unit/extensions/swiftmailer/MessageTest.php new file mode 100644 index 0000000..6309f15 --- /dev/null +++ b/tests/unit/extensions/swiftmailer/MessageTest.php @@ -0,0 +1,340 @@ +<?php + +namespace yiiunit\extensions\swiftmailer; + +use Yii; +use yii\helpers\FileHelper; +use yii\swiftmailer\Mailer; +use yii\swiftmailer\Message; +use yiiunit\VendorTestCase; + +/** + * @group vendor + * @group mail + * @group swiftmailer + */ +class MessageTest extends VendorTestCase +{ + /** + * @var string test email address, which will be used as receiver for the messages. + */ + protected $testEmailReceiver = 'someuser@somedomain.com'; + + public function setUp() + { + $this->mockApplication([ + 'components' => [ + 'mail' => $this->createTestEmailComponent() + ] + ]); + $filePath = $this->getTestFilePath(); + if (!file_exists($filePath)) { + FileHelper::createDirectory($filePath); + } + } + + public function tearDown() + { + $filePath = $this->getTestFilePath(); + if (file_exists($filePath)) { + FileHelper::removeDirectory($filePath); + } + } + + /** + * @return string test file path. + */ + protected function getTestFilePath() + { + return Yii::getAlias('@yiiunit/runtime') . DIRECTORY_SEPARATOR . basename(get_class($this)) . '_' . getmypid(); + } + + /** + * @return Mailer test email component instance. + */ + protected function createTestEmailComponent() + { + $component = new Mailer(); + return $component; + } + + /** + * @return Message test message instance. + */ + protected function createTestMessage() + { + return Yii::$app->getComponent('mail')->compose(); + } + + /** + * Creates image file with given text. + * @param string $fileName file name. + * @param string $text text to be applied on image. + * @return string image file full name. + */ + protected function createImageFile($fileName = 'test.jpg', $text = 'Test Image') + { + if (!function_exists('imagecreatetruecolor')) { + $this->markTestSkipped('GD lib required.'); + } + $fileFullName = $this->getTestFilePath() . DIRECTORY_SEPARATOR . $fileName; + $image = imagecreatetruecolor(120, 20); + $textColor = imagecolorallocate($image, 233, 14, 91); + imagestring($image, 1, 5, 5, $text, $textColor); + imagejpeg($image, $fileFullName); + imagedestroy($image); + return $fileFullName; + } + + /** + * Finds the attachment object in the message. + * @param Message $message message instance + * @return null|\Swift_Mime_Attachment attachment instance. + */ + protected function getAttachment(Message $message) + { + $messageParts = $message->getSwiftMessage()->getChildren(); + $attachment = null; + foreach ($messageParts as $part) { + if ($part instanceof \Swift_Mime_Attachment) { + $attachment = $part; + break; + } + } + return $attachment; + } + + // Tests : + + public function testGetSwiftMessage() + { + $message = new Message(); + $this->assertTrue(is_object($message->getSwiftMessage()), 'Unable to get Swift message!'); + } + + /** + * @depends testGetSwiftMessage + */ + public function testSetGet() + { + $message = new Message(); + + $charset = 'utf-16'; + $message->setCharset($charset); + $this->assertEquals($charset, $message->getCharset(), 'Unable to set charset!'); + + $subject = 'Test Subject'; + $message->setSubject($subject); + $this->assertEquals($subject, $message->getSubject(), 'Unable to set subject!'); + + $from = 'from@somedomain.com'; + $message->setFrom($from); + $this->assertContains($from, array_keys($message->getFrom()), 'Unable to set from!'); + + $replyTo = 'reply-to@somedomain.com'; + $message->setReplyTo($replyTo); + $this->assertContains($replyTo, array_keys($message->getReplyTo()), 'Unable to set replyTo!'); + + $to = 'someuser@somedomain.com'; + $message->setTo($to); + $this->assertContains($to, array_keys($message->getTo()), 'Unable to set to!'); + + $cc = 'ccuser@somedomain.com'; + $message->setCc($cc); + $this->assertContains($cc, array_keys($message->getCc()), 'Unable to set cc!'); + + $bcc = 'bccuser@somedomain.com'; + $message->setBcc($bcc); + $this->assertContains($bcc, array_keys($message->getBcc()), 'Unable to set bcc!'); + } + + /** + * @depends testGetSwiftMessage + */ + public function testSetupHeaders() + { + $charset = 'utf-16'; + $subject = 'Test Subject'; + $from = 'from@somedomain.com'; + $replyTo = 'reply-to@somedomain.com'; + $to = 'someuser@somedomain.com'; + $cc = 'ccuser@somedomain.com'; + $bcc = 'bccuser@somedomain.com'; + + $messageString = $this->createTestMessage() + ->setCharset($charset) + ->setSubject($subject) + ->setFrom($from) + ->setReplyTo($replyTo) + ->setTo($to) + ->setCc($cc) + ->setBcc($bcc) + ->toString(); + + $this->assertContains('charset=' . $charset, $messageString, 'Incorrect charset!'); + $this->assertContains('Subject: ' . $subject, $messageString, 'Incorrect "Subject" header!'); + $this->assertContains('From: ' . $from, $messageString, 'Incorrect "From" header!'); + $this->assertContains('Reply-To: ' . $replyTo, $messageString, 'Incorrect "Reply-To" header!'); + $this->assertContains('To: ' . $to, $messageString, 'Incorrect "To" header!'); + $this->assertContains('Cc: ' . $cc, $messageString, 'Incorrect "Cc" header!'); + $this->assertContains('Bcc: ' . $bcc, $messageString, 'Incorrect "Bcc" header!'); + } + + /** + * @depends testGetSwiftMessage + */ + public function testSend() + { + $message = $this->createTestMessage(); + $message->setTo($this->testEmailReceiver); + $message->setFrom('someuser@somedomain.com'); + $message->setSubject('Yii Swift Test'); + $message->setTextBody('Yii Swift Test body'); + $this->assertTrue($message->send()); + } + + /** + * @depends testSend + */ + public function testAttachFile() + { + $message = $this->createTestMessage(); + + $message->setTo($this->testEmailReceiver); + $message->setFrom('someuser@somedomain.com'); + $message->setSubject('Yii Swift Attach File Test'); + $message->setTextBody('Yii Swift Attach File Test body'); + $fileName = __FILE__; + $message->attach($fileName); + + $this->assertTrue($message->send()); + + $attachment = $this->getAttachment($message); + $this->assertTrue(is_object($attachment), 'No attachment found!'); + $this->assertContains($attachment->getFilename(), $fileName, 'Invalid file name!'); + } + + /** + * @depends testSend + */ + public function testAttachContent() + { + $message = $this->createTestMessage(); + + $message->setTo($this->testEmailReceiver); + $message->setFrom('someuser@somedomain.com'); + $message->setSubject('Yii Swift Create Attachment Test'); + $message->setTextBody('Yii Swift Create Attachment Test body'); + $fileName = 'test.txt'; + $fileContent = 'Test attachment content'; + $message->attachContent($fileContent, ['fileName' => $fileName]); + + $this->assertTrue($message->send()); + + $attachment = $this->getAttachment($message); + $this->assertTrue(is_object($attachment), 'No attachment found!'); + $this->assertEquals($fileName, $attachment->getFilename(), 'Invalid file name!'); + } + + /** + * @depends testSend + */ + public function testEmbedFile() + { + $fileName = $this->createImageFile('embed_file.jpg', 'Embed Image File'); + + $message = $this->createTestMessage(); + + $cid = $message->embed($fileName); + + $message->setTo($this->testEmailReceiver); + $message->setFrom('someuser@somedomain.com'); + $message->setSubject('Yii Swift Embed File Test'); + $message->setHtmlBody('Embed image: <img src="' . $cid. '" alt="pic">'); + + $this->assertTrue($message->send()); + + $attachment = $this->getAttachment($message); + $this->assertTrue(is_object($attachment), 'No attachment found!'); + $this->assertContains($attachment->getFilename(), $fileName, 'Invalid file name!'); + } + + /** + * @depends testSend + */ + public function testEmbedContent() + { + $fileFullName = $this->createImageFile('embed_file.jpg', 'Embed Image File'); + $message = $this->createTestMessage(); + + $fileName = basename($fileFullName); + $contentType = 'image/jpeg'; + $fileContent = file_get_contents($fileFullName); + + $cid = $message->embedContent($fileContent, ['fileName' => $fileName, 'contentType' => $contentType]); + + $message->setTo($this->testEmailReceiver); + $message->setFrom('someuser@somedomain.com'); + $message->setSubject('Yii Swift Embed File Test'); + $message->setHtmlBody('Embed image: <img src="' . $cid. '" alt="pic">'); + + $this->assertTrue($message->send()); + + $attachment = $this->getAttachment($message); + $this->assertTrue(is_object($attachment), 'No attachment found!'); + $this->assertEquals($fileName, $attachment->getFilename(), 'Invalid file name!'); + $this->assertEquals($contentType, $attachment->getContentType(), 'Invalid content type!'); + } + + /** + * @depends testSend + */ + public function testSendAlternativeBody() + { + $message = $this->createTestMessage(); + + $message->setTo($this->testEmailReceiver); + $message->setFrom('someuser@somedomain.com'); + $message->setSubject('Yii Swift Alternative Body Test'); + $message->setHtmlBody('<b>Yii Swift</b> test HTML body'); + $message->setTextBody('Yii Swift test plain text body'); + + $this->assertTrue($message->send()); + + $messageParts = $message->getSwiftMessage()->getChildren(); + $textPresent = false; + $htmlPresent = false; + foreach ($messageParts as $part) { + if (!($part instanceof \Swift_Mime_Attachment)) { + /* @var \Swift_Mime_MimePart $part */ + if ($part->getContentType() == 'text/plain') { + $textPresent = true; + } + if ($part->getContentType() == 'text/html') { + $htmlPresent = true; + } + } + } + $this->assertTrue($textPresent, 'No text!'); + $this->assertTrue($htmlPresent, 'No HTML!'); + } + + /** + * @depends testGetSwiftMessage + */ + public function testSerialize() + { + $message = $this->createTestMessage(); + + $message->setTo($this->testEmailReceiver); + $message->setFrom('someuser@somedomain.com'); + $message->setSubject('Yii Swift Alternative Body Test'); + $message->setTextBody('Yii Swift test plain text body'); + + $serializedMessage = serialize($message); + $this->assertNotEmpty($serializedMessage, 'Unable to serialize message!'); + + $unserializedMessaage = unserialize($serializedMessage); + $this->assertEquals($message, $unserializedMessaage, 'Unable to unserialize message!'); + } +} diff --git a/tests/unit/framework/YiiBaseTest.php b/tests/unit/framework/BaseYiiTest.php similarity index 94% rename from tests/unit/framework/YiiBaseTest.php rename to tests/unit/framework/BaseYiiTest.php index 72b5f24..6011f6b 100644 --- a/tests/unit/framework/YiiBaseTest.php +++ b/tests/unit/framework/BaseYiiTest.php @@ -5,10 +5,10 @@ use Yii; use yiiunit\TestCase; /** - * YiiBaseTest + * BaseYiiTest * @group base */ -class YiiBaseTest extends TestCase +class BaseYiiTest extends TestCase { public $aliases; @@ -28,7 +28,7 @@ class YiiBaseTest extends TestCase { $this->assertEquals(YII_PATH, Yii::getAlias('@yii')); - Yii::$aliases = array(); + Yii::$aliases = []; $this->assertFalse(Yii::getAlias('@yii', false)); Yii::setAlias('@yii', '/yii/framework'); diff --git a/tests/unit/framework/base/BehaviorTest.php b/tests/unit/framework/base/BehaviorTest.php index b6eda09..9e1d0a5 100644 --- a/tests/unit/framework/base/BehaviorTest.php +++ b/tests/unit/framework/base/BehaviorTest.php @@ -14,9 +14,9 @@ class FooClass extends Component { public function behaviors() { - return array( + return [ 'foo' => __NAMESPACE__ . '\BarBehavior', - ); + ]; } } @@ -67,7 +67,7 @@ class BehaviorTest extends TestCase $this->assertEquals('behavior property', $bar->getBehavior('bar')->behaviorProperty); $this->assertEquals('behavior method', $bar->getBehavior('bar')->behaviorMethod()); - $behavior = new BarBehavior(array('behaviorProperty' => 'reattached')); + $behavior = new BarBehavior(['behaviorProperty' => 'reattached']); $bar->attachBehavior('bar', $behavior); $this->assertEquals('reattached', $bar->behaviorProperty); } diff --git a/tests/unit/framework/base/ComponentTest.php b/tests/unit/framework/base/ComponentTest.php index 98786e2..2cad56d 100644 --- a/tests/unit/framework/base/ComponentTest.php +++ b/tests/unit/framework/base/ComponentTest.php @@ -187,7 +187,7 @@ class ComponentTest extends TestCase public function testTrigger() { - $this->component->on('click', array($this->component, 'myEventHandler')); + $this->component->on('click', [$this->component, 'myEventHandler']); $this->assertFalse($this->component->eventHandled); $this->assertNull($this->component->event); $this->component->raiseEvent(); @@ -223,7 +223,7 @@ class ComponentTest extends TestCase { $component = new NewComponent; $component->on('click', 'yiiunit\framework\base\globalEventHandler2'); - $component->on('click', array($this->component, 'myEventHandler')); + $component->on('click', [$this->component, 'myEventHandler']); $component->raiseEvent(); $this->assertTrue($component->eventHandled); $this->assertFalse($this->component->eventHandled); @@ -250,7 +250,7 @@ class ComponentTest extends TestCase $p = 'as b'; $component = new NewComponent; - $component->$p = array('class' => 'NewBehavior'); + $component->$p = ['class' => 'NewBehavior']; $this->assertSame($behavior, $component->getBehavior('a')); $this->assertTrue($component->hasProperty('p')); $component->test(); @@ -265,12 +265,12 @@ class ComponentTest extends TestCase $behavior = new NewBehavior; - $component->attachBehaviors(array( + $component->attachBehaviors([ 'a' => $behavior, 'b' => $behavior, - )); + ]); - $this->assertSame(array('a' => $behavior, 'b' => $behavior), $component->getBehaviors()); + $this->assertSame(['a' => $behavior, 'b' => $behavior], $component->getBehaviors()); } public function testDetachBehavior() @@ -302,7 +302,6 @@ class ComponentTest extends TestCase $component->detachBehaviors(); $this->assertNull($component->getBehavior('a')); $this->assertNull($component->getBehavior('b')); - } } @@ -310,7 +309,7 @@ class NewComponent extends Component { private $_object = null; private $_text = 'default'; - private $_items = array(); + private $_items = []; public $content; public function getText() diff --git a/tests/unit/framework/base/EventTest.php b/tests/unit/framework/base/EventTest.php new file mode 100644 index 0000000..6226793 --- /dev/null +++ b/tests/unit/framework/base/EventTest.php @@ -0,0 +1,93 @@ +<?php +/** + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yiiunit\framework\base; + +use yii\base\Component; +use yii\base\Event; +use yiiunit\TestCase; + +/** + * @author Qiang Xue <qiang.xue@gmail.com> + * @since 2.0 + */ +class EventTest extends TestCase +{ + public $counter; + + public function setUp() + { + $this->counter = 0; + Event::off(ActiveRecord::className(), 'save'); + Event::off(Post::className(), 'save'); + Event::off(User::className(), 'save'); + } + + public function testOn() + { + Event::on(Post::className(), 'save', function ($event) { + $this->counter += 1; + }); + Event::on(ActiveRecord::className(), 'save', function ($event) { + $this->counter += 3; + }); + $this->assertEquals(0, $this->counter); + $post = new Post; + $post->save(); + $this->assertEquals(4, $this->counter); + $user = new User; + $user->save(); + $this->assertEquals(7, $this->counter); + } + + public function testOff() + { + $handler = function ($event) { + $this->counter ++; + }; + $this->assertFalse(Event::hasHandlers(Post::className(), 'save')); + Event::on(Post::className(), 'save', $handler); + $this->assertTrue(Event::hasHandlers(Post::className(), 'save')); + Event::off(Post::className(), 'save', $handler); + $this->assertFalse(Event::hasHandlers(Post::className(), 'save')); + } + + public function testHasHandlers() + { + $this->assertFalse(Event::hasHandlers(Post::className(), 'save')); + $this->assertFalse(Event::hasHandlers(ActiveRecord::className(), 'save')); + Event::on(Post::className(), 'save', function ($event) { + $this->counter += 1; + }); + $this->assertTrue(Event::hasHandlers(Post::className(), 'save')); + $this->assertFalse(Event::hasHandlers(ActiveRecord::className(), 'save')); + + $this->assertFalse(Event::hasHandlers(User::className(), 'save')); + Event::on(ActiveRecord::className(), 'save', function ($event) { + $this->counter += 1; + }); + $this->assertTrue(Event::hasHandlers(User::className(), 'save')); + $this->assertTrue(Event::hasHandlers(ActiveRecord::className(), 'save')); + } +} + +class ActiveRecord extends Component +{ + public function save() + { + $this->trigger('save'); + } +} + +class Post extends ActiveRecord +{ +} + +class User extends ActiveRecord +{ + +} diff --git a/tests/unit/framework/base/ExceptionTest.php b/tests/unit/framework/base/ExceptionTest.php new file mode 100644 index 0000000..5a623b1 --- /dev/null +++ b/tests/unit/framework/base/ExceptionTest.php @@ -0,0 +1,23 @@ +<?php +namespace yiiunit\framework\base; + +use yiiunit\TestCase; +use yii\base\UserException; +use yii\base\InvalidCallException; + + +class ExceptionTest extends TestCase +{ + public function testToArrayWithPrevious() + { + $e = new InvalidCallException('bar', 0 ,new InvalidCallException('foo')); + $array = $e->toArray(); + $this->assertEquals('bar', $array['message']); + $this->assertEquals('foo', $array['previous']['message']); + + $e = new InvalidCallException('bar', 0 ,new UserException('foo')); + $array = $e->toArray(); + $this->assertEquals('bar', $array['message']); + $this->assertEquals('foo', $array['previous']['message']); + } +} diff --git a/tests/unit/framework/base/FormatterTest.php b/tests/unit/framework/base/FormatterTest.php index ae71a5c..322b751 100644 --- a/tests/unit/framework/base/FormatterTest.php +++ b/tests/unit/framework/base/FormatterTest.php @@ -193,7 +193,7 @@ class FormatterTest extends TestCase $value = time(); $this->assertSame(date('Y/m/d', $value), $this->formatter->format($value, 'date')); $this->assertSame(date('Y/m/d', $value), $this->formatter->format($value, 'DATE')); - $this->assertSame(date('Y-m-d', $value), $this->formatter->format($value, array('date', 'Y-m-d'))); + $this->assertSame(date('Y-m-d', $value), $this->formatter->format($value, ['date', 'Y-m-d'])); $this->setExpectedException('\yii\base\InvalidParamException'); $this->assertSame(date('Y-m-d', $value), $this->formatter->format($value, 'data')); } diff --git a/tests/unit/framework/base/ModelTest.php b/tests/unit/framework/base/ModelTest.php index d500933..da8495b 100644 --- a/tests/unit/framework/base/ModelTest.php +++ b/tests/unit/framework/base/ModelTest.php @@ -33,44 +33,44 @@ class ModelTest extends TestCase $speaker->firstName = 'Qiang'; $speaker->lastName = 'Xue'; - $this->assertEquals(array( + $this->assertEquals([ 'firstName' => 'Qiang', 'lastName' => 'Xue', 'customLabel' => null, 'underscore_style' => null, - ), $speaker->getAttributes()); + ], $speaker->getAttributes()); - $this->assertEquals(array( + $this->assertEquals([ 'firstName' => 'Qiang', 'lastName' => 'Xue', - ), $speaker->getAttributes(array('firstName', 'lastName'))); + ], $speaker->getAttributes(['firstName', 'lastName'])); - $this->assertEquals(array( + $this->assertEquals([ 'firstName' => 'Qiang', 'lastName' => 'Xue', - ), $speaker->getAttributes(null, array('customLabel', 'underscore_style'))); + ], $speaker->getAttributes(null, ['customLabel', 'underscore_style'])); - $this->assertEquals(array( + $this->assertEquals([ 'firstName' => 'Qiang', - ), $speaker->getAttributes(array('firstName', 'lastName'), array('lastName', 'customLabel', 'underscore_style'))); + ], $speaker->getAttributes(['firstName', 'lastName'], ['lastName', 'customLabel', 'underscore_style'])); } public function testSetAttributes() { // by default mass assignment doesn't work at all $speaker = new Speaker(); - $speaker->setAttributes(array('firstName' => 'Qiang', 'underscore_style' => 'test')); + $speaker->setAttributes(['firstName' => 'Qiang', 'underscore_style' => 'test']); $this->assertNull($speaker->firstName); $this->assertNull($speaker->underscore_style); // in the test scenario $speaker = new Speaker(); $speaker->setScenario('test'); - $speaker->setAttributes(array('firstName' => 'Qiang', 'underscore_style' => 'test')); + $speaker->setAttributes(['firstName' => 'Qiang', 'underscore_style' => 'test']); $this->assertNull($speaker->underscore_style); $this->assertEquals('Qiang', $speaker->firstName); - $speaker->setAttributes(array('firstName' => 'Qiang', 'underscore_style' => 'test'), false); + $speaker->setAttributes(['firstName' => 'Qiang', 'underscore_style' => 'test'], false); $this->assertEquals('test', $speaker->underscore_style); $this->assertEquals('Qiang', $speaker->firstName); } @@ -80,7 +80,7 @@ class ModelTest extends TestCase $singer = new Singer(); $this->assertEquals('Singer', $singer->formName()); - $post = array('firstName' => 'Qiang'); + $post = ['firstName' => 'Qiang']; Speaker::$formName = ''; $model = new Speaker(); @@ -91,13 +91,13 @@ class ModelTest extends TestCase Speaker::$formName = 'Speaker'; $model = new Speaker(); $model->setScenario('test'); - $this->assertTrue($model->load(array('Speaker' => $post))); + $this->assertTrue($model->load(['Speaker' => $post])); $this->assertEquals('Qiang', $model->firstName); Speaker::$formName = 'Speaker'; $model = new Speaker(); $model->setScenario('test'); - $this->assertFalse($model->load(array('Example' => array()))); + $this->assertFalse($model->load(['Example' => []])); $this->assertEquals('', $model->firstName); } @@ -109,7 +109,7 @@ class ModelTest extends TestCase $speaker = new Speaker(); $speaker->setScenario('test'); - $this->assertEquals(array('firstName', 'lastName', 'underscore_style'), $speaker->activeAttributes()); + $this->assertEquals(['firstName', 'lastName', 'underscore_style'], $speaker->activeAttributes()); } public function testIsAttributeSafe() @@ -136,34 +136,34 @@ class ModelTest extends TestCase $this->assertFalse($speaker->hasErrors('firstName')); $speaker->addError('firstName', 'Something is wrong!'); - $this->assertEquals(array('firstName' => array('Something is wrong!')), $speaker->getErrors()); - $this->assertEquals(array('Something is wrong!'), $speaker->getErrors('firstName')); + $this->assertEquals(['firstName' => ['Something is wrong!']], $speaker->getErrors()); + $this->assertEquals(['Something is wrong!'], $speaker->getErrors('firstName')); $speaker->addError('firstName', 'Totally wrong!'); - $this->assertEquals(array('firstName' => array('Something is wrong!', 'Totally wrong!')), $speaker->getErrors()); - $this->assertEquals(array('Something is wrong!', 'Totally wrong!'), $speaker->getErrors('firstName')); + $this->assertEquals(['firstName' => ['Something is wrong!', 'Totally wrong!']], $speaker->getErrors()); + $this->assertEquals(['Something is wrong!', 'Totally wrong!'], $speaker->getErrors('firstName')); $this->assertTrue($speaker->hasErrors()); $this->assertTrue($speaker->hasErrors('firstName')); $this->assertFalse($speaker->hasErrors('lastName')); - $this->assertEquals(array('Something is wrong!'), $speaker->getFirstErrors()); + $this->assertEquals(['Something is wrong!'], $speaker->getFirstErrors()); $this->assertEquals('Something is wrong!', $speaker->getFirstError('firstName')); $this->assertNull($speaker->getFirstError('lastName')); $speaker->addError('lastName', 'Another one!'); - $this->assertEquals(array( - 'firstName' => array( + $this->assertEquals([ + 'firstName' => [ 'Something is wrong!', 'Totally wrong!', - ), - 'lastName' => array('Another one!'), - ), $speaker->getErrors()); + ], + 'lastName' => ['Another one!'], + ], $speaker->getErrors()); $speaker->clearErrors('firstName'); - $this->assertEquals(array( - 'lastName' => array('Another one!'), - ), $speaker->getErrors()); + $this->assertEquals([ + 'lastName' => ['Another one!'], + ], $speaker->getErrors()); $speaker->clearErrors(); $this->assertEmpty($speaker->getErrors()); @@ -187,16 +187,16 @@ class ModelTest extends TestCase $this->assertTrue(isset($speaker['firstName'])); // iteration - $attributes = array(); + $attributes = []; foreach ($speaker as $key => $attribute) { $attributes[$key] = $attribute; } - $this->assertEquals(array( + $this->assertEquals([ 'firstName' => 'Qiang', 'lastName' => null, 'customLabel' => null, 'underscore_style' => null, - ), $attributes); + ], $attributes); // unset unset($speaker['firstName']); @@ -209,14 +209,14 @@ class ModelTest extends TestCase public function testDefaults() { $singer = new Model(); - $this->assertEquals(array(), $singer->rules()); - $this->assertEquals(array(), $singer->attributeLabels()); + $this->assertEquals([], $singer->rules()); + $this->assertEquals([], $singer->attributeLabels()); } public function testDefaultScenarios() { $singer = new Singer(); - $this->assertEquals(array('default' => array('lastName', 'underscore_style')), $singer->scenarios()); + $this->assertEquals(['default' => ['lastName', 'underscore_style']], $singer->scenarios()); } public function testIsAttributeRequired() diff --git a/tests/unit/framework/base/ObjectTest.php b/tests/unit/framework/base/ObjectTest.php index 0bd9b1d..a889f42 100644 --- a/tests/unit/framework/base/ObjectTest.php +++ b/tests/unit/framework/base/ObjectTest.php @@ -118,11 +118,11 @@ class ObjectTest extends TestCase public function testArrayProperty() { - $this->assertEquals(array(), $this->object->items); + $this->assertEquals([], $this->object->items); // the following won't work /* $this->object->items[] = 1; - $this->assertEquals(array(1), $this->object->items); + $this->assertEquals([1], $this->object->items); */ } @@ -136,7 +136,7 @@ class ObjectTest extends TestCase public function testConstruct() { - $object = new NewObject(array('text' => 'test text')); + $object = new NewObject(['text' => 'test text']); $this->assertEquals('test text', $object->getText()); } } @@ -146,7 +146,7 @@ class NewObject extends Object { private $_object = null; private $_text = 'default'; - private $_items = array(); + private $_items = []; public $content; public function getText() diff --git a/tests/unit/framework/behaviors/AutoTimestampTest.php b/tests/unit/framework/behaviors/AutoTimestampTest.php index c26d912..ca1217b 100644 --- a/tests/unit/framework/behaviors/AutoTimestampTest.php +++ b/tests/unit/framework/behaviors/AutoTimestampTest.php @@ -30,22 +30,20 @@ class AutoTimestampTest extends TestCase public function setUp() { - $this->mockApplication( - array( - 'components' => array( - 'db' => array( - 'class' => '\yii\db\Connection', - 'dsn' => 'sqlite::memory:', - ) - ) - ) - ); - - $columns = array( + $this->mockApplication([ + 'components' => [ + 'db' => [ + 'class' => '\yii\db\Connection', + 'dsn' => 'sqlite::memory:', + ] + ] + ]); + + $columns = [ 'id' => 'pk', 'create_time' => 'integer', 'update_time' => 'integer', - ); + ]; Yii::$app->getDb()->createCommand()->createTable('test_auto_timestamp', $columns)->execute(); } @@ -100,15 +98,15 @@ class ActiveRecordAutoTimestamp extends ActiveRecord { public function behaviors() { - return array( - 'timestamp' => array( + return [ + 'timestamp' => [ 'class' => AutoTimestamp::className(), - 'attributes' => array( - static::EVENT_BEFORE_INSERT => array('create_time', 'update_time'), + 'attributes' => [ + static::EVENT_BEFORE_INSERT => ['create_time', 'update_time'], static::EVENT_BEFORE_UPDATE => 'update_time', - ), - ), - ); + ], + ], + ]; } public static function tableName() diff --git a/tests/unit/framework/caching/ApcCacheTest.php b/tests/unit/framework/caching/ApcCacheTest.php index adda151..b65ec41 100644 --- a/tests/unit/framework/caching/ApcCacheTest.php +++ b/tests/unit/framework/caching/ApcCacheTest.php @@ -37,4 +37,9 @@ class ApcCacheTest extends CacheTestCase { $this->markTestSkipped("APC keys are expiring only on the next request."); } + + public function testExpireAdd() + { + $this->markTestSkipped("APC keys are expiring only on the next request."); + } } diff --git a/tests/unit/framework/caching/CacheTestCase.php b/tests/unit/framework/caching/CacheTestCase.php index 94894a3..afd514d 100644 --- a/tests/unit/framework/caching/CacheTestCase.php +++ b/tests/unit/framework/caching/CacheTestCase.php @@ -53,7 +53,7 @@ abstract class CacheTestCase extends TestCase $cache->flush(); $cache->set('string_test', 'string_test'); $cache->set('number_test', 42); - $cache->set('array_test', array('array_test' => 'array_test')); + $cache->set('array_test', ['array_test' => 'array_test']); $cache['arrayaccess_test'] = new \stdClass(); return $cache; @@ -75,7 +75,7 @@ abstract class CacheTestCase extends TestCase $this->assertTrue($cache->set('string_test', 'string_test')); $this->assertTrue($cache->set('number_test', 42)); - $this->assertTrue($cache->set('array_test', array('array_test' => 'array_test'))); + $this->assertTrue($cache->set('array_test', ['array_test' => 'array_test'])); } public function testGet() @@ -91,6 +91,37 @@ abstract class CacheTestCase extends TestCase $this->assertEquals('array_test', $array['array_test']); } + /** + * @return array testing mset with and without expiry + */ + public function msetExpiry() + { + return [[0], [2]]; + } + + /** + * @dataProvider msetExpiry + */ + public function testMset($expiry) + { + $cache = $this->getCacheInstance(); + $cache->flush(); + + $cache->mset([ + 'string_test' => 'string_test', + 'number_test' => 42, + 'array_test' => ['array_test' => 'array_test'], + ], $expiry); + + $this->assertEquals('string_test', $cache->get('string_test')); + + $this->assertEquals(42, $cache->get('number_test')); + + $array = $cache->get('array_test'); + $this->assertArrayHasKey('array_test', $array); + $this->assertEquals('array_test', $array['array_test']); + } + public function testExists() { $cache = $this->prepare(); @@ -133,10 +164,10 @@ abstract class CacheTestCase extends TestCase { $cache = $this->prepare(); - $this->assertEquals(array('string_test' => 'string_test', 'number_test' => 42), $cache->mget(array('string_test', 'number_test'))); + $this->assertEquals(['string_test' => 'string_test', 'number_test' => 42], $cache->mget(['string_test', 'number_test'])); // ensure that order does not matter - $this->assertEquals(array('number_test' => 42, 'string_test' => 'string_test'), $cache->mget(array('number_test', 'string_test'))); - $this->assertEquals(array('number_test' => 42, 'non_existent_key' => null), $cache->mget(array('number_test', 'non_existent_key'))); + $this->assertEquals(['number_test' => 42, 'string_test' => 'string_test'], $cache->mget(['number_test', 'string_test'])); + $this->assertEquals(['number_test' => 42, 'non_existent_key' => null], $cache->mget(['number_test', 'non_existent_key'])); } public function testExpire() @@ -144,13 +175,23 @@ abstract class CacheTestCase extends TestCase $cache = $this->getCacheInstance(); $this->assertTrue($cache->set('expire_test', 'expire_test', 2)); - sleep(1); + usleep(500000); $this->assertEquals('expire_test', $cache->get('expire_test')); - // wait a bit more than 2 sec to avoid random test failure usleep(2500000); $this->assertFalse($cache->get('expire_test')); } + public function testExpireAdd() + { + $cache = $this->getCacheInstance(); + + $this->assertTrue($cache->add('expire_testa', 'expire_testa', 2)); + usleep(500000); + $this->assertEquals('expire_testa', $cache->get('expire_testa')); + usleep(2500000); + $this->assertFalse($cache->get('expire_testa')); + } + public function testAdd() { $cache = $this->prepare(); @@ -165,6 +206,21 @@ abstract class CacheTestCase extends TestCase $this->assertEquals(13, $cache->get('add_test')); } + public function testMadd() + { + $cache = $this->prepare(); + + $this->assertFalse($cache->get('add_test')); + + $cache->madd([ + 'number_test' => 13, + 'add_test' => 13, + ]); + + $this->assertEquals(42, $cache->get('number_test')); + $this->assertEquals(13, $cache->get('add_test')); + } + public function testDelete() { $cache = $this->prepare(); diff --git a/tests/unit/framework/caching/DbCacheTest.php b/tests/unit/framework/caching/DbCacheTest.php index 1e94d58..c2d03e2 100644 --- a/tests/unit/framework/caching/DbCacheTest.php +++ b/tests/unit/framework/caching/DbCacheTest.php @@ -67,9 +67,7 @@ class DbCacheTest extends CacheTestCase protected function getCacheInstance() { if ($this->_cacheInstance === null) { - $this->_cacheInstance = new DbCache(array( - 'db' => $this->getConnection(), - )); + $this->_cacheInstance = new DbCache(['db' => $this->getConnection()]); } return $this->_cacheInstance; } @@ -85,4 +83,16 @@ class DbCacheTest extends CacheTestCase static::$time++; $this->assertFalse($cache->get('expire_test')); } + + public function testExpireAdd() + { + $cache = $this->getCacheInstance(); + + static::$time = \time(); + $this->assertTrue($cache->add('expire_testa', 'expire_testa', 2)); + static::$time++; + $this->assertEquals('expire_testa', $cache->get('expire_testa')); + static::$time++; + $this->assertFalse($cache->get('expire_testa')); + } } diff --git a/tests/unit/framework/caching/FileCacheTest.php b/tests/unit/framework/caching/FileCacheTest.php index 263ecb4..f102614 100644 --- a/tests/unit/framework/caching/FileCacheTest.php +++ b/tests/unit/framework/caching/FileCacheTest.php @@ -17,9 +17,7 @@ class FileCacheTest extends CacheTestCase protected function getCacheInstance() { if ($this->_cacheInstance === null) { - $this->_cacheInstance = new FileCache(array( - 'cachePath' => '@yiiunit/runtime/cache', - )); + $this->_cacheInstance = new FileCache(['cachePath' => '@yiiunit/runtime/cache']); } return $this->_cacheInstance; } @@ -35,4 +33,16 @@ class FileCacheTest extends CacheTestCase static::$time++; $this->assertFalse($cache->get('expire_test')); } + + public function testExpireAdd() + { + $cache = $this->getCacheInstance(); + + static::$time = \time(); + $this->assertTrue($cache->add('expire_testa', 'expire_testa', 2)); + static::$time++; + $this->assertEquals('expire_testa', $cache->get('expire_testa')); + static::$time++; + $this->assertFalse($cache->get('expire_testa')); + } } diff --git a/tests/unit/framework/caching/MemCacheTest.php b/tests/unit/framework/caching/MemCacheTest.php index 32374b5..63f8be1 100644 --- a/tests/unit/framework/caching/MemCacheTest.php +++ b/tests/unit/framework/caching/MemCacheTest.php @@ -26,4 +26,20 @@ class MemCacheTest extends CacheTestCase } return $this->_cacheInstance; } + + public function testExpire() + { + if (getenv('TRAVIS') == 'true') { + $this->markTestSkipped('Can not reliably test memcache expiry on travis-ci.'); + } + parent::testExpire(); + } + + public function testExpireAdd() + { + if (getenv('TRAVIS') == 'true') { + $this->markTestSkipped('Can not reliably test memcache expiry on travis-ci.'); + } + parent::testExpireAdd(); + } } diff --git a/tests/unit/framework/caching/MemCachedTest.php b/tests/unit/framework/caching/MemCachedTest.php index f39ed17..3a9d415 100644 --- a/tests/unit/framework/caching/MemCachedTest.php +++ b/tests/unit/framework/caching/MemCachedTest.php @@ -22,10 +22,24 @@ class MemCachedTest extends CacheTestCase } if ($this->_cacheInstance === null) { - $this->_cacheInstance = new MemCache(array( - 'useMemcached' => true, - )); + $this->_cacheInstance = new MemCache(['useMemcached' => true]); } return $this->_cacheInstance; } + + public function testExpire() + { + if (getenv('TRAVIS') == 'true') { + $this->markTestSkipped('Can not reliably test memcached expiry on travis-ci.'); + } + parent::testExpire(); + } + + public function testExpireAdd() + { + if (getenv('TRAVIS') == 'true') { + $this->markTestSkipped('Can not reliably test memcached expiry on travis-ci.'); + } + parent::testExpireAdd(); + } } diff --git a/tests/unit/framework/caching/RedisCacheTest.php b/tests/unit/framework/caching/RedisCacheTest.php index d02773d..3201a49 100644 --- a/tests/unit/framework/caching/RedisCacheTest.php +++ b/tests/unit/framework/caching/RedisCacheTest.php @@ -2,7 +2,6 @@ namespace yiiunit\framework\caching; use yii\caching\MemCache; use yii\caching\RedisCache; -use yiiunit\TestCase; /** * Class for testing redis cache backend @@ -18,18 +17,18 @@ class RedisCacheTest extends CacheTestCase */ protected function getCacheInstance() { - $config = array( + $config = [ 'hostname' => 'localhost', 'port' => 6379, 'database' => 0, 'dataTimeout' => 0.1, - ); + ]; $dsn = $config['hostname'] . ':' .$config['port']; - if(!@stream_socket_client($dsn, $errorNumber, $errorDescription, 0.5)) { + if (!@stream_socket_client($dsn, $errorNumber, $errorDescription, 0.5)) { $this->markTestSkipped('No redis server running at ' . $dsn .' : ' . $errorNumber . ' - ' . $errorDescription); } - if($this->_cacheInstance === null) { + if ($this->_cacheInstance === null) { $this->_cacheInstance = new RedisCache($config); } return $this->_cacheInstance; @@ -46,6 +45,17 @@ class RedisCacheTest extends CacheTestCase $this->assertFalse($cache->get('expire_test_ms')); } + public function testExpireAddMilliseconds() + { + $cache = $this->getCacheInstance(); + + $this->assertTrue($cache->add('expire_testa_ms', 'expire_testa_ms', 0.2)); + usleep(100000); + $this->assertEquals('expire_testa_ms', $cache->get('expire_testa_ms')); + usleep(300000); + $this->assertFalse($cache->get('expire_testa_ms')); + } + /** * Store a value that is 2 times buffer size big * https://github.com/yiisoft/yii2/issues/743 @@ -74,7 +84,7 @@ class RedisCacheTest extends CacheTestCase { $cache = $this->getCacheInstance(); - $data=array('abc'=>'ежик',2=>'def'); + $data=['abc'=>'ежик',2=>'def']; $key='data1'; $this->assertFalse($cache->get($key)); diff --git a/tests/unit/framework/caching/ZendDataCacheTest.php b/tests/unit/framework/caching/ZendDataCacheTest.php index 96354cd..2a0af9f 100644 --- a/tests/unit/framework/caching/ZendDataCacheTest.php +++ b/tests/unit/framework/caching/ZendDataCacheTest.php @@ -1,7 +1,6 @@ <?php namespace yiiunit\framework\caching; -use yii\caching\Cache; use yii\caching\ZendDataCache; /** diff --git a/tests/unit/framework/console/controllers/AssetControllerTest.php b/tests/unit/framework/console/controllers/AssetControllerTest.php index 67dbdaa..e694dd4 100644 --- a/tests/unit/framework/console/controllers/AssetControllerTest.php +++ b/tests/unit/framework/console/controllers/AssetControllerTest.php @@ -62,7 +62,7 @@ class AssetControllerTest extends TestCase */ protected function createAssetController() { - $module = $this->getMock('yii\\base\\Module', array('fake'), array('console')); + $module = $this->getMock('yii\\base\\Module', ['fake'], ['console']); $assetController = new AssetController('asset', $module); $assetController->interactive = false; $assetController->jsCompressor = 'cp {from} {to}'; @@ -76,7 +76,7 @@ class AssetControllerTest extends TestCase * @param array $args action arguments. * @return string command output. */ - protected function runAssetControllerAction($actionId, array $args = array()) + protected function runAssetControllerAction($actionId, array $args = []) { $controller = $this->createAssetController(); ob_start(); @@ -93,21 +93,21 @@ class AssetControllerTest extends TestCase protected function createCompressConfig(array $bundles) { $baseUrl = '/test'; - $config = array( + $config = [ 'bundles' => $this->createBundleConfig($bundles), - 'targets' => array( - 'all' => array( + 'targets' => [ + 'all' => [ 'basePath' => $this->testAssetsBasePath, 'baseUrl' => $baseUrl, 'js' => 'all.js', 'css' => 'all.css', - ), - ), - 'assetManager' => array( + ], + ], + 'assetManager' => [ 'basePath' => $this->testAssetsBasePath, 'baseUrl' => '', - ), - ); + ], + ]; return $config; } @@ -175,7 +175,7 @@ class AssetControllerTest extends TestCase * @param array $args method arguments. * @return mixed method invoke result. */ - protected function invokeAssetControllerMethod($methodName, array $args = array()) + protected function invokeAssetControllerMethod($methodName, array $args = []) { $controller = $this->createAssetController(); $controllerClassReflection = new ReflectionClass(get_class($controller)); @@ -191,14 +191,14 @@ class AssetControllerTest extends TestCase public function testActionTemplate() { $configFileName = $this->testFilePath . DIRECTORY_SEPARATOR . 'config.php'; - $this->runAssetControllerAction('template', array($configFileName)); + $this->runAssetControllerAction('template', [$configFileName]); $this->assertTrue(file_exists($configFileName), 'Unable to create config file template!'); } public function atestActionCompress() { // Given : - $cssFiles = array( + $cssFiles = [ 'css/test_body.css' => 'body { padding-top: 20px; padding-bottom: 60px; @@ -207,35 +207,35 @@ class AssetControllerTest extends TestCase margin: 20px; display: block; }', - ); + ]; $this->createAssetSourceFiles($cssFiles); - $jsFiles = array( + $jsFiles = [ 'js/test_alert.js' => "function test() { alert('Test message'); }", 'js/test_sum_ab.js' => "function sumAB(a, b) { return a + b; }", - ); + ]; $this->createAssetSourceFiles($jsFiles); - $bundles = array( - 'app' => array( + $bundles = [ + 'app' => [ 'css' => array_keys($cssFiles), 'js' => array_keys($jsFiles), - 'depends' => array( + 'depends' => [ 'yii', - ), - ), - ); + ], + ], + ]; $bundleFile = $this->testFilePath . DIRECTORY_SEPARATOR . 'bundle.php'; $configFile = $this->testFilePath . DIRECTORY_SEPARATOR . 'config.php'; $this->createCompressConfigFile($configFile, $bundles); // When : - $this->runAssetControllerAction('compress', array($configFile, $bundleFile)); + $this->runAssetControllerAction('compress', [$configFile, $bundleFile]); // Then : $this->assertTrue(file_exists($bundleFile), 'Unable to create output bundle file!'); @@ -262,44 +262,44 @@ class AssetControllerTest extends TestCase */ public function adjustCssUrlDataProvider() { - return array( - array( + return [ + [ '.published-same-dir-class {background-image: url(published_same_dir.png);}', '/test/base/path/assets/input', '/test/base/path/assets/output', '.published-same-dir-class {background-image: url(../input/published_same_dir.png);}', - ), - array( + ], + [ '.published-relative-dir-class {background-image: url(../img/published_relative_dir.png);}', '/test/base/path/assets/input', '/test/base/path/assets/output', '.published-relative-dir-class {background-image: url(../img/published_relative_dir.png);}', - ), - array( + ], + [ '.static-same-dir-class {background-image: url(\'static_same_dir.png\');}', '/test/base/path/css', '/test/base/path/assets/output', '.static-same-dir-class {background-image: url(\'../../css/static_same_dir.png\');}', - ), - array( + ], + [ '.static-relative-dir-class {background-image: url("../img/static_relative_dir.png");}', '/test/base/path/css', '/test/base/path/assets/output', '.static-relative-dir-class {background-image: url("../../img/static_relative_dir.png");}', - ), - array( + ], + [ '.absolute-url-class {background-image: url(http://domain.com/img/image.gif);}', '/test/base/path/assets/input', '/test/base/path/assets/output', '.absolute-url-class {background-image: url(http://domain.com/img/image.gif);}', - ), - array( + ], + [ '.absolute-url-secure-class {background-image: url(https://secure.domain.com/img/image.gif);}', '/test/base/path/assets/input', '/test/base/path/assets/output', '.absolute-url-secure-class {background-image: url(https://secure.domain.com/img/image.gif);}', - ), - ); + ], + ]; } /** @@ -312,7 +312,7 @@ class AssetControllerTest extends TestCase */ public function testAdjustCssUrl($cssContent, $inputFilePath, $outputFilePath, $expectedCssContent) { - $adjustedCssContent = $this->invokeAssetControllerMethod('adjustCssUrl', array($cssContent, $inputFilePath, $outputFilePath)); + $adjustedCssContent = $this->invokeAssetControllerMethod('adjustCssUrl', [$cssContent, $inputFilePath, $outputFilePath]); $this->assertEquals($expectedCssContent, $adjustedCssContent, 'Unable to adjust CSS correctly!'); } diff --git a/tests/unit/framework/console/controllers/MessageControllerTest.php b/tests/unit/framework/console/controllers/MessageControllerTest.php index b0c697c..8bd2b12 100644 --- a/tests/unit/framework/console/controllers/MessageControllerTest.php +++ b/tests/unit/framework/console/controllers/MessageControllerTest.php @@ -86,7 +86,7 @@ class MessageControllerTest extends TestCase */ protected function createMessageController() { - $module = $this->getMock('yii\\base\\Module', array('fake'), array('console')); + $module = $this->getMock('yii\\base\\Module', ['fake'], ['console']); $messageController = new MessageController('message', $module); $messageController->interactive = false; return $messageController; @@ -98,7 +98,7 @@ class MessageControllerTest extends TestCase * @param array $args action arguments. * @return string command output. */ - protected function runMessageControllerAction($actionId, array $args = array()) + protected function runMessageControllerAction($actionId, array $args = []) { $controller = $this->createMessageController(); ob_start(); @@ -138,7 +138,7 @@ class MessageControllerTest extends TestCase * @param string $name file name * @param array $messages messages. */ - protected function createMessageFile($name, array $messages = array()) + protected function createMessageFile($name, array $messages = []) { $fileName = $this->messagePath . DIRECTORY_SEPARATOR . $name; if (file_exists($fileName)) { @@ -158,14 +158,14 @@ class MessageControllerTest extends TestCase public function testActionConfig() { $configFileName = $this->configFileName; - $this->runMessageControllerAction('config', array($configFileName)); + $this->runMessageControllerAction('config', [$configFileName]); $this->assertTrue(file_exists($configFileName), 'Unable to create config file template!'); } public function testConfigFileNotExist() { $this->setExpectedException('yii\\console\\Exception'); - $this->runMessageControllerAction('extract', array('not_existing_file.php')); + $this->runMessageControllerAction('extract', ['not_existing_file.php']); } public function testCreateTranslation() @@ -177,12 +177,12 @@ class MessageControllerTest extends TestCase $sourceFileContent = "Yii::t('{$category}', '{$message}')"; $this->createSourceFile($sourceFileContent); - $this->composeConfigFile(array( - 'languages' => array($language), + $this->composeConfigFile([ + 'languages' => [$language], 'sourcePath' => $this->sourcePath, 'messagePath' => $this->messagePath, - )); - $this->runMessageControllerAction('extract', array($this->configFileName)); + ]); + $this->runMessageControllerAction('extract', [$this->configFileName]); $this->assertTrue(file_exists($this->messagePath . DIRECTORY_SEPARATOR . $language), 'No language dir created!'); $messageFileName = $this->messagePath . DIRECTORY_SEPARATOR . $language . DIRECTORY_SEPARATOR . $category . '.php'; @@ -204,12 +204,12 @@ class MessageControllerTest extends TestCase $sourceFileContent = "Yii::t('{$category}', '{$message}')"; $this->createSourceFile($sourceFileContent); - $this->composeConfigFile(array( - 'languages' => array($language), + $this->composeConfigFile([ + 'languages' => [$language], 'sourcePath' => $this->sourcePath, 'messagePath' => $this->messagePath, - )); - $this->runMessageControllerAction('extract', array($this->configFileName)); + ]); + $this->runMessageControllerAction('extract', [$this->configFileName]); $messageFileName = $this->messagePath . DIRECTORY_SEPARATOR . $language . DIRECTORY_SEPARATOR . $category . '.php'; @@ -218,7 +218,7 @@ class MessageControllerTest extends TestCase $messageFileContent .= '// some not generated by command content'; file_put_contents($messageFileName, $messageFileContent); - $this->runMessageControllerAction('extract', array($this->configFileName)); + $this->runMessageControllerAction('extract', [$this->configFileName]); $this->assertEquals($messageFileContent, file_get_contents($messageFileName)); } @@ -234,22 +234,22 @@ class MessageControllerTest extends TestCase $existingMessage = 'test existing message'; $existingMessageContent = 'test existing message content'; - $this->createMessageFile($messageFileName, array( + $this->createMessageFile($messageFileName, [ $existingMessage => $existingMessageContent - )); + ]); $newMessage = 'test new message'; $sourceFileContent = "Yii::t('{$category}', '{$existingMessage}')"; $sourceFileContent .= "Yii::t('{$category}', '{$newMessage}')"; $this->createSourceFile($sourceFileContent); - $this->composeConfigFile(array( - 'languages' => array($language), + $this->composeConfigFile([ + 'languages' => [$language], 'sourcePath' => $this->sourcePath, 'messagePath' => $this->messagePath, 'overwrite' => true, - )); - $this->runMessageControllerAction('extract', array($this->configFileName)); + ]); + $this->runMessageControllerAction('extract', [$this->configFileName]); $messages = require($this->messagePath . DIRECTORY_SEPARATOR . $messageFileName); $this->assertTrue(array_key_exists($newMessage, $messages), 'Unable to add new message!'); @@ -269,21 +269,21 @@ class MessageControllerTest extends TestCase $oldMessage = 'test old message'; $oldMessageContent = 'test old message content'; - $this->createMessageFile($messageFileName, array( + $this->createMessageFile($messageFileName, [ $oldMessage => $oldMessageContent - )); + ]); $sourceFileContent = "Yii::t('{$category}', 'some new message')"; $this->createSourceFile($sourceFileContent); - $this->composeConfigFile(array( - 'languages' => array($language), + $this->composeConfigFile([ + 'languages' => [$language], 'sourcePath' => $this->sourcePath, 'messagePath' => $this->messagePath, 'overwrite' => true, 'removeUnused' => false, - )); - $this->runMessageControllerAction('extract', array($this->configFileName)); + ]); + $this->runMessageControllerAction('extract', [$this->configFileName]); $messages = require($this->messagePath . DIRECTORY_SEPARATOR . $messageFileName); @@ -304,10 +304,10 @@ class MessageControllerTest extends TestCase $zeroMessageContent = '0'; $falseMessage = 'test false message'; $falseMessageContent = 'false'; - $this->createMessageFile($messageFileName, array( + $this->createMessageFile($messageFileName, [ $zeroMessage => $zeroMessageContent, $falseMessage => $falseMessageContent, - )); + ]); $newMessage = 'test new message'; $sourceFileContent = "Yii::t('{$category}', '{$zeroMessage}')"; @@ -315,13 +315,13 @@ class MessageControllerTest extends TestCase $sourceFileContent .= "Yii::t('{$category}', '{$newMessage}')"; $this->createSourceFile($sourceFileContent); - $this->composeConfigFile(array( - 'languages' => array($language), + $this->composeConfigFile([ + 'languages' => [$language], 'sourcePath' => $this->sourcePath, 'messagePath' => $this->messagePath, 'overwrite' => true, - )); - $this->runMessageControllerAction('extract', array($this->configFileName)); + ]); + $this->runMessageControllerAction('extract', [$this->configFileName]); $messages = require($this->messagePath . DIRECTORY_SEPARATOR . $messageFileName); $this->assertTrue($zeroMessageContent === $messages[$zeroMessage], 'Message content "0" is lost!'); @@ -336,28 +336,28 @@ class MessageControllerTest extends TestCase $language = 'en'; $category = 'test_category'; - $translators = array( + $translators = [ 'Yii::t', 'Custom::translate', - ); + ]; - $sourceMessages = array( + $sourceMessages = [ 'first message', 'second message', - ); + ]; $sourceFileContent = ''; foreach ($sourceMessages as $key => $message) { $sourceFileContent .= $translators[$key] . "('{$category}', '{$message}');\n"; } $this->createSourceFile($sourceFileContent); - $this->composeConfigFile(array( - 'languages' => array($language), + $this->composeConfigFile([ + 'languages' => [$language], 'sourcePath' => $this->sourcePath, 'messagePath' => $this->messagePath, 'translator' => $translators, - )); - $this->runMessageControllerAction('extract', array($this->configFileName)); + ]); + $this->runMessageControllerAction('extract', [$this->configFileName]); $messageFileName = $this->messagePath . DIRECTORY_SEPARATOR . $language . DIRECTORY_SEPARATOR . $category . '.php'; $messages = require($messageFileName); diff --git a/tests/unit/framework/data/ActiveDataProviderTest.php b/tests/unit/framework/data/ActiveDataProviderTest.php index 3f65ebb..4e4ca77 100644 --- a/tests/unit/framework/data/ActiveDataProviderTest.php +++ b/tests/unit/framework/data/ActiveDataProviderTest.php @@ -10,6 +10,8 @@ namespace yiiunit\framework\data; use yii\data\ActiveDataProvider; use yii\db\Query; use yiiunit\data\ar\ActiveRecord; +use yiiunit\data\ar\Customer; +use yiiunit\data\ar\Item; use yiiunit\framework\db\DatabaseTestCase; use yiiunit\data\ar\Order; @@ -18,6 +20,7 @@ use yiiunit\data\ar\Order; * @since 2.0 * * @group data + * @group db */ class ActiveDataProviderTest extends DatabaseTestCase { @@ -29,44 +32,115 @@ class ActiveDataProviderTest extends DatabaseTestCase public function testActiveQuery() { - $provider = new ActiveDataProvider(array( + $provider = new ActiveDataProvider([ 'query' => Order::find()->orderBy('id'), - )); + ]); $orders = $provider->getModels(); $this->assertEquals(3, count($orders)); $this->assertTrue($orders[0] instanceof Order); - $this->assertEquals(array(1, 2, 3), $provider->getKeys()); + $this->assertTrue($orders[1] instanceof Order); + $this->assertTrue($orders[2] instanceof Order); + $this->assertEquals([1, 2, 3], $provider->getKeys()); - $provider = new ActiveDataProvider(array( + $provider = new ActiveDataProvider([ 'query' => Order::find(), - 'pagination' => array( + 'pagination' => [ 'pageSize' => 2, - ) - )); + ] + ]); $orders = $provider->getModels(); $this->assertEquals(2, count($orders)); } + public function testActiveRelation() + { + /** @var Customer $customer */ + $customer = Customer::find(2); + $provider = new ActiveDataProvider([ + 'query' => $customer->getOrders(), + ]); + $orders = $provider->getModels(); + $this->assertEquals(2, count($orders)); + $this->assertTrue($orders[0] instanceof Order); + $this->assertTrue($orders[1] instanceof Order); + $this->assertEquals([2, 3], $provider->getKeys()); + + $provider = new ActiveDataProvider([ + 'query' => $customer->getOrders(), + 'pagination' => [ + 'pageSize' => 1, + ] + ]); + $orders = $provider->getModels(); + $this->assertEquals(1, count($orders)); + } + + public function testActiveRelationVia() + { + /** @var Order $order */ + $order = Order::find(2); + $provider = new ActiveDataProvider([ + 'query' => $order->getItems(), + ]); + $items = $provider->getModels(); + $this->assertEquals(3, count($items)); + $this->assertTrue($items[0] instanceof Item); + $this->assertTrue($items[1] instanceof Item); + $this->assertTrue($items[2] instanceof Item); + $this->assertEquals([3, 4, 5], $provider->getKeys()); + + $provider = new ActiveDataProvider([ + 'query' => $order->getItems(), + 'pagination' => [ + 'pageSize' => 2, + ] + ]); + $items = $provider->getModels(); + $this->assertEquals(2, count($items)); + } + + public function testActiveRelationViaTable() + { + /** @var Order $order */ + $order = Order::find(1); + $provider = new ActiveDataProvider([ + 'query' => $order->getBooks(), + ]); + $items = $provider->getModels(); + $this->assertEquals(2, count($items)); + $this->assertTrue($items[0] instanceof Item); + $this->assertTrue($items[1] instanceof Item); + + $provider = new ActiveDataProvider([ + 'query' => $order->getBooks(), + 'pagination' => [ + 'pageSize' => 1, + ] + ]); + $items = $provider->getModels(); + $this->assertEquals(1, count($items)); + } + public function testQuery() { $query = new Query; - $provider = new ActiveDataProvider(array( + $provider = new ActiveDataProvider([ 'db' => $this->getConnection(), 'query' => $query->from('tbl_order')->orderBy('id'), - )); + ]); $orders = $provider->getModels(); $this->assertEquals(3, count($orders)); $this->assertTrue(is_array($orders[0])); - $this->assertEquals(array(0, 1, 2), $provider->getKeys()); + $this->assertEquals([0, 1, 2], $provider->getKeys()); $query = new Query; - $provider = new ActiveDataProvider(array( + $provider = new ActiveDataProvider([ 'db' => $this->getConnection(), 'query' => $query->from('tbl_order'), - 'pagination' => array( + 'pagination' => [ 'pageSize' => 2, - ) - )); + ] + ]); $orders = $provider->getModels(); $this->assertEquals(2, count($orders)); } @@ -74,10 +148,10 @@ class ActiveDataProviderTest extends DatabaseTestCase public function testRefresh() { $query = new Query; - $provider = new ActiveDataProvider(array( + $provider = new ActiveDataProvider([ 'db' => $this->getConnection(), 'query' => $query->from('tbl_order')->orderBy('id'), - )); + ]); $this->assertEquals(3, count($provider->getModels())); $provider->getPagination()->pageSize = 2; @@ -85,4 +159,22 @@ class ActiveDataProviderTest extends DatabaseTestCase $provider->refresh(); $this->assertEquals(2, count($provider->getModels())); } + + public function testPaginationBeforeModels() + { + $query = new Query; + $provider = new ActiveDataProvider([ + 'db' => $this->getConnection(), + 'query' => $query->from('tbl_order')->orderBy('id'), + ]); + $pagination = $provider->getPagination(); + $this->assertEquals(0, $pagination->getPageCount()); + $this->assertCount(3, $provider->getModels()); + $this->assertEquals(1, $pagination->getPageCount()); + + $provider->getPagination()->pageSize = 2; + $this->assertEquals(3, count($provider->getModels())); + $provider->refresh(); + $this->assertEquals(2, count($provider->getModels())); + } } diff --git a/tests/unit/framework/data/SortTest.php b/tests/unit/framework/data/SortTest.php index c4cc6aa..dca2fcb 100644 --- a/tests/unit/framework/data/SortTest.php +++ b/tests/unit/framework/data/SortTest.php @@ -21,96 +21,96 @@ class SortTest extends TestCase { public function testGetOrders() { - $sort = new Sort(array( - 'attributes' => array( + $sort = new Sort([ + 'attributes' => [ 'age', - 'name' => array( - 'asc' => array('first_name' => Sort::ASC, 'last_name' => Sort::ASC), - 'desc' => array('first_name' => Sort::DESC, 'last_name' => Sort::DESC), - ), - ), - 'params' => array( + 'name' => [ + 'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC], + 'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC], + ], + ], + 'params' => [ 'sort' => 'age.name-desc' - ), + ], 'enableMultiSort' => true, - )); + ]); $orders = $sort->getOrders(); $this->assertEquals(3, count($orders)); - $this->assertEquals(Sort::ASC, $orders['age']); - $this->assertEquals(Sort::DESC, $orders['first_name']); - $this->assertEquals(Sort::DESC, $orders['last_name']); + $this->assertEquals(SORT_ASC, $orders['age']); + $this->assertEquals(SORT_DESC, $orders['first_name']); + $this->assertEquals(SORT_DESC, $orders['last_name']); $sort->enableMultiSort = false; $orders = $sort->getOrders(true); $this->assertEquals(1, count($orders)); - $this->assertEquals(Sort::ASC, $orders['age']); + $this->assertEquals(SORT_ASC, $orders['age']); } public function testGetAttributeOrders() { - $sort = new Sort(array( - 'attributes' => array( + $sort = new Sort([ + 'attributes' => [ 'age', - 'name' => array( - 'asc' => array('first_name' => Sort::ASC, 'last_name' => Sort::ASC), - 'desc' => array('first_name' => Sort::DESC, 'last_name' => Sort::DESC), - ), - ), - 'params' => array( + 'name' => [ + 'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC], + 'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC], + ], + ], + 'params' => [ 'sort' => 'age.name-desc' - ), + ], 'enableMultiSort' => true, - )); + ]); $orders = $sort->getAttributeOrders(); $this->assertEquals(2, count($orders)); - $this->assertEquals(Sort::ASC, $orders['age']); - $this->assertEquals(Sort::DESC, $orders['name']); + $this->assertEquals(SORT_ASC, $orders['age']); + $this->assertEquals(SORT_DESC, $orders['name']); $sort->enableMultiSort = false; $orders = $sort->getAttributeOrders(true); $this->assertEquals(1, count($orders)); - $this->assertEquals(Sort::ASC, $orders['age']); + $this->assertEquals(SORT_ASC, $orders['age']); } public function testGetAttributeOrder() { - $sort = new Sort(array( - 'attributes' => array( + $sort = new Sort([ + 'attributes' => [ 'age', - 'name' => array( - 'asc' => array('first_name' => Sort::ASC, 'last_name' => Sort::ASC), - 'desc' => array('first_name' => Sort::DESC, 'last_name' => Sort::DESC), - ), - ), - 'params' => array( + 'name' => [ + 'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC], + 'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC], + ], + ], + 'params' => [ 'sort' => 'age.name-desc' - ), + ], 'enableMultiSort' => true, - )); + ]); - $this->assertEquals(Sort::ASC, $sort->getAttributeOrder('age')); - $this->assertEquals(Sort::DESC, $sort->getAttributeOrder('name')); + $this->assertEquals(SORT_ASC, $sort->getAttributeOrder('age')); + $this->assertEquals(SORT_DESC, $sort->getAttributeOrder('name')); $this->assertNull($sort->getAttributeOrder('xyz')); } public function testCreateSortVar() { - $sort = new Sort(array( - 'attributes' => array( + $sort = new Sort([ + 'attributes' => [ 'age', - 'name' => array( - 'asc' => array('first_name' => Sort::ASC, 'last_name' => Sort::ASC), - 'desc' => array('first_name' => Sort::DESC, 'last_name' => Sort::DESC), - ), - ), - 'params' => array( + 'name' => [ + 'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC], + 'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC], + ], + ], + 'params' => [ 'sort' => 'age.name-desc' - ), + ], 'enableMultiSort' => true, 'route' => 'site/index', - )); + ]); $this->assertEquals('age-desc.name-desc', $sort->createSortVar('age')); $this->assertEquals('name.age', $sort->createSortVar('name')); @@ -118,26 +118,26 @@ class SortTest extends TestCase public function testCreateUrl() { - $manager = new UrlManager(array( + $manager = new UrlManager([ 'baseUrl' => '/index.php', 'cache' => null, - )); + ]); - $sort = new Sort(array( - 'attributes' => array( + $sort = new Sort([ + 'attributes' => [ 'age', - 'name' => array( - 'asc' => array('first_name' => Sort::ASC, 'last_name' => Sort::ASC), - 'desc' => array('first_name' => Sort::DESC, 'last_name' => Sort::DESC), - ), - ), - 'params' => array( + 'name' => [ + 'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC], + 'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC], + ], + ], + 'params' => [ 'sort' => 'age.name-desc' - ), + ], 'enableMultiSort' => true, 'urlManager' => $manager, 'route' => 'site/index', - )); + ]); $this->assertEquals('/index.php?r=site/index&sort=age-desc.name-desc', $sort->createUrl('age')); $this->assertEquals('/index.php?r=site/index&sort=name.age', $sort->createUrl('name')); @@ -146,26 +146,26 @@ class SortTest extends TestCase public function testLink() { $this->mockApplication(); - $manager = new UrlManager(array( + $manager = new UrlManager([ 'baseUrl' => '/index.php', 'cache' => null, - )); + ]); - $sort = new Sort(array( - 'attributes' => array( + $sort = new Sort([ + 'attributes' => [ 'age', - 'name' => array( - 'asc' => array('first_name' => Sort::ASC, 'last_name' => Sort::ASC), - 'desc' => array('first_name' => Sort::DESC, 'last_name' => Sort::DESC), - ), - ), - 'params' => array( + 'name' => [ + 'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC], + 'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC], + ], + ], + 'params' => [ 'sort' => 'age.name-desc' - ), + ], 'enableMultiSort' => true, 'urlManager' => $manager, 'route' => 'site/index', - )); + ]); $this->assertEquals('<a class="asc" href="/index.php?r=site/index&sort=age-desc.name-desc" data-sort="age-desc.name-desc">Age</a>', $sort->link('age')); } diff --git a/tests/unit/framework/db/ActiveRecordTest.php b/tests/unit/framework/db/ActiveRecordTest.php index 85e5987..3de40dd 100644 --- a/tests/unit/framework/db/ActiveRecordTest.php +++ b/tests/unit/framework/db/ActiveRecordTest.php @@ -1,10 +1,10 @@ <?php namespace yiiunit\framework\db; -use yii\db\Query; use yii\db\ActiveQuery; use yiiunit\data\ar\ActiveRecord; use yiiunit\data\ar\Customer; +use yiiunit\data\ar\NullValues; use yiiunit\data\ar\OrderItem; use yiiunit\data\ar\Order; use yiiunit\data\ar\Item; @@ -48,20 +48,20 @@ class ActiveRecordTest extends DatabaseTestCase $this->assertEquals('user2', $customerName); // find by column values - $customer = Customer::find(array('id' => 2, 'name' => 'user2')); + $customer = Customer::find(['id' => 2, 'name' => 'user2']); $this->assertTrue($customer instanceof Customer); $this->assertEquals('user2', $customer->name); - $customer = Customer::find(array('id' => 2, 'name' => 'user1')); + $customer = Customer::find(['id' => 2, 'name' => 'user1']); $this->assertNull($customer); // find by attributes - $customer = Customer::find()->where(array('name' => 'user2'))->one(); + $customer = Customer::find()->where(['name' => 'user2'])->one(); $this->assertTrue($customer instanceof Customer); $this->assertEquals(2, $customer->id); // find custom column - $customer = Customer::find()->select(array('*', '(status*2) AS status2')) - ->where(array('name' => 'user3'))->one(); + $customer = Customer::find()->select(['*', '(status*2) AS status2']) + ->where(['name' => 'user3'])->one(); $this->assertEquals(3, $customer->id); $this->assertEquals(4, $customer->status2); @@ -79,13 +79,13 @@ class ActiveRecordTest extends DatabaseTestCase // asArray $customer = Customer::find()->where('id=2')->asArray()->one(); - $this->assertEquals(array( + $this->assertEquals([ 'id' => '2', 'email' => 'user2@example.com', 'name' => 'user2', 'address' => 'address2', 'status' => '1', - ), $customer); + ], $customer); // indexBy $customers = Customer::find()->indexBy('name')->orderBy('id')->all(); @@ -116,19 +116,28 @@ class ActiveRecordTest extends DatabaseTestCase $this->assertEquals(3, count($customers)); // find with parameter binding - $customer = Customer::findBySql('SELECT * FROM tbl_customer WHERE id=:id', array(':id' => 2))->one(); + $customer = Customer::findBySql('SELECT * FROM tbl_customer WHERE id=:id', [':id' => 2])->one(); $this->assertTrue($customer instanceof Customer); $this->assertEquals('user2', $customer->name); } public function testFindLazy() { - /** @var $customer Customer */ + /** @var Customer $customer */ $customer = Customer::find(2); + $this->assertFalse($customer->isRelationPopulated('orders')); $orders = $customer->orders; + $this->assertTrue($customer->isRelationPopulated('orders')); $this->assertEquals(2, count($orders)); + $this->assertEquals(1, count($customer->populatedRelations)); + /** @var Customer $customer */ + $customer = Customer::find(2); + $this->assertFalse($customer->isRelationPopulated('orders')); $orders = $customer->getOrders()->where('id=3')->all(); + $this->assertFalse($customer->isRelationPopulated('orders')); + $this->assertEquals(0, count($customer->populatedRelations)); + $this->assertEquals(1, count($orders)); $this->assertEquals(3, $orders[0]->id); } @@ -137,13 +146,20 @@ class ActiveRecordTest extends DatabaseTestCase { $customers = Customer::find()->with('orders')->all(); $this->assertEquals(3, count($customers)); + $this->assertTrue($customers[0]->isRelationPopulated('orders')); + $this->assertTrue($customers[1]->isRelationPopulated('orders')); $this->assertEquals(1, count($customers[0]->orders)); $this->assertEquals(2, count($customers[1]->orders)); + + $customer = Customer::find()->with('orders')->one(); + $this->assertTrue($customer->isRelationPopulated('orders')); + $this->assertEquals(1, count($customer->orders)); + $this->assertEquals(1, count($customer->populatedRelations)); } public function testFindLazyVia() { - /** @var $order Order */ + /** @var Order $order */ $order = Order::find(1); $this->assertEquals(1, $order->id); $this->assertEquals(2, count($order->items)); @@ -152,7 +168,7 @@ class ActiveRecordTest extends DatabaseTestCase $order = Order::find(1); $order->id = 100; - $this->assertEquals(array(), $order->items); + $this->assertEquals([], $order->items); } public function testFindEagerViaRelation() @@ -168,7 +184,7 @@ class ActiveRecordTest extends DatabaseTestCase public function testFindLazyViaTable() { - /** @var $order Order */ + /** @var Order $order */ $order = Order::find(1); $this->assertEquals(1, $order->id); $this->assertEquals(2, count($order->books)); @@ -242,12 +258,12 @@ class ActiveRecordTest extends DatabaseTestCase // via table $order = Order::find(2); $this->assertEquals(0, count($order->books)); - $orderItem = OrderItem::find(array('order_id' => 2, 'item_id' => 1)); + $orderItem = OrderItem::find(['order_id' => 2, 'item_id' => 1]); $this->assertNull($orderItem); $item = Item::find(1); - $order->link('books', $item, array('quantity' => 10, 'subtotal' => 100)); + $order->link('books', $item, ['quantity' => 10, 'subtotal' => 100]); $this->assertEquals(1, count($order->books)); - $orderItem = OrderItem::find(array('order_id' => 2, 'item_id' => 1)); + $orderItem = OrderItem::find(['order_id' => 2, 'item_id' => 1]); $this->assertTrue($orderItem instanceof OrderItem); $this->assertEquals(10, $orderItem->quantity); $this->assertEquals(100, $orderItem->subtotal); @@ -256,13 +272,13 @@ class ActiveRecordTest extends DatabaseTestCase $order = Order::find(1); $this->assertEquals(2, count($order->items)); $this->assertEquals(2, count($order->orderItems)); - $orderItem = OrderItem::find(array('order_id' => 1, 'item_id' => 3)); + $orderItem = OrderItem::find(['order_id' => 1, 'item_id' => 3]); $this->assertNull($orderItem); $item = Item::find(3); - $order->link('items', $item, array('quantity' => 10, 'subtotal' => 100)); + $order->link('items', $item, ['quantity' => 10, 'subtotal' => 100]); $this->assertEquals(3, count($order->items)); $this->assertEquals(3, count($order->orderItems)); - $orderItem = OrderItem::find(array('order_id' => 1, 'item_id' => 3)); + $orderItem = OrderItem::find(['order_id' => 1, 'item_id' => 3]); $this->assertTrue($orderItem instanceof OrderItem); $this->assertEquals(10, $orderItem->quantity); $this->assertEquals(100, $orderItem->subtotal); @@ -302,10 +318,14 @@ class ActiveRecordTest extends DatabaseTestCase $this->assertNull($customer->id); $this->assertTrue($customer->isNewRecord); + Customer::$afterSaveNewRecord = null; + Customer::$afterSaveInsert = null; $customer->save(); $this->assertEquals(4, $customer->id); + $this->assertFalse(Customer::$afterSaveNewRecord); + $this->assertTrue(Customer::$afterSaveInsert); $this->assertFalse($customer->isNewRecord); } @@ -316,18 +336,23 @@ class ActiveRecordTest extends DatabaseTestCase $this->assertTrue($customer instanceof Customer); $this->assertEquals('user2', $customer->name); $this->assertFalse($customer->isNewRecord); + Customer::$afterSaveNewRecord = null; + Customer::$afterSaveInsert = null; + $customer->name = 'user2x'; $customer->save(); $this->assertEquals('user2x', $customer->name); $this->assertFalse($customer->isNewRecord); + $this->assertFalse(Customer::$afterSaveNewRecord); + $this->assertFalse(Customer::$afterSaveInsert); $customer2 = Customer::find(2); $this->assertEquals('user2x', $customer2->name); // updateCounters - $pk = array('order_id' => 2, 'item_id' => 4); + $pk = ['order_id' => 2, 'item_id' => 4]; $orderItem = OrderItem::find($pk); $this->assertEquals(1, $orderItem->quantity); - $ret = $orderItem->updateCounters(array('quantity' => -1)); + $ret = $orderItem->updateCounters(['quantity' => -1]); $this->assertTrue($ret); $this->assertEquals(0, $orderItem->quantity); $orderItem = OrderItem::find($pk); @@ -336,21 +361,19 @@ class ActiveRecordTest extends DatabaseTestCase // updateAll $customer = Customer::find(3); $this->assertEquals('user3', $customer->name); - $ret = Customer::updateAll(array( - 'name' => 'temp', - ), array('id' => 3)); + $ret = Customer::updateAll(['name' => 'temp'], ['id' => 3]); $this->assertEquals(1, $ret); $customer = Customer::find(3); $this->assertEquals('temp', $customer->name); // updateCounters - $pk = array('order_id' => 1, 'item_id' => 2); + $pk = ['order_id' => 1, 'item_id' => 2]; $orderItem = OrderItem::find($pk); $this->assertEquals(2, $orderItem->quantity); - $ret = OrderItem::updateAllCounters(array( + $ret = OrderItem::updateAllCounters([ 'quantity' => 3, 'subtotal' => -10, - ), $pk); + ], $pk); $this->assertEquals(1, $ret); $orderItem = OrderItem::find($pk); $this->assertEquals(5, $orderItem->quantity); @@ -375,4 +398,118 @@ class ActiveRecordTest extends DatabaseTestCase $customers = Customer::find()->all(); $this->assertEquals(0, count($customers)); } + + public function testStoreNull() + { + $record = new NullValues(); + $this->assertNull($record->var1); + $this->assertNull($record->var2); + $this->assertNull($record->var3); + $this->assertNull($record->stringcol); + + $record->id = 1; + + $record->var1 = 123; + $record->var2 = 456; + $record->var3 = 789; + $record->stringcol = 'hello!'; + + $record->save(false); + $this->assertTrue($record->refresh()); + + $this->assertEquals(123, $record->var1); + $this->assertEquals(456, $record->var2); + $this->assertEquals(789, $record->var3); + $this->assertEquals('hello!', $record->stringcol); + + $record->var1 = null; + $record->var2 = null; + $record->var3 = null; + $record->stringcol = null; + + $record->save(false); + $this->assertTrue($record->refresh()); + + $this->assertNull($record->var1); + $this->assertNull($record->var2); + $this->assertNull($record->var3); + $this->assertNull($record->stringcol); + + $record->var1 = 0; + $record->var2 = 0; + $record->var3 = 0; + $record->stringcol = ''; + + $record->save(false); + $this->assertTrue($record->refresh()); + + $this->assertEquals(0, $record->var1); + $this->assertEquals(0, $record->var2); + $this->assertEquals(0, $record->var3); + $this->assertEquals('', $record->stringcol); + } + + public function testStoreEmpty() + { + $record = new NullValues(); + $record->id = 1; + + // this is to simulate empty html form submission + $record->var1 = ''; + $record->var2 = ''; + $record->var3 = ''; + $record->stringcol = ''; + + $record->save(false); + $this->assertTrue($record->refresh()); + + // https://github.com/yiisoft/yii2/commit/34945b0b69011bc7cab684c7f7095d837892a0d4#commitcomment-4458225 + $this->assertTrue($record->var1 === $record->var2); + $this->assertTrue($record->var2 === $record->var3); + } + + /** + * Some PDO implementations(e.g. cubrid) do not support boolean values. + * Make sure this does not affect AR layer. + */ + public function testBooleanAttribute() + { + $customer = new Customer(); + $customer->name = 'boolean customer'; + $customer->email = 'mail@example.com'; + $customer->status = true; + $customer->save(false); + + $customer->refresh(); + $this->assertEquals(1, $customer->status); + + $customer->status = false; + $customer->save(false); + + $customer->refresh(); + $this->assertEquals(0, $customer->status); + + $customers = Customer::find()->where(['status' => true])->all(); + $this->assertEquals(2, count($customers)); + + $customers = Customer::find()->where(['status' => false])->all(); + $this->assertEquals(1, count($customers)); + } + + public function testIsPrimaryKey() + { + $this->assertFalse(Customer::isPrimaryKey([])); + $this->assertTrue(Customer::isPrimaryKey(['id'])); + $this->assertFalse(Customer::isPrimaryKey(['id', 'name'])); + $this->assertFalse(Customer::isPrimaryKey(['name'])); + $this->assertFalse(Customer::isPrimaryKey(['name', 'email'])); + + $this->assertFalse(OrderItem::isPrimaryKey([])); + $this->assertFalse(OrderItem::isPrimaryKey(['order_id'])); + $this->assertFalse(OrderItem::isPrimaryKey(['item_id'])); + $this->assertFalse(OrderItem::isPrimaryKey(['quantity'])); + $this->assertFalse(OrderItem::isPrimaryKey(['quantity', 'subtotal'])); + $this->assertTrue(OrderItem::isPrimaryKey(['order_id', 'item_id'])); + $this->assertFalse(OrderItem::isPrimaryKey(['order_id', 'item_id', 'quantity'])); + } } diff --git a/tests/unit/framework/db/CommandTest.php b/tests/unit/framework/db/CommandTest.php index d9eb0e7..64eb2df 100644 --- a/tests/unit/framework/db/CommandTest.php +++ b/tests/unit/framework/db/CommandTest.php @@ -2,9 +2,6 @@ namespace yiiunit\framework\db; -use yii\db\Connection; -use yii\db\Command; -use yii\db\Query; use yii\db\DataReader; /** @@ -95,7 +92,7 @@ class CommandTest extends DatabaseTestCase $this->assertEquals('user3', $row['name']); $rows = $db->createCommand('SELECT * FROM tbl_customer WHERE id=10')->queryAll(); - $this->assertEquals(array(), $rows); + $this->assertEquals([], $rows); // queryOne $sql = 'SELECT * FROM tbl_customer ORDER BY id'; @@ -120,7 +117,7 @@ class CommandTest extends DatabaseTestCase $this->assertEquals(range(1, 3), $column); $command = $db->createCommand('SELECT id FROM tbl_customer WHERE id=10'); - $this->assertEquals(array(), $command->queryColumn()); + $this->assertEquals([], $command->queryColumn()); // queryScalar $sql = 'SELECT * FROM tbl_customer ORDER BY id'; @@ -215,33 +212,10 @@ class CommandTest extends DatabaseTestCase // FETCH_NUM, customized in query method $sql = 'SELECT * FROM tbl_customer'; $command = $db->createCommand($sql); - $result = $command->queryOne(array(), \PDO::FETCH_NUM); + $result = $command->queryOne([], \PDO::FETCH_NUM); $this->assertTrue(is_array($result) && isset($result[0])); } - // getPDOType is currently private -// public function testGetPDOType() -// { -// $values = array( -// array(null, \PDO::PARAM_NULL), -// array('', \PDO::PARAM_STR), -// array('hello', \PDO::PARAM_STR), -// array(0, \PDO::PARAM_INT), -// array(1, \PDO::PARAM_INT), -// array(1337, \PDO::PARAM_INT), -// array(true, \PDO::PARAM_BOOL), -// array(false, \PDO::PARAM_BOOL), -// array($fp=fopen(__FILE__, 'rb'), \PDO::PARAM_LOB), -// ); -// -// $command = $this->getConnection()->createCommand(); -// -// foreach($values as $value) { -// $this->assertEquals($value[1], $command->getPdoType($value[0])); -// } -// fclose($fp); -// } - public function testInsert() { } diff --git a/tests/unit/framework/db/DatabaseTestCase.php b/tests/unit/framework/db/DatabaseTestCase.php index d8d2916..8ef1234 100644 --- a/tests/unit/framework/db/DatabaseTestCase.php +++ b/tests/unit/framework/db/DatabaseTestCase.php @@ -21,7 +21,7 @@ abstract class DatabaseTestCase extends TestCase $pdo_database = 'pdo_'.$this->driverName; if (!extension_loaded('pdo') || !extension_loaded($pdo_database)) { - $this->markTestSkipped('pdo and pdo_'.$pdo_database.' extension are required.'); + $this->markTestSkipped('pdo and '.$pdo_database.' extension are required.'); } $this->mockApplication(); } diff --git a/tests/unit/framework/db/QueryBuilderTest.php b/tests/unit/framework/db/QueryBuilderTest.php index d43c901..b8eefe0 100644 --- a/tests/unit/framework/db/QueryBuilderTest.php +++ b/tests/unit/framework/db/QueryBuilderTest.php @@ -43,65 +43,65 @@ class QueryBuilderTest extends DatabaseTestCase */ public function columnTypes() { - return array( - array(Schema::TYPE_PK, 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY'), - array(Schema::TYPE_PK . '(8)', 'int(8) NOT NULL AUTO_INCREMENT PRIMARY KEY'), - array(Schema::TYPE_PK . ' CHECK (value > 5)', 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY CHECK (value > 5)'), - array(Schema::TYPE_PK . '(8) CHECK (value > 5)', 'int(8) NOT NULL AUTO_INCREMENT PRIMARY KEY CHECK (value > 5)'), - array(Schema::TYPE_STRING, 'varchar(255)'), - array(Schema::TYPE_STRING . '(32)', 'varchar(32)'), - array(Schema::TYPE_STRING . ' CHECK (value LIKE "test%")', 'varchar(255) CHECK (value LIKE "test%")'), - array(Schema::TYPE_STRING . '(32) CHECK (value LIKE "test%")', 'varchar(32) CHECK (value LIKE "test%")'), - array(Schema::TYPE_STRING . ' NOT NULL', 'varchar(255) NOT NULL'), - array(Schema::TYPE_TEXT, 'text'), - array(Schema::TYPE_TEXT . '(255)', 'text'), - array(Schema::TYPE_TEXT . ' CHECK (value LIKE "test%")', 'text CHECK (value LIKE "test%")'), - array(Schema::TYPE_TEXT . '(255) CHECK (value LIKE "test%")', 'text CHECK (value LIKE "test%")'), - array(Schema::TYPE_TEXT . ' NOT NULL', 'text NOT NULL'), - array(Schema::TYPE_TEXT . '(255) NOT NULL', 'text NOT NULL'), - array(Schema::TYPE_SMALLINT, 'smallint(6)'), - array(Schema::TYPE_SMALLINT . '(8)', 'smallint(8)'), - array(Schema::TYPE_INTEGER, 'int(11)'), - array(Schema::TYPE_INTEGER . '(8)', 'int(8)'), - array(Schema::TYPE_INTEGER . ' CHECK (value > 5)', 'int(11) CHECK (value > 5)'), - array(Schema::TYPE_INTEGER . '(8) CHECK (value > 5)', 'int(8) CHECK (value > 5)'), - array(Schema::TYPE_INTEGER . ' NOT NULL', 'int(11) NOT NULL'), - array(Schema::TYPE_BIGINT, 'bigint(20)'), - array(Schema::TYPE_BIGINT . '(8)', 'bigint(8)'), - array(Schema::TYPE_BIGINT . ' CHECK (value > 5)', 'bigint(20) CHECK (value > 5)'), - array(Schema::TYPE_BIGINT . '(8) CHECK (value > 5)', 'bigint(8) CHECK (value > 5)'), - array(Schema::TYPE_BIGINT . ' NOT NULL', 'bigint(20) NOT NULL'), - array(Schema::TYPE_FLOAT, 'float'), - array(Schema::TYPE_FLOAT . '(16,5)', 'float'), - array(Schema::TYPE_FLOAT . ' CHECK (value > 5.6)', 'float CHECK (value > 5.6)'), - array(Schema::TYPE_FLOAT . '(16,5) CHECK (value > 5.6)', 'float CHECK (value > 5.6)'), - array(Schema::TYPE_FLOAT . ' NOT NULL', 'float NOT NULL'), - array(Schema::TYPE_DECIMAL, 'decimal(10,0)'), - array(Schema::TYPE_DECIMAL . '(12,4)', 'decimal(12,4)'), - array(Schema::TYPE_DECIMAL . ' CHECK (value > 5.6)', 'decimal(10,0) CHECK (value > 5.6)'), - array(Schema::TYPE_DECIMAL . '(12,4) CHECK (value > 5.6)', 'decimal(12,4) CHECK (value > 5.6)'), - array(Schema::TYPE_DECIMAL . ' NOT NULL', 'decimal(10,0) NOT NULL'), - array(Schema::TYPE_DATETIME, 'datetime'), - array(Schema::TYPE_DATETIME . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "datetime CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"), - array(Schema::TYPE_DATETIME . ' NOT NULL', 'datetime NOT NULL'), - array(Schema::TYPE_TIMESTAMP, 'timestamp'), - array(Schema::TYPE_TIMESTAMP . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "timestamp CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"), - array(Schema::TYPE_TIMESTAMP . ' NOT NULL', 'timestamp NOT NULL'), - array(Schema::TYPE_TIME, 'time'), - array(Schema::TYPE_TIME . " CHECK(value BETWEEN '12:00:00' AND '13:01:01')", "time CHECK(value BETWEEN '12:00:00' AND '13:01:01')"), - array(Schema::TYPE_TIME . ' NOT NULL', 'time NOT NULL'), - array(Schema::TYPE_DATE, 'date'), - array(Schema::TYPE_DATE . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "date CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"), - array(Schema::TYPE_DATE . ' NOT NULL', 'date NOT NULL'), - array(Schema::TYPE_BINARY, 'blob'), - array(Schema::TYPE_BOOLEAN, 'tinyint(1)'), - array(Schema::TYPE_BOOLEAN . ' NOT NULL DEFAULT 1', 'tinyint(1) NOT NULL DEFAULT 1'), - array(Schema::TYPE_MONEY, 'decimal(19,4)'), - array(Schema::TYPE_MONEY . '(16,2)', 'decimal(16,2)'), - array(Schema::TYPE_MONEY . ' CHECK (value > 0.0)', 'decimal(19,4) CHECK (value > 0.0)'), - array(Schema::TYPE_MONEY . '(16,2) CHECK (value > 0.0)', 'decimal(16,2) CHECK (value > 0.0)'), - array(Schema::TYPE_MONEY . ' NOT NULL', 'decimal(19,4) NOT NULL'), - ); + return [ + [Schema::TYPE_PK, 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY'], + [Schema::TYPE_PK . '(8)', 'int(8) NOT NULL AUTO_INCREMENT PRIMARY KEY'], + [Schema::TYPE_PK . ' CHECK (value > 5)', 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY CHECK (value > 5)'], + [Schema::TYPE_PK . '(8) CHECK (value > 5)', 'int(8) NOT NULL AUTO_INCREMENT PRIMARY KEY CHECK (value > 5)'], + [Schema::TYPE_STRING, 'varchar(255)'], + [Schema::TYPE_STRING . '(32)', 'varchar(32)'], + [Schema::TYPE_STRING . ' CHECK (value LIKE "test%")', 'varchar(255) CHECK (value LIKE "test%")'], + [Schema::TYPE_STRING . '(32) CHECK (value LIKE "test%")', 'varchar(32) CHECK (value LIKE "test%")'], + [Schema::TYPE_STRING . ' NOT NULL', 'varchar(255) NOT NULL'], + [Schema::TYPE_TEXT, 'text'], + [Schema::TYPE_TEXT . '(255)', 'text'], + [Schema::TYPE_TEXT . ' CHECK (value LIKE "test%")', 'text CHECK (value LIKE "test%")'], + [Schema::TYPE_TEXT . '(255) CHECK (value LIKE "test%")', 'text CHECK (value LIKE "test%")'], + [Schema::TYPE_TEXT . ' NOT NULL', 'text NOT NULL'], + [Schema::TYPE_TEXT . '(255) NOT NULL', 'text NOT NULL'], + [Schema::TYPE_SMALLINT, 'smallint(6)'], + [Schema::TYPE_SMALLINT . '(8)', 'smallint(8)'], + [Schema::TYPE_INTEGER, 'int(11)'], + [Schema::TYPE_INTEGER . '(8)', 'int(8)'], + [Schema::TYPE_INTEGER . ' CHECK (value > 5)', 'int(11) CHECK (value > 5)'], + [Schema::TYPE_INTEGER . '(8) CHECK (value > 5)', 'int(8) CHECK (value > 5)'], + [Schema::TYPE_INTEGER . ' NOT NULL', 'int(11) NOT NULL'], + [Schema::TYPE_BIGINT, 'bigint(20)'], + [Schema::TYPE_BIGINT . '(8)', 'bigint(8)'], + [Schema::TYPE_BIGINT . ' CHECK (value > 5)', 'bigint(20) CHECK (value > 5)'], + [Schema::TYPE_BIGINT . '(8) CHECK (value > 5)', 'bigint(8) CHECK (value > 5)'], + [Schema::TYPE_BIGINT . ' NOT NULL', 'bigint(20) NOT NULL'], + [Schema::TYPE_FLOAT, 'float'], + [Schema::TYPE_FLOAT . '(16,5)', 'float'], + [Schema::TYPE_FLOAT . ' CHECK (value > 5.6)', 'float CHECK (value > 5.6)'], + [Schema::TYPE_FLOAT . '(16,5) CHECK (value > 5.6)', 'float CHECK (value > 5.6)'], + [Schema::TYPE_FLOAT . ' NOT NULL', 'float NOT NULL'], + [Schema::TYPE_DECIMAL, 'decimal(10,0)'], + [Schema::TYPE_DECIMAL . '(12,4)', 'decimal(12,4)'], + [Schema::TYPE_DECIMAL . ' CHECK (value > 5.6)', 'decimal(10,0) CHECK (value > 5.6)'], + [Schema::TYPE_DECIMAL . '(12,4) CHECK (value > 5.6)', 'decimal(12,4) CHECK (value > 5.6)'], + [Schema::TYPE_DECIMAL . ' NOT NULL', 'decimal(10,0) NOT NULL'], + [Schema::TYPE_DATETIME, 'datetime'], + [Schema::TYPE_DATETIME . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "datetime CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"], + [Schema::TYPE_DATETIME . ' NOT NULL', 'datetime NOT NULL'], + [Schema::TYPE_TIMESTAMP, 'timestamp'], + [Schema::TYPE_TIMESTAMP . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "timestamp CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"], + [Schema::TYPE_TIMESTAMP . ' NOT NULL', 'timestamp NOT NULL'], + [Schema::TYPE_TIME, 'time'], + [Schema::TYPE_TIME . " CHECK(value BETWEEN '12:00:00' AND '13:01:01')", "time CHECK(value BETWEEN '12:00:00' AND '13:01:01')"], + [Schema::TYPE_TIME . ' NOT NULL', 'time NOT NULL'], + [Schema::TYPE_DATE, 'date'], + [Schema::TYPE_DATE . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "date CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"], + [Schema::TYPE_DATE . ' NOT NULL', 'date NOT NULL'], + [Schema::TYPE_BINARY, 'blob'], + [Schema::TYPE_BOOLEAN, 'tinyint(1)'], + [Schema::TYPE_BOOLEAN . ' NOT NULL DEFAULT 1', 'tinyint(1) NOT NULL DEFAULT 1'], + [Schema::TYPE_MONEY, 'decimal(19,4)'], + [Schema::TYPE_MONEY . '(16,2)', 'decimal(16,2)'], + [Schema::TYPE_MONEY . ' CHECK (value > 0.0)', 'decimal(19,4) CHECK (value > 0.0)'], + [Schema::TYPE_MONEY . '(16,2) CHECK (value > 0.0)', 'decimal(16,2) CHECK (value > 0.0)'], + [Schema::TYPE_MONEY . ' NOT NULL', 'decimal(19,4) NOT NULL'], + ]; } public function testGetColumnType() @@ -120,7 +120,7 @@ class QueryBuilderTest extends DatabaseTestCase // ADD $qb = $this->getQueryBuilder(); - $qb->db->createCommand()->addPrimaryKey($pkeyName, $tableName, array('id'))->execute(); + $qb->db->createCommand()->addPrimaryKey($pkeyName, $tableName, ['id'])->execute(); $tableSchema = $qb->db->getSchema()->getTableSchema($tableName); $this->assertEquals(1, count($tableSchema->primaryKey)); diff --git a/tests/unit/framework/db/QueryTest.php b/tests/unit/framework/db/QueryTest.php index a275afd..77c1ac0 100644 --- a/tests/unit/framework/db/QueryTest.php +++ b/tests/unit/framework/db/QueryTest.php @@ -2,10 +2,7 @@ namespace yiiunit\framework\db; -use yii\db\Connection; -use yii\db\Command; use yii\db\Query; -use yii\db\DataReader; /** * @group db @@ -18,13 +15,13 @@ class QueryTest extends DatabaseTestCase // default $query = new Query; $query->select('*'); - $this->assertEquals(array('*'), $query->select); + $this->assertEquals(['*'], $query->select); $this->assertNull($query->distinct); $this->assertEquals(null, $query->selectOption); $query = new Query; $query->select('id, name', 'something')->distinct(true); - $this->assertEquals(array('id', 'name'), $query->select); + $this->assertEquals(['id', 'name'], $query->select); $this->assertTrue($query->distinct); $this->assertEquals('something', $query->selectOption); } @@ -33,23 +30,23 @@ class QueryTest extends DatabaseTestCase { $query = new Query; $query->from('tbl_user'); - $this->assertEquals(array('tbl_user'), $query->from); + $this->assertEquals(['tbl_user'], $query->from); } public function testWhere() { $query = new Query; - $query->where('id = :id', array(':id' => 1)); + $query->where('id = :id', [':id' => 1]); $this->assertEquals('id = :id', $query->where); - $this->assertEquals(array(':id' => 1), $query->params); + $this->assertEquals([':id' => 1], $query->params); - $query->andWhere('name = :name', array(':name' => 'something')); - $this->assertEquals(array('and', 'id = :id', 'name = :name'), $query->where); - $this->assertEquals(array(':id' => 1, ':name' => 'something'), $query->params); + $query->andWhere('name = :name', [':name' => 'something']); + $this->assertEquals(['and', 'id = :id', 'name = :name'], $query->where); + $this->assertEquals([':id' => 1, ':name' => 'something'], $query->params); - $query->orWhere('age = :age', array(':age' => '30')); - $this->assertEquals(array('or', array('and', 'id = :id', 'name = :name'), 'age = :age'), $query->where); - $this->assertEquals(array(':id' => 1, ':name' => 'something', ':age' => '30'), $query->params); + $query->orWhere('age = :age', [':age' => '30']); + $this->assertEquals(['or', ['and', 'id = :id', 'name = :name'], 'age = :age'], $query->where); + $this->assertEquals([':id' => 1, ':name' => 'something', ':age' => '30'], $query->params); } public function testJoin() @@ -60,48 +57,48 @@ class QueryTest extends DatabaseTestCase { $query = new Query; $query->groupBy('team'); - $this->assertEquals(array('team'), $query->groupBy); + $this->assertEquals(['team'], $query->groupBy); $query->addGroupBy('company'); - $this->assertEquals(array('team', 'company'), $query->groupBy); + $this->assertEquals(['team', 'company'], $query->groupBy); $query->addGroupBy('age'); - $this->assertEquals(array('team', 'company', 'age'), $query->groupBy); + $this->assertEquals(['team', 'company', 'age'], $query->groupBy); } public function testHaving() { $query = new Query; - $query->having('id = :id', array(':id' => 1)); + $query->having('id = :id', [':id' => 1]); $this->assertEquals('id = :id', $query->having); - $this->assertEquals(array(':id' => 1), $query->params); + $this->assertEquals([':id' => 1], $query->params); - $query->andHaving('name = :name', array(':name' => 'something')); - $this->assertEquals(array('and', 'id = :id', 'name = :name'), $query->having); - $this->assertEquals(array(':id' => 1, ':name' => 'something'), $query->params); + $query->andHaving('name = :name', [':name' => 'something']); + $this->assertEquals(['and', 'id = :id', 'name = :name'], $query->having); + $this->assertEquals([':id' => 1, ':name' => 'something'], $query->params); - $query->orHaving('age = :age', array(':age' => '30')); - $this->assertEquals(array('or', array('and', 'id = :id', 'name = :name'), 'age = :age'), $query->having); - $this->assertEquals(array(':id' => 1, ':name' => 'something', ':age' => '30'), $query->params); + $query->orHaving('age = :age', [':age' => '30']); + $this->assertEquals(['or', ['and', 'id = :id', 'name = :name'], 'age = :age'], $query->having); + $this->assertEquals([':id' => 1, ':name' => 'something', ':age' => '30'], $query->params); } public function testOrder() { $query = new Query; $query->orderBy('team'); - $this->assertEquals(array('team' => false), $query->orderBy); + $this->assertEquals(['team' => SORT_ASC], $query->orderBy); $query->addOrderBy('company'); - $this->assertEquals(array('team' => false, 'company' => false), $query->orderBy); + $this->assertEquals(['team' => SORT_ASC, 'company' => SORT_ASC], $query->orderBy); $query->addOrderBy('age'); - $this->assertEquals(array('team' => false, 'company' => false, 'age' => false), $query->orderBy); + $this->assertEquals(['team' => SORT_ASC, 'company' => SORT_ASC, 'age' => SORT_ASC], $query->orderBy); - $query->addOrderBy(array('age' => true)); - $this->assertEquals(array('team' => false, 'company' => false, 'age' => true), $query->orderBy); + $query->addOrderBy(['age' => SORT_DESC]); + $this->assertEquals(['team' => SORT_ASC, 'company' => SORT_ASC, 'age' => SORT_DESC], $query->orderBy); $query->addOrderBy('age ASC, company DESC'); - $this->assertEquals(array('team' => false, 'company' => true, 'age' => false), $query->orderBy); + $this->assertEquals(['team' => SORT_ASC, 'company' => SORT_DESC, 'age' => SORT_ASC], $query->orderBy); } public function testLimitOffset() diff --git a/tests/unit/framework/db/SchemaTest.php b/tests/unit/framework/db/SchemaTest.php index 2a3015d..3727737 100644 --- a/tests/unit/framework/db/SchemaTest.php +++ b/tests/unit/framework/db/SchemaTest.php @@ -67,4 +67,27 @@ class SchemaTest extends DatabaseTestCase $this->assertEquals('order_id', $table->foreignKeys[0]['order_id']); $this->assertEquals('item_id', $table->foreignKeys[0]['item_id']); } + + public function testGetPDOType() + { + $values = [ + [null, \PDO::PARAM_NULL], + ['', \PDO::PARAM_STR], + ['hello', \PDO::PARAM_STR], + [0, \PDO::PARAM_INT], + [1, \PDO::PARAM_INT], + [1337, \PDO::PARAM_INT], + [true, \PDO::PARAM_BOOL], + [false, \PDO::PARAM_BOOL], + [$fp=fopen(__FILE__, 'rb'), \PDO::PARAM_LOB], + ]; + + /** @var Schema $schema */ + $schema = $this->getConnection()->schema; + + foreach($values as $value) { + $this->assertEquals($value[1], $schema->getPdoType($value[0])); + } + fclose($fp); + } } diff --git a/tests/unit/framework/db/cubrid/CubridActiveDataProviderTest.php b/tests/unit/framework/db/cubrid/CubridActiveDataProviderTest.php new file mode 100644 index 0000000..191442a --- /dev/null +++ b/tests/unit/framework/db/cubrid/CubridActiveDataProviderTest.php @@ -0,0 +1,14 @@ +<?php +namespace yiiunit\framework\db\cubrid; + +use yiiunit\framework\data\ActiveDataProviderTest; + +/** + * @group db + * @group cubrid + * @group data + */ +class CubridActiveDataProviderTest extends ActiveDataProviderTest +{ + public $driverName = 'cubrid'; +} diff --git a/tests/unit/framework/db/cubrid/CubridActiveRecordTest.php b/tests/unit/framework/db/cubrid/CubridActiveRecordTest.php index 2d2db15..3949ba2 100644 --- a/tests/unit/framework/db/cubrid/CubridActiveRecordTest.php +++ b/tests/unit/framework/db/cubrid/CubridActiveRecordTest.php @@ -11,26 +11,4 @@ use yiiunit\framework\db\ActiveRecordTest; class CubridActiveRecordTest extends ActiveRecordTest { public $driverName = 'cubrid'; - - /** - * cubrid PDO does not support boolean values. - * Make sure this does not affect AR layer. - */ - public function testBooleanAttribute() - { - $customer = new Customer(); - $customer->name = 'boolean customer'; - $customer->email = 'mail@example.com'; - $customer->status = true; - $customer->save(false); - - $customer->refresh(); - $this->assertEquals(1, $customer->status); - - $customer->status = false; - $customer->save(false); - - $customer->refresh(); - $this->assertEquals(0, $customer->status); - } } diff --git a/tests/unit/framework/db/cubrid/CubridQueryBuilderTest.php b/tests/unit/framework/db/cubrid/CubridQueryBuilderTest.php index 107b73b..5fe6e45 100644 --- a/tests/unit/framework/db/cubrid/CubridQueryBuilderTest.php +++ b/tests/unit/framework/db/cubrid/CubridQueryBuilderTest.php @@ -2,7 +2,6 @@ namespace yiiunit\framework\db\cubrid; -use yii\base\NotSupportedException; use yii\db\sqlite\Schema; use yiiunit\framework\db\QueryBuilderTest; @@ -20,65 +19,65 @@ class CubridQueryBuilderTest extends QueryBuilderTest */ public function columnTypes() { - return array( - array(Schema::TYPE_PK, 'int NOT NULL AUTO_INCREMENT PRIMARY KEY'), - array(Schema::TYPE_PK . '(8)', 'int NOT NULL AUTO_INCREMENT PRIMARY KEY'), - array(Schema::TYPE_PK . ' CHECK (value > 5)', 'int NOT NULL AUTO_INCREMENT PRIMARY KEY CHECK (value > 5)'), - array(Schema::TYPE_PK . '(8) CHECK (value > 5)', 'int NOT NULL AUTO_INCREMENT PRIMARY KEY CHECK (value > 5)'), - array(Schema::TYPE_STRING, 'varchar(255)'), - array(Schema::TYPE_STRING . '(32)', 'varchar(32)'), - array(Schema::TYPE_STRING . ' CHECK (value LIKE "test%")', 'varchar(255) CHECK (value LIKE "test%")'), - array(Schema::TYPE_STRING . '(32) CHECK (value LIKE "test%")', 'varchar(32) CHECK (value LIKE "test%")'), - array(Schema::TYPE_STRING . ' NOT NULL', 'varchar(255) NOT NULL'), - array(Schema::TYPE_TEXT, 'varchar'), - array(Schema::TYPE_TEXT . '(255)', 'varchar'), - array(Schema::TYPE_TEXT . ' CHECK (value LIKE "test%")', 'varchar CHECK (value LIKE "test%")'), - array(Schema::TYPE_TEXT . '(255) CHECK (value LIKE "test%")', 'varchar CHECK (value LIKE "test%")'), - array(Schema::TYPE_TEXT . ' NOT NULL', 'varchar NOT NULL'), - array(Schema::TYPE_TEXT . '(255) NOT NULL', 'varchar NOT NULL'), - array(Schema::TYPE_SMALLINT, 'smallint'), - array(Schema::TYPE_SMALLINT . '(8)', 'smallint'), - array(Schema::TYPE_INTEGER, 'int'), - array(Schema::TYPE_INTEGER . '(8)', 'int'), - array(Schema::TYPE_INTEGER . ' CHECK (value > 5)', 'int CHECK (value > 5)'), - array(Schema::TYPE_INTEGER . '(8) CHECK (value > 5)', 'int CHECK (value > 5)'), - array(Schema::TYPE_INTEGER . ' NOT NULL', 'int NOT NULL'), - array(Schema::TYPE_BIGINT, 'bigint'), - array(Schema::TYPE_BIGINT . '(8)', 'bigint'), - array(Schema::TYPE_BIGINT . ' CHECK (value > 5)', 'bigint CHECK (value > 5)'), - array(Schema::TYPE_BIGINT . '(8) CHECK (value > 5)', 'bigint CHECK (value > 5)'), - array(Schema::TYPE_BIGINT . ' NOT NULL', 'bigint NOT NULL'), - array(Schema::TYPE_FLOAT, 'float(7)'), - array(Schema::TYPE_FLOAT . '(16)', 'float(16)'), - array(Schema::TYPE_FLOAT . ' CHECK (value > 5.6)', 'float(7) CHECK (value > 5.6)'), - array(Schema::TYPE_FLOAT . '(16) CHECK (value > 5.6)', 'float(16) CHECK (value > 5.6)'), - array(Schema::TYPE_FLOAT . ' NOT NULL', 'float(7) NOT NULL'), - array(Schema::TYPE_DECIMAL, 'decimal(10,0)'), - array(Schema::TYPE_DECIMAL . '(12,4)', 'decimal(12,4)'), - array(Schema::TYPE_DECIMAL . ' CHECK (value > 5.6)', 'decimal(10,0) CHECK (value > 5.6)'), - array(Schema::TYPE_DECIMAL . '(12,4) CHECK (value > 5.6)', 'decimal(12,4) CHECK (value > 5.6)'), - array(Schema::TYPE_DECIMAL . ' NOT NULL', 'decimal(10,0) NOT NULL'), - array(Schema::TYPE_DATETIME, 'datetime'), - array(Schema::TYPE_DATETIME . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "datetime CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"), - array(Schema::TYPE_DATETIME . ' NOT NULL', 'datetime NOT NULL'), - array(Schema::TYPE_TIMESTAMP, 'timestamp'), - array(Schema::TYPE_TIMESTAMP . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "timestamp CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"), - array(Schema::TYPE_TIMESTAMP . ' NOT NULL', 'timestamp NOT NULL'), - array(Schema::TYPE_TIME, 'time'), - array(Schema::TYPE_TIME . " CHECK(value BETWEEN '12:00:00' AND '13:01:01')", "time CHECK(value BETWEEN '12:00:00' AND '13:01:01')"), - array(Schema::TYPE_TIME . ' NOT NULL', 'time NOT NULL'), - array(Schema::TYPE_DATE, 'date'), - array(Schema::TYPE_DATE . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "date CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"), - array(Schema::TYPE_DATE . ' NOT NULL', 'date NOT NULL'), - array(Schema::TYPE_BINARY, 'blob'), - array(Schema::TYPE_BOOLEAN, 'smallint'), - array(Schema::TYPE_BOOLEAN . ' NOT NULL DEFAULT 1', 'smallint NOT NULL DEFAULT 1'), - array(Schema::TYPE_MONEY, 'decimal(19,4)'), - array(Schema::TYPE_MONEY . '(16,2)', 'decimal(16,2)'), - array(Schema::TYPE_MONEY . ' CHECK (value > 0.0)', 'decimal(19,4) CHECK (value > 0.0)'), - array(Schema::TYPE_MONEY . '(16,2) CHECK (value > 0.0)', 'decimal(16,2) CHECK (value > 0.0)'), - array(Schema::TYPE_MONEY . ' NOT NULL', 'decimal(19,4) NOT NULL'), - ); + return [ + [Schema::TYPE_PK, 'int NOT NULL AUTO_INCREMENT PRIMARY KEY'], + [Schema::TYPE_PK . '(8)', 'int NOT NULL AUTO_INCREMENT PRIMARY KEY'], + [Schema::TYPE_PK . ' CHECK (value > 5)', 'int NOT NULL AUTO_INCREMENT PRIMARY KEY CHECK (value > 5)'], + [Schema::TYPE_PK . '(8) CHECK (value > 5)', 'int NOT NULL AUTO_INCREMENT PRIMARY KEY CHECK (value > 5)'], + [Schema::TYPE_STRING, 'varchar(255)'], + [Schema::TYPE_STRING . '(32)', 'varchar(32)'], + [Schema::TYPE_STRING . ' CHECK (value LIKE "test%")', 'varchar(255) CHECK (value LIKE "test%")'], + [Schema::TYPE_STRING . '(32) CHECK (value LIKE "test%")', 'varchar(32) CHECK (value LIKE "test%")'], + [Schema::TYPE_STRING . ' NOT NULL', 'varchar(255) NOT NULL'], + [Schema::TYPE_TEXT, 'varchar'], + [Schema::TYPE_TEXT . '(255)', 'varchar'], + [Schema::TYPE_TEXT . ' CHECK (value LIKE "test%")', 'varchar CHECK (value LIKE "test%")'], + [Schema::TYPE_TEXT . '(255) CHECK (value LIKE "test%")', 'varchar CHECK (value LIKE "test%")'], + [Schema::TYPE_TEXT . ' NOT NULL', 'varchar NOT NULL'], + [Schema::TYPE_TEXT . '(255) NOT NULL', 'varchar NOT NULL'], + [Schema::TYPE_SMALLINT, 'smallint'], + [Schema::TYPE_SMALLINT . '(8)', 'smallint'], + [Schema::TYPE_INTEGER, 'int'], + [Schema::TYPE_INTEGER . '(8)', 'int'], + [Schema::TYPE_INTEGER . ' CHECK (value > 5)', 'int CHECK (value > 5)'], + [Schema::TYPE_INTEGER . '(8) CHECK (value > 5)', 'int CHECK (value > 5)'], + [Schema::TYPE_INTEGER . ' NOT NULL', 'int NOT NULL'], + [Schema::TYPE_BIGINT, 'bigint'], + [Schema::TYPE_BIGINT . '(8)', 'bigint'], + [Schema::TYPE_BIGINT . ' CHECK (value > 5)', 'bigint CHECK (value > 5)'], + [Schema::TYPE_BIGINT . '(8) CHECK (value > 5)', 'bigint CHECK (value > 5)'], + [Schema::TYPE_BIGINT . ' NOT NULL', 'bigint NOT NULL'], + [Schema::TYPE_FLOAT, 'float(7)'], + [Schema::TYPE_FLOAT . '(16)', 'float(16)'], + [Schema::TYPE_FLOAT . ' CHECK (value > 5.6)', 'float(7) CHECK (value > 5.6)'], + [Schema::TYPE_FLOAT . '(16) CHECK (value > 5.6)', 'float(16) CHECK (value > 5.6)'], + [Schema::TYPE_FLOAT . ' NOT NULL', 'float(7) NOT NULL'], + [Schema::TYPE_DECIMAL, 'decimal(10,0)'], + [Schema::TYPE_DECIMAL . '(12,4)', 'decimal(12,4)'], + [Schema::TYPE_DECIMAL . ' CHECK (value > 5.6)', 'decimal(10,0) CHECK (value > 5.6)'], + [Schema::TYPE_DECIMAL . '(12,4) CHECK (value > 5.6)', 'decimal(12,4) CHECK (value > 5.6)'], + [Schema::TYPE_DECIMAL . ' NOT NULL', 'decimal(10,0) NOT NULL'], + [Schema::TYPE_DATETIME, 'datetime'], + [Schema::TYPE_DATETIME . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "datetime CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"], + [Schema::TYPE_DATETIME . ' NOT NULL', 'datetime NOT NULL'], + [Schema::TYPE_TIMESTAMP, 'timestamp'], + [Schema::TYPE_TIMESTAMP . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "timestamp CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"], + [Schema::TYPE_TIMESTAMP . ' NOT NULL', 'timestamp NOT NULL'], + [Schema::TYPE_TIME, 'time'], + [Schema::TYPE_TIME . " CHECK(value BETWEEN '12:00:00' AND '13:01:01')", "time CHECK(value BETWEEN '12:00:00' AND '13:01:01')"], + [Schema::TYPE_TIME . ' NOT NULL', 'time NOT NULL'], + [Schema::TYPE_DATE, 'date'], + [Schema::TYPE_DATE . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "date CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"], + [Schema::TYPE_DATE . ' NOT NULL', 'date NOT NULL'], + [Schema::TYPE_BINARY, 'blob'], + [Schema::TYPE_BOOLEAN, 'smallint'], + [Schema::TYPE_BOOLEAN . ' NOT NULL DEFAULT 1', 'smallint NOT NULL DEFAULT 1'], + [Schema::TYPE_MONEY, 'decimal(19,4)'], + [Schema::TYPE_MONEY . '(16,2)', 'decimal(16,2)'], + [Schema::TYPE_MONEY . ' CHECK (value > 0.0)', 'decimal(19,4) CHECK (value > 0.0)'], + [Schema::TYPE_MONEY . '(16,2) CHECK (value > 0.0)', 'decimal(16,2) CHECK (value > 0.0)'], + [Schema::TYPE_MONEY . ' NOT NULL', 'decimal(19,4) NOT NULL'], + ]; } } diff --git a/tests/unit/framework/db/cubrid/CubridSchemaTest.php b/tests/unit/framework/db/cubrid/CubridSchemaTest.php index 9a0c139..41f4110 100644 --- a/tests/unit/framework/db/cubrid/CubridSchemaTest.php +++ b/tests/unit/framework/db/cubrid/CubridSchemaTest.php @@ -10,4 +10,27 @@ use yiiunit\framework\db\SchemaTest; class CubridSchemaTest extends SchemaTest { public $driverName = 'cubrid'; + + public function testGetPDOType() + { + $values = [ + [null, \PDO::PARAM_NULL], + ['', \PDO::PARAM_STR], + ['hello', \PDO::PARAM_STR], + [0, \PDO::PARAM_INT], + [1, \PDO::PARAM_INT], + [1337, \PDO::PARAM_INT], + [true, \PDO::PARAM_INT], + [false, \PDO::PARAM_INT], + [$fp=fopen(__FILE__, 'rb'), \PDO::PARAM_LOB], + ]; + + /** @var Schema $schema */ + $schema = $this->getConnection()->schema; + + foreach($values as $value) { + $this->assertEquals($value[1], $schema->getPdoType($value[0])); + } + fclose($fp); + } } diff --git a/tests/unit/framework/db/mssql/MssqlActiveDataProviderTest.php b/tests/unit/framework/db/mssql/MssqlActiveDataProviderTest.php new file mode 100644 index 0000000..001b660 --- /dev/null +++ b/tests/unit/framework/db/mssql/MssqlActiveDataProviderTest.php @@ -0,0 +1,14 @@ +<?php +namespace yiiunit\framework\db\mssql; + +use yiiunit\framework\data\ActiveDataProviderTest; + +/** + * @group db + * @group mssql + * @group data + */ +class MssqlActiveDataProviderTest extends ActiveDataProviderTest +{ + public $driverName = 'sqlsrv'; +} diff --git a/tests/unit/framework/db/pgsql/PostgreSQLActiveDataProviderTest.php b/tests/unit/framework/db/pgsql/PostgreSQLActiveDataProviderTest.php new file mode 100644 index 0000000..674b564 --- /dev/null +++ b/tests/unit/framework/db/pgsql/PostgreSQLActiveDataProviderTest.php @@ -0,0 +1,14 @@ +<?php +namespace yiiunit\framework\db\pgsql; + +use yiiunit\framework\data\ActiveDataProviderTest; + +/** + * @group db + * @group pgsql + * @group data + */ +class PostgreSQLActiveDataProviderTest extends ActiveDataProviderTest +{ + public $driverName = 'pgsql'; +} diff --git a/tests/unit/framework/db/pgsql/PostgreSQLQueryBuilderTest.php b/tests/unit/framework/db/pgsql/PostgreSQLQueryBuilderTest.php index 3ef329e..e2a62d0 100644 --- a/tests/unit/framework/db/pgsql/PostgreSQLQueryBuilderTest.php +++ b/tests/unit/framework/db/pgsql/PostgreSQLQueryBuilderTest.php @@ -15,63 +15,63 @@ class PostgreSQLQueryBuilderTest extends QueryBuilderTest public function columnTypes() { - return array( - array(Schema::TYPE_PK, 'serial NOT NULL PRIMARY KEY'), - array(Schema::TYPE_PK . '(8)', 'serial NOT NULL PRIMARY KEY'), - array(Schema::TYPE_PK . ' CHECK (value > 5)', 'serial NOT NULL PRIMARY KEY CHECK (value > 5)'), - array(Schema::TYPE_PK . '(8) CHECK (value > 5)', 'serial NOT NULL PRIMARY KEY CHECK (value > 5)'), - array(Schema::TYPE_STRING, 'varchar(255)'), - array(Schema::TYPE_STRING . '(32)', 'varchar(32)'), - array(Schema::TYPE_STRING . ' CHECK (value LIKE "test%")', 'varchar(255) CHECK (value LIKE "test%")'), - array(Schema::TYPE_STRING . '(32) CHECK (value LIKE "test%")', 'varchar(32) CHECK (value LIKE "test%")'), - array(Schema::TYPE_STRING . ' NOT NULL', 'varchar(255) NOT NULL'), - array(Schema::TYPE_TEXT, 'text'), - array(Schema::TYPE_TEXT . '(255)', 'text'), - array(Schema::TYPE_TEXT . ' CHECK (value LIKE "test%")', 'text CHECK (value LIKE "test%")'), - array(Schema::TYPE_TEXT . '(255) CHECK (value LIKE "test%")', 'text CHECK (value LIKE "test%")'), - array(Schema::TYPE_TEXT . ' NOT NULL', 'text NOT NULL'), - array(Schema::TYPE_TEXT . '(255) NOT NULL', 'text NOT NULL'), - array(Schema::TYPE_SMALLINT, 'smallint'), - array(Schema::TYPE_SMALLINT . '(8)', 'smallint'), - array(Schema::TYPE_INTEGER, 'integer'), - array(Schema::TYPE_INTEGER . '(8)', 'integer'), - array(Schema::TYPE_INTEGER . ' CHECK (value > 5)', 'integer CHECK (value > 5)'), - array(Schema::TYPE_INTEGER . '(8) CHECK (value > 5)', 'integer CHECK (value > 5)'), - array(Schema::TYPE_INTEGER . ' NOT NULL', 'integer NOT NULL'), - array(Schema::TYPE_BIGINT, 'bigint'), - array(Schema::TYPE_BIGINT . '(8)', 'bigint'), - array(Schema::TYPE_BIGINT . ' CHECK (value > 5)', 'bigint CHECK (value > 5)'), - array(Schema::TYPE_BIGINT . '(8) CHECK (value > 5)', 'bigint CHECK (value > 5)'), - array(Schema::TYPE_BIGINT . ' NOT NULL', 'bigint NOT NULL'), - array(Schema::TYPE_FLOAT, 'double precision'), - array(Schema::TYPE_FLOAT . ' CHECK (value > 5.6)', 'double precision CHECK (value > 5.6)'), - array(Schema::TYPE_FLOAT . '(16,5) CHECK (value > 5.6)', 'double precision CHECK (value > 5.6)'), - array(Schema::TYPE_FLOAT . ' NOT NULL', 'double precision NOT NULL'), - array(Schema::TYPE_DECIMAL, 'numeric(10,0)'), - array(Schema::TYPE_DECIMAL . '(12,4)', 'numeric(12,4)'), - array(Schema::TYPE_DECIMAL . ' CHECK (value > 5.6)', 'numeric(10,0) CHECK (value > 5.6)'), - array(Schema::TYPE_DECIMAL . '(12,4) CHECK (value > 5.6)', 'numeric(12,4) CHECK (value > 5.6)'), - array(Schema::TYPE_DECIMAL . ' NOT NULL', 'numeric(10,0) NOT NULL'), - array(Schema::TYPE_DATETIME, 'timestamp'), - array(Schema::TYPE_DATETIME . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "timestamp CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"), - array(Schema::TYPE_DATETIME . ' NOT NULL', 'timestamp NOT NULL'), - array(Schema::TYPE_TIMESTAMP, 'timestamp'), - array(Schema::TYPE_TIMESTAMP . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "timestamp CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"), - array(Schema::TYPE_TIMESTAMP . ' NOT NULL', 'timestamp NOT NULL'), - array(Schema::TYPE_TIME, 'time'), - array(Schema::TYPE_TIME . " CHECK(value BETWEEN '12:00:00' AND '13:01:01')", "time CHECK(value BETWEEN '12:00:00' AND '13:01:01')"), - array(Schema::TYPE_TIME . ' NOT NULL', 'time NOT NULL'), - array(Schema::TYPE_DATE, 'date'), - array(Schema::TYPE_DATE . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "date CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"), - array(Schema::TYPE_DATE . ' NOT NULL', 'date NOT NULL'), - array(Schema::TYPE_BINARY, 'bytea'), - array(Schema::TYPE_BOOLEAN, 'boolean'), - array(Schema::TYPE_BOOLEAN . ' NOT NULL DEFAULT 1', 'boolean NOT NULL DEFAULT 1'), - array(Schema::TYPE_MONEY, 'numeric(19,4)'), - array(Schema::TYPE_MONEY . '(16,2)', 'numeric(16,2)'), - array(Schema::TYPE_MONEY . ' CHECK (value > 0.0)', 'numeric(19,4) CHECK (value > 0.0)'), - array(Schema::TYPE_MONEY . '(16,2) CHECK (value > 0.0)', 'numeric(16,2) CHECK (value > 0.0)'), - array(Schema::TYPE_MONEY . ' NOT NULL', 'numeric(19,4) NOT NULL'), - ); + return [ + [Schema::TYPE_PK, 'serial NOT NULL PRIMARY KEY'], + [Schema::TYPE_PK . '(8)', 'serial NOT NULL PRIMARY KEY'], + [Schema::TYPE_PK . ' CHECK (value > 5)', 'serial NOT NULL PRIMARY KEY CHECK (value > 5)'], + [Schema::TYPE_PK . '(8) CHECK (value > 5)', 'serial NOT NULL PRIMARY KEY CHECK (value > 5)'], + [Schema::TYPE_STRING, 'varchar(255)'], + [Schema::TYPE_STRING . '(32)', 'varchar(32)'], + [Schema::TYPE_STRING . ' CHECK (value LIKE "test%")', 'varchar(255) CHECK (value LIKE "test%")'], + [Schema::TYPE_STRING . '(32) CHECK (value LIKE "test%")', 'varchar(32) CHECK (value LIKE "test%")'], + [Schema::TYPE_STRING . ' NOT NULL', 'varchar(255) NOT NULL'], + [Schema::TYPE_TEXT, 'text'], + [Schema::TYPE_TEXT . '(255)', 'text'], + [Schema::TYPE_TEXT . ' CHECK (value LIKE "test%")', 'text CHECK (value LIKE "test%")'], + [Schema::TYPE_TEXT . '(255) CHECK (value LIKE "test%")', 'text CHECK (value LIKE "test%")'], + [Schema::TYPE_TEXT . ' NOT NULL', 'text NOT NULL'], + [Schema::TYPE_TEXT . '(255) NOT NULL', 'text NOT NULL'], + [Schema::TYPE_SMALLINT, 'smallint'], + [Schema::TYPE_SMALLINT . '(8)', 'smallint'], + [Schema::TYPE_INTEGER, 'integer'], + [Schema::TYPE_INTEGER . '(8)', 'integer'], + [Schema::TYPE_INTEGER . ' CHECK (value > 5)', 'integer CHECK (value > 5)'], + [Schema::TYPE_INTEGER . '(8) CHECK (value > 5)', 'integer CHECK (value > 5)'], + [Schema::TYPE_INTEGER . ' NOT NULL', 'integer NOT NULL'], + [Schema::TYPE_BIGINT, 'bigint'], + [Schema::TYPE_BIGINT . '(8)', 'bigint'], + [Schema::TYPE_BIGINT . ' CHECK (value > 5)', 'bigint CHECK (value > 5)'], + [Schema::TYPE_BIGINT . '(8) CHECK (value > 5)', 'bigint CHECK (value > 5)'], + [Schema::TYPE_BIGINT . ' NOT NULL', 'bigint NOT NULL'], + [Schema::TYPE_FLOAT, 'double precision'], + [Schema::TYPE_FLOAT . ' CHECK (value > 5.6)', 'double precision CHECK (value > 5.6)'], + [Schema::TYPE_FLOAT . '(16,5) CHECK (value > 5.6)', 'double precision CHECK (value > 5.6)'], + [Schema::TYPE_FLOAT . ' NOT NULL', 'double precision NOT NULL'], + [Schema::TYPE_DECIMAL, 'numeric(10,0)'], + [Schema::TYPE_DECIMAL . '(12,4)', 'numeric(12,4)'], + [Schema::TYPE_DECIMAL . ' CHECK (value > 5.6)', 'numeric(10,0) CHECK (value > 5.6)'], + [Schema::TYPE_DECIMAL . '(12,4) CHECK (value > 5.6)', 'numeric(12,4) CHECK (value > 5.6)'], + [Schema::TYPE_DECIMAL . ' NOT NULL', 'numeric(10,0) NOT NULL'], + [Schema::TYPE_DATETIME, 'timestamp'], + [Schema::TYPE_DATETIME . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "timestamp CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"], + [Schema::TYPE_DATETIME . ' NOT NULL', 'timestamp NOT NULL'], + [Schema::TYPE_TIMESTAMP, 'timestamp'], + [Schema::TYPE_TIMESTAMP . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "timestamp CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"], + [Schema::TYPE_TIMESTAMP . ' NOT NULL', 'timestamp NOT NULL'], + [Schema::TYPE_TIME, 'time'], + [Schema::TYPE_TIME . " CHECK(value BETWEEN '12:00:00' AND '13:01:01')", "time CHECK(value BETWEEN '12:00:00' AND '13:01:01')"], + [Schema::TYPE_TIME . ' NOT NULL', 'time NOT NULL'], + [Schema::TYPE_DATE, 'date'], + [Schema::TYPE_DATE . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "date CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"], + [Schema::TYPE_DATE . ' NOT NULL', 'date NOT NULL'], + [Schema::TYPE_BINARY, 'bytea'], + [Schema::TYPE_BOOLEAN, 'boolean'], + [Schema::TYPE_BOOLEAN . ' NOT NULL DEFAULT 1', 'boolean NOT NULL DEFAULT 1'], + [Schema::TYPE_MONEY, 'numeric(19,4)'], + [Schema::TYPE_MONEY . '(16,2)', 'numeric(16,2)'], + [Schema::TYPE_MONEY . ' CHECK (value > 0.0)', 'numeric(19,4) CHECK (value > 0.0)'], + [Schema::TYPE_MONEY . '(16,2) CHECK (value > 0.0)', 'numeric(16,2) CHECK (value > 0.0)'], + [Schema::TYPE_MONEY . ' NOT NULL', 'numeric(19,4) NOT NULL'], + ]; } } diff --git a/tests/unit/framework/db/sqlite/SqliteActiveDataProviderTest.php b/tests/unit/framework/db/sqlite/SqliteActiveDataProviderTest.php new file mode 100644 index 0000000..660b14d --- /dev/null +++ b/tests/unit/framework/db/sqlite/SqliteActiveDataProviderTest.php @@ -0,0 +1,14 @@ +<?php +namespace yiiunit\framework\db\sqlite; + +use yiiunit\framework\data\ActiveDataProviderTest; + +/** + * @group db + * @group sqlite + * @group data + */ +class SqliteActiveDataProviderTest extends ActiveDataProviderTest +{ + public $driverName = 'sqlite'; +} diff --git a/tests/unit/framework/db/sqlite/SqliteActiveRecordTest.php b/tests/unit/framework/db/sqlite/SqliteActiveRecordTest.php index a689e5d..88e950a 100644 --- a/tests/unit/framework/db/sqlite/SqliteActiveRecordTest.php +++ b/tests/unit/framework/db/sqlite/SqliteActiveRecordTest.php @@ -1,6 +1,7 @@ <?php namespace yiiunit\framework\db\sqlite; +use yiiunit\data\ar\Customer; use yiiunit\framework\db\ActiveRecordTest; /** diff --git a/tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php b/tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php index b20acad..d7904d0 100644 --- a/tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php +++ b/tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php @@ -15,65 +15,65 @@ class SqliteQueryBuilderTest extends QueryBuilderTest public function columnTypes() { - return array( - array(Schema::TYPE_PK, 'integer PRIMARY KEY AUTOINCREMENT NOT NULL'), - array(Schema::TYPE_PK . '(8)', 'integer PRIMARY KEY AUTOINCREMENT NOT NULL'), - array(Schema::TYPE_PK . ' CHECK (value > 5)', 'integer PRIMARY KEY AUTOINCREMENT NOT NULL CHECK (value > 5)'), - array(Schema::TYPE_PK . '(8) CHECK (value > 5)', 'integer PRIMARY KEY AUTOINCREMENT NOT NULL CHECK (value > 5)'), - array(Schema::TYPE_STRING, 'varchar(255)'), - array(Schema::TYPE_STRING . '(32)', 'varchar(32)'), - array(Schema::TYPE_STRING . ' CHECK (value LIKE "test%")', 'varchar(255) CHECK (value LIKE "test%")'), - array(Schema::TYPE_STRING . '(32) CHECK (value LIKE "test%")', 'varchar(32) CHECK (value LIKE "test%")'), - array(Schema::TYPE_STRING . ' NOT NULL', 'varchar(255) NOT NULL'), - array(Schema::TYPE_TEXT, 'text'), - array(Schema::TYPE_TEXT . '(255)', 'text'), - array(Schema::TYPE_TEXT . ' CHECK (value LIKE "test%")', 'text CHECK (value LIKE "test%")'), - array(Schema::TYPE_TEXT . '(255) CHECK (value LIKE "test%")', 'text CHECK (value LIKE "test%")'), - array(Schema::TYPE_TEXT . ' NOT NULL', 'text NOT NULL'), - array(Schema::TYPE_TEXT . '(255) NOT NULL', 'text NOT NULL'), - array(Schema::TYPE_SMALLINT, 'smallint'), - array(Schema::TYPE_SMALLINT . '(8)', 'smallint'), - array(Schema::TYPE_INTEGER, 'integer'), - array(Schema::TYPE_INTEGER . '(8)', 'integer'), - array(Schema::TYPE_INTEGER . ' CHECK (value > 5)', 'integer CHECK (value > 5)'), - array(Schema::TYPE_INTEGER . '(8) CHECK (value > 5)', 'integer CHECK (value > 5)'), - array(Schema::TYPE_INTEGER . ' NOT NULL', 'integer NOT NULL'), - array(Schema::TYPE_BIGINT, 'bigint'), - array(Schema::TYPE_BIGINT . '(8)', 'bigint'), - array(Schema::TYPE_BIGINT . ' CHECK (value > 5)', 'bigint CHECK (value > 5)'), - array(Schema::TYPE_BIGINT . '(8) CHECK (value > 5)', 'bigint CHECK (value > 5)'), - array(Schema::TYPE_BIGINT . ' NOT NULL', 'bigint NOT NULL'), - array(Schema::TYPE_FLOAT, 'float'), - array(Schema::TYPE_FLOAT . '(16,5)', 'float'), - array(Schema::TYPE_FLOAT . ' CHECK (value > 5.6)', 'float CHECK (value > 5.6)'), - array(Schema::TYPE_FLOAT . '(16,5) CHECK (value > 5.6)', 'float CHECK (value > 5.6)'), - array(Schema::TYPE_FLOAT . ' NOT NULL', 'float NOT NULL'), - array(Schema::TYPE_DECIMAL, 'decimal(10,0)'), - array(Schema::TYPE_DECIMAL . '(12,4)', 'decimal(12,4)'), - array(Schema::TYPE_DECIMAL . ' CHECK (value > 5.6)', 'decimal(10,0) CHECK (value > 5.6)'), - array(Schema::TYPE_DECIMAL . '(12,4) CHECK (value > 5.6)', 'decimal(12,4) CHECK (value > 5.6)'), - array(Schema::TYPE_DECIMAL . ' NOT NULL', 'decimal(10,0) NOT NULL'), - array(Schema::TYPE_DATETIME, 'datetime'), - array(Schema::TYPE_DATETIME . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "datetime CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"), - array(Schema::TYPE_DATETIME . ' NOT NULL', 'datetime NOT NULL'), - array(Schema::TYPE_TIMESTAMP, 'timestamp'), - array(Schema::TYPE_TIMESTAMP . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "timestamp CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"), - array(Schema::TYPE_TIMESTAMP . ' NOT NULL', 'timestamp NOT NULL'), - array(Schema::TYPE_TIME, 'time'), - array(Schema::TYPE_TIME . " CHECK(value BETWEEN '12:00:00' AND '13:01:01')", "time CHECK(value BETWEEN '12:00:00' AND '13:01:01')"), - array(Schema::TYPE_TIME . ' NOT NULL', 'time NOT NULL'), - array(Schema::TYPE_DATE, 'date'), - array(Schema::TYPE_DATE . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "date CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"), - array(Schema::TYPE_DATE . ' NOT NULL', 'date NOT NULL'), - array(Schema::TYPE_BINARY, 'blob'), - array(Schema::TYPE_BOOLEAN, 'boolean'), - array(Schema::TYPE_BOOLEAN . ' NOT NULL DEFAULT 1', 'boolean NOT NULL DEFAULT 1'), - array(Schema::TYPE_MONEY, 'decimal(19,4)'), - array(Schema::TYPE_MONEY . '(16,2)', 'decimal(16,2)'), - array(Schema::TYPE_MONEY . ' CHECK (value > 0.0)', 'decimal(19,4) CHECK (value > 0.0)'), - array(Schema::TYPE_MONEY . '(16,2) CHECK (value > 0.0)', 'decimal(16,2) CHECK (value > 0.0)'), - array(Schema::TYPE_MONEY . ' NOT NULL', 'decimal(19,4) NOT NULL'), - ); + return [ + [Schema::TYPE_PK, 'integer PRIMARY KEY AUTOINCREMENT NOT NULL'], + [Schema::TYPE_PK . '(8)', 'integer PRIMARY KEY AUTOINCREMENT NOT NULL'], + [Schema::TYPE_PK . ' CHECK (value > 5)', 'integer PRIMARY KEY AUTOINCREMENT NOT NULL CHECK (value > 5)'], + [Schema::TYPE_PK . '(8) CHECK (value > 5)', 'integer PRIMARY KEY AUTOINCREMENT NOT NULL CHECK (value > 5)'], + [Schema::TYPE_STRING, 'varchar(255)'], + [Schema::TYPE_STRING . '(32)', 'varchar(32)'], + [Schema::TYPE_STRING . ' CHECK (value LIKE "test%")', 'varchar(255) CHECK (value LIKE "test%")'], + [Schema::TYPE_STRING . '(32) CHECK (value LIKE "test%")', 'varchar(32) CHECK (value LIKE "test%")'], + [Schema::TYPE_STRING . ' NOT NULL', 'varchar(255) NOT NULL'], + [Schema::TYPE_TEXT, 'text'], + [Schema::TYPE_TEXT . '(255)', 'text'], + [Schema::TYPE_TEXT . ' CHECK (value LIKE "test%")', 'text CHECK (value LIKE "test%")'], + [Schema::TYPE_TEXT . '(255) CHECK (value LIKE "test%")', 'text CHECK (value LIKE "test%")'], + [Schema::TYPE_TEXT . ' NOT NULL', 'text NOT NULL'], + [Schema::TYPE_TEXT . '(255) NOT NULL', 'text NOT NULL'], + [Schema::TYPE_SMALLINT, 'smallint'], + [Schema::TYPE_SMALLINT . '(8)', 'smallint'], + [Schema::TYPE_INTEGER, 'integer'], + [Schema::TYPE_INTEGER . '(8)', 'integer'], + [Schema::TYPE_INTEGER . ' CHECK (value > 5)', 'integer CHECK (value > 5)'], + [Schema::TYPE_INTEGER . '(8) CHECK (value > 5)', 'integer CHECK (value > 5)'], + [Schema::TYPE_INTEGER . ' NOT NULL', 'integer NOT NULL'], + [Schema::TYPE_BIGINT, 'bigint'], + [Schema::TYPE_BIGINT . '(8)', 'bigint'], + [Schema::TYPE_BIGINT . ' CHECK (value > 5)', 'bigint CHECK (value > 5)'], + [Schema::TYPE_BIGINT . '(8) CHECK (value > 5)', 'bigint CHECK (value > 5)'], + [Schema::TYPE_BIGINT . ' NOT NULL', 'bigint NOT NULL'], + [Schema::TYPE_FLOAT, 'float'], + [Schema::TYPE_FLOAT . '(16,5)', 'float'], + [Schema::TYPE_FLOAT . ' CHECK (value > 5.6)', 'float CHECK (value > 5.6)'], + [Schema::TYPE_FLOAT . '(16,5) CHECK (value > 5.6)', 'float CHECK (value > 5.6)'], + [Schema::TYPE_FLOAT . ' NOT NULL', 'float NOT NULL'], + [Schema::TYPE_DECIMAL, 'decimal(10,0)'], + [Schema::TYPE_DECIMAL . '(12,4)', 'decimal(12,4)'], + [Schema::TYPE_DECIMAL . ' CHECK (value > 5.6)', 'decimal(10,0) CHECK (value > 5.6)'], + [Schema::TYPE_DECIMAL . '(12,4) CHECK (value > 5.6)', 'decimal(12,4) CHECK (value > 5.6)'], + [Schema::TYPE_DECIMAL . ' NOT NULL', 'decimal(10,0) NOT NULL'], + [Schema::TYPE_DATETIME, 'datetime'], + [Schema::TYPE_DATETIME . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "datetime CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"], + [Schema::TYPE_DATETIME . ' NOT NULL', 'datetime NOT NULL'], + [Schema::TYPE_TIMESTAMP, 'timestamp'], + [Schema::TYPE_TIMESTAMP . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "timestamp CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"], + [Schema::TYPE_TIMESTAMP . ' NOT NULL', 'timestamp NOT NULL'], + [Schema::TYPE_TIME, 'time'], + [Schema::TYPE_TIME . " CHECK(value BETWEEN '12:00:00' AND '13:01:01')", "time CHECK(value BETWEEN '12:00:00' AND '13:01:01')"], + [Schema::TYPE_TIME . ' NOT NULL', 'time NOT NULL'], + [Schema::TYPE_DATE, 'date'], + [Schema::TYPE_DATE . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "date CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"], + [Schema::TYPE_DATE . ' NOT NULL', 'date NOT NULL'], + [Schema::TYPE_BINARY, 'blob'], + [Schema::TYPE_BOOLEAN, 'boolean'], + [Schema::TYPE_BOOLEAN . ' NOT NULL DEFAULT 1', 'boolean NOT NULL DEFAULT 1'], + [Schema::TYPE_MONEY, 'decimal(19,4)'], + [Schema::TYPE_MONEY . '(16,2)', 'decimal(16,2)'], + [Schema::TYPE_MONEY . ' CHECK (value > 0.0)', 'decimal(19,4) CHECK (value > 0.0)'], + [Schema::TYPE_MONEY . '(16,2) CHECK (value > 0.0)', 'decimal(16,2) CHECK (value > 0.0)'], + [Schema::TYPE_MONEY . ' NOT NULL', 'decimal(19,4) NOT NULL'], + ]; } public function testAddDropPrimaryKey() diff --git a/tests/unit/framework/helpers/ArrayHelperTest.php b/tests/unit/framework/helpers/ArrayHelperTest.php index ca8c95a..6aa2a45 100644 --- a/tests/unit/framework/helpers/ArrayHelperTest.php +++ b/tests/unit/framework/helpers/ArrayHelperTest.php @@ -4,7 +4,7 @@ namespace yiiunit\framework\helpers; use yii\base\Object; use yii\helpers\ArrayHelper; -use yii\test\TestCase; +use yiiunit\TestCase; use yii\data\Sort; class Post1 @@ -49,47 +49,48 @@ class ArrayHelperTest extends TestCase $object1 = new Post1; $object2 = new Post2; - $this->assertEquals(array( + $this->assertEquals([ get_object_vars($object1), get_object_vars($object2), - ), ArrayHelper::toArray(array( + ], ArrayHelper::toArray([ $object1, $object2, - ))); + ])); $object = new Post2; - $this->assertEquals(array( + $this->assertEquals([ 'id' => 123, 'secret' => 's', '_content' => 'test', 'length' => 4, - ), ArrayHelper::toArray($object, array( - $object->className() => array( + ], ArrayHelper::toArray($object, [ + $object->className() => [ 'id', 'secret', '_content' => 'content', 'length' => function ($post) { return strlen($post->content); } - )))); + ] + ])); $object = new Post3(); - $this->assertEquals(get_object_vars($object), ArrayHelper::toArray($object, array(), false)); - $this->assertEquals(array( + $this->assertEquals(get_object_vars($object), ArrayHelper::toArray($object, [], false)); + $this->assertEquals([ 'id' => 33, - 'subObject' => array( + 'subObject' => [ 'id' => 123, 'content' => 'test', - ), - ), ArrayHelper::toArray($object)); + ], + ], ArrayHelper::toArray($object)); } public function testRemove() { - $array = array('name' => 'b', 'age' => 3); + $array = ['name' => 'b', 'age' => 3]; $name = ArrayHelper::remove($array, 'name'); $this->assertEquals($name, 'b'); - $this->assertEquals($array, array('age' => 3)); + $this->assertEquals($array, ['age' => 3]); $default = ArrayHelper::remove($array, 'nonExisting', 'defaultValue'); $this->assertEquals('defaultValue', $default); @@ -99,201 +100,201 @@ class ArrayHelperTest extends TestCase public function testMultisort() { // single key - $array = array( - array('name' => 'b', 'age' => 3), - array('name' => 'a', 'age' => 1), - array('name' => 'c', 'age' => 2), - ); + $array = [ + ['name' => 'b', 'age' => 3], + ['name' => 'a', 'age' => 1], + ['name' => 'c', 'age' => 2], + ]; ArrayHelper::multisort($array, 'name'); - $this->assertEquals(array('name' => 'a', 'age' => 1), $array[0]); - $this->assertEquals(array('name' => 'b', 'age' => 3), $array[1]); - $this->assertEquals(array('name' => 'c', 'age' => 2), $array[2]); + $this->assertEquals(['name' => 'a', 'age' => 1], $array[0]); + $this->assertEquals(['name' => 'b', 'age' => 3], $array[1]); + $this->assertEquals(['name' => 'c', 'age' => 2], $array[2]); // multiple keys - $array = array( - array('name' => 'b', 'age' => 3), - array('name' => 'a', 'age' => 2), - array('name' => 'a', 'age' => 1), - ); - ArrayHelper::multisort($array, array('name', 'age')); - $this->assertEquals(array('name' => 'a', 'age' => 1), $array[0]); - $this->assertEquals(array('name' => 'a', 'age' => 2), $array[1]); - $this->assertEquals(array('name' => 'b', 'age' => 3), $array[2]); + $array = [ + ['name' => 'b', 'age' => 3], + ['name' => 'a', 'age' => 2], + ['name' => 'a', 'age' => 1], + ]; + ArrayHelper::multisort($array, ['name', 'age']); + $this->assertEquals(['name' => 'a', 'age' => 1], $array[0]); + $this->assertEquals(['name' => 'a', 'age' => 2], $array[1]); + $this->assertEquals(['name' => 'b', 'age' => 3], $array[2]); // case-insensitive - $array = array( - array('name' => 'a', 'age' => 3), - array('name' => 'b', 'age' => 2), - array('name' => 'B', 'age' => 4), - array('name' => 'A', 'age' => 1), - ); - - ArrayHelper::multisort($array, array('name', 'age'), false, array(SORT_STRING, SORT_REGULAR)); - $this->assertEquals(array('name' => 'A', 'age' => 1), $array[0]); - $this->assertEquals(array('name' => 'B', 'age' => 4), $array[1]); - $this->assertEquals(array('name' => 'a', 'age' => 3), $array[2]); - $this->assertEquals(array('name' => 'b', 'age' => 2), $array[3]); - - ArrayHelper::multisort($array, array('name', 'age'), false, array(SORT_STRING, SORT_REGULAR), false); - $this->assertEquals(array('name' => 'A', 'age' => 1), $array[0]); - $this->assertEquals(array('name' => 'a', 'age' => 3), $array[1]); - $this->assertEquals(array('name' => 'b', 'age' => 2), $array[2]); - $this->assertEquals(array('name' => 'B', 'age' => 4), $array[3]); + $array = [ + ['name' => 'a', 'age' => 3], + ['name' => 'b', 'age' => 2], + ['name' => 'B', 'age' => 4], + ['name' => 'A', 'age' => 1], + ]; + + ArrayHelper::multisort($array, ['name', 'age'], SORT_ASC, [SORT_STRING, SORT_REGULAR]); + $this->assertEquals(['name' => 'A', 'age' => 1], $array[0]); + $this->assertEquals(['name' => 'B', 'age' => 4], $array[1]); + $this->assertEquals(['name' => 'a', 'age' => 3], $array[2]); + $this->assertEquals(['name' => 'b', 'age' => 2], $array[3]); + + ArrayHelper::multisort($array, ['name', 'age'], SORT_ASC, [SORT_STRING | SORT_FLAG_CASE, SORT_REGULAR]); + $this->assertEquals(['name' => 'A', 'age' => 1], $array[0]); + $this->assertEquals(['name' => 'a', 'age' => 3], $array[1]); + $this->assertEquals(['name' => 'b', 'age' => 2], $array[2]); + $this->assertEquals(['name' => 'B', 'age' => 4], $array[3]); } public function testMultisortUseSort() { // single key - $sort = new Sort(array( - 'attributes' => array('name', 'age'), - 'defaultOrder' => array('name' => Sort::ASC), - )); + $sort = new Sort([ + 'attributes' => ['name', 'age'], + 'defaultOrder' => ['name' => SORT_ASC], + ]); $orders = $sort->getOrders(); - $array = array( - array('name' => 'b', 'age' => 3), - array('name' => 'a', 'age' => 1), - array('name' => 'c', 'age' => 2), - ); + $array = [ + ['name' => 'b', 'age' => 3], + ['name' => 'a', 'age' => 1], + ['name' => 'c', 'age' => 2], + ]; ArrayHelper::multisort($array, array_keys($orders), array_values($orders)); - $this->assertEquals(array('name' => 'a', 'age' => 1), $array[0]); - $this->assertEquals(array('name' => 'b', 'age' => 3), $array[1]); - $this->assertEquals(array('name' => 'c', 'age' => 2), $array[2]); + $this->assertEquals(['name' => 'a', 'age' => 1], $array[0]); + $this->assertEquals(['name' => 'b', 'age' => 3], $array[1]); + $this->assertEquals(['name' => 'c', 'age' => 2], $array[2]); // multiple keys - $sort = new Sort(array( - 'attributes' => array('name', 'age'), - 'defaultOrder' => array('name' => Sort::ASC, 'age' => Sort::DESC), - )); + $sort = new Sort([ + 'attributes' => ['name', 'age'], + 'defaultOrder' => ['name' => SORT_ASC, 'age' => SORT_DESC], + ]); $orders = $sort->getOrders(); - $array = array( - array('name' => 'b', 'age' => 3), - array('name' => 'a', 'age' => 2), - array('name' => 'a', 'age' => 1), - ); + $array = [ + ['name' => 'b', 'age' => 3], + ['name' => 'a', 'age' => 2], + ['name' => 'a', 'age' => 1], + ]; ArrayHelper::multisort($array, array_keys($orders), array_values($orders)); - $this->assertEquals(array('name' => 'a', 'age' => 2), $array[0]); - $this->assertEquals(array('name' => 'a', 'age' => 1), $array[1]); - $this->assertEquals(array('name' => 'b', 'age' => 3), $array[2]); + $this->assertEquals(['name' => 'a', 'age' => 2], $array[0]); + $this->assertEquals(['name' => 'a', 'age' => 1], $array[1]); + $this->assertEquals(['name' => 'b', 'age' => 3], $array[2]); } public function testMerge() { - $a = array( + $a = [ 'name' => 'Yii', 'version' => '1.0', - 'options' => array( + 'options' => [ 'namespace' => false, 'unittest' => false, - ), - 'features' => array( + ], + 'features' => [ 'mvc', - ), - ); - $b = array( + ], + ]; + $b = [ 'version' => '1.1', - 'options' => array( + 'options' => [ 'unittest' => true, - ), - 'features' => array( + ], + 'features' => [ 'gii', - ), - ); - $c = array( + ], + ]; + $c = [ 'version' => '2.0', - 'options' => array( + 'options' => [ 'namespace' => true, - ), - 'features' => array( + ], + 'features' => [ 'debug', - ), - ); + ], + ]; $result = ArrayHelper::merge($a, $b, $c); - $expected = array( + $expected = [ 'name' => 'Yii', 'version' => '2.0', - 'options' => array( + 'options' => [ 'namespace' => true, 'unittest' => true, - ), - 'features' => array( + ], + 'features' => [ 'mvc', 'gii', 'debug', - ), - ); + ], + ]; $this->assertEquals($expected, $result); } public function testIndex() { - $array = array( - array('id' => '123', 'data' => 'abc'), - array('id' => '345', 'data' => 'def'), - ); + $array = [ + ['id' => '123', 'data' => 'abc'], + ['id' => '345', 'data' => 'def'], + ]; $result = ArrayHelper::index($array, 'id'); - $this->assertEquals(array( - '123' => array('id' => '123', 'data' => 'abc'), - '345' => array('id' => '345', 'data' => 'def'), - ), $result); + $this->assertEquals([ + '123' => ['id' => '123', 'data' => 'abc'], + '345' => ['id' => '345', 'data' => 'def'], + ], $result); $result = ArrayHelper::index($array, function ($element) { return $element['data']; }); - $this->assertEquals(array( - 'abc' => array('id' => '123', 'data' => 'abc'), - 'def' => array('id' => '345', 'data' => 'def'), - ), $result); + $this->assertEquals([ + 'abc' => ['id' => '123', 'data' => 'abc'], + 'def' => ['id' => '345', 'data' => 'def'], + ], $result); } public function testGetColumn() { - $array = array( - 'a' => array('id' => '123', 'data' => 'abc'), - 'b' => array('id' => '345', 'data' => 'def'), - ); + $array = [ + 'a' => ['id' => '123', 'data' => 'abc'], + 'b' => ['id' => '345', 'data' => 'def'], + ]; $result = ArrayHelper::getColumn($array, 'id'); - $this->assertEquals(array('a' => '123', 'b' => '345'), $result); + $this->assertEquals(['a' => '123', 'b' => '345'], $result); $result = ArrayHelper::getColumn($array, 'id', false); - $this->assertEquals(array('123', '345'), $result); + $this->assertEquals(['123', '345'], $result); $result = ArrayHelper::getColumn($array, function ($element) { return $element['data']; }); - $this->assertEquals(array('a' => 'abc', 'b' => 'def'), $result); + $this->assertEquals(['a' => 'abc', 'b' => 'def'], $result); $result = ArrayHelper::getColumn($array, function ($element) { return $element['data']; }, false); - $this->assertEquals(array('abc', 'def'), $result); + $this->assertEquals(['abc', 'def'], $result); } public function testMap() { - $array = array( - array('id' => '123', 'name' => 'aaa', 'class' => 'x'), - array('id' => '124', 'name' => 'bbb', 'class' => 'x'), - array('id' => '345', 'name' => 'ccc', 'class' => 'y'), - ); + $array = [ + ['id' => '123', 'name' => 'aaa', 'class' => 'x'], + ['id' => '124', 'name' => 'bbb', 'class' => 'x'], + ['id' => '345', 'name' => 'ccc', 'class' => 'y'], + ]; $result = ArrayHelper::map($array, 'id', 'name'); - $this->assertEquals(array( + $this->assertEquals([ '123' => 'aaa', '124' => 'bbb', '345' => 'ccc', - ), $result); + ], $result); $result = ArrayHelper::map($array, 'id', 'name', 'class'); - $this->assertEquals(array( - 'x' => array( + $this->assertEquals([ + 'x' => [ '123' => 'aaa', '124' => 'bbb', - ), - 'y' => array( + ], + 'y' => [ '345' => 'ccc', - ), - ), $result); + ], + ], $result); } } diff --git a/tests/unit/framework/helpers/ConsoleTest.php b/tests/unit/framework/helpers/ConsoleTest.php index 4b983f8..ce6d3fe 100644 --- a/tests/unit/framework/helpers/ConsoleTest.php +++ b/tests/unit/framework/helpers/ConsoleTest.php @@ -57,11 +57,11 @@ class ConsoleTest extends TestCase echo 'a'; Console::restoreCursorPosition(); echo 'a'; - Console::beginAnsiFormat(array(Console::FG_GREEN, Console::BG_BLUE, Console::UNDERLINE)); + Console::beginAnsiFormat([Console::FG_GREEN, Console::BG_BLUE, Console::UNDERLINE]); echo 'a'; Console::endAnsiFormat(); echo 'a'; - Console::beginAnsiFormat(array(Console::xtermBgColor(128), Console::xtermFgColor(55))); + Console::beginAnsiFormat([Console::xtermBgColor(128), Console::xtermFgColor(55)]); echo 'a'; Console::endAnsiFormat(); echo 'a'; @@ -73,7 +73,7 @@ class ConsoleTest extends TestCase /* public function testScreenSize() { - for($i = 1; $i < 20; $i++) { + for ($i = 1; $i < 20; $i++) { echo implode(', ', Console::getScreenSize(true)) . "\n"; ob_flush(); sleep(1); diff --git a/tests/unit/framework/helpers/FileHelperTest.php b/tests/unit/framework/helpers/FileHelperTest.php index 05bd7af..3b5f49e 100644 --- a/tests/unit/framework/helpers/FileHelperTest.php +++ b/tests/unit/framework/helpers/FileHelperTest.php @@ -1,7 +1,7 @@ <?php use yii\helpers\FileHelper; -use yii\test\TestCase; +use yiiunit\TestCase; /** * Unit test for [[yii\helpers\FileHelper]] @@ -112,13 +112,13 @@ class FileHelperTest extends TestCase public function testCopyDirectory() { $srcDirName = 'test_src_dir'; - $files = array( + $files = [ 'file1.txt' => 'file 1 content', 'file2.txt' => 'file 2 content', - ); - $this->createFileStructure(array( + ]; + $this->createFileStructure([ $srcDirName => $files - )); + ]); $basePath = $this->testFilePath; $srcDirName = $basePath . DIRECTORY_SEPARATOR . $srcDirName; @@ -126,10 +126,10 @@ class FileHelperTest extends TestCase FileHelper::copyDirectory($srcDirName, $dstDirName); - $this->assertTrue(file_exists($dstDirName), 'Destination directory does not exist!'); + $this->assertFileExists($dstDirName, 'Destination directory does not exist!'); foreach ($files as $name => $content) { $fileName = $dstDirName . DIRECTORY_SEPARATOR . $name; - $this->assertTrue(file_exists($fileName), 'Directory file is missing!'); + $this->assertFileExists($fileName); $this->assertEquals($content, file_get_contents($fileName), 'Incorrect file content!'); } } @@ -146,12 +146,12 @@ class FileHelperTest extends TestCase $srcDirName = 'test_src_dir'; $subDirName = 'test_sub_dir'; $fileName = 'test_file.txt'; - $this->createFileStructure(array( - $srcDirName => array( - $subDirName => array(), + $this->createFileStructure([ + $srcDirName => [ + $subDirName => [], $fileName => 'test file content', - ), - )); + ], + ]); $basePath = $this->testFilePath; $srcDirName = $basePath . DIRECTORY_SEPARATOR . $srcDirName; @@ -159,10 +159,10 @@ class FileHelperTest extends TestCase $dirMode = 0755; $fileMode = 0755; - $options = array( + $options = [ 'dirMode' => $dirMode, 'fileMode' => $fileMode, - ); + ]; FileHelper::copyDirectory($srcDirName, $dstDirName, $options); $this->assertFileMode($dirMode, $dstDirName, 'Destination directory has wrong mode!'); @@ -173,23 +173,23 @@ class FileHelperTest extends TestCase public function testRemoveDirectory() { $dirName = 'test_dir_for_remove'; - $this->createFileStructure(array( - $dirName => array( + $this->createFileStructure([ + $dirName => [ 'file1.txt' => 'file 1 content', 'file2.txt' => 'file 2 content', - 'test_sub_dir' => array( + 'test_sub_dir' => [ 'sub_dir_file_1.txt' => 'sub dir file 1 content', 'sub_dir_file_2.txt' => 'sub dir file 2 content', - ), - ), - )); + ], + ], + ]); $basePath = $this->testFilePath; $dirName = $basePath . DIRECTORY_SEPARATOR . $dirName; FileHelper::removeDirectory($dirName); - $this->assertFalse(file_exists($dirName), 'Unable to remove directory!'); + $this->assertFileNotExists($dirName, 'Unable to remove directory!'); // should be silent about non-existing directories FileHelper::removeDirectory($basePath . DIRECTORY_SEPARATOR . 'nonExisting'); @@ -198,24 +198,24 @@ class FileHelperTest extends TestCase public function testFindFiles() { $dirName = 'test_dir'; - $this->createFileStructure(array( - $dirName => array( + $this->createFileStructure([ + $dirName => [ 'file_1.txt' => 'file 1 content', 'file_2.txt' => 'file 2 content', - 'test_sub_dir' => array( + 'test_sub_dir' => [ 'file_1_1.txt' => 'sub dir file 1 content', 'file_1_2.txt' => 'sub dir file 2 content', - ), - ), - )); + ], + ], + ]); $basePath = $this->testFilePath; $dirName = $basePath . DIRECTORY_SEPARATOR . $dirName; - $expectedFiles = array( + $expectedFiles = [ $dirName . DIRECTORY_SEPARATOR . 'file_1.txt', $dirName . DIRECTORY_SEPARATOR . 'file_2.txt', $dirName . DIRECTORY_SEPARATOR . 'test_sub_dir' . DIRECTORY_SEPARATOR . 'file_1_1.txt', $dirName . DIRECTORY_SEPARATOR . 'test_sub_dir' . DIRECTORY_SEPARATOR . 'file_1_2.txt', - ); + ]; $foundFiles = FileHelper::findFiles($dirName); sort($expectedFiles); @@ -230,22 +230,22 @@ class FileHelperTest extends TestCase { $dirName = 'test_dir'; $passedFileName = 'passed.txt'; - $this->createFileStructure(array( - $dirName => array( + $this->createFileStructure([ + $dirName => [ $passedFileName => 'passed file content', 'declined.txt' => 'declined file content', - ), - )); + ], + ]); $basePath = $this->testFilePath; $dirName = $basePath . DIRECTORY_SEPARATOR . $dirName; - $options = array( + $options = [ 'filter' => function ($path) use ($passedFileName) { return $passedFileName == basename($path); } - ); + ]; $foundFiles = FileHelper::findFiles($dirName, $options); - $this->assertEquals(array($dirName . DIRECTORY_SEPARATOR . $passedFileName), $foundFiles); + $this->assertEquals([$dirName . DIRECTORY_SEPARATOR . $passedFileName], $foundFiles); } /** @@ -256,20 +256,20 @@ class FileHelperTest extends TestCase $dirName = 'test_dir'; $fileName = 'test_file.txt'; $excludeFileName = 'exclude_file.txt'; - $this->createFileStructure(array( - $dirName => array( + $this->createFileStructure([ + $dirName => [ $fileName => 'file content', $excludeFileName => 'exclude file content', - ), - )); + ], + ]); $basePath = $this->testFilePath; $dirName = $basePath . DIRECTORY_SEPARATOR . $dirName; - $options = array( - 'except' => array($excludeFileName), - ); + $options = [ + 'except' => [$excludeFileName], + ]; $foundFiles = FileHelper::findFiles($dirName, $options); - $this->assertEquals(array($dirName . DIRECTORY_SEPARATOR . $fileName), $foundFiles); + $this->assertEquals([$dirName . DIRECTORY_SEPARATOR . $fileName], $foundFiles); } public function testCreateDirectory() @@ -277,17 +277,17 @@ class FileHelperTest extends TestCase $basePath = $this->testFilePath; $dirName = $basePath . DIRECTORY_SEPARATOR . 'test_dir_level_1' . DIRECTORY_SEPARATOR . 'test_dir_level_2'; $this->assertTrue(FileHelper::createDirectory($dirName), 'FileHelper::createDirectory should return true if directory was created!'); - $this->assertTrue(file_exists($dirName), 'Unable to create directory recursively!'); + $this->assertFileExists($dirName, 'Unable to create directory recursively!'); $this->assertTrue(FileHelper::createDirectory($dirName), 'FileHelper::createDirectory should return true for already existing directories!'); } public function testGetMimeTypeByExtension() { $magicFile = $this->testFilePath . DIRECTORY_SEPARATOR . 'mime_type.php'; - $mimeTypeMap = array( + $mimeTypeMap = [ 'txa' => 'application/json', 'txb' => 'another/mime', - ); + ]; $magicFileContent = '<?php return ' . var_export($mimeTypeMap, true) . ';'; file_put_contents($magicFile, $magicFileContent); diff --git a/tests/unit/framework/helpers/HtmlTest.php b/tests/unit/framework/helpers/HtmlTest.php index 88aa33a..7781958 100644 --- a/tests/unit/framework/helpers/HtmlTest.php +++ b/tests/unit/framework/helpers/HtmlTest.php @@ -14,18 +14,18 @@ class HtmlTest extends TestCase protected function setUp() { parent::setUp(); - $this->mockApplication(array( - 'components' => array( - 'request' => array( + $this->mockApplication([ + 'components' => [ + 'request' => [ 'class' => 'yii\web\Request', 'url' => '/test', 'enableCsrfValidation' => false, - ), - 'response' => array( + ], + 'response' => [ 'class' => 'yii\web\Response', - ), - ), - )); + ], + ], + ]); } public function assertEqualsWithoutLE($expected, $actual) @@ -51,14 +51,14 @@ class HtmlTest extends TestCase $this->assertEquals('<br>', Html::tag('br')); $this->assertEquals('<span></span>', Html::tag('span')); $this->assertEquals('<div>content</div>', Html::tag('div', 'content')); - $this->assertEquals('<input type="text" name="test" value="<>">', Html::tag('input', '', array('type' => 'text', 'name' => 'test', 'value' => '<>'))); - $this->assertEquals('<span disabled></span>', Html::tag('span', '', array('disabled' => true))); + $this->assertEquals('<input type="text" name="test" value="<>">', Html::tag('input', '', ['type' => 'text', 'name' => 'test', 'value' => '<>'])); + $this->assertEquals('<span disabled></span>', Html::tag('span', '', ['disabled' => true])); } public function testBeginTag() { $this->assertEquals('<br>', Html::beginTag('br')); - $this->assertEquals('<span id="test" class="title">', Html::beginTag('span', array('id' => 'test', 'class' => 'title'))); + $this->assertEquals('<span id="test" class="title">', Html::beginTag('span', ['id' => 'test', 'class' => 'title'])); } public function testEndTag() @@ -71,14 +71,14 @@ class HtmlTest extends TestCase { $content = 'a <>'; $this->assertEquals("<style>$content</style>", Html::style($content)); - $this->assertEquals("<style type=\"text/less\">$content</style>", Html::style($content, array('type' => 'text/less'))); + $this->assertEquals("<style type=\"text/less\">$content</style>", Html::style($content, ['type' => 'text/less'])); } public function testScript() { $content = 'a <>'; $this->assertEquals("<script>$content</script>", Html::script($content)); - $this->assertEquals("<script type=\"text/js\">$content</script>", Html::script($content, array('type' => 'text/js'))); + $this->assertEquals("<script type=\"text/js\">$content</script>", Html::script($content, ['type' => 'text/js'])); } public function testCssFile() @@ -97,10 +97,10 @@ class HtmlTest extends TestCase { $this->assertEquals('<form action="/test" method="post">', Html::beginForm()); $this->assertEquals('<form action="/example" method="get">', Html::beginForm('/example', 'get')); - $hiddens = array( + $hiddens = [ '<input type="hidden" name="id" value="1">', '<input type="hidden" name="title" value="<">', - ); + ]; $this->assertEquals('<form action="/example" method="get">' . "\n" . implode("\n", $hiddens), Html::beginForm('/example?id=1&title=%3C', 'get')); } @@ -126,127 +126,127 @@ class HtmlTest extends TestCase { $this->assertEquals('<img src="/example" alt="">', Html::img('/example')); $this->assertEquals('<img src="/test" alt="">', Html::img('')); - $this->assertEquals('<img src="/example" width="10" alt="something">', Html::img('/example', array('alt' => 'something', 'width' => 10))); + $this->assertEquals('<img src="/example" width="10" alt="something">', Html::img('/example', ['alt' => 'something', 'width' => 10])); } public function testLabel() { $this->assertEquals('<label>something<></label>', Html::label('something<>')); $this->assertEquals('<label for="a">something<></label>', Html::label('something<>', 'a')); - $this->assertEquals('<label class="test" for="a">something<></label>', Html::label('something<>', 'a', array('class' => 'test'))); + $this->assertEquals('<label class="test" for="a">something<></label>', Html::label('something<>', 'a', ['class' => 'test'])); } public function testButton() { $this->assertEquals('<button>Button</button>', Html::button()); - $this->assertEquals('<button name="test" value="value">content<></button>', Html::button('content<>', array('name' => 'test', 'value' => 'value'))); - $this->assertEquals('<button type="submit" class="t" name="test" value="value">content<></button>', Html::button('content<>', array('type' => 'submit', 'name' => 'test', 'value' => 'value', 'class' => "t"))); + $this->assertEquals('<button name="test" value="value">content<></button>', Html::button('content<>', ['name' => 'test', 'value' => 'value'])); + $this->assertEquals('<button type="submit" class="t" name="test" value="value">content<></button>', Html::button('content<>', ['type' => 'submit', 'name' => 'test', 'value' => 'value', 'class' => "t"])); } public function testSubmitButton() { $this->assertEquals('<button type="submit">Submit</button>', Html::submitButton()); - $this->assertEquals('<button type="submit" class="t" name="test" value="value">content<></button>', Html::submitButton('content<>', array('name' => 'test', 'value' => 'value', 'class' => 't'))); + $this->assertEquals('<button type="submit" class="t" name="test" value="value">content<></button>', Html::submitButton('content<>', ['name' => 'test', 'value' => 'value', 'class' => 't'])); } public function testResetButton() { $this->assertEquals('<button type="reset">Reset</button>', Html::resetButton()); - $this->assertEquals('<button type="reset" class="t" name="test" value="value">content<></button>', Html::resetButton('content<>', array('name' => 'test', 'value' => 'value', 'class' => 't'))); + $this->assertEquals('<button type="reset" class="t" name="test" value="value">content<></button>', Html::resetButton('content<>', ['name' => 'test', 'value' => 'value', 'class' => 't'])); } public function testInput() { $this->assertEquals('<input type="text">', Html::input('text')); - $this->assertEquals('<input type="text" class="t" name="test" value="value">', Html::input('text', 'test', 'value', array('class' => 't'))); + $this->assertEquals('<input type="text" class="t" name="test" value="value">', Html::input('text', 'test', 'value', ['class' => 't'])); } public function testButtonInput() { $this->assertEquals('<input type="button" value="Button">', Html::buttonInput()); - $this->assertEquals('<input type="button" class="a" name="test" value="text">', Html::buttonInput('text', array('name' => 'test', 'class' => 'a'))); + $this->assertEquals('<input type="button" class="a" name="test" value="text">', Html::buttonInput('text', ['name' => 'test', 'class' => 'a'])); } public function testSubmitInput() { $this->assertEquals('<input type="submit" value="Submit">', Html::submitInput()); - $this->assertEquals('<input type="submit" class="a" name="test" value="text">', Html::submitInput('text', array('name' => 'test', 'class' => 'a'))); + $this->assertEquals('<input type="submit" class="a" name="test" value="text">', Html::submitInput('text', ['name' => 'test', 'class' => 'a'])); } public function testResetInput() { $this->assertEquals('<input type="reset" value="Reset">', Html::resetInput()); - $this->assertEquals('<input type="reset" class="a" name="test" value="text">', Html::resetInput('text', array('name' => 'test', 'class' => 'a'))); + $this->assertEquals('<input type="reset" class="a" name="test" value="text">', Html::resetInput('text', ['name' => 'test', 'class' => 'a'])); } public function testTextInput() { $this->assertEquals('<input type="text" name="test">', Html::textInput('test')); - $this->assertEquals('<input type="text" class="t" name="test" value="value">', Html::textInput('test', 'value', array('class' => 't'))); + $this->assertEquals('<input type="text" class="t" name="test" value="value">', Html::textInput('test', 'value', ['class' => 't'])); } public function testHiddenInput() { $this->assertEquals('<input type="hidden" name="test">', Html::hiddenInput('test')); - $this->assertEquals('<input type="hidden" class="t" name="test" value="value">', Html::hiddenInput('test', 'value', array('class' => 't'))); + $this->assertEquals('<input type="hidden" class="t" name="test" value="value">', Html::hiddenInput('test', 'value', ['class' => 't'])); } public function testPasswordInput() { $this->assertEquals('<input type="password" name="test">', Html::passwordInput('test')); - $this->assertEquals('<input type="password" class="t" name="test" value="value">', Html::passwordInput('test', 'value', array('class' => 't'))); + $this->assertEquals('<input type="password" class="t" name="test" value="value">', Html::passwordInput('test', 'value', ['class' => 't'])); } public function testFileInput() { $this->assertEquals('<input type="file" name="test">', Html::fileInput('test')); - $this->assertEquals('<input type="file" class="t" name="test" value="value">', Html::fileInput('test', 'value', array('class' => 't'))); + $this->assertEquals('<input type="file" class="t" name="test" value="value">', Html::fileInput('test', 'value', ['class' => 't'])); } public function testTextarea() { $this->assertEquals('<textarea name="test"></textarea>', Html::textarea('test')); - $this->assertEquals('<textarea class="t" name="test">value<></textarea>', Html::textarea('test', 'value<>', array('class' => 't'))); + $this->assertEquals('<textarea class="t" name="test">value<></textarea>', Html::textarea('test', 'value<>', ['class' => 't'])); } public function testRadio() { $this->assertEquals('<input type="radio" name="test" value="1">', Html::radio('test')); - $this->assertEquals('<input type="radio" class="a" name="test" checked>', Html::radio('test', true, array('class' => 'a', 'value' => null))); - $this->assertEquals('<input type="hidden" name="test" value="0"><input type="radio" class="a" name="test" value="2" checked>', Html::radio('test', true, array('class' => 'a' , 'uncheck' => '0', 'value' => 2))); + $this->assertEquals('<input type="radio" class="a" name="test" checked>', Html::radio('test', true, ['class' => 'a', 'value' => null])); + $this->assertEquals('<input type="hidden" name="test" value="0"><input type="radio" class="a" name="test" value="2" checked>', Html::radio('test', true, ['class' => 'a' , 'uncheck' => '0', 'value' => 2])); - $this->assertEquals('<div class="radio"><label class="bbb"><input type="radio" class="a" name="test" checked> ccc</label></div>', Html::radio('test', true, array( + $this->assertEquals('<div class="radio"><label class="bbb"><input type="radio" class="a" name="test" checked> ccc</label></div>', Html::radio('test', true, [ 'class' => 'a', 'value' => null, 'label' => 'ccc', - 'labelOptions' => array('class' =>'bbb'), - ))); - $this->assertEquals('<input type="hidden" name="test" value="0"><div class="radio"><label><input type="radio" class="a" name="test" value="2" checked> ccc</label></div>', Html::radio('test', true, array( + 'labelOptions' => ['class' =>'bbb'], + ])); + $this->assertEquals('<input type="hidden" name="test" value="0"><div class="radio"><label><input type="radio" class="a" name="test" value="2" checked> ccc</label></div>', Html::radio('test', true, [ 'class' => 'a', 'uncheck' => '0', 'label' => 'ccc', 'value' => 2, - ))); + ])); } public function testCheckbox() { $this->assertEquals('<input type="checkbox" name="test" value="1">', Html::checkbox('test')); - $this->assertEquals('<input type="checkbox" class="a" name="test" checked>', Html::checkbox('test', true, array('class' => 'a', 'value' => null))); - $this->assertEquals('<input type="hidden" name="test" value="0"><input type="checkbox" class="a" name="test" value="2" checked>', Html::checkbox('test', true, array('class' => 'a', 'uncheck' => '0', 'value' => 2))); + $this->assertEquals('<input type="checkbox" class="a" name="test" checked>', Html::checkbox('test', true, ['class' => 'a', 'value' => null])); + $this->assertEquals('<input type="hidden" name="test" value="0"><input type="checkbox" class="a" name="test" value="2" checked>', Html::checkbox('test', true, ['class' => 'a', 'uncheck' => '0', 'value' => 2])); - $this->assertEquals('<div class="checkbox"><label class="bbb"><input type="checkbox" class="a" name="test" checked> ccc</label></div>', Html::checkbox('test', true, array( + $this->assertEquals('<div class="checkbox"><label class="bbb"><input type="checkbox" class="a" name="test" checked> ccc</label></div>', Html::checkbox('test', true, [ 'class' => 'a', 'value' => null, 'label' => 'ccc', - 'labelOptions' => array('class' =>'bbb'), - ))); - $this->assertEquals('<input type="hidden" name="test" value="0"><div class="checkbox"><label><input type="checkbox" class="a" name="test" value="2" checked> ccc</label></div>', Html::checkbox('test', true, array( + 'labelOptions' => ['class' =>'bbb'], + ])); + $this->assertEquals('<input type="hidden" name="test" value="0"><div class="checkbox"><label><input type="checkbox" class="a" name="test" value="2" checked> ccc</label></div>', Html::checkbox('test', true, [ 'class' => 'a', 'uncheck' => '0', 'label' => 'ccc', 'value' => 2, - ))); + ])); } public function testDropDownList() @@ -287,7 +287,7 @@ EOD; <option value="value2">text2</option> </select> EOD; - $this->assertEqualsWithoutLE($expected, Html::listBox('test', null, $this->getDataItems(), array('size' => 5))); + $this->assertEqualsWithoutLE($expected, Html::listBox('test', null, $this->getDataItems(), ['size' => 5])); $expected = <<<EOD <select name="test" size="4"> <option value="value1<>">text1<></option> @@ -308,20 +308,20 @@ EOD; <option value="value2" selected>text2</option> </select> EOD; - $this->assertEqualsWithoutLE($expected, Html::listBox('test', array('value1', 'value2'), $this->getDataItems())); + $this->assertEqualsWithoutLE($expected, Html::listBox('test', ['value1', 'value2'], $this->getDataItems())); $expected = <<<EOD <select name="test[]" multiple size="4"> </select> EOD; - $this->assertEqualsWithoutLE($expected, Html::listBox('test', null, array(), array('multiple' => true))); + $this->assertEqualsWithoutLE($expected, Html::listBox('test', null, [], ['multiple' => true])); $expected = <<<EOD <input type="hidden" name="test" value="0"><select name="test" size="4"> </select> EOD; - $this->assertEqualsWithoutLE($expected, Html::listBox('test', '', array(), array('unselect' => '0'))); + $this->assertEqualsWithoutLE($expected, Html::listBox('test', '', [], ['unselect' => '0'])); } public function testCheckboxList() @@ -332,32 +332,32 @@ EOD; <div><div class="checkbox"><label><input type="checkbox" name="test[]" value="value1"> text1</label></div> <div class="checkbox"><label><input type="checkbox" name="test[]" value="value2" checked> text2</label></div></div> EOD; - $this->assertEqualsWithoutLE($expected, Html::checkboxList('test', array('value2'), $this->getDataItems())); + $this->assertEqualsWithoutLE($expected, Html::checkboxList('test', ['value2'], $this->getDataItems())); $expected = <<<EOD <div><div class="checkbox"><label><input type="checkbox" name="test[]" value="value1<>"> text1<></label></div> <div class="checkbox"><label><input type="checkbox" name="test[]" value="value 2"> text 2</label></div></div> EOD; - $this->assertEqualsWithoutLE($expected, Html::checkboxList('test', array('value2'), $this->getDataItems2())); + $this->assertEqualsWithoutLE($expected, Html::checkboxList('test', ['value2'], $this->getDataItems2())); $expected = <<<EOD <input type="hidden" name="test" value="0"><div><div class="checkbox"><label><input type="checkbox" name="test[]" value="value1"> text1</label></div><br> <div class="checkbox"><label><input type="checkbox" name="test[]" value="value2" checked> text2</label></div></div> EOD; - $this->assertEqualsWithoutLE($expected, Html::checkboxList('test', array('value2'), $this->getDataItems(), array( + $this->assertEqualsWithoutLE($expected, Html::checkboxList('test', ['value2'], $this->getDataItems(), [ 'separator' => "<br>\n", 'unselect' => '0', - ))); + ])); $expected = <<<EOD <div>0<label>text1 <input type="checkbox" name="test[]" value="value1"></label> 1<label>text2 <input type="checkbox" name="test[]" value="value2" checked></label></div> EOD; - $this->assertEqualsWithoutLE($expected, Html::checkboxList('test', array('value2'), $this->getDataItems(), array( + $this->assertEqualsWithoutLE($expected, Html::checkboxList('test', ['value2'], $this->getDataItems(), [ 'item' => function ($index, $label, $name, $checked, $value) { - return $index . Html::label($label . ' ' . Html::checkbox($name, $checked, array('value' => $value))); + return $index . Html::label($label . ' ' . Html::checkbox($name, $checked, ['value' => $value])); } - ))); + ])); } public function testRadioList() @@ -368,39 +368,39 @@ EOD; <div><div class="radio"><label><input type="radio" name="test" value="value1"> text1</label></div> <div class="radio"><label><input type="radio" name="test" value="value2" checked> text2</label></div></div> EOD; - $this->assertEqualsWithoutLE($expected, Html::radioList('test', array('value2'), $this->getDataItems())); + $this->assertEqualsWithoutLE($expected, Html::radioList('test', ['value2'], $this->getDataItems())); $expected = <<<EOD <div><div class="radio"><label><input type="radio" name="test" value="value1<>"> text1<></label></div> <div class="radio"><label><input type="radio" name="test" value="value 2"> text 2</label></div></div> EOD; - $this->assertEqualsWithoutLE($expected, Html::radioList('test', array('value2'), $this->getDataItems2())); + $this->assertEqualsWithoutLE($expected, Html::radioList('test', ['value2'], $this->getDataItems2())); $expected = <<<EOD <input type="hidden" name="test" value="0"><div><div class="radio"><label><input type="radio" name="test" value="value1"> text1</label></div><br> <div class="radio"><label><input type="radio" name="test" value="value2" checked> text2</label></div></div> EOD; - $this->assertEqualsWithoutLE($expected, Html::radioList('test', array('value2'), $this->getDataItems(), array( + $this->assertEqualsWithoutLE($expected, Html::radioList('test', ['value2'], $this->getDataItems(), [ 'separator' => "<br>\n", 'unselect' => '0', - ))); + ])); $expected = <<<EOD <div>0<label>text1 <input type="radio" name="test" value="value1"></label> 1<label>text2 <input type="radio" name="test" value="value2" checked></label></div> EOD; - $this->assertEqualsWithoutLE($expected, Html::radioList('test', array('value2'), $this->getDataItems(), array( + $this->assertEqualsWithoutLE($expected, Html::radioList('test', ['value2'], $this->getDataItems(), [ 'item' => function ($index, $label, $name, $checked, $value) { - return $index . Html::label($label . ' ' . Html::radio($name, $checked, array('value' => $value))); + return $index . Html::label($label . ' ' . Html::radio($name, $checked, ['value' => $value])); } - ))); + ])); } public function testUl() { - $data = array( + $data = [ 1, 'abc', '<>', - ); + ]; $expected = <<<EOD <ul> <li>1</li> @@ -416,19 +416,19 @@ EOD; <li class="item-2"><></li> </ul> EOD; - $this->assertEqualsWithoutLE($expected, Html::ul($data, array( + $this->assertEqualsWithoutLE($expected, Html::ul($data, [ 'class' => 'test', 'item' => function ($item, $index) { return "<li class=\"item-$index\">$item</li>"; } - ))); + ])); } public function testOl() { - $data = array( + $data = [ 1, 'abc', '<>', - ); + ]; $expected = <<<EOD <ol> <li class="ti">1</li> @@ -436,9 +436,9 @@ EOD; <li class="ti"><></li> </ol> EOD; - $this->assertEqualsWithoutLE($expected, Html::ol($data, array( - 'itemOptions' => array('class' => 'ti'), - ))); + $this->assertEqualsWithoutLE($expected, Html::ol($data, [ + 'itemOptions' => ['class' => 'ti'], + ])); $expected = <<<EOD <ol class="test"> <li class="item-0">1</li> @@ -446,28 +446,28 @@ EOD; <li class="item-2"><></li> </ol> EOD; - $this->assertEqualsWithoutLE($expected, Html::ol($data, array( + $this->assertEqualsWithoutLE($expected, Html::ol($data, [ 'class' => 'test', 'item' => function ($item, $index) { return "<li class=\"item-$index\">$item</li>"; } - ))); + ])); } public function testRenderOptions() { - $data = array( + $data = [ 'value1' => 'label1', - 'group1' => array( + 'group1' => [ 'value11' => 'label11', - 'group11' => array( + 'group11' => [ 'value111' => 'label111', - ), - 'group12' => array(), - ), + ], + 'group12' => [], + ], 'value2' => 'label2', - 'group2' => array(), - ); + 'group2' => [], + ]; $expected = <<<EOD <option value="">please select<></option> <option value="value1" selected>label1</option> @@ -485,70 +485,70 @@ EOD; </optgroup> EOD; - $attributes = array( + $attributes = [ 'prompt' => 'please select<>', - 'options' => array( - 'value111' => array('class' => 'option'), - ), - 'groups' => array( - 'group12' => array('class' => 'group'), - ), - ); - $this->assertEqualsWithoutLE($expected, Html::renderSelectOptions(array('value111', 'value1'), $data, $attributes)); + 'options' => [ + 'value111' => ['class' => 'option'], + ], + 'groups' => [ + 'group12' => ['class' => 'group'], + ], + ]; + $this->assertEqualsWithoutLE($expected, Html::renderSelectOptions(['value111', 'value1'], $data, $attributes)); } public function testRenderAttributes() { - $this->assertEquals('', Html::renderTagAttributes(array())); - $this->assertEquals(' name="test" value="1<>"', Html::renderTagAttributes(array('name' => 'test', 'empty' => null, 'value' => '1<>'))); - $this->assertEquals(' checked disabled', Html::renderTagAttributes(array('checked' => true, 'disabled' => true, 'hidden' => false))); + $this->assertEquals('', Html::renderTagAttributes([])); + $this->assertEquals(' name="test" value="1<>"', Html::renderTagAttributes(['name' => 'test', 'empty' => null, 'value' => '1<>'])); + $this->assertEquals(' checked disabled', Html::renderTagAttributes(['checked' => true, 'disabled' => true, 'hidden' => false])); } public function testAddCssClass() { - $options = array(); + $options = []; Html::addCssClass($options, 'test'); - $this->assertEquals(array('class' => 'test'), $options); + $this->assertEquals(['class' => 'test'], $options); Html::addCssClass($options, 'test'); - $this->assertEquals(array('class' => 'test'), $options); + $this->assertEquals(['class' => 'test'], $options); Html::addCssClass($options, 'test2'); - $this->assertEquals(array('class' => 'test test2'), $options); + $this->assertEquals(['class' => 'test test2'], $options); Html::addCssClass($options, 'test'); - $this->assertEquals(array('class' => 'test test2'), $options); + $this->assertEquals(['class' => 'test test2'], $options); Html::addCssClass($options, 'test2'); - $this->assertEquals(array('class' => 'test test2'), $options); + $this->assertEquals(['class' => 'test test2'], $options); Html::addCssClass($options, 'test3'); - $this->assertEquals(array('class' => 'test test2 test3'), $options); + $this->assertEquals(['class' => 'test test2 test3'], $options); Html::addCssClass($options, 'test2'); - $this->assertEquals(array('class' => 'test test2 test3'), $options); + $this->assertEquals(['class' => 'test test2 test3'], $options); } public function testRemoveCssClass() { - $options = array('class' => 'test test2 test3'); + $options = ['class' => 'test test2 test3']; Html::removeCssClass($options, 'test2'); - $this->assertEquals(array('class' => 'test test3'), $options); + $this->assertEquals(['class' => 'test test3'], $options); Html::removeCssClass($options, 'test2'); - $this->assertEquals(array('class' => 'test test3'), $options); + $this->assertEquals(['class' => 'test test3'], $options); Html::removeCssClass($options, 'test'); - $this->assertEquals(array('class' => 'test3'), $options); + $this->assertEquals(['class' => 'test3'], $options); Html::removeCssClass($options, 'test3'); - $this->assertEquals(array(), $options); + $this->assertEquals([], $options); } protected function getDataItems() { - return array( + return [ 'value1' => 'text1', 'value2' => 'text2', - ); + ]; } protected function getDataItems2() { - return array( + return [ 'value1<>' => 'text1<>', 'value 2' => 'text 2', - ); + ]; } } diff --git a/tests/unit/framework/helpers/InflectorTest.php b/tests/unit/framework/helpers/InflectorTest.php index de7fe01..2cd3c9f 100644 --- a/tests/unit/framework/helpers/InflectorTest.php +++ b/tests/unit/framework/helpers/InflectorTest.php @@ -13,7 +13,7 @@ class InflectorTest extends TestCase { public function testPluralize() { - $testData = array( + $testData = [ 'move' => 'moves', 'foot' => 'feet', 'child' => 'children', @@ -30,7 +30,7 @@ class InflectorTest extends TestCase 'bus' => 'buses', 'test' => 'tests', 'car' => 'cars', - ); + ]; foreach ($testData as $testIn => $testOut) { $this->assertEquals($testOut, Inflector::pluralize($testIn)); @@ -40,7 +40,7 @@ class InflectorTest extends TestCase public function testSingularize() { - $testData = array( + $testData = [ 'moves' => 'move', 'feet' => 'foot', 'children' => 'child', @@ -57,7 +57,7 @@ class InflectorTest extends TestCase 'buses' => 'bus', 'tests' => 'test', 'cars' => 'car', - ); + ]; foreach ($testData as $testIn => $testOut) { $this->assertEquals($testOut, Inflector::singularize($testIn)); $this->assertEquals(ucfirst($testOut), ucfirst(Inflector::singularize($testIn))); diff --git a/tests/unit/framework/helpers/JsonTest.php b/tests/unit/framework/helpers/JsonTest.php index df2ca5f..20da347 100644 --- a/tests/unit/framework/helpers/JsonTest.php +++ b/tests/unit/framework/helpers/JsonTest.php @@ -4,7 +4,7 @@ namespace yiiunit\framework\helpers; use yii\helpers\Json; -use yii\test\TestCase; +use yiiunit\TestCase; use yii\web\JsExpression; /** @@ -19,9 +19,9 @@ class JsonTest extends TestCase $this->assertSame('"1"', Json::encode($data)); // simple array encoding - $data = array(1, 2); + $data = [1, 2]; $this->assertSame('[1,2]', Json::encode($data)); - $data = array('a' => 1, 'b' => 2); + $data = ['a' => 1, 'b' => 2]; $this->assertSame('{"a":1,"b":2}', Json::encode($data)); // simple object encoding @@ -38,13 +38,17 @@ class JsonTest extends TestCase // complex data $expression1 = 'function (a) {}'; $expression2 = 'function (b) {}'; - $data = array( - 'a' => array( + $data = [ + 'a' => [ 1, new JsExpression($expression1) - ), + ], 'b' => new JsExpression($expression2), - ); + ]; $this->assertSame("{\"a\":[1,$expression1],\"b\":$expression2}", Json::encode($data)); + + // https://github.com/yiisoft/yii2/issues/957 + $data = (object)null; + $this->assertSame('{}', Json::encode($data)); } public function testDecode() @@ -55,7 +59,7 @@ class JsonTest extends TestCase // array decoding $json = '{"a":1,"b":2}'; - $this->assertSame(array('a' => 1, 'b' => 2), Json::decode($json)); + $this->assertSame(['a' => 1, 'b' => 2], Json::decode($json)); // exception $json = '{"a":1,"b":2'; diff --git a/tests/unit/framework/helpers/StringHelperTest.php b/tests/unit/framework/helpers/StringHelperTest.php index 8af731d..2f1fb06 100644 --- a/tests/unit/framework/helpers/StringHelperTest.php +++ b/tests/unit/framework/helpers/StringHelperTest.php @@ -1,8 +1,8 @@ <?php namespace yiiunit\framework\helpers; -use \yii\helpers\StringHelper as StringHelper; -use yii\test\TestCase; +use yii\helpers\StringHelper; +use yiiunit\TestCase; /** * StringHelperTest diff --git a/tests/unit/framework/helpers/VarDumperTest.php b/tests/unit/framework/helpers/VarDumperTest.php index d41a69d..11fe6d4 100644 --- a/tests/unit/framework/helpers/VarDumperTest.php +++ b/tests/unit/framework/helpers/VarDumperTest.php @@ -1,8 +1,8 @@ <?php namespace yiiunit\framework\helpers; -use \yii\helpers\VarDumper; -use yii\test\TestCase; +use yii\helpers\VarDumper; +use yiiunit\TestCase; /** * @group helpers diff --git a/tests/unit/framework/i18n/FallbackMessageFormatterTest.php b/tests/unit/framework/i18n/FallbackMessageFormatterTest.php new file mode 100644 index 0000000..bdc4e43 --- /dev/null +++ b/tests/unit/framework/i18n/FallbackMessageFormatterTest.php @@ -0,0 +1,171 @@ +<?php +/** + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yiiunit\framework\i18n; + +use yii\i18n\MessageFormatter; +use yiiunit\TestCase; + +/** + * @author Carsten Brandt <mail@cebe.cc> + * @since 2.0 + * @group i18n + */ +class FallbackMessageFormatterTest extends TestCase +{ + const N = 'n'; + const N_VALUE = 42; + const SUBJECT = 'сабж'; + const SUBJECT_VALUE = 'Answer to the Ultimate Question of Life, the Universe, and Everything'; + + public function patterns() + { + return [ + [ + '{'.self::SUBJECT.'} is {'.self::N.'}', // pattern + self::SUBJECT_VALUE.' is '.self::N_VALUE, // expected + [ // params + self::N => self::N_VALUE, + self::SUBJECT => self::SUBJECT_VALUE, + ] + ], + + [ + '{'.self::SUBJECT.'} is {'.self::N.', number}', // pattern + self::SUBJECT_VALUE.' is '.self::N_VALUE, // expected + [ // params + self::N => self::N_VALUE, + self::SUBJECT => self::SUBJECT_VALUE, + ] + ], + + [ + '{'.self::SUBJECT.'} is {'.self::N.', number, integer}', // pattern + self::SUBJECT_VALUE.' is '.self::N_VALUE, // expected + [ // params + self::N => self::N_VALUE, + self::SUBJECT => self::SUBJECT_VALUE, + ] + ], + + // This one was provided by Aura.Intl. Thanks! + [<<<_MSG_ +{gender_of_host, select, + female {{num_guests, plural, offset:1 + =0 {{host} does not give a party.} + =1 {{host} invites {guest} to her party.} + =2 {{host} invites {guest} and one other person to her party.} + other {{host} invites {guest} and # other people to her party.}}} + male {{num_guests, plural, offset:1 + =0 {{host} does not give a party.} + =1 {{host} invites {guest} to his party.} + =2 {{host} invites {guest} and one other person to his party.} + other {{host} invites {guest} and # other people to his party.}}} + other {{num_guests, plural, offset:1 + =0 {{host} does not give a party.} + =1 {{host} invites {guest} to their party.} + =2 {{host} invites {guest} and one other person to their party.} + other {{host} invites {guest} and # other people to their party.}}}} +_MSG_ + , + 'ralph invites beep and 3 other people to his party.', + [ + 'gender_of_host' => 'male', + 'num_guests' => 4, + 'host' => 'ralph', + 'guest' => 'beep' + ] + ], + + [ + '{name} is {gender} and {gender, select, female{she} male{he} other{it}} loves Yii!', + 'Alexander is male and he loves Yii!', + [ + 'name' => 'Alexander', + 'gender' => 'male', + ], + ], + + // verify pattern in select does not get replaced + [ + '{name} is {gender} and {gender, select, female{she} male{he} other{it}} loves Yii!', + 'Alexander is male and he loves Yii!', + [ + 'name' => 'Alexander', + 'gender' => 'male', + // following should not be replaced + 'he' => 'wtf', + 'she' => 'wtf', + 'it' => 'wtf', + ] + ], + + // verify pattern in select message gets replaced + [ + '{name} is {gender} and {gender, select, female{she} male{{he}} other{it}} loves Yii!', + 'Alexander is male and wtf loves Yii!', + [ + 'name' => 'Alexander', + 'gender' => 'male', + 'he' => 'wtf', + 'she' => 'wtf', + ], + ], + + // some parser specific verifications + [ + '{gender} and {gender, select, female{she} male{{he}} other{it}} loves {nr} is {gender}!', + 'male and wtf loves 42 is male!', + [ + 'nr' => 42, + 'gender' => 'male', + 'he' => 'wtf', + 'she' => 'wtf', + ], + ], + ]; + } + + /** + * @dataProvider patterns + */ + public function testNamedArguments($pattern, $expected, $args) + { + $formatter = new FallbackMessageFormatter(); + $result = $formatter->fallbackFormat($pattern, $args, 'en-US'); + $this->assertEquals($expected, $result, $formatter->getErrorMessage()); + } + + public function testInsufficientArguments() + { + $expected = '{'.self::SUBJECT.'} is '.self::N_VALUE; + + $formatter = new FallbackMessageFormatter(); + $result = $formatter->fallbackFormat('{'.self::SUBJECT.'} is {'.self::N.'}', [ + self::N => self::N_VALUE, + ], 'en-US'); + + $this->assertEquals($expected, $result); + } + + public function testNoParams() + { + $pattern = '{'.self::SUBJECT.'} is '.self::N; + + $formatter = new FallbackMessageFormatter(); + $result = $formatter->fallbackFormat($pattern, [], 'en-US'); + $this->assertEquals($pattern, $result, $formatter->getErrorMessage()); + } +} + +class FallbackMessageFormatter extends MessageFormatter +{ + public function fallbackFormat($pattern, $args, $locale) + { + return parent::fallbackFormat($pattern, $args, $locale); + } +} diff --git a/tests/unit/framework/i18n/FormatterTest.php b/tests/unit/framework/i18n/FormatterTest.php index 6966853..ed5ab33 100644 --- a/tests/unit/framework/i18n/FormatterTest.php +++ b/tests/unit/framework/i18n/FormatterTest.php @@ -29,9 +29,7 @@ class FormatterTest extends TestCase $this->markTestSkipped('intl extension is required.'); } $this->mockApplication(); - $this->formatter = new Formatter(array( - 'locale' => 'en_US', - )); + $this->formatter = new Formatter(['locale' => 'en-US']); } protected function tearDown() diff --git a/tests/unit/framework/i18n/GettextMessageSourceTest.php b/tests/unit/framework/i18n/GettextMessageSourceTest.php index d039629..727fee9 100644 --- a/tests/unit/framework/i18n/GettextMessageSourceTest.php +++ b/tests/unit/framework/i18n/GettextMessageSourceTest.php @@ -2,7 +2,6 @@ namespace yiiunit\framework\i18n; -use yii\i18n\GettextMessageSource; use yiiunit\TestCase; /** @@ -12,6 +11,6 @@ class GettextMessageSourceTest extends TestCase { public function testLoadMessages() { - $this->markTestSkipped(); + $this->markTestIncomplete(); } } diff --git a/tests/unit/framework/i18n/GettextMoFileTest.php b/tests/unit/framework/i18n/GettextMoFileTest.php index 9b61145..373fa20 100644 --- a/tests/unit/framework/i18n/GettextMoFileTest.php +++ b/tests/unit/framework/i18n/GettextMoFileTest.php @@ -44,7 +44,7 @@ class GettextMoFileTest extends TestCase { // initial data $s = chr(4); - $messages = array( + $messages = [ 'Hello!' => 'Привет!', "context1{$s}Hello?" => 'Привет?', 'Hello!?' => '', @@ -53,7 +53,7 @@ class GettextMoFileTest extends TestCase "context2{$s}\nNew lines\n" => "\nПереносы строк\n", "context2{$s}\tTabs\t" => "\tТабы\t", "context2{$s}\rCarriage returns\r" => "\rВозвраты кареток\r", - ); + ]; // create temporary directory and dump messages $poFileDirectory = __DIR__ . '/../../runtime/i18n'; diff --git a/tests/unit/framework/i18n/GettextPoFileTest.php b/tests/unit/framework/i18n/GettextPoFileTest.php index 4165b81..29db141 100644 --- a/tests/unit/framework/i18n/GettextPoFileTest.php +++ b/tests/unit/framework/i18n/GettextPoFileTest.php @@ -44,7 +44,7 @@ class GettextPoFileTest extends TestCase { // initial data $s = chr(4); - $messages = array( + $messages = [ 'Hello!' => 'Привет!', "context1{$s}Hello?" => 'Привет?', 'Hello!?' => '', @@ -53,7 +53,7 @@ class GettextPoFileTest extends TestCase "context2{$s}\nNew lines\n" => "\nПереносы строк\n", "context2{$s}\tTabs\t" => "\tТабы\t", "context2{$s}\rCarriage returns\r" => "\rВозвраты кареток\r", - ); + ]; // create temporary directory and dump messages $poFileDirectory = __DIR__ . '/../../runtime/i18n'; diff --git a/tests/unit/framework/i18n/I18NTest.php b/tests/unit/framework/i18n/I18NTest.php new file mode 100644 index 0000000..aa2356b --- /dev/null +++ b/tests/unit/framework/i18n/I18NTest.php @@ -0,0 +1,88 @@ +<?php +/** + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yiiunit\framework\i18n; + +use yii\base\Model; +use yii\i18n\I18N; +use yii\i18n\PhpMessageSource; +use yiiunit\TestCase; + +/** + * @author Carsten Brandt <mail@cebe.cc> + * @since 2.0 + * @group i18n + */ +class I18NTest extends TestCase +{ + /** + * @var I18N + */ + public $i18n; + + protected function setUp() + { + parent::setUp(); + $this->mockApplication(); + $this->i18n = new I18N([ + 'translations' => [ + 'test' => new PhpMessageSource([ + 'basePath' => '@yiiunit/data/i18n/messages', + ]) + ] + ]); + } + + public function testTranslate() + { + $msg = 'The dog runs fast.'; + $this->assertEquals('The dog runs fast.', $this->i18n->translate('test', $msg, [], 'en-US')); + $this->assertEquals('Der Hund rennt schnell.', $this->i18n->translate('test', $msg, [], 'de-DE')); + } + + public function testTranslateParams() + { + $msg = 'His speed is about {n} km/h.'; + $params = ['n' => 42]; + $this->assertEquals('His speed is about 42 km/h.', $this->i18n->translate('test', $msg, $params, 'en-US')); + $this->assertEquals('Seine Geschwindigkeit beträgt 42 km/h.', $this->i18n->translate('test', $msg, $params, 'de-DE')); + } + + public function testTranslateParams2() + { + if (!extension_loaded("intl")) { + $this->markTestSkipped("intl not installed. Skipping."); + } + $msg = 'His name is {name} and his speed is about {n, number} km/h.'; + $params = [ + 'n' => 42, + 'name' => 'DA VINCI', // http://petrix.com/dognames/d.html + ]; + $this->assertEquals('His name is DA VINCI and his speed is about 42 km/h.', $this->i18n->translate('test', $msg, $params, 'en-US')); + $this->assertEquals('Er heißt DA VINCI und ist 42 km/h schnell.', $this->i18n->translate('test', $msg, $params, 'de-DE')); + } + + public function testSpecialParams() + { + $msg = 'His speed is about {0} km/h.'; + + $this->assertEquals('His speed is about 0 km/h.', $this->i18n->translate('test', $msg, 0, 'en-US')); + $this->assertEquals('His speed is about 42 km/h.', $this->i18n->translate('test', $msg, 42, 'en-US')); + $this->assertEquals('His speed is about {0} km/h.', $this->i18n->translate('test', $msg, null, 'en-US')); + $this->assertEquals('His speed is about {0} km/h.', $this->i18n->translate('test', $msg, [], 'en-US')); + + $msg = 'His name is {name} and he is {age} years old.'; + $model = new ParamModel(); + $this->assertEquals('His name is peer and he is 5 years old.', $this->i18n->translate('test', $msg, $model, 'en-US')); + } +} + +class ParamModel extends Model +{ + public $name = 'peer'; + public $age = 5; +} diff --git a/tests/unit/framework/i18n/MessageFormatterTest.php b/tests/unit/framework/i18n/MessageFormatterTest.php new file mode 100644 index 0000000..1d5d007 --- /dev/null +++ b/tests/unit/framework/i18n/MessageFormatterTest.php @@ -0,0 +1,337 @@ +<?php +/** + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yiiunit\framework\i18n; + +use yii\i18n\MessageFormatter; +use yiiunit\TestCase; + +/** + * @author Alexander Makarov <sam@rmcreative.ru> + * @since 2.0 + * @group i18n + */ +class MessageFormatterTest extends TestCase +{ + const N = 'n'; + const N_VALUE = 42; + const SUBJECT = 'сабж'; + const SUBJECT_VALUE = 'Answer to the Ultimate Question of Life, the Universe, and Everything'; + + public function patterns() + { + return [ + [ + '{'.self::SUBJECT.'} is {'.self::N.', number}', // pattern + self::SUBJECT_VALUE.' is '.self::N_VALUE, // expected + [ // params + self::N => self::N_VALUE, + self::SUBJECT => self::SUBJECT_VALUE, + ] + ], + + [ + '{'.self::SUBJECT.'} is {'.self::N.', number, integer}', // pattern + self::SUBJECT_VALUE.' is '.self::N_VALUE, // expected + [ // params + self::N => self::N_VALUE, + self::SUBJECT => self::SUBJECT_VALUE, + ] + ], + + // This one was provided by Aura.Intl. Thanks! + [<<<_MSG_ +{gender_of_host, select, + female {{num_guests, plural, offset:1 + =0 {{host} does not give a party.} + =1 {{host} invites {guest} to her party.} + =2 {{host} invites {guest} and one other person to her party.} + other {{host} invites {guest} and # other people to her party.}}} + male {{num_guests, plural, offset:1 + =0 {{host} does not give a party.} + =1 {{host} invites {guest} to his party.} + =2 {{host} invites {guest} and one other person to his party.} + other {{host} invites {guest} and # other people to his party.}}} + other {{num_guests, plural, offset:1 + =0 {{host} does not give a party.} + =1 {{host} invites {guest} to their party.} + =2 {{host} invites {guest} and one other person to their party.} + other {{host} invites {guest} and # other people to their party.}}}} +_MSG_ + , + 'ralph invites beep and 3 other people to his party.', + [ + 'gender_of_host' => 'male', + 'num_guests' => 4, + 'host' => 'ralph', + 'guest' => 'beep' + ], + defined('INTL_ICU_VERSION') && version_compare(INTL_ICU_VERSION, '4.8', '<'), + 'select format is available in ICU > 4.4 and plural format with =X selector is avilable since 4.8' + ], + + [ + '{name} is {gender} and {gender, select, female{she} male{he} other{it}} loves Yii!', + 'Alexander is male and he loves Yii!', + [ + 'name' => 'Alexander', + 'gender' => 'male', + ], + defined('INTL_ICU_VERSION') && version_compare(INTL_ICU_VERSION, '4.4.2', '<'), + 'select format is available in ICU > 4.4' + ], + + // verify pattern in select does not get replaced + [ + '{name} is {gender} and {gender, select, female{she} male{he} other{it}} loves Yii!', + 'Alexander is male and he loves Yii!', + [ + 'name' => 'Alexander', + 'gender' => 'male', + // following should not be replaced + 'he' => 'wtf', + 'she' => 'wtf', + 'it' => 'wtf', + ], + defined('INTL_ICU_VERSION') && version_compare(INTL_ICU_VERSION, '4.4.2', '<'), + 'select format is available in ICU > 4.4' + ], + + // verify pattern in select message gets replaced + [ + '{name} is {gender} and {gender, select, female{she} male{{he}} other{it}} loves Yii!', + 'Alexander is male and wtf loves Yii!', + [ + 'name' => 'Alexander', + 'gender' => 'male', + 'he' => 'wtf', + 'she' => 'wtf', + ], + defined('INTL_ICU_VERSION') && version_compare(INTL_ICU_VERSION, '4.8', '<'), + 'parameters in select format do not seem to work in ICU < 4.8' + ], + + // some parser specific verifications + [ + '{gender} and {gender, select, female{she} male{{he}} other{it}} loves {nr, number} is {gender}!', + 'male and wtf loves 42 is male!', + [ + 'nr' => 42, + 'gender' => 'male', + 'he' => 'wtf', + 'she' => 'wtf', + ], + defined('INTL_ICU_VERSION') && version_compare(INTL_ICU_VERSION, '4.4.2', '<'), + 'select format is available in ICU > 4.4' + ], + + // test ICU version compatibility + [ + 'Showing <b>{begin, number}-{end, number}</b> of <b>{totalCount, number}</b> {totalCount, plural, one{item} other{items}}.', + 'Showing <b>{begin, number}-{end, number}</b> of <b>{totalCount, number}</b> {totalCount, plural, one{item} other{items}}.', + [], + ], + [ + 'Showing <b>{begin, number}-{end, number}</b> of <b>{totalCount, number}</b> {totalCount, plural, one{item} other{items}}.', + 'Showing <b>1-10</b> of <b>12</b> items.', + [// A + 'begin' => 1, + 'end' => 10, + 'count' => 10, + 'totalCount' => 12, + 'page' => 1, + 'pageCount' => 2, + ] + ], + [ + 'Showing <b>{begin, number}-{end, number}</b> of <b>{totalCount, number}</b> {totalCount, plural, one{item} other{items}}.', + 'Showing <b>1-1</b> of <b>1</b> item.', + [// B + 'begin' => 1, + 'end' => 1, + 'count' => 1, + 'totalCount' => 1, + 'page' => 1, + 'pageCount' => 1, + ] + ], + [ + 'Showing <b>{begin, number}-{end, number}</b> of <b>{totalCount, number}</b> {totalCount, plural, one{item} other{items}}.', + 'Showing <b>0-0</b> of <b>0</b> items.', + [// C + 'begin' => 0, + 'end' => 0, + 'count' => 0, + 'totalCount' => 0, + 'page' => 1, + 'pageCount' => 1, + ] + ], + [ + 'Total <b>{count, number}</b> {count, plural, one{item} other{items}}.', + 'Total <b>{count, number}</b> {count, plural, one{item} other{items}}.', + [] + ], + [ + 'Total <b>{count, number}</b> {count, plural, one{item} other{items}}.', + 'Total <b>1</b> item.', + [ + 'count' => 1, + ] + ], + [ + 'Total <b>{count, number}</b> {count, plural, one{item} other{items}}.', + 'Total <b>1</b> item.', + [ + 'begin' => 5, + 'count' => 1, + 'end' => 10, + ] + ], + [ + '{0, plural, one {offer} other {offers}}', + '{0, plural, one {offer} other {offers}}', + [], + ], + [ + '{0, plural, one {offer} other {offers}}', + 'offers', + [0], + ], + [ + '{0, plural, one {offer} other {offers}}', + 'offer', + [1], + ], + [ + '{0, plural, one {offer} other {offers}}', + 'offers', + [13], + ], + ]; + } + + public function parsePatterns() + { + return [ + [ + self::SUBJECT_VALUE.' is {0, number}', // pattern + self::SUBJECT_VALUE.' is '.self::N_VALUE, // expected + [ // params + 0 => self::N_VALUE, + ] + ], + + [ + self::SUBJECT_VALUE.' is {'.self::N.', number}', // pattern + self::SUBJECT_VALUE.' is '.self::N_VALUE, // expected + [ // params + self::N => self::N_VALUE, + ] + ], + + [ + self::SUBJECT_VALUE.' is {'.self::N.', number, integer}', // pattern + self::SUBJECT_VALUE.' is '.self::N_VALUE, // expected + [ // params + self::N => self::N_VALUE, + ] + ], + + [ + "{0,number,integer} monkeys on {1,number,integer} trees make {2,number} monkeys per tree", + "4,560 monkeys on 123 trees make 37.073 monkeys per tree", + [ + 0 => 4560, + 1 => 123, + 2 => 37.073 + ], + 'en-US' + ], + + [ + "{0,number,integer} Affen auf {1,number,integer} Bäumen sind {2,number} Affen pro Baum", + "4.560 Affen auf 123 Bäumen sind 37,073 Affen pro Baum", + [ + 0 => 4560, + 1 => 123, + 2 => 37.073 + ], + 'de', + ], + + [ + "{monkeyCount,number,integer} monkeys on {trees,number,integer} trees make {monkeysPerTree,number} monkeys per tree", + "4,560 monkeys on 123 trees make 37.073 monkeys per tree", + [ + 'monkeyCount' => 4560, + 'trees' => 123, + 'monkeysPerTree' => 37.073 + ], + 'en-US' + ], + + [ + "{monkeyCount,number,integer} Affen auf {trees,number,integer} Bäumen sind {monkeysPerTree,number} Affen pro Baum", + "4.560 Affen auf 123 Bäumen sind 37,073 Affen pro Baum", + [ + 'monkeyCount' => 4560, + 'trees' => 123, + 'monkeysPerTree' => 37.073 + ], + 'de', + ], + ]; + } + + /** + * @dataProvider patterns + */ + public function testNamedArguments($pattern, $expected, $args, $skip = false, $skipMessage = '') + { + if ($skip) { + $this->markTestSkipped($skipMessage); + } + $formatter = new MessageFormatter(); + $result = $formatter->format($pattern, $args, 'en-US'); + $this->assertEquals($expected, $result, $formatter->getErrorMessage()); + } + + /** + * @dataProvider parsePatterns + */ + public function testParseNamedArguments($pattern, $expected, $args, $locale = 'en-US') + { + if (!extension_loaded("intl")) { + $this->markTestSkipped("intl not installed. Skipping."); + } + + $formatter = new MessageFormatter(); + $result = $formatter->parse($pattern, $expected, $locale); + $this->assertEquals($args, $result, $formatter->getErrorMessage() . ' Pattern: ' . $pattern); + } + + public function testInsufficientArguments() + { + $expected = '{'.self::SUBJECT.'} is '.self::N_VALUE; + + $formatter = new MessageFormatter(); + $result = $formatter->format('{'.self::SUBJECT.'} is {'.self::N.', number}', [ + self::N => self::N_VALUE, + ], 'en-US'); + + $this->assertEquals($expected, $result, $formatter->getErrorMessage()); + } + + public function testNoParams() + { + $pattern = '{'.self::SUBJECT.'} is '.self::N; + $formatter = new MessageFormatter(); + $result = $formatter->format($pattern, [], 'en-US'); + $this->assertEquals($pattern, $result, $formatter->getErrorMessage()); + } +} diff --git a/tests/unit/framework/log/LoggerTest.php b/tests/unit/framework/log/LoggerTest.php new file mode 100644 index 0000000..31a4c3b --- /dev/null +++ b/tests/unit/framework/log/LoggerTest.php @@ -0,0 +1,33 @@ +<?php +/** + * @author Carsten Brandt <mail@cebe.cc> + */ + +namespace yiiunit\framework\log; + + +use yii\debug\LogTarget; +use yii\log\FileTarget; +use yii\log\Logger; +use yiiunit\TestCase; + +class LoggerTest extends TestCase +{ + + public function testLog() + { + $logger = new Logger(); + + $logger->log('test1', Logger::LEVEL_INFO); + $this->assertEquals(1, count($logger->messages)); + $this->assertEquals('test1', $logger->messages[0][0]); + $this->assertEquals(Logger::LEVEL_INFO, $logger->messages[0][1]); + $this->assertEquals('application', $logger->messages[0][2]); + + $logger->log('test2', Logger::LEVEL_ERROR, 'category'); + $this->assertEquals(2, count($logger->messages)); + $this->assertEquals('test2', $logger->messages[1][0]); + $this->assertEquals(Logger::LEVEL_ERROR, $logger->messages[1][1]); + $this->assertEquals('category', $logger->messages[1][2]); + } +} \ No newline at end of file diff --git a/tests/unit/framework/log/TargetTest.php b/tests/unit/framework/log/TargetTest.php new file mode 100644 index 0000000..b4ceb4c --- /dev/null +++ b/tests/unit/framework/log/TargetTest.php @@ -0,0 +1,90 @@ +<?php +/** + * @author Carsten Brandt <mail@cebe.cc> + */ + +namespace yiiunit\framework\log; + + +use yii\debug\LogTarget; +use yii\log\FileTarget; +use yii\log\Logger; +use yii\log\Target; +use yiiunit\TestCase; + +class TargetTest extends TestCase +{ + public static $messages; + + public function filters() + { + return [ + [[], ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']], + + [['levels' => 0], ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']], + [ + ['levels' => Logger::LEVEL_INFO | Logger::LEVEL_WARNING | Logger::LEVEL_ERROR | Logger::LEVEL_TRACE], + ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'] + ], + [['levels' => ['error']], ['B', 'G', 'H']], + [['levels' => Logger::LEVEL_ERROR], ['B', 'G', 'H']], + [['levels' => ['error', 'warning']], ['B', 'C', 'G', 'H']], + [['levels' => Logger::LEVEL_ERROR | Logger::LEVEL_WARNING], ['B', 'C', 'G', 'H']], + + [['categories' => ['application']], ['A', 'B', 'C', 'D', 'E']], + [['categories' => ['application*']], ['A', 'B', 'C', 'D', 'E', 'F']], + [['categories' => ['application.*']], ['F']], + [['categories' => ['application.components']], []], + [['categories' => ['application.components.Test']], ['F']], + [['categories' => ['application.components.*']], ['F']], + [['categories' => ['application.*', 'yii.db.*']], ['F', 'G', 'H']], + [['categories' => ['application.*', 'yii.db.*'], 'except' => ['yii.db.Command.*']], ['F', 'G']], + + [['categories' => ['application', 'yii.db.*'], 'levels' => Logger::LEVEL_ERROR], ['B', 'G', 'H']], + [['categories' => ['application'], 'levels' => Logger::LEVEL_ERROR], ['B']], + [['categories' => ['application'], 'levels' => Logger::LEVEL_ERROR | Logger::LEVEL_WARNING], ['B', 'C']], + ]; + } + + /** + * @dataProvider filters + */ + public function testFilter($filter, $expected) + { + static::$messages = []; + + $logger = new Logger([ + 'targets' => [new TestTarget(array_merge($filter, ['logVars' => []]))], + 'flushInterval' => 1, + ]); + $logger->log('testA', Logger::LEVEL_INFO); + $logger->log('testB', Logger::LEVEL_ERROR); + $logger->log('testC', Logger::LEVEL_WARNING); + $logger->log('testD', Logger::LEVEL_TRACE); + $logger->log('testE', Logger::LEVEL_INFO, 'application'); + $logger->log('testF', Logger::LEVEL_INFO, 'application.components.Test'); + $logger->log('testG', Logger::LEVEL_ERROR, 'yii.db.Command'); + $logger->log('testH', Logger::LEVEL_ERROR, 'yii.db.Command.whatever'); + + $this->assertEquals(count($expected), count(static::$messages)); + $i = 0; + foreach($expected as $e) { + $this->assertEquals('test' . $e, static::$messages[$i++][0]); + } + } +} + +class TestTarget extends Target +{ + public $exportInterval = 1; + + /** + * Exports log [[messages]] to a specific destination. + * Child classes must implement this method. + */ + public function export() + { + TargetTest::$messages = array_merge(TargetTest::$messages, $this->messages); + $this->messages = []; + } +} \ No newline at end of file diff --git a/tests/unit/framework/mail/BaseMailerTest.php b/tests/unit/framework/mail/BaseMailerTest.php new file mode 100644 index 0000000..1c3ee22 --- /dev/null +++ b/tests/unit/framework/mail/BaseMailerTest.php @@ -0,0 +1,367 @@ +<?php + +namespace yiiunit\framework\mail; + +use Yii; +use yii\base\View; +use yii\mail\BaseMailer; +use yii\mail\BaseMessage; +use yii\helpers\FileHelper; +use yiiunit\TestCase; + +/** + * @group mail + */ +class BaseMailerTest extends TestCase +{ + public function setUp() + { + $this->mockApplication([ + 'components' => [ + 'mail' => $this->createTestMailComponent(), + ] + ]); + $filePath = $this->getTestFilePath(); + if (!file_exists($filePath)) { + FileHelper::createDirectory($filePath); + } + } + + public function tearDown() + { + $filePath = $this->getTestFilePath(); + if (file_exists($filePath)) { + FileHelper::removeDirectory($filePath); + } + } + + /** + * @return string test file path. + */ + protected function getTestFilePath() + { + return Yii::getAlias('@yiiunit/runtime') . DIRECTORY_SEPARATOR . basename(get_class($this)) . '_' . getmypid(); + } + + /** + * @return Mailer test email component instance. + */ + protected function createTestMailComponent() + { + $component = new Mailer(); + $component->viewPath = $this->getTestFilePath(); + return $component; + } + + /** + * @return Mailer mailer instance + */ + protected function getTestMailComponent() + { + return Yii::$app->getComponent('mail'); + } + + // Tests : + + public function testSetupView() + { + $mailer = new Mailer(); + + $view = new View(); + $mailer->setView($view); + $this->assertEquals($view, $mailer->getView(), 'Unable to setup view!'); + + $viewConfig = [ + 'params' => [ + 'param1' => 'value1', + 'param2' => 'value2', + ] + ]; + $mailer->setView($viewConfig); + $view = $mailer->getView(); + $this->assertTrue(is_object($view), 'Unable to setup view via config!'); + $this->assertEquals($viewConfig['params'], $view->params, 'Unable to configure view via config array!'); + } + + /** + * @depends testSetupView + */ + public function testGetDefaultView() + { + $mailer = new Mailer(); + $view = $mailer->getView(); + $this->assertTrue(is_object($view), 'Unable to get default view!'); + } + + public function testCreateMessage() + { + $mailer = new Mailer(); + $message = $mailer->compose(); + $this->assertTrue(is_object($message), 'Unable to create message instance!'); + $this->assertEquals($mailer->messageClass, get_class($message), 'Invalid message class!'); + } + + /** + * @depends testCreateMessage + */ + public function testDefaultMessageConfig() + { + $mailer = new Mailer(); + + $notPropertyConfig = [ + 'charset' => 'utf-16', + 'from' => 'from@domain.com', + 'to' => 'to@domain.com', + 'cc' => 'cc@domain.com', + 'bcc' => 'bcc@domain.com', + 'subject' => 'Test subject', + 'textBody' => 'Test text body', + 'htmlBody' => 'Test HTML body', + ]; + $propertyConfig = [ + 'id' => 'test-id', + 'encoding' => 'test-encoding', + ]; + $messageConfig = array_merge($notPropertyConfig, $propertyConfig); + $mailer->messageConfig = $messageConfig; + + $message = $mailer->compose(); + + foreach ($notPropertyConfig as $name => $value) { + $this->assertEquals($value, $message->{'_' . $name}); + } + foreach ($propertyConfig as $name => $value) { + $this->assertEquals($value, $message->$name); + } + } + + /** + * @depends testGetDefaultView + */ + public function testRender() + { + $mailer = $this->getTestMailComponent(); + + $viewName = 'test_view'; + $viewFileName = $this->getTestFilePath() . DIRECTORY_SEPARATOR . $viewName . '.php'; + $viewFileContent = '<?php echo $testParam; ?>'; + file_put_contents($viewFileName, $viewFileContent); + + $params = [ + 'testParam' => 'test output' + ]; + $renderResult = $mailer->render($viewName, $params); + $this->assertEquals($params['testParam'], $renderResult); + } + + /** + * @depends testRender + */ + public function testRenderLayout() + { + $mailer = $this->getTestMailComponent(); + + $filePath = $this->getTestFilePath(); + + $viewName = 'test_view'; + $viewFileName = $filePath . DIRECTORY_SEPARATOR . $viewName . '.php'; + $viewFileContent = 'view file content'; + file_put_contents($viewFileName, $viewFileContent); + + $layoutName = 'test_layout'; + $layoutFileName = $filePath . DIRECTORY_SEPARATOR . $layoutName . '.php'; + $layoutFileContent = 'Begin Layout <?php echo $content; ?> End Layout'; + file_put_contents($layoutFileName, $layoutFileContent); + + $renderResult = $mailer->render($viewName, [], $layoutName); + $this->assertEquals('Begin Layout ' . $viewFileContent . ' End Layout', $renderResult); + } + + /** + * @depends testCreateMessage + * @depends testRender + */ + public function testCompose() + { + $mailer = $this->getTestMailComponent(); + $mailer->htmlLayout = false; + $mailer->textLayout = false; + + $htmlViewName = 'test_html_view'; + $htmlViewFileName = $this->getTestFilePath() . DIRECTORY_SEPARATOR . $htmlViewName . '.php'; + $htmlViewFileContent = 'HTML <b>view file</b> content'; + file_put_contents($htmlViewFileName, $htmlViewFileContent); + + $textViewName = 'test_text_view'; + $textViewFileName = $this->getTestFilePath() . DIRECTORY_SEPARATOR . $textViewName . '.php'; + $textViewFileContent = 'Plain text view file content'; + file_put_contents($textViewFileName, $textViewFileContent); + + $message = $mailer->compose([ + 'html' => $htmlViewName, + 'text' => $textViewName, + ]); + $this->assertEquals($htmlViewFileContent, $message->_htmlBody, 'Unable to render html!'); + $this->assertEquals($textViewFileContent, $message->_textBody, 'Unable to render text!'); + + $message = $mailer->compose($htmlViewName); + $this->assertEquals($htmlViewFileContent, $message->_htmlBody, 'Unable to render html by direct view!'); + $this->assertEquals(strip_tags($htmlViewFileContent), $message->_textBody, 'Unable to render text by direct view!'); + } + + public function testUseFileTransport() + { + $mailer = new Mailer(); + $this->assertFalse($mailer->useFileTransport); + $this->assertEquals('@runtime/mail', $mailer->fileTransportPath); + + $mailer->fileTransportPath = '@yiiunit/runtime/mail'; + $mailer->useFileTransport = true; + $mailer->fileTransportCallback = function () { + return 'message.txt'; + }; + $message = $mailer->compose() + ->setTo('to@example.com') + ->setFrom('from@example.com') + ->setSubject('test subject') + ->setTextBody('text body' . microtime(true)); + $this->assertTrue($mailer->send($message)); + $file = Yii::getAlias($mailer->fileTransportPath) . '/message.txt'; + $this->assertTrue(is_file($file)); + $this->assertEquals($message->toString(), file_get_contents($file)); + } +} + +/** + * Test Mailer class + */ +class Mailer extends BaseMailer +{ + public $messageClass = 'yiiunit\framework\mail\Message'; + public $sentMessages = []; + + protected function sendMessage($message) + { + $this->sentMessages[] = $message; + } +} + +/** + * Test Message class + */ +class Message extends BaseMessage +{ + public $id; + public $encoding; + public $_charset; + public $_from; + public $_replyTo; + public $_to; + public $_cc; + public $_bcc; + public $_subject; + public $_textBody; + public $_htmlBody; + + public function getCharset() + { + return $this->_charset; + } + + public function setCharset($charset) + { + $this->_charset = $charset; + return $this; + } + + public function getFrom() + { + return $this->_from; + } + + public function setFrom($from) + { + $this->_from = $from; + return $this; + } + + public function getTo() + { + return $this->_to; + } + + public function setTo($to) + { + $this->_to = $to; + return $this; + } + + public function getCc() + { + return $this->_cc; + } + + public function setCc($cc) + { + $this->_cc = $cc; + return $this; + } + + public function getBcc() + { + return $this->_bcc; + } + + public function setBcc($bcc) + { + $this->_bcc = $bcc; + return $this; + } + + public function getSubject() + { + return $this->_subject; + } + + public function setSubject($subject) + { + $this->_subject = $subject; + return $this; + } + + public function getReplyTo() + { + return $this->_replyTo; + } + + public function setReplyTo($replyTo) + { + $this->_replyTo = $replyTo; + return $this; + } + + public function setTextBody($text) + { + $this->_textBody = $text; + return $this; + } + + public function setHtmlBody($html) + { + $this->_htmlBody = $html; + return $this; + } + + public function attachContent($content, array $options = []) {} + + public function attach($fileName, array $options = []) {} + + public function embed($fileName, array $options = []) {} + + public function embedContent($content, array $options = []) {} + + public function toString() + { + return var_export($this, true); + } +} diff --git a/tests/unit/framework/mail/BaseMessageTest.php b/tests/unit/framework/mail/BaseMessageTest.php new file mode 100644 index 0000000..35fa549 --- /dev/null +++ b/tests/unit/framework/mail/BaseMessageTest.php @@ -0,0 +1,136 @@ +<?php + +namespace yiiunit\framework\mail; + +use Yii; +use yii\mail\BaseMailer; +use yii\mail\BaseMessage; +use yiiunit\TestCase; + +/** + * @group mail + */ +class BaseMessageTest extends TestCase +{ + public function setUp() + { + $this->mockApplication([ + 'components' => [ + 'mail' => $this->createTestEmailComponent() + ] + ]); + } + + /** + * @return Mailer test email component instance. + */ + protected function createTestEmailComponent() + { + $component = new TestMailer(); + return $component; + } + + /** + * @return TestMailer mailer instance. + */ + protected function getMailer() + { + return Yii::$app->getComponent('mail'); + } + + // Tests : + + public function testGetMailer() + { + $mailer = $this->getMailer(); + $message = $mailer->compose(); + $this->assertEquals($mailer, $message->getMailer()); + } + + public function testSend() + { + $mailer = $this->getMailer(); + $message = $mailer->compose(); + $message->send(); + $this->assertEquals($message, $mailer->sentMessages[0], 'Unable to send message!'); + } + + public function testToString() + { + $mailer = $this->getMailer(); + $message = $mailer->compose(); + $this->assertEquals($message->toString(), '' . $message); + } +} + +/** + * Test Mailer class + */ +class TestMailer extends BaseMailer +{ + public $messageClass = 'yiiunit\framework\mail\TestMessage'; + public $sentMessages = []; + + protected function sendMessage($message) + { + $this->sentMessages[] = $message; + } +} + +/** + * Test Message class + */ +class TestMessage extends BaseMessage +{ + public $text; + public $html; + + public function getCharset() {return '';} + + public function setCharset($charset) {} + + public function getFrom() {return '';} + + public function setFrom($from) {} + + public function getReplyTo() {return '';} + + public function setReplyTo($replyTo) {} + + public function getTo() {return '';} + + public function setTo($to) {} + + public function getCc() {return '';} + + public function setCc($cc) {} + + public function getBcc() {return '';} + + public function setBcc($bcc) {} + + public function getSubject() {return '';} + + public function setSubject($subject) {} + + public function setTextBody($text) { + $this->text = $text; + } + + public function setHtmlBody($html) { + $this->html = $html; + } + + public function attachContent($content, array $options = []) {} + + public function attach($fileName, array $options = []) {} + + public function embed($fileName, array $options = []) {} + + public function embedContent($content, array $options = []) {} + + public function toString() + { + return get_class($this); + } +} diff --git a/tests/unit/framework/rbac/ManagerTestCase.php b/tests/unit/framework/rbac/ManagerTestCase.php index 7cb4941..3bf80ad 100644 --- a/tests/unit/framework/rbac/ManagerTestCase.php +++ b/tests/unit/framework/rbac/ManagerTestCase.php @@ -17,7 +17,7 @@ abstract class ManagerTestCase extends TestCase $name = 'editUser'; $description = 'edit a user'; $bizRule = 'checkUserIdentity()'; - $data = array(1, 2, 3); + $data = [1, 2, 3]; $item = $this->auth->createItem($name, $type, $description, $bizRule, $data); $this->assertTrue($item instanceof Item); $this->assertEquals($item->type, $type); @@ -90,7 +90,7 @@ abstract class ManagerTestCase extends TestCase public function testGetItemChildren() { - $this->assertEquals(array(), $this->auth->getItemChildren('readPost')); + $this->assertEquals([], $this->auth->getItemChildren('readPost')); $children = $this->auth->getItemChildren('author'); $this->assertEquals(3, count($children)); $this->assertTrue(reset($children) instanceof Item); @@ -164,50 +164,50 @@ abstract class ManagerTestCase extends TestCase public function testExecuteBizRule() { - $this->assertTrue($this->auth->executeBizRule(null, array(), null)); - $this->assertTrue($this->auth->executeBizRule('return 1 == true;', array(), null)); - $this->assertTrue($this->auth->executeBizRule('return $params[0] == $params[1];', array(1, '1'), null)); - $this->assertFalse($this->auth->executeBizRule('invalid;', array(), null)); + $this->assertTrue($this->auth->executeBizRule(null, [], null)); + $this->assertTrue($this->auth->executeBizRule('return 1 == true;', [], null)); + $this->assertTrue($this->auth->executeBizRule('return $params[0] == $params[1];', [1, '1'], null)); + $this->assertFalse($this->auth->executeBizRule('invalid;', [], null)); } public function testCheckAccess() { - $results = array( - 'reader A' => array( + $results = [ + 'reader A' => [ 'createPost' => false, 'readPost' => true, 'updatePost' => false, 'updateOwnPost' => false, 'deletePost' => false, - ), - 'author B' => array( + ], + 'author B' => [ 'createPost' => true, 'readPost' => true, 'updatePost' => true, 'updateOwnPost' => true, 'deletePost' => false, - ), - 'editor C' => array( + ], + 'editor C' => [ 'createPost' => false, 'readPost' => true, 'updatePost' => true, 'updateOwnPost' => false, 'deletePost' => false, - ), - 'admin D' => array( + ], + 'admin D' => [ 'createPost' => true, 'readPost' => true, 'updatePost' => true, 'updateOwnPost' => false, 'deletePost' => true, - ), - ); + ], + ]; - $params = array('authorID' => 'author B'); + $params = ['authorID' => 'author B']; - foreach (array('reader A', 'author B', 'editor C', 'admin D') as $user) { + foreach (['reader A', 'author B', 'editor C', 'admin D'] as $user) { $params['userID'] = $user; - foreach (array('createPost', 'readPost', 'updatePost', 'updateOwnPost', 'deletePost') as $operation) { + foreach (['createPost', 'readPost', 'updatePost', 'updateOwnPost', 'deletePost'] as $operation) { $result = $this->auth->checkAccess($user, $operation, $params); $this->assertEquals($results[$user][$operation], $result); } diff --git a/tests/unit/framework/requirements/YiiRequirementCheckerTest.php b/tests/unit/framework/requirements/YiiRequirementCheckerTest.php index 652d003..4339dcf 100644 --- a/tests/unit/framework/requirements/YiiRequirementCheckerTest.php +++ b/tests/unit/framework/requirements/YiiRequirementCheckerTest.php @@ -15,29 +15,29 @@ class YiiRequirementCheckerTest extends TestCase { $requirementsChecker = new YiiRequirementChecker(); - $requirements = array( - 'requirementPass' => array( + $requirements = [ + 'requirementPass' => [ 'name' => 'Requirement 1', 'mandatory' => true, 'condition' => true, 'by' => 'Requirement 1', 'memo' => 'Requirement 1', - ), - 'requirementError' => array( + ], + 'requirementError' => [ 'name' => 'Requirement 2', 'mandatory' => true, 'condition' => false, 'by' => 'Requirement 2', 'memo' => 'Requirement 2', - ), - 'requirementWarning' => array( + ], + 'requirementWarning' => [ 'name' => 'Requirement 3', 'mandatory' => false, 'condition' => false, 'by' => 'Requirement 3', 'memo' => 'Requirement 3', - ), - ); + ], + ]; $checkResult = $requirementsChecker->check($requirements)->getResult(); $summary = $checkResult['summary']; @@ -65,22 +65,22 @@ class YiiRequirementCheckerTest extends TestCase { $requirementsChecker = new YiiRequirementChecker(); - $requirements = array( - 'requirementPass' => array( + $requirements = [ + 'requirementPass' => [ 'name' => 'Requirement 1', 'mandatory' => true, 'condition' => 'eval:2>1', 'by' => 'Requirement 1', 'memo' => 'Requirement 1', - ), - 'requirementError' => array( + ], + 'requirementError' => [ 'name' => 'Requirement 2', 'mandatory' => true, 'condition' => 'eval:2<1', 'by' => 'Requirement 2', 'memo' => 'Requirement 2', - ), - ); + ], + ]; $checkResult = $requirementsChecker->check($requirements)->getResult(); $checkedRequirements = $checkResult['requirements']; @@ -99,24 +99,24 @@ class YiiRequirementCheckerTest extends TestCase { $requirementsChecker = new YiiRequirementChecker(); - $requirements1 = array( - array( + $requirements1 = [ + [ 'name' => 'Requirement 1', 'mandatory' => true, 'condition' => true, 'by' => 'Requirement 1', 'memo' => 'Requirement 1', - ), - ); - $requirements2 = array( - array( + ], + ]; + $requirements2 = [ + [ 'name' => 'Requirement 2', 'mandatory' => true, 'condition' => true, 'by' => 'Requirement 2', 'memo' => 'Requirement 2', - ), - ); + ], + ]; $checkResult = $requirementsChecker->check($requirements1)->check($requirements2)->getResult(); $mergedRequirements = array_merge($requirements1, $requirements2); @@ -141,15 +141,15 @@ class YiiRequirementCheckerTest extends TestCase */ public function dataProviderGetByteSize() { - return array( - array('456', 456), - array('5K', 5*1024), - array('16KB', 16*1024), - array('4M', 4*1024*1024), - array('14MB', 14*1024*1024), - array('7G', 7*1024*1024*1024), - array('12GB', 12*1024*1024*1024), - ); + return [ + ['456', 456], + ['5K', 5*1024], + ['16KB', 16*1024], + ['4M', 4*1024*1024], + ['14MB', 14*1024*1024], + ['7G', 7*1024*1024*1024], + ['12GB', 12*1024*1024*1024], + ]; } /** @@ -171,13 +171,13 @@ class YiiRequirementCheckerTest extends TestCase */ public function dataProviderCompareByteSize() { - return array( - array('2M', '2K', '>', true), - array('2M', '2K', '>=', true), - array('1K', '1024', '==', true), - array('10M', '11M', '<', true), - array('10M', '11M', '<=', true), - ); + return [ + ['2M', '2K', '>', true], + ['2M', '2K', '>=', true], + ['1K', '1024', '==', true], + ['10M', '11M', '<', true], + ['10M', '11M', '<=', true], + ]; } /** diff --git a/tests/unit/framework/validators/BooleanValidatorTest.php b/tests/unit/framework/validators/BooleanValidatorTest.php new file mode 100644 index 0000000..0f09daf --- /dev/null +++ b/tests/unit/framework/validators/BooleanValidatorTest.php @@ -0,0 +1,59 @@ +<?php +namespace yiiunit\framework\validators; + +use yiiunit\data\validators\models\FakedValidationModel; +use yii\validators\BooleanValidator; +use yiiunit\TestCase; + +/** + * BooleanValidatorTest + */ +class BooleanValidatorTest extends TestCase +{ + protected function setUp() + { + parent::setUp(); + $this->mockApplication(); + } + + public function testValidateValue() + { + $val = new BooleanValidator; + $this->assertTrue($val->validateValue(true)); + $this->assertTrue($val->validateValue(false)); + $this->assertTrue($val->validateValue('0')); + $this->assertTrue($val->validateValue('1')); + $this->assertFalse($val->validateValue(null)); + $this->assertFalse($val->validateValue([])); + $val->strict = true; + $this->assertTrue($val->validateValue('0')); + $this->assertTrue($val->validateValue('1')); + $this->assertFalse($val->validateValue(true)); + $this->assertFalse($val->validateValue(false)); + $val->trueValue = true; + $val->falseValue = false; + $this->assertFalse($val->validateValue('0')); + $this->assertFalse($val->validateValue([])); + $this->assertTrue($val->validateValue(true)); + $this->assertTrue($val->validateValue(false)); + } + + public function testValidateAttributeAndError() + { + $obj = new FakedValidationModel; + $obj->attrA = true; + $obj->attrB = '1'; + $obj->attrC = '0'; + $obj->attrD = []; + $val = new BooleanValidator; + $val->validateAttribute($obj, 'attrA'); + $this->assertFalse($obj->hasErrors('attrA')); + $val->validateAttribute($obj, 'attrC'); + $this->assertFalse($obj->hasErrors('attrC')); + $val->strict = true; + $val->validateAttribute($obj, 'attrB'); + $this->assertFalse($obj->hasErrors('attrB')); + $val->validateAttribute($obj, 'attrD'); + $this->assertTrue($obj->hasErrors('attrD')); + } +} diff --git a/tests/unit/framework/validators/CompareValidatorTest.php b/tests/unit/framework/validators/CompareValidatorTest.php new file mode 100644 index 0000000..d1bdf34 --- /dev/null +++ b/tests/unit/framework/validators/CompareValidatorTest.php @@ -0,0 +1,175 @@ +<?php +namespace yiiunit\framework\validators; + +use yii\base\InvalidConfigException; +use yii\validators\CompareValidator; +use yiiunit\data\validators\models\FakedValidationModel; +use yiiunit\TestCase; + + + +class CompareValidatorTest extends TestCase +{ + protected function setUp() + { + parent::setUp(); + $this->mockApplication(); + } + + public function testValidateValueException() + { + $this->setExpectedException('yii\base\InvalidConfigException'); + $val = new CompareValidator; + $val->validateValue('val'); + } + + public function testValidateValue() + { + $value = 18449; + // default config + $val = new CompareValidator(['compareValue' => $value]); + $this->assertTrue($val->validateValue($value)); + $this->assertTrue($val->validateValue((string)$value)); + $this->assertFalse($val->validateValue($value + 1)); + foreach ($this->getOperationTestData($value) as $op => $tests) { + $val = new CompareValidator(['compareValue' => $value]); + $val->operator = $op; + foreach ($tests as $test) { + $this->assertEquals($test[1], $val->validateValue($test[0])); + } + } + } + + protected function getOperationTestData($value) + { + return [ + '===' => [ + [$value, true], + [(string)$value, false], + [(float)$value, false], + [$value + 1, false], + ], + '!=' => [ + [$value, false], + [(string)$value, false], + [(float)$value, false], + [$value + 0.00001, true], + [false, true], + ], + '!==' => [ + [$value, false], + [(string)$value, true], + [(float)$value, true], + [false, true], + ], + '>' => [ + [$value, false], + [$value + 1, true], + [$value - 1, false], + ], + '>=' => [ + [$value, true], + [$value + 1, true], + [$value - 1, false], + ], + '<' => [ + [$value, false], + [$value + 1, false], + [$value - 1, true], + ], + '<=' => [ + [$value, true], + [$value + 1, false], + [$value - 1, true], + ], + //'non-op' => [ + // [$value, false], + // [$value + 1, false], + // [$value - 1, false], + //], + ]; + } + + public function testValidateAttribute() + { + // invalid-array + $val = new CompareValidator; + $model = new FakedValidationModel; + $model->attr = ['test_val']; + $val->validateAttribute($model, 'attr'); + $this->assertTrue($model->hasErrors('attr')); + $val = new CompareValidator(['compareValue' => 'test-string']); + $model = new FakedValidationModel; + $model->attr_test = 'test-string'; + $val->validateAttribute($model, 'attr_test'); + $this->assertFalse($model->hasErrors('attr_test')); + $val = new CompareValidator(['compareAttribute' => 'attr_test_val']); + $model = new FakedValidationModel; + $model->attr_test = 'test-string'; + $model->attr_test_val = 'test-string'; + $val->validateAttribute($model, 'attr_test'); + $this->assertFalse($model->hasErrors('attr_test')); + $this->assertFalse($model->hasErrors('attr_test_val')); + $val = new CompareValidator(['compareAttribute' => 'attr_test_val']); + $model = new FakedValidationModel; + $model->attr_test = 'test-string'; + $model->attr_test_val = 'test-string-false'; + $val->validateAttribute($model, 'attr_test'); + $this->assertTrue($model->hasErrors('attr_test')); + $this->assertFalse($model->hasErrors('attr_test_val')); + // assume: _repeat + $val = new CompareValidator; + $model = new FakedValidationModel; + $model->attr_test = 'test-string'; + $model->attr_test_repeat = 'test-string'; + $val->validateAttribute($model, 'attr_test'); + $this->assertFalse($model->hasErrors('attr_test')); + $this->assertFalse($model->hasErrors('attr_test_repeat')); + $val = new CompareValidator; + $model = new FakedValidationModel; + $model->attr_test = 'test-string'; + $model->attr_test_repeat = 'test-string2'; + $val->validateAttribute($model, 'attr_test'); + $this->assertTrue($model->hasErrors('attr_test')); + $this->assertFalse($model->hasErrors('attr_test_repeat')); + // not existing op + $val = new CompareValidator(); + $val->operator = '<>'; + $model = FakedValidationModel::createWithAttributes(['attr_o' => 5, 'attr_o_repeat' => 5]); + $val->validateAttribute($model, 'attr_o'); + $this->assertTrue($model->hasErrors('attr_o')); + } + + public function testValidateAttributeOperators() + { + $value = 55; + foreach ($this->getOperationTestData($value) as $operator => $tests) { + $val = new CompareValidator(['operator' => $operator, 'compareValue' => $value]); + foreach ($tests as $test) { + $model = new FakedValidationModel; + $model->attr_test = $test[0]; + $val->validateAttribute($model, 'attr_test'); + $this->assertEquals($test[1], !$model->hasErrors('attr_test')); + } + + } + } + + public function testEnsureMessageSetOnInit() + { + foreach ($this->getOperationTestData(1337) as $operator => $tests) { + $val = new CompareValidator(['operator' => $operator]); + $this->assertTrue(strlen($val->message) > 1); + } + try { + $val = new CompareValidator(['operator' => '<>']); + } catch (InvalidConfigException $e) { + return; + } + catch (\Exception $e) { + $this->fail('InvalidConfigException expected' . get_class($e) . 'received'); + return; + } + $this->fail('InvalidConfigException expected none received'); + } +} \ No newline at end of file diff --git a/tests/unit/framework/validators/DateValidatorTest.php b/tests/unit/framework/validators/DateValidatorTest.php new file mode 100644 index 0000000..d5d0f93 --- /dev/null +++ b/tests/unit/framework/validators/DateValidatorTest.php @@ -0,0 +1,71 @@ +<?php + +namespace yiiunit\framework\validators; + +use DateTime; +use yii\validators\DateValidator; +use yiiunit\data\validators\models\FakedValidationModel; +use yiiunit\TestCase; + + +class DateValidatorTest extends TestCase +{ + protected function setUp() + { + parent::setUp(); + $this->mockApplication(); + } + + public function testEnsureMessageIsSet() + { + $val = new DateValidator; + $this->assertTrue($val->message !== null && strlen($val->message) > 1); + } + + public function testValidateValue() + { + $val = new DateValidator; + $this->assertFalse($val->validateValue('3232-32-32')); + $this->assertTrue($val->validateValue('2013-09-13')); + $this->assertFalse($val->validateValue('31.7.2013')); + $this->assertFalse($val->validateValue('31-7-2013')); + $this->assertFalse($val->validateValue(time())); + $val->format = 'U'; + $this->assertTrue($val->validateValue(time())); + $val->format = 'd.m.Y'; + $this->assertTrue($val->validateValue('31.7.2013')); + $val->format = 'Y-m-!d H:i:s'; + $this->assertTrue($val->validateValue('2009-02-15 15:16:17')); + } + + public function testValidateAttribute() + { + // error-array-add + $val = new DateValidator; + $model = new FakedValidationModel; + $model->attr_date = '2013-09-13'; + $val->validateAttribute($model, 'attr_date'); + $this->assertFalse($model->hasErrors('attr_date')); + $model = new FakedValidationModel; + $model->attr_date = '1375293913'; + $val->validateAttribute($model, 'attr_date'); + $this->assertTrue($model->hasErrors('attr_date')); + //// timestamp attribute + $val = new DateValidator(['timestampAttribute' => 'attr_timestamp']); + $model = new FakedValidationModel; + $model->attr_date = '2013-09-13'; + $model->attr_timestamp = true; + $val->validateAttribute($model, 'attr_date'); + $this->assertFalse($model->hasErrors('attr_date')); + $this->assertFalse($model->hasErrors('attr_timestamp')); + $this->assertEquals( + DateTime::createFromFormat($val->format, '2013-09-13')->getTimestamp(), + $model->attr_timestamp + ); + $val = new DateValidator(); + $model = FakedValidationModel::createWithAttributes(['attr_date' => []]); + $val->validateAttribute($model, 'attr_date'); + $this->assertTrue($model->hasErrors('attr_date')); + + } +} \ No newline at end of file diff --git a/tests/unit/framework/validators/DefaultValueValidatorTest.php b/tests/unit/framework/validators/DefaultValueValidatorTest.php new file mode 100644 index 0000000..48537d8 --- /dev/null +++ b/tests/unit/framework/validators/DefaultValueValidatorTest.php @@ -0,0 +1,38 @@ +<?php +namespace yiiunit\framework\validators; +use yii\validators\DefaultValueValidator; +use yiiunit\TestCase; + +/** + * DefaultValueValidatorTest + */ +class DefaultValueValidatorTest extends TestCase +{ + protected function setUp() + { + parent::setUp(); + $this->mockApplication(); + } + + public function testValidateAttribute() + { + $val = new DefaultValueValidator; + $val->value = 'test_value'; + $obj = new \stdclass; + $obj->attrA = 'attrA'; + $obj->attrB = null; + $obj->attrC = ''; + // original values to chek which attritubes where modified + $objB = clone $obj; + $val->validateAttribute($obj, 'attrB'); + $this->assertEquals($val->value, $obj->attrB); + $this->assertEquals($objB->attrA, $obj->attrA); + $val->value = 'new_test_value'; + $obj = clone $objB; // get clean object + $val->validateAttribute($obj, 'attrC'); + $this->assertEquals('new_test_value', $obj->attrC); + $this->assertEquals($objB->attrA, $obj->attrA); + $val->validateAttribute($obj, 'attrA'); + $this->assertEquals($objB->attrA, $obj->attrA); + } +} diff --git a/tests/unit/framework/validators/EmailValidatorTest.php b/tests/unit/framework/validators/EmailValidatorTest.php index b33a809..770914d 100644 --- a/tests/unit/framework/validators/EmailValidatorTest.php +++ b/tests/unit/framework/validators/EmailValidatorTest.php @@ -2,6 +2,7 @@ namespace yiiunit\framework\validators; use yii\validators\EmailValidator; +use yiiunit\data\validators\models\FakedValidationModel; use yiiunit\TestCase; /** @@ -10,6 +11,12 @@ use yiiunit\TestCase; */ class EmailValidatorTest extends TestCase { + protected function setUp() + { + parent::setUp(); + $this->mockApplication(); + } + public function testValidateValue() { $validator = new EmailValidator(); @@ -17,14 +24,85 @@ class EmailValidatorTest extends TestCase $this->assertTrue($validator->validateValue('sam@rmcreative.ru')); $this->assertTrue($validator->validateValue('5011@gmail.com')); $this->assertFalse($validator->validateValue('rmcreative.ru')); + $this->assertFalse($validator->validateValue('Carsten Brandt <mail@cebe.cc>')); + $this->assertFalse($validator->validateValue('"Carsten Brandt" <mail@cebe.cc>')); + $this->assertFalse($validator->validateValue('<mail@cebe.cc>')); + $this->assertFalse($validator->validateValue('info@örtliches.de')); + $this->assertFalse($validator->validateValue('sam@рмкреатиф.ru')); + + $validator->allowName = true; + + $this->assertTrue($validator->validateValue('sam@rmcreative.ru')); + $this->assertTrue($validator->validateValue('5011@gmail.com')); + $this->assertFalse($validator->validateValue('rmcreative.ru')); + $this->assertTrue($validator->validateValue('Carsten Brandt <mail@cebe.cc>')); + $this->assertTrue($validator->validateValue('"Carsten Brandt" <mail@cebe.cc>')); + $this->assertTrue($validator->validateValue('<mail@cebe.cc>')); + $this->assertFalse($validator->validateValue('info@örtliches.de')); + $this->assertFalse($validator->validateValue('sam@рмкреатиф.ru')); + $this->assertFalse($validator->validateValue('Informtation info@oertliches.de')); + $this->assertTrue($validator->validateValue('test@example.com')); + $this->assertTrue($validator->validateValue('John Smith <john.smith@example.com>')); + $this->assertFalse($validator->validateValue('John Smith <example.com>')); + } + + public function testValidateValueIdn() + { + if (!function_exists('idn_to_ascii')) { + $this->markTestSkipped('Intl extension required'); + return; + } + $validator = new EmailValidator(); + $validator->enableIDN = true; + + $this->assertTrue($validator->validateValue('5011@example.com')); + $this->assertTrue($validator->validateValue('example@äüößìà.de')); + $this->assertTrue($validator->validateValue('example@xn--zcack7ayc9a.de')); + $this->assertTrue($validator->validateValue('info@örtliches.de')); + $this->assertTrue($validator->validateValue('sam@рмкреатиф.ru')); + $this->assertTrue($validator->validateValue('sam@rmcreative.ru')); + $this->assertTrue($validator->validateValue('5011@gmail.com')); + $this->assertFalse($validator->validateValue('rmcreative.ru')); + $this->assertFalse($validator->validateValue('Carsten Brandt <mail@cebe.cc>')); + $this->assertFalse($validator->validateValue('"Carsten Brandt" <mail@cebe.cc>')); + $this->assertFalse($validator->validateValue('<mail@cebe.cc>')); + + $validator->allowName = true; + + $this->assertTrue($validator->validateValue('info@örtliches.de')); + $this->assertTrue($validator->validateValue('Informtation <info@örtliches.de>')); + $this->assertFalse($validator->validateValue('Informtation info@örtliches.de')); + $this->assertTrue($validator->validateValue('sam@рмкреатиф.ru')); + $this->assertTrue($validator->validateValue('sam@rmcreative.ru')); + $this->assertTrue($validator->validateValue('5011@gmail.com')); + $this->assertFalse($validator->validateValue('rmcreative.ru')); + $this->assertTrue($validator->validateValue('Carsten Brandt <mail@cebe.cc>')); + $this->assertTrue($validator->validateValue('"Carsten Brandt" <mail@cebe.cc>')); + $this->assertTrue($validator->validateValue('<mail@cebe.cc>')); + $this->assertTrue($validator->validateValue('test@example.com')); + $this->assertTrue($validator->validateValue('John Smith <john.smith@example.com>')); + $this->assertFalse($validator->validateValue('John Smith <example.com>')); } public function testValidateValueMx() { $validator = new EmailValidator(); - $validator->checkMX = true; + $validator->checkDNS = true; $this->assertTrue($validator->validateValue('5011@gmail.com')); - $this->assertFalse($validator->validateValue('test@example.com')); + + $validator->checkDNS = false; + $this->assertTrue($validator->validateValue('test@nonexistingsubdomain.example.com')); + $validator->checkDNS = true; + $this->assertFalse($validator->validateValue('test@nonexistingsubdomain.example.com')); + } + + public function testValidateAttribute() + { + $val = new EmailValidator(); + $model = new FakedValidationModel(); + $model->attr_email = '5011@gmail.com'; + $val->validateAttribute($model, 'attr_email'); + $this->assertFalse($model->hasErrors('attr_email')); } } diff --git a/tests/unit/framework/validators/ExistValidatorDriverTests/ExistValidatorPostgresTest.php b/tests/unit/framework/validators/ExistValidatorDriverTests/ExistValidatorPostgresTest.php new file mode 100644 index 0000000..539b458 --- /dev/null +++ b/tests/unit/framework/validators/ExistValidatorDriverTests/ExistValidatorPostgresTest.php @@ -0,0 +1,10 @@ +<?php +namespace yiiunit\framework\validators\ExistValidatorDriverTests; + + +use yiiunit\framework\validators\ExistValidatorTest; + +class ExistValidatorPostgresTest extends ExistValidatorTest +{ + protected $driverName = 'pgsql'; +} \ No newline at end of file diff --git a/tests/unit/framework/validators/ExistValidatorDriverTests/ExistValidatorSQliteTest.php b/tests/unit/framework/validators/ExistValidatorDriverTests/ExistValidatorSQliteTest.php new file mode 100644 index 0000000..cc83fd6 --- /dev/null +++ b/tests/unit/framework/validators/ExistValidatorDriverTests/ExistValidatorSQliteTest.php @@ -0,0 +1,10 @@ +<?php +namespace yiiunit\framework\validators\ExistValidatorDriverTests; + + +use yiiunit\framework\validators\ExistValidatorTest; + +class ExistValidatorSQliteTest extends ExistValidatorTest +{ + protected $driverName = 'sqlite'; +} \ No newline at end of file diff --git a/tests/unit/framework/validators/ExistValidatorTest.php b/tests/unit/framework/validators/ExistValidatorTest.php new file mode 100644 index 0000000..a0f0328 --- /dev/null +++ b/tests/unit/framework/validators/ExistValidatorTest.php @@ -0,0 +1,95 @@ +<?php + +namespace yiiunit\framework\validators; + + +use Yii; +use yii\base\Exception; +use yii\validators\ExistValidator; +use yiiunit\data\ar\ActiveRecord; +use yiiunit\data\validators\models\ValidatorTestMainModel; +use yiiunit\data\validators\models\ValidatorTestRefModel; +use yiiunit\framework\db\DatabaseTestCase; + +class ExistValidatorTest extends DatabaseTestCase +{ + protected $driverName = 'mysql'; + + public function setUp() + { + parent::setUp(); + $this->mockApplication(); + ActiveRecord::$db = $this->getConnection(); + } + + public function testValidateValueExpectedException() + { + try { + $val = new ExistValidator(); + $result = $val->validateValue('ref'); + $this->fail('Exception should have been thrown at this time'); + } catch (Exception $e) { + $this->assertInstanceOf('yii\base\InvalidConfigException', $e); + $this->assertEquals('The "className" property must be set.', $e->getMessage()); + } + // combine to save the time creating a new db-fixture set (likely ~5 sec) + try { + $val = new ExistValidator(['className' => ValidatorTestMainModel::className()]); + $val->validateValue('ref'); + $this->fail('Exception should have been thrown at this time'); + } catch (Exception $e) { + $this->assertInstanceOf('yii\base\InvalidConfigException', $e); + $this->assertEquals('The "attributeName" property must be set.', $e->getMessage()); + } + } + + public function testValidateValue() + { + $val = new ExistValidator(['className' => ValidatorTestRefModel::className(), 'attributeName' => 'id']); + $this->assertTrue($val->validateValue(2)); + $this->assertTrue($val->validateValue(5)); + $this->assertFalse($val->validateValue(99)); + $this->assertFalse($val->validateValue(['1'])); + } + + public function testValidateAttribute() + { + // existing value on different table + $val = new ExistValidator(['className' => ValidatorTestMainModel::className(), 'attributeName' => 'id']); + $m = ValidatorTestRefModel::find(['id' => 1]); + $val->validateAttribute($m, 'ref'); + $this->assertFalse($m->hasErrors()); + // non-existing value on different table + $val = new ExistValidator(['className' => ValidatorTestMainModel::className(), 'attributeName' => 'id']); + $m = ValidatorTestRefModel::find(['id' => 6]); + $val->validateAttribute($m, 'ref'); + $this->assertTrue($m->hasErrors('ref')); + // existing value on same table + $val = new ExistValidator(['attributeName' => 'ref']); + $m = ValidatorTestRefModel::find(['id' => 2]); + $val->validateAttribute($m, 'test_val'); + $this->assertFalse($m->hasErrors()); + // non-existing value on same table + $val = new ExistValidator(['attributeName' => 'ref']); + $m = ValidatorTestRefModel::find(['id' => 5]); + $val->validateAttribute($m, 'test_val_fail'); + $this->assertTrue($m->hasErrors('test_val_fail')); + // check for given value (true) + $val = new ExistValidator(); + $m = ValidatorTestRefModel::find(['id' => 3]); + $val->validateAttribute($m, 'ref'); + $this->assertFalse($m->hasErrors()); + // check for given defaults (false) + $val = new ExistValidator(); + $m = ValidatorTestRefModel::find(['id' => 4]); + $m->a_field = 'some new value'; + $val->validateAttribute($m, 'a_field'); + $this->assertTrue($m->hasErrors('a_field')); + // check array + $val = new ExistValidator(['attributeName' => 'ref']); + $m = ValidatorTestRefModel::find(['id' => 2]); + $m->test_val = [1,2,3]; + $val->validateAttribute($m, 'test_val'); + $this->assertTrue($m->hasErrors('test_val')); + } +} \ No newline at end of file diff --git a/tests/unit/framework/validators/FileValidatorTest.php b/tests/unit/framework/validators/FileValidatorTest.php new file mode 100644 index 0000000..f68fbe7 --- /dev/null +++ b/tests/unit/framework/validators/FileValidatorTest.php @@ -0,0 +1,303 @@ +<?php + +namespace yiiunit\framework\validators; + + +use yii\validators\FileValidator; +use yii\web\UploadedFile; +use Yii; +use yiiunit\data\validators\models\FakedValidationModel; +use yiiunit\TestCase; + +class FileValidatorTest extends TestCase +{ + public function setUp() + { + $this->mockApplication(); + } + + public function testAssureMessagesSetOnInit() + { + $val = new FileValidator(); + foreach (['message', 'uploadRequired', 'tooMany', 'wrongType', 'tooBig', 'tooSmall'] as $attr) { + $this->assertTrue(is_string($val->$attr)); + } + } + + public function testTypeSplitOnInit() + { + $val = new FileValidator(['types' => 'jpeg, jpg, gif']); + $this->assertEquals(['jpeg', 'jpg', 'gif'], $val->types); + $val = new FileValidator(['types' => 'jpeg']); + $this->assertEquals(['jpeg'], $val->types); + $val = new FileValidator(['types' => '']); + $this->assertEquals([], $val->types); + $val = new FileValidator(['types' => []]); + $this->assertEquals([], $val->types); + $val = new FileValidator(); + $this->assertEquals([], $val->types); + $val = new FileValidator(['types' => ['jpeg', 'exe']]); + $this->assertEquals(['jpeg', 'exe'], $val->types); + } + + public function testGetSizeLimit() + { + $size = $this->sizeToBytes(ini_get('upload_max_filesize')); + $val = new FileValidator(); + $this->assertEquals($size, $val->getSizeLimit()); + $val->maxSize = $size + 1; // set and test if value is overridden + $this->assertEquals($size, $val->getSizeLimit()); + $val->maxSize = abs($size - 1); + $this->assertEquals($size - 1, $val->getSizeLimit()); + $_POST['MAX_FILE_SIZE'] = $size + 1; + $this->assertEquals($size - 1, $val->getSizeLimit()); + $_POST['MAX_FILE_SIZE'] = abs($size - 2); + $this->assertSame($_POST['MAX_FILE_SIZE'], $val->getSizeLimit()); + } + + protected function sizeToBytes($sizeStr) + { + switch (substr($sizeStr, -1)) { + case 'M': + case 'm': + return (int)$sizeStr * 1048576; + case 'K': + case 'k': + return (int)$sizeStr * 1024; + case 'G': + case 'g': + return (int)$sizeStr * 1073741824; + default: + return (int)$sizeStr; + } + } + + public function testValidateAttributeMultiple() + { + $val = new FileValidator(['maxFiles' => 2]); + $m = FakedValidationModel::createWithAttributes(['attr_files' => 'path']); + $val->validateAttribute($m, 'attr_files'); + $this->assertTrue($m->hasErrors('attr_files')); + $m = FakedValidationModel::createWithAttributes(['attr_files' => []]); + $val->validateAttribute($m, 'attr_files'); + $this->assertTrue($m->hasErrors('attr_files')); + $this->assertSame($val->uploadRequired, current($m->getErrors('attr_files'))); + $m = FakedValidationModel::createWithAttributes( + [ + 'attr_files' => $this->createTestFiles( + [ + [ + 'name' => 'test_up_1.txt', + 'size' => 1024, + ], + [ + 'error' => UPLOAD_ERR_NO_FILE, + ], + ] + ) + ] + ); + $val->validateAttribute($m, 'attr_files'); + $this->assertFalse($m->hasErrors('attr_files')); + $m = FakedValidationModel::createWithAttributes([ + 'attr_files' => $this->createTestFiles([ + [''], [''], [''] + ]) + ]); + $val->validateAttribute($m, 'attr_files'); + $this->assertTrue($m->hasErrors()); + $this->assertTrue(stripos(current($m->getErrors('attr_files')), 'you can upload at most') !== false); + } + + /** + * @param array $params + * @return UploadedFile[] + */ + protected function createTestFiles($params = []) + { + $rndString = function ($len = 10) { + $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + $randomString = ''; + for ($i = 0; $i < $len; $i++) { + $randomString .= $characters[rand(0, strlen($characters) - 1)]; + } + return $randomString; + }; + $files = []; + foreach ($params as $param) { + if (empty($param) && count($params) != 1) { + $files[] = ['no instance of UploadedFile']; + continue; + } + $name = isset($param['name']) ? $param['name'] : $rndString(); + $tempName = \Yii::getAlias('@yiiunit/runtime/validators/file/tmp') . $name; + if (is_readable($tempName)) { + $size = filesize($tempName); + } else { + $size = isset($param['size']) ? $param['size'] : rand( + 1, + $this->sizeToBytes(ini_get('upload_max_filesize')) + ); + } + $type = isset($param['type']) ? $param['type'] : 'text/plain'; + $error = isset($param['error']) ? $param['error'] : UPLOAD_ERR_OK; + if (count($params) == 1) { + $error = empty($param) ? UPLOAD_ERR_NO_FILE : $error; + return new UploadedFile([ + 'name' => $name, + 'tempName' => $tempName, + 'type' => $type, + 'size' => $size, + 'error' => $error + ]); + } + $files[] = new UploadedFile([ + 'name' => $name, + 'tempName' => $tempName, + 'type' => $type, + 'size' => $size, + 'error' => $error + ]); + } + return $files; + } + + public function testValidateAttribute() + { + // single File + $val = new FileValidator(); + $m = $this->createModelForAttributeTest(); + $val->validateAttribute($m, 'attr_files'); + $this->assertFalse($m->hasErrors()); + $val->validateAttribute($m, 'attr_files_empty'); + $this->assertTrue($m->hasErrors('attr_files_empty')); + $this->assertSame($val->uploadRequired, current($m->getErrors('attr_files_empty'))); + $m = $this->createModelForAttributeTest(); + // too big + $val = new FileValidator(['maxSize' => 128]); + $val->validateAttribute($m, 'attr_files'); + $this->assertTrue($m->hasErrors('attr_files')); + $this->assertTrue( + stripos( + current($m->getErrors('attr_files')), + str_ireplace(['{file}', '{limit}'], [$m->attr_files->name, 128], $val->tooBig) + ) !== false + ); + // to Small + $m = $this->createModelForAttributeTest(); + $val = new FileValidator(['minSize' => 2048]); + $val->validateAttribute($m, 'attr_files'); + $this->assertTrue($m->hasErrors('attr_files')); + $this->assertTrue( + stripos( + current($m->getErrors('attr_files')), + str_ireplace(['{file}', '{limit}'], [$m->attr_files->name, 2048], $val->tooSmall) + ) !== false + ); + // UPLOAD_ERR_INI_SIZE/UPLOAD_ERR_FORM_SIZE + $m = $this->createModelForAttributeTest(); + $val = new FileValidator(); + $val->validateAttribute($m, 'attr_err_ini'); + $this->assertTrue($m->hasErrors('attr_err_ini')); + $this->assertTrue( + stripos( + current($m->getErrors('attr_err_ini')), + str_ireplace( + ['{file}', '{limit}'], + [$m->attr_err_ini->name, $val->getSizeLimit()], + $val->tooBig + ) + ) !== false + ); + // UPLOAD_ERR_PARTIAL + $m = $this->createModelForAttributeTest(); + $val = new FileValidator(); + $val->validateAttribute($m, 'attr_err_part'); + $this->assertTrue($m->hasErrors('attr_err_part')); + $this->assertSame(Yii::t('yii', 'File upload failed.'), current($m->getErrors('attr_err_part'))); + $log = Yii::$app->getLog()->toArray(); + $this->assertSame(FileValidator::className() . '::validateFile', $log['messages'][0][2]); + } + + public function testValidateAttributeType() + { + $val = new FileValidator(['types' => 'jpeg, jpg']); + $m = FakedValidationModel::createWithAttributes( + [ + 'attr_jpg' => $this->createTestFiles([['name' => 'one.jpeg']]), + 'attr_exe' => $this->createTestFiles([['name' => 'bad.exe']]), + ] + ); + $val->validateAttribute($m, 'attr_jpg'); + $this->assertFalse($m->hasErrors('attr_jpg')); + $val->validateAttribute($m, 'attr_exe'); + $this->assertTrue($m->hasErrors('attr_exe')); + $this->assertTrue(stripos(current($m->getErrors('attr_exe')), 'Only files with these extensions ') !== false); + } + + + protected function createModelForAttributeTest() + { + return FakedValidationModel::createWithAttributes( + [ + 'attr_files' => $this->createTestFiles([ + ['name' => 'abc.jpg', 'size' => 1024, 'type' => 'image/jpeg'], + ]), + 'attr_files_empty' => $this->createTestFiles([[]]), + 'attr_err_ini' => $this->createTestFiles([['error' => UPLOAD_ERR_INI_SIZE]]), + 'attr_err_part' => $this->createTestFiles([['error' => UPLOAD_ERR_PARTIAL]]), + 'attr_err_tmp' => $this->createTestFiles([['error' => UPLOAD_ERR_NO_TMP_DIR]]), + 'attr_err_write' => $this->createTestFiles([['error' => UPLOAD_ERR_CANT_WRITE]]), + 'attr_err_ext' => $this->createTestFiles([['error' => UPLOAD_ERR_EXTENSION]]), + ] + ); + } + + public function testValidateAttributeErrPartial() + { + $m = $this->createModelForAttributeTest(); + $val = new FileValidator(); + $val->validateAttribute($m, 'attr_err_part'); + $this->assertTrue($m->hasErrors('attr_err_part')); + $this->assertSame(Yii::t('yii', 'File upload failed.'), current($m->getErrors('attr_err_part'))); + $log = Yii::$app->getLog()->toArray(); + $this->assertSame(FileValidator::className() . '::validateFile', $log['messages'][0][2]); + $this->assertContains('File was only', $log['messages'][0][0]); + } + + public function testValidateAttributeErrCantWrite() + { + $m = $this->createModelForAttributeTest(); + $val = new FileValidator(); + $val->validateAttribute($m, 'attr_err_write'); + $this->assertTrue($m->hasErrors('attr_err_write')); + $this->assertSame(Yii::t('yii', 'File upload failed.'), current($m->getErrors('attr_err_write'))); + $log = Yii::$app->getLog()->toArray(); + $this->assertSame(FileValidator::className() . '::validateFile', $log['messages'][0][2]); + $this->assertContains('Failed to write', $log['messages'][0][0]); + } + + public function testValidateAttributeErrExtension() + { + $m = $this->createModelForAttributeTest(); + $val = new FileValidator(); + $val->validateAttribute($m, 'attr_err_ext'); + $this->assertTrue($m->hasErrors('attr_err_ext')); + $this->assertSame(Yii::t('yii', 'File upload failed.'), current($m->getErrors('attr_err_ext'))); + $log = Yii::$app->getLog()->toArray(); + $this->assertSame(FileValidator::className() . '::validateFile', $log['messages'][0][2]); + $this->assertContains('PHP extension', $log['messages'][0][0]); + } + + public function testValidateAttributeErrNoTmpDir() + { + $m = $this->createModelForAttributeTest(); + $val = new FileValidator(); + $val->validateAttribute($m, 'attr_err_tmp'); + $this->assertTrue($m->hasErrors('attr_err_tmp')); + $this->assertSame(Yii::t('yii', 'File upload failed.'), current($m->getErrors('attr_err_tmp'))); + $log = Yii::$app->getLog()->toArray(); + $this->assertSame(FileValidator::className() . '::validateFile', $log['messages'][0][2]); + $this->assertContains('Missing the temporary folder', $log['messages'][0][0]); + } +} \ No newline at end of file diff --git a/tests/unit/framework/validators/FilterValidatorTest.php b/tests/unit/framework/validators/FilterValidatorTest.php new file mode 100644 index 0000000..ec96f21 --- /dev/null +++ b/tests/unit/framework/validators/FilterValidatorTest.php @@ -0,0 +1,52 @@ +<?php + +namespace yiiunit\framework\validators; + + +use yii\validators\FilterValidator; +use yiiunit\data\validators\models\FakedValidationModel; +use yiiunit\TestCase; + +class FilterValidatorTest extends TestCase +{ + protected function setUp() + { + parent::setUp(); + $this->mockApplication(); + } + + public function testAssureExceptionOnInit() + { + $this->setExpectedException('yii\base\InvalidConfigException'); + $val = new FilterValidator(); + } + + public function testValidateAttribute() + { + $m = FakedValidationModel::createWithAttributes([ + 'attr_one' => ' to be trimmed ', + 'attr_two' => 'set this to null', + 'attr_empty1' => '', + 'attr_empty2' => null + ]); + $val = new FilterValidator(['filter' => 'trim']); + $val->validateAttribute($m, 'attr_one'); + $this->assertSame('to be trimmed', $m->attr_one); + $val->filter = function ($value) { + return null; + }; + $val->validateAttribute($m, 'attr_two'); + $this->assertNull($m->attr_two); + $val->filter = [$this, 'notToBeNull']; + $val->validateAttribute($m, 'attr_empty1'); + $this->assertSame($this->notToBeNull(''), $m->attr_empty1); + $val->skipOnEmpty = true; + $val->validateAttribute($m, 'attr_empty2'); + $this->assertNotNull($m->attr_empty2); + } + + public function notToBeNull($value) + { + return 'not null'; + } +} \ No newline at end of file diff --git a/tests/unit/framework/validators/NumberValidatorTest.php b/tests/unit/framework/validators/NumberValidatorTest.php new file mode 100644 index 0000000..4e9897e --- /dev/null +++ b/tests/unit/framework/validators/NumberValidatorTest.php @@ -0,0 +1,166 @@ +<?php + +namespace yiiunit\framework\validators; + + +use yii\validators\NumberValidator; +use yiiunit\data\validators\models\FakedValidationModel; +use yiiunit\TestCase; + +class NumberValidatorTest extends TestCase +{ + protected function setUp() + { + parent::setUp(); + $this->mockApplication(); + } + + public function testEnsureMessageOnInit() + { + $val = new NumberValidator; + $this->assertTrue(is_string($val->message)); + $this->assertTrue(is_null($val->max)); + $val = new NumberValidator(['min' => -1, 'max' => 20, 'integerOnly' => true]); + $this->assertTrue(is_string($val->message)); + $this->assertTrue(is_string($val->tooSmall)); + $this->assertTrue(is_string($val->tooBig)); + } + + public function testValidateValueSimple() + { + $val = new NumberValidator(); + $this->assertTrue($val->validateValue(20)); + $this->assertTrue($val->validateValue(0)); + $this->assertTrue($val->validateValue(-20)); + $this->assertTrue($val->validateValue('20')); + $this->assertTrue($val->validateValue(25.45)); + $this->assertFalse($val->validateValue('25,45')); + $this->assertFalse($val->validateValue('12:45')); + $val = new NumberValidator(['integerOnly' => true]); + $this->assertTrue($val->validateValue(20)); + $this->assertTrue($val->validateValue(0)); + $this->assertFalse($val->validateValue(25.45)); + $this->assertTrue($val->validateValue('20')); + $this->assertFalse($val->validateValue('25,45')); + $this->assertTrue($val->validateValue('020')); + $this->assertTrue($val->validateValue(0x14)); + $this->assertFalse($val->validateValue('0x14')); // todo check this + } + + public function testValidateValueAdvanced() + { + $val = new NumberValidator(); + $this->assertTrue($val->validateValue('-1.23')); // signed float + $this->assertTrue($val->validateValue('-4.423e-12')); // signed float + exponent + $this->assertTrue($val->validateValue('12E3')); // integer + exponent + $this->assertFalse($val->validateValue('e12')); // just exponent + $this->assertFalse($val->validateValue('-e3')); + $this->assertFalse($val->validateValue('-4.534-e-12')); // 'signed' exponent + $this->assertFalse($val->validateValue('12.23^4')); // expression instead of value + $val = new NumberValidator(['integerOnly' => true]); + $this->assertFalse($val->validateValue('-1.23')); + $this->assertFalse($val->validateValue('-4.423e-12')); + $this->assertFalse($val->validateValue('12E3')); + $this->assertFalse($val->validateValue('e12')); + $this->assertFalse($val->validateValue('-e3')); + $this->assertFalse($val->validateValue('-4.534-e-12')); + $this->assertFalse($val->validateValue('12.23^4')); + } + + public function testValidateValueMin() + { + $val = new NumberValidator(['min' => 1]); + $this->assertTrue($val->validateValue(1)); + $this->assertFalse($val->validateValue(-1)); + $this->assertFalse($val->validateValue('22e-12')); + $this->assertTrue($val->validateValue(PHP_INT_MAX + 1)); + $val = new NumberValidator(['min' => 1], ['integerOnly' => true]); + $this->assertTrue($val->validateValue(1)); + $this->assertFalse($val->validateValue(-1)); + $this->assertFalse($val->validateValue('22e-12')); + $this->assertTrue($val->validateValue(PHP_INT_MAX + 1)); + } + + public function testValidateValueMax() + { + $val = new NumberValidator(['max' => 1.25]); + $this->assertTrue($val->validateValue(1)); + $this->assertFalse($val->validateValue(1.5)); + $this->assertTrue($val->validateValue('22e-12')); + $this->assertTrue($val->validateValue('125e-2')); + $val = new NumberValidator(['max' => 1.25, 'integerOnly' => true]); + $this->assertTrue($val->validateValue(1)); + $this->assertFalse($val->validateValue(1.5)); + $this->assertFalse($val->validateValue('22e-12')); + $this->assertFalse($val->validateValue('125e-2')); + } + + public function testValidateValueRange() + { + $val = new NumberValidator(['min' => -10, 'max' => 20]); + $this->assertTrue($val->validateValue(0)); + $this->assertTrue($val->validateValue(-10)); + $this->assertFalse($val->validateValue(-11)); + $this->assertFalse($val->validateValue(21)); + $val = new NumberValidator(['min' => -10, 'max' => 20, 'integerOnly' => true]); + $this->assertTrue($val->validateValue(0)); + $this->assertFalse($val->validateValue(-11)); + $this->assertFalse($val->validateValue(22)); + $this->assertFalse($val->validateValue('20e-1')); + } + + public function testValidateAttribute() + { + $val = new NumberValidator(); + $model = new FakedValidationModel(); + $model->attr_number = '5.5e1'; + $val->validateAttribute($model, 'attr_number'); + $this->assertFalse($model->hasErrors('attr_number')); + $model->attr_number = '43^32'; //expression + $val->validateAttribute($model, 'attr_number'); + $this->assertTrue($model->hasErrors('attr_number')); + $val = new NumberValidator(['min' => 10]); + $model = new FakedValidationModel(); + $model->attr_number = 10; + $val->validateAttribute($model, 'attr_number'); + $this->assertFalse($model->hasErrors('attr_number')); + $model->attr_number = 5; + $val->validateAttribute($model, 'attr_number'); + $this->assertTrue($model->hasErrors('attr_number')); + $val = new NumberValidator(['max' => 10]); + $model = new FakedValidationModel(); + $model->attr_number = 10; + $val->validateAttribute($model, 'attr_number'); + $this->assertFalse($model->hasErrors('attr_number')); + $model->attr_number = 15; + $val->validateAttribute($model, 'attr_number'); + $this->assertTrue($model->hasErrors('attr_number')); + $val = new NumberValidator(['max' => 10, 'integerOnly' => true]); + $model = new FakedValidationModel(); + $model->attr_number = 10; + $val->validateAttribute($model, 'attr_number'); + $this->assertFalse($model->hasErrors('attr_number')); + $model->attr_number = 3.43; + $val->validateAttribute($model, 'attr_number'); + $this->assertTrue($model->hasErrors('attr_number')); + $val = new NumberValidator(['min' => 1]); + $model = FakedValidationModel::createWithAttributes(['attr_num' => [1,2,3]]); + $val->validateAttribute($model, 'attr_num'); + $this->assertTrue($model->hasErrors('attr_num')); + } + + public function testEnsureCustomMessageIsSetOnValidateAttribute() + { + $val = new NumberValidator([ + 'tooSmall' => '{attribute} is to small.', + 'min' => 5 + ]); + $model = new FakedValidationModel(); + $model->attr_number = 0; + $val->validateAttribute($model, 'attr_number'); + $this->assertTrue($model->hasErrors('attr_number')); + $this->assertEquals(1, count($model->getErrors('attr_number'))); + $msgs = $model->getErrors('attr_number'); + $this->assertSame('attr_number is to small.', $msgs[0]); + } +} \ No newline at end of file diff --git a/tests/unit/framework/validators/RangeValidatorTest.php b/tests/unit/framework/validators/RangeValidatorTest.php new file mode 100644 index 0000000..345951c --- /dev/null +++ b/tests/unit/framework/validators/RangeValidatorTest.php @@ -0,0 +1,76 @@ +<?php + +namespace yiiunit\framework\validators; + + +use yii\validators\RangeValidator; +use yiiunit\data\validators\models\FakedValidationModel; +use yiiunit\TestCase; + +class RangeValidatorTest extends TestCase +{ + protected function setUp() + { + parent::setUp(); + $this->mockApplication(); + } + + public function testInitException() + { + $this->setExpectedException('yii\base\InvalidConfigException', 'The "range" property must be set.'); + $val = new RangeValidator(['range' => 'not an array']); + } + + public function testAssureMessageSetOnInit() + { + $val = new RangeValidator(['range' => []]); + $this->assertTrue(is_string($val->message)); + } + + public function testValidateValue() + { + $val = new RangeValidator(['range' => range(1, 10, 1)]); + $this->assertTrue($val->validateValue(1)); + $this->assertFalse($val->validateValue(0)); + $this->assertFalse($val->validateValue(11)); + $this->assertFalse($val->validateValue(5.5)); + $this->assertTrue($val->validateValue(10)); + $this->assertTrue($val->validateValue("10")); + $this->assertTrue($val->validateValue("5")); + } + + public function testValidateValueStrict() + { + $val = new RangeValidator(['range' => range(1, 10, 1), 'strict' => true]); + $this->assertTrue($val->validateValue(1)); + $this->assertTrue($val->validateValue(5)); + $this->assertTrue($val->validateValue(10)); + $this->assertFalse($val->validateValue("1")); + $this->assertFalse($val->validateValue("10")); + $this->assertFalse($val->validateValue("5.5")); + } + + public function testValidateValueNot() + { + $val = new RangeValidator(['range' => range(1, 10, 1), 'not' => true]); + $this->assertFalse($val->validateValue(1)); + $this->assertTrue($val->validateValue(0)); + $this->assertTrue($val->validateValue(11)); + $this->assertTrue($val->validateValue(5.5)); + $this->assertFalse($val->validateValue(10)); + $this->assertFalse($val->validateValue("10")); + $this->assertFalse($val->validateValue("5")); + } + + public function testValidateAttribute() + { + $val = new RangeValidator(['range' => range(1, 10, 1)]); + $m = FakedValidationModel::createWithAttributes(['attr_r1' => 5, 'attr_r2' => 999]); + $val->validateAttribute($m, 'attr_r1'); + $this->assertFalse($m->hasErrors()); + $val->validateAttribute($m, 'attr_r2'); + $this->assertTrue($m->hasErrors('attr_r2')); + $err = $m->getErrors('attr_r2'); + $this->assertTrue(stripos($err[0], 'attr_r2') !== false); + } +} \ No newline at end of file diff --git a/tests/unit/framework/validators/RegularExpressionValidatorTest.php b/tests/unit/framework/validators/RegularExpressionValidatorTest.php new file mode 100644 index 0000000..e5b33b2 --- /dev/null +++ b/tests/unit/framework/validators/RegularExpressionValidatorTest.php @@ -0,0 +1,54 @@ +<?php + +namespace yiiunit\framework\validators; + + +use yii\validators\RegularExpressionValidator; +use yiiunit\data\validators\models\FakedValidationModel; +use yiiunit\TestCase; + +class RegularExpressionValidatorTest extends TestCase +{ + protected function setUp() + { + parent::setUp(); + $this->mockApplication(); + } + + public function testValidateValue() + { + $val = new RegularExpressionValidator(['pattern' => '/^[a-zA-Z0-9](\.)?([^\/]*)$/m']); + $this->assertTrue($val->validateValue('b.4')); + $this->assertFalse($val->validateValue('b./')); + $this->assertFalse($val->validateValue(['a', 'b'])); + $val->not = true; + $this->assertFalse($val->validateValue('b.4')); + $this->assertTrue($val->validateValue('b./')); + $this->assertFalse($val->validateValue(['a', 'b'])); + } + + public function testValidateAttribute() + { + $val = new RegularExpressionValidator(['pattern' => '/^[a-zA-Z0-9](\.)?([^\/]*)$/m']); + $m = FakedValidationModel::createWithAttributes(['attr_reg1' => 'b.4']); + $val->validateAttribute($m, 'attr_reg1'); + $this->assertFalse($m->hasErrors('attr_reg1')); + $m->attr_reg1 = 'b./'; + $val->validateAttribute($m, 'attr_reg1'); + $this->assertTrue($m->hasErrors('attr_reg1')); + } + + public function testMessageSetOnInit() + { + $val = new RegularExpressionValidator(['pattern' => '/^[a-zA-Z0-9](\.)?([^\/]*)$/m']); + $this->assertTrue(is_string($val->message)); + } + + public function testInitException() + { + $this->setExpectedException('yii\base\InvalidConfigException'); + $val = new RegularExpressionValidator(); + $val->validateValue('abc'); + } + +} \ No newline at end of file diff --git a/tests/unit/framework/validators/RequiredValidatorTest.php b/tests/unit/framework/validators/RequiredValidatorTest.php new file mode 100644 index 0000000..0708cc9 --- /dev/null +++ b/tests/unit/framework/validators/RequiredValidatorTest.php @@ -0,0 +1,60 @@ +<?php +namespace yiiunit\framework\validators; + + +use yii\validators\RequiredValidator; +use yiiunit\data\validators\models\FakedValidationModel; +use yiiunit\TestCase; + +class RequiredValidatorTest extends TestCase +{ + protected function setUp() + { + parent::setUp(); + $this->mockApplication(); + } + + public function testValidateValueWithDefaults() + { + $val = new RequiredValidator(); + $this->assertFalse($val->validateValue(null)); + $this->assertFalse($val->validateValue([])); + $this->assertTrue($val->validateValue('not empty')); + $this->assertTrue($val->validateValue(['with', 'elements'])); + } + + public function testValidateValueWithValue() + { + $val = new RequiredValidator(['requiredValue' => 55]); + $this->assertTrue($val->validateValue(55)); + $this->assertTrue($val->validateValue("55")); + $this->assertTrue($val->validateValue("0x37")); + $this->assertFalse($val->validateValue("should fail")); + $this->assertTrue($val->validateValue(true)); + $val->strict = true; + $this->assertTrue($val->validateValue(55)); + $this->assertFalse($val->validateValue("55")); + $this->assertFalse($val->validateValue("0x37")); + $this->assertFalse($val->validateValue("should fail")); + $this->assertFalse($val->validateValue(true)); + } + + public function testValidateAttribute() + { + // empty req-value + $val = new RequiredValidator(); + $m = FakedValidationModel::createWithAttributes(['attr_val' => null]); + $val->validateAttribute($m, 'attr_val'); + $this->assertTrue($m->hasErrors('attr_val')); + $this->assertTrue(stripos(current($m->getErrors('attr_val')), 'blank') !== false); + $val = new RequiredValidator(['requiredValue' => 55]); + $m = FakedValidationModel::createWithAttributes(['attr_val' => 56]); + $val->validateAttribute($m, 'attr_val'); + $this->assertTrue($m->hasErrors('attr_val')); + $this->assertTrue(stripos(current($m->getErrors('attr_val')), 'must be') !== false); + $val = new RequiredValidator(['requiredValue' => 55]); + $m = FakedValidationModel::createWithAttributes(['attr_val' => 55]); + $val->validateAttribute($m, 'attr_val'); + $this->assertFalse($m->hasErrors('attr_val')); + } +} \ No newline at end of file diff --git a/tests/unit/framework/validators/StringValidatorTest.php b/tests/unit/framework/validators/StringValidatorTest.php new file mode 100644 index 0000000..a99567b --- /dev/null +++ b/tests/unit/framework/validators/StringValidatorTest.php @@ -0,0 +1,116 @@ +<?php + +namespace yiiunit\framework\validators; + + +use yii\validators\StringValidator; +use yiiunit\data\validators\models\FakedValidationModel; +use yiiunit\TestCase; + +class StringValidatorTest extends TestCase +{ + public function setUp() + { + parent::setUp(); + $this->mockApplication(); + } + + public function testValidateValue() + { + $val = new StringValidator(); + $this->assertFalse($val->validateValue(['not a string'])); + $this->assertTrue($val->validateValue('Just some string')); + } + + public function testValidateValueLength() + { + $val = new StringValidator(['length' => 25]); + $this->assertTrue($val->validateValue(str_repeat('x', 25))); + $this->assertTrue($val->validateValue(str_repeat('€', 25))); + $this->assertFalse($val->validateValue(str_repeat('x', 125))); + $this->assertFalse($val->validateValue('')); + $val = new StringValidator(['length' => [25]]); + $this->assertTrue($val->validateValue(str_repeat('x', 25))); + $this->assertTrue($val->validateValue(str_repeat('x', 1250))); + $this->assertFalse($val->validateValue(str_repeat('Ä', 24))); + $this->assertFalse($val->validateValue('')); + $val = new StringValidator(['length' => [10, 20]]); + $this->assertTrue($val->validateValue(str_repeat('x', 15))); + $this->assertTrue($val->validateValue(str_repeat('x', 10))); + $this->assertTrue($val->validateValue(str_repeat('x', 20))); + $this->assertFalse($val->validateValue(str_repeat('x', 5))); + $this->assertFalse($val->validateValue(str_repeat('x', 25))); + $this->assertFalse($val->validateValue('')); + // make sure min/max are overridden + $val = new StringValidator(['length' => [10, 20], 'min' => 25, 'max' => 35]); + $this->assertTrue($val->validateValue(str_repeat('x', 15))); + $this->assertFalse($val->validateValue(str_repeat('x', 30))); + } + + public function testValidateValueMinMax() + { + $val = new StringValidator(['min' => 10]); + $this->assertTrue($val->validateValue(str_repeat('x', 10))); + $this->assertFalse($val->validateValue('xxxx')); + $val = new StringValidator(['max' => 10]); + $this->assertTrue($val->validateValue('xxxx')); + $this->assertFalse($val->validateValue(str_repeat('y', 20))); + $val = new StringValidator(['min' => 10, 'max' => 20]); + $this->assertTrue($val->validateValue(str_repeat('y', 15))); + $this->assertFalse($val->validateValue('abc')); + $this->assertFalse($val->validateValue(str_repeat('b', 25))); + } + + public function testValidateAttribute() + { + $val = new StringValidator(); + $model = new FakedValidationModel(); + $model->attr_string = 'a tet string'; + $val->validateAttribute($model, 'attr_string'); + $this->assertFalse($model->hasErrors()); + $val = new StringValidator(['length' => 20]); + $model = new FakedValidationModel(); + $model->attr_string = str_repeat('x', 20); + $val->validateAttribute($model, 'attr_string'); + $this->assertFalse($model->hasErrors()); + $model = new FakedValidationModel(); + $model->attr_string = 'abc'; + $val->validateAttribute($model, 'attr_string'); + $this->assertTrue($model->hasErrors('attr_string')); + $val = new StringValidator(['max' => 2]); + $model = new FakedValidationModel(); + $model->attr_string = 'a'; + $val->validateAttribute($model, 'attr_string'); + $this->assertFalse($model->hasErrors()); + $model = new FakedValidationModel(); + $model->attr_string = 'abc'; + $val->validateAttribute($model, 'attr_string'); + $this->assertTrue($model->hasErrors('attr_string')); + $val = new StringValidator(['max' => 1]); + $model = FakedValidationModel::createWithAttributes(['attr_str' => ['abc']]); + $val->validateAttribute($model, 'attr_str'); + $this->assertTrue($model->hasErrors('attr_str')); + } + + public function testEnsureMessagesOnInit() + { + $val = new StringValidator(['min' => 1, 'max' => 2]); + $this->assertTrue(is_string($val->message)); + $this->assertTrue(is_string($val->tooLong)); + $this->assertTrue(is_string($val->tooShort)); + } + + public function testCustomErrorMessageInValidateAttribute() + { + $val = new StringValidator([ + 'min' => 5, + 'tooShort' => '{attribute} to short. Min is {min}', + ]); + $model = new FakedValidationModel(); + $model->attr_string = 'abc'; + $val->validateAttribute($model, 'attr_string'); + $this->assertTrue($model->hasErrors('attr_string')); + $errorMsg = $model->getErrors('attr_string'); + $this->assertEquals('attr_string to short. Min is 5', $errorMsg[0]); + } +} \ No newline at end of file diff --git a/tests/unit/framework/validators/UniqueValidatorDriverTests/UniqueValidatorPostgresTest.php b/tests/unit/framework/validators/UniqueValidatorDriverTests/UniqueValidatorPostgresTest.php new file mode 100644 index 0000000..9adc57c --- /dev/null +++ b/tests/unit/framework/validators/UniqueValidatorDriverTests/UniqueValidatorPostgresTest.php @@ -0,0 +1,11 @@ +<?php + +namespace yiiunit\framework\validators\UniqueValidatorDriverTests; + + +use yiiunit\framework\validators\UniqueValidatorTest; + +class UniqueValidatorPostgresTest extends UniqueValidatorTest +{ + protected $driverName = 'pgsql'; +} \ No newline at end of file diff --git a/tests/unit/framework/validators/UniqueValidatorDriverTests/UniqueValidatorSQliteTest.php b/tests/unit/framework/validators/UniqueValidatorDriverTests/UniqueValidatorSQliteTest.php new file mode 100644 index 0000000..9263404 --- /dev/null +++ b/tests/unit/framework/validators/UniqueValidatorDriverTests/UniqueValidatorSQliteTest.php @@ -0,0 +1,11 @@ +<?php + +namespace yiiunit\framework\validators\UniqueValidatorDriverTests; + + +use yiiunit\framework\validators\UniqueValidatorTest; + +class UniqueValidatorSQliteTest extends UniqueValidatorTest +{ + protected $driverName = 'sqlite'; +} \ No newline at end of file diff --git a/tests/unit/framework/validators/UniqueValidatorTest.php b/tests/unit/framework/validators/UniqueValidatorTest.php new file mode 100644 index 0000000..d0a7091 --- /dev/null +++ b/tests/unit/framework/validators/UniqueValidatorTest.php @@ -0,0 +1,88 @@ +<?php + +namespace yiiunit\framework\validators; + + +use yii\validators\UniqueValidator; +use Yii; +use yiiunit\data\ar\ActiveRecord; +use yiiunit\data\validators\models\FakedValidationModel; +use yiiunit\data\validators\models\ValidatorTestMainModel; +use yiiunit\data\validators\models\ValidatorTestRefModel; +use yiiunit\framework\db\DatabaseTestCase; + +class UniqueValidatorTest extends DatabaseTestCase +{ + protected $driverName = 'mysql'; + + public function setUp() + { + parent::setUp(); + $this->mockApplication(); + ActiveRecord::$db = $this->getConnection(); + } + + public function testAssureMessageSetOnInit() + { + $val = new UniqueValidator(); + $this->assertTrue(is_string($val->message)); + } + + public function testValidateAttributeDefault() + { + $val = new UniqueValidator(); + $m = ValidatorTestMainModel::find()->one(); + $val->validateAttribute($m, 'id'); + $this->assertFalse($m->hasErrors('id')); + $m = ValidatorTestRefModel::find(1); + $val->validateAttribute($m, 'ref'); + $this->assertTrue($m->hasErrors('ref')); + // new record: + $m = new ValidatorTestRefModel(); + $m->ref = 5; + $val->validateAttribute($m, 'ref'); + $this->assertTrue($m->hasErrors('ref')); + $m = new ValidatorTestRefModel(); + $m->id = 7; + $m->ref = 12121; + $val->validateAttribute($m, 'ref'); + $this->assertFalse($m->hasErrors('ref')); + $m->save(false); + $val->validateAttribute($m, 'ref'); + $this->assertFalse($m->hasErrors('ref')); + // array error + $m = FakedValidationModel::createWithAttributes(['attr_arr' => ['a', 'b']]); + $val->validateAttribute($m, 'attr_arr'); + $this->assertTrue($m->hasErrors('attr_arr')); + } + + public function testValidateAttributeOfNonARModel() + { + $val = new UniqueValidator(['className' => ValidatorTestRefModel::className(), 'attributeName' => 'ref']); + $m = FakedValidationModel::createWithAttributes(['attr_1' => 5, 'attr_2' => 1313]); + $val->validateAttribute($m, 'attr_1'); + $this->assertTrue($m->hasErrors('attr_1')); + $val->validateAttribute($m, 'attr_2'); + $this->assertFalse($m->hasErrors('attr_2')); + } + + public function testValidateNonDatabaseAttribute() + { + $val = new UniqueValidator(['className' => ValidatorTestRefModel::className(), 'attributeName' => 'ref']); + $m = ValidatorTestMainModel::find(1); + $val->validateAttribute($m, 'testMainVal'); + $this->assertFalse($m->hasErrors('testMainVal')); + $m = ValidatorTestMainModel::find(1); + $m->testMainVal = 4; + $val->validateAttribute($m, 'testMainVal'); + $this->assertTrue($m->hasErrors('testMainVal')); + } + + public function testValidateAttributeAttributeNotInTableException() + { + $this->setExpectedException('yii\base\InvalidConfigException'); + $val = new UniqueValidator(); + $m = new ValidatorTestMainModel(); + $val->validateAttribute($m, 'testMainVal'); + } +} \ No newline at end of file diff --git a/tests/unit/framework/validators/UrlValidatorTest.php b/tests/unit/framework/validators/UrlValidatorTest.php new file mode 100644 index 0000000..50baa9c --- /dev/null +++ b/tests/unit/framework/validators/UrlValidatorTest.php @@ -0,0 +1,100 @@ +<?php +namespace yiiunit\framework\validators; +use yiiunit\data\validators\models\FakedValidationModel; +use yii\validators\UrlValidator; +use yiiunit\TestCase; + +/** + * UrlValidatorTest + */ +class UrlValidatorTest extends TestCase +{ + protected function setUp() + { + parent::setUp(); + $this->mockApplication(); + } + + public function testValidateValue() + { + $val = new UrlValidator; + $this->assertFalse($val->validateValue('google.de')); + $this->assertTrue($val->validateValue('http://google.de')); + $this->assertTrue($val->validateValue('https://google.de')); + $this->assertFalse($val->validateValue('htp://yiiframework.com')); + $this->assertTrue($val->validateValue('https://www.google.de/search?q=yii+framework&ie=utf-8&oe=utf-8' + .'&rls=org.mozilla:de:official&client=firefox-a&gws_rd=cr')); + $this->assertFalse($val->validateValue('ftp://ftp.ruhr-uni-bochum.de/')); + $this->assertFalse($val->validateValue('http://invalid,domain')); + $this->assertFalse($val->validateValue('http://äüö?=!"§$%&/()=}][{³²€.edu')); + } + + public function testValidateValueWithDefaultScheme() + { + $val = new UrlValidator(['defaultScheme' => 'https']); + $this->assertTrue($val->validateValue('yiiframework.com')); + $this->assertTrue($val->validateValue('http://yiiframework.com')); + } + + public function testValidateValueWithoutScheme() + { + $val = new UrlValidator(['pattern' => '/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)/i']); + $this->assertTrue($val->validateValue('yiiframework.com')); + } + + public function testValidateWithCustomScheme() + { + $val = new UrlValidator([ + 'validSchemes' => ['http', 'https', 'ftp', 'ftps'], + 'defaultScheme' => 'http', + ]); + $this->assertTrue($val->validateValue('ftp://ftp.ruhr-uni-bochum.de/')); + $this->assertTrue($val->validateValue('google.de')); + $this->assertTrue($val->validateValue('http://google.de')); + $this->assertTrue($val->validateValue('https://google.de')); + $this->assertFalse($val->validateValue('htp://yiiframework.com')); + // relative urls not supported + $this->assertFalse($val->validateValue('//yiiframework.com')); + } + + public function testValidateWithIdn() + { + if (!function_exists('idn_to_ascii')) { + $this->markTestSkipped('intl package required'); + return; + } + $val = new UrlValidator([ + 'enableIDN' => true, + ]); + $this->assertTrue($val->validateValue('http://äüößìà.de')); + // converted via http://mct.verisign-grs.com/convertServlet + $this->assertTrue($val->validateValue('http://xn--zcack7ayc9a.de')); + } + + public function testValidateLength() + { + $url = 'http://' . str_pad('base', 2000, 'url') . '.de'; + $val = new UrlValidator; + $this->assertFalse($val->validateValue($url)); + } + + public function testValidateAttributeAndError() + { + $obj = new FakedValidationModel; + $obj->attr_url = 'http://google.de'; + $val = new UrlValidator; + $val->validateAttribute($obj, 'attr_url'); + $this->assertFalse($obj->hasErrors('attr_url')); + $this->assertSame('http://google.de', $obj->attr_url); + $obj = new FakedValidationModel; + $val->defaultScheme = 'http'; + $obj->attr_url = 'google.de'; + $val->validateAttribute($obj, 'attr_url'); + $this->assertFalse($obj->hasErrors('attr_url')); + $this->assertTrue(stripos($obj->attr_url, 'http') !== false); + $obj = new FakedValidationModel; + $obj->attr_url = 'gttp;/invalid string'; + $val->validateAttribute($obj, 'attr_url'); + $this->assertTrue($obj->hasErrors('attr_url')); + } +} diff --git a/tests/unit/framework/validators/ValidatorTest.php b/tests/unit/framework/validators/ValidatorTest.php new file mode 100644 index 0000000..5e5385b --- /dev/null +++ b/tests/unit/framework/validators/ValidatorTest.php @@ -0,0 +1,232 @@ +<?php + +namespace yiiunit\framework\validators; + + +use yii\validators\BooleanValidator; +use yii\validators\InlineValidator; +use yii\validators\NumberValidator; +use yiiunit\data\validators\models\FakedValidationModel; +use yiiunit\data\validators\TestValidator; +use yiiunit\TestCase; + +class ValidatorTest extends TestCase +{ + protected function setUp() + { + parent::setUp(); + $this->mockApplication(); + } + + protected function getTestModel($additionalAttributes = []) + { + $attributes = array_merge( + ['attr_runMe1' => true, 'attr_runMe2' => true, 'attr_skip' => true], + $additionalAttributes + ); + return FakedValidationModel::createWithAttributes($attributes); + } + + public function testCreateValidator() + { + $model = FakedValidationModel::createWithAttributes(['attr_test1' => 'abc', 'attr_test2' => '2013']); + /** @var NumberValidator $numberVal */ + $numberVal = TestValidator::createValidator('number', $model, ['attr_test1']); + $this->assertInstanceOf(NumberValidator::className(), $numberVal); + $numberVal = TestValidator::createValidator('integer', $model, ['attr_test2']); + $this->assertInstanceOf(NumberValidator::className(), $numberVal); + $this->assertTrue($numberVal->integerOnly); + $val = TestValidator::createValidator( + 'boolean', + $model, + ['attr_test1', 'attr_test2'], + ['on' => ['a', 'b']] + ); + $this->assertInstanceOf(BooleanValidator::className(), $val); + $this->assertSame(['a', 'b'], $val->on); + $this->assertSame(['attr_test1', 'attr_test2'], $val->attributes); + $val = TestValidator::createValidator( + 'boolean', + $model, + ['attr_test1', 'attr_test2'], + ['on' => ['a', 'b'], 'except' => ['c', 'd', 'e']] + ); + $this->assertInstanceOf(BooleanValidator::className(), $val); + $this->assertSame(['a', 'b'], $val->on); + $this->assertSame(['c', 'd', 'e'], $val->except); + $val = TestValidator::createValidator('inlineVal', $model, ['val_attr_a']); + $this->assertInstanceOf(InlineValidator::className(), $val); + $this->assertSame('inlineVal', $val->method); + } + + public function testValidate() + { + $val = new TestValidator(['attributes' => ['attr_runMe1', 'attr_runMe2']]); + $model = $this->getTestModel(); + $val->validate($model); + $this->assertTrue($val->isAttributeValidated('attr_runMe1')); + $this->assertTrue($val->isAttributeValidated('attr_runMe2')); + $this->assertFalse($val->isAttributeValidated('attr_skip')); + } + + public function testValidateWithAttributeIntersect() + { + $val = new TestValidator(['attributes' => ['attr_runMe1', 'attr_runMe2']]); + $model = $this->getTestModel(); + $val->validate($model, ['attr_runMe1']); + $this->assertTrue($val->isAttributeValidated('attr_runMe1')); + $this->assertFalse($val->isAttributeValidated('attr_runMe2')); + $this->assertFalse($val->isAttributeValidated('attr_skip')); + } + + public function testValidateWithEmptyAttributes() + { + $val = new TestValidator(); + $model = $this->getTestModel(); + $val->validate($model, ['attr_runMe1']); + $this->assertFalse($val->isAttributeValidated('attr_runMe1')); + $this->assertFalse($val->isAttributeValidated('attr_runMe2')); + $this->assertFalse($val->isAttributeValidated('attr_skip')); + $val->validate($model); + $this->assertFalse($val->isAttributeValidated('attr_runMe1')); + $this->assertFalse($val->isAttributeValidated('attr_runMe2')); + $this->assertFalse($val->isAttributeValidated('attr_skip')); + } + + public function testValidateWithError() + { + $val = new TestValidator(['attributes' => ['attr_runMe1', 'attr_runMe2'], 'skipOnError' => false]); + $model = $this->getTestModel(); + $val->validate($model); + $this->assertTrue($val->isAttributeValidated('attr_runMe1')); + $this->assertTrue($val->isAttributeValidated('attr_runMe2')); + $this->assertFalse($val->isAttributeValidated('attr_skip')); + $this->assertEquals(1, $val->countAttributeValidations('attr_runMe2')); + $this->assertEquals(1, $val->countAttributeValidations('attr_runMe1')); + $val->validate($model, ['attr_runMe2']); + $this->assertEquals(2, $val->countAttributeValidations('attr_runMe2')); + $this->assertEquals(1, $val->countAttributeValidations('attr_runMe1')); + $this->assertEquals(0, $val->countAttributeValidations('attr_skip')); + $val = new TestValidator(['attributes' => ['attr_runMe1', 'attr_runMe2'], 'skipOnError' => true]); + $model = $this->getTestModel(); + $val->enableErrorOnValidateAttribute(); + $val->validate($model); + $this->assertTrue($val->isAttributeValidated('attr_runMe1')); + $this->assertTrue($val->isAttributeValidated('attr_runMe2')); + $this->assertFalse($val->isAttributeValidated('attr_skip')); + $this->assertEquals(1, $val->countAttributeValidations('attr_runMe1')); + $this->assertEquals(1, $val->countAttributeValidations('attr_runMe1')); + $this->assertEquals(0, $val->countAttributeValidations('attr_skip')); + $val->validate($model, ['attr_runMe2']); + $this->assertEquals(1, $val->countAttributeValidations('attr_runMe2')); + $this->assertEquals(1, $val->countAttributeValidations('attr_runMe1')); + $this->assertEquals(0, $val->countAttributeValidations('attr_skip')); + } + + public function testValidateWithEmpty() + { + $val = new TestValidator([ + 'attributes' => [ + 'attr_runMe1', + 'attr_runMe2', + 'attr_empty1', + 'attr_empty2' + ], + 'skipOnEmpty' => true, + ]); + $model = $this->getTestModel(['attr_empty1' => '', 'attr_emtpy2' => ' ']); + $val->validate($model); + $this->assertTrue($val->isAttributeValidated('attr_runMe1')); + $this->assertTrue($val->isAttributeValidated('attr_runMe2')); + $this->assertFalse($val->isAttributeValidated('attr_empty1')); + $this->assertFalse($val->isAttributeValidated('attr_empty2')); + $model->attr_empty1 = 'not empty anymore'; + $val->validate($model); + $this->assertTrue($val->isAttributeValidated('attr_empty1')); + $this->assertFalse($val->isAttributeValidated('attr_empty2')); + $val = new TestValidator([ + 'attributes' => [ + 'attr_runMe1', + 'attr_runMe2', + 'attr_empty1', + 'attr_empty2' + ], + 'skipOnEmpty' => false, + ]); + $model = $this->getTestModel(['attr_empty1' => '', 'attr_emtpy2' => ' ']); + $val->validate($model); + $this->assertTrue($val->isAttributeValidated('attr_runMe1')); + $this->assertTrue($val->isAttributeValidated('attr_runMe2')); + $this->assertTrue($val->isAttributeValidated('attr_empty1')); + $this->assertTrue($val->isAttributeValidated('attr_empty2')); + } + + public function testIsEmpty() + { + $val = new TestValidator(); + $this->assertTrue($val->isEmpty(null)); + $this->assertTrue($val->isEmpty([])); + $this->assertTrue($val->isEmpty('')); + $this->assertFalse($val->isEmpty(5)); + $this->assertFalse($val->isEmpty(0)); + $this->assertFalse($val->isEmpty(new \stdClass())); + $this->assertFalse($val->isEmpty(' ')); + // trim + $this->assertTrue($val->isEmpty(' ', true)); + $this->assertTrue($val->isEmpty('', true)); + $this->assertTrue($val->isEmpty(" \t\n\r\0\x0B", true)); + $this->assertTrue($val->isEmpty('', true)); + $this->assertFalse($val->isEmpty('0', true)); + $this->assertFalse($val->isEmpty(0, true)); + $this->assertFalse($val->isEmpty('this ain\'t an empty value', true)); + } + + public function testValidateValue() + { + $this->setExpectedException( + 'yii\base\NotSupportedException', + TestValidator::className() . ' does not support validateValue().' + ); + $val = new TestValidator(); + $val->validateValue('abc'); + } + + public function testClientValidateAttribute() + { + $val = new TestValidator(); + $this->assertNull( + $val->clientValidateAttribute($this->getTestModel(), 'attr_runMe1', []) + ); //todo pass a view instead of array + } + + public function testIsActive() + { + $val = new TestValidator(); + $this->assertTrue($val->isActive('scenA')); + $this->assertTrue($val->isActive('scenB')); + $val->except = ['scenB']; + $this->assertTrue($val->isActive('scenA')); + $this->assertFalse($val->isActive('scenB')); + $val->on = ['scenC']; + $this->assertFalse($val->isActive('scenA')); + $this->assertFalse($val->isActive('scenB')); + $this->assertTrue($val->isActive('scenC')); + } + + public function testAddError() + { + $val = new TestValidator(); + $m = $this->getTestModel(['attr_msg_val' => 'abc']); + $val->addError($m, 'attr_msg_val', '{attribute}::{value}'); + $errors = $m->getErrors('attr_msg_val'); + $this->assertEquals('attr_msg_val::abc', $errors[0]); + $m = $this->getTestModel(['attr_msg_val' => ['bcc']]); + $val->addError($m, 'attr_msg_val', '{attribute}::{value}'); + $errors = $m->getErrors('attr_msg_val'); + $this->assertEquals('attr_msg_val::array()', $errors[0]); + $m = $this->getTestModel(['attr_msg_val' => 'abc']); + $val->addError($m, 'attr_msg_val', '{attribute}::{value}::{param}', ['param' => 'param_value']); + $errors = $m->getErrors('attr_msg_val'); + $this->assertEquals('attr_msg_val::abc::param_value', $errors[0]); + } +} diff --git a/tests/unit/framework/web/AssetBundleTest.php b/tests/unit/framework/web/AssetBundleTest.php new file mode 100644 index 0000000..24c36d6 --- /dev/null +++ b/tests/unit/framework/web/AssetBundleTest.php @@ -0,0 +1,256 @@ +<?php +/** + * + * + * @author Carsten Brandt <mail@cebe.cc> + */ + +namespace yiiunit\framework\web; + +use Yii; +use yii\web\View; +use yii\web\AssetBundle; +use yii\web\AssetManager; + +/** + * @group web + */ +class AssetBundleTest extends \yiiunit\TestCase +{ + protected function setUp() + { + parent::setUp(); + $this->mockApplication(); + + Yii::setAlias('@testWeb', '/'); + Yii::setAlias('@testWebRoot', '@yiiunit/data/web'); + } + + protected function getView() + { + $view = new View(); + $view->setAssetManager(new AssetManager([ + 'basePath' => '@testWebRoot/assets', + 'baseUrl' => '@testWeb/assets', + ])); + + return $view; + } + + public function testRegister() + { + $view = $this->getView(); + + $this->assertEmpty($view->assetBundles); + TestSimpleAsset::register($view); + $this->assertEquals(1, count($view->assetBundles)); + $this->assertArrayHasKey('yiiunit\\framework\\web\\TestSimpleAsset', $view->assetBundles); + $this->assertTrue($view->assetBundles['yiiunit\\framework\\web\\TestSimpleAsset'] instanceof AssetBundle); + + $expected = <<<EOF +123<script src="/js/jquery.js"></script>4 +EOF; + $this->assertEquals($expected, $view->renderFile('@yiiunit/data/views/rawlayout.php')); + } + + public function testSimpleDependency() + { + $view = $this->getView(); + + $this->assertEmpty($view->assetBundles); + TestAssetBundle::register($view); + $this->assertEquals(3, count($view->assetBundles)); + $this->assertArrayHasKey('yiiunit\\framework\\web\\TestAssetBundle', $view->assetBundles); + $this->assertArrayHasKey('yiiunit\\framework\\web\\TestJqueryAsset', $view->assetBundles); + $this->assertArrayHasKey('yiiunit\\framework\\web\\TestAssetLevel3', $view->assetBundles); + $this->assertTrue($view->assetBundles['yiiunit\\framework\\web\\TestAssetBundle'] instanceof AssetBundle); + $this->assertTrue($view->assetBundles['yiiunit\\framework\\web\\TestJqueryAsset'] instanceof AssetBundle); + $this->assertTrue($view->assetBundles['yiiunit\\framework\\web\\TestAssetLevel3'] instanceof AssetBundle); + + $expected = <<<EOF +1<link href="/files/cssFile.css" rel="stylesheet">23<script src="/js/jquery.js"></script> +<script src="/files/jsFile.js"></script>4 +EOF; + $this->assertEquals($expected, $view->renderFile('@yiiunit/data/views/rawlayout.php')); + } + + public function positionProvider() + { + return [ + [View::POS_HEAD, true], + [View::POS_HEAD, false], + [View::POS_BEGIN, true], + [View::POS_BEGIN, false], + [View::POS_END, true], + [View::POS_END, false], + ]; + } + + /** + * @dataProvider positionProvider + */ + public function testPositionDependency($pos, $jqAlreadyRegistered) + { + $view = $this->getView(); + + $view->getAssetManager()->bundles['yiiunit\\framework\\web\\TestAssetBundle'] = [ + 'jsOptions' => [ + 'position' => $pos, + ], + ]; + + $this->assertEmpty($view->assetBundles); + if ($jqAlreadyRegistered) { + TestJqueryAsset::register($view); + } + TestAssetBundle::register($view); + $this->assertEquals(3, count($view->assetBundles)); + $this->assertArrayHasKey('yiiunit\\framework\\web\\TestAssetBundle', $view->assetBundles); + $this->assertArrayHasKey('yiiunit\\framework\\web\\TestJqueryAsset', $view->assetBundles); + $this->assertArrayHasKey('yiiunit\\framework\\web\\TestAssetLevel3', $view->assetBundles); + + $this->assertTrue($view->assetBundles['yiiunit\\framework\\web\\TestAssetBundle'] instanceof AssetBundle); + $this->assertTrue($view->assetBundles['yiiunit\\framework\\web\\TestJqueryAsset'] instanceof AssetBundle); + $this->assertTrue($view->assetBundles['yiiunit\\framework\\web\\TestAssetLevel3'] instanceof AssetBundle); + + $this->assertArrayHasKey('position', $view->assetBundles['yiiunit\\framework\\web\\TestAssetBundle']->jsOptions); + $this->assertEquals($pos, $view->assetBundles['yiiunit\\framework\\web\\TestAssetBundle']->jsOptions['position']); + $this->assertArrayHasKey('position', $view->assetBundles['yiiunit\\framework\\web\\TestJqueryAsset']->jsOptions); + $this->assertEquals($pos, $view->assetBundles['yiiunit\\framework\\web\\TestJqueryAsset']->jsOptions['position']); + $this->assertArrayHasKey('position', $view->assetBundles['yiiunit\\framework\\web\\TestAssetLevel3']->jsOptions); + $this->assertEquals($pos, $view->assetBundles['yiiunit\\framework\\web\\TestAssetLevel3']->jsOptions['position']); + + switch($pos) + { + case View::POS_HEAD: + $expected = <<<EOF +1<link href="/files/cssFile.css" rel="stylesheet"> +<script src="/js/jquery.js"></script> +<script src="/files/jsFile.js"></script>234 +EOF; + break; + case View::POS_BEGIN: + $expected = <<<EOF +1<link href="/files/cssFile.css" rel="stylesheet">2<script src="/js/jquery.js"></script> +<script src="/files/jsFile.js"></script>34 +EOF; + break; + default: + case View::POS_END: + $expected = <<<EOF +1<link href="/files/cssFile.css" rel="stylesheet">23<script src="/js/jquery.js"></script> +<script src="/files/jsFile.js"></script>4 +EOF; + break; + } + $this->assertEquals($expected, $view->renderFile('@yiiunit/data/views/rawlayout.php')); + } + + public function positionProvider2() + { + return [ + [View::POS_BEGIN, true], + [View::POS_BEGIN, false], + [View::POS_END, true], + [View::POS_END, false], + ]; + } + + /** + * @dataProvider positionProvider + */ + public function testPositionDependencyConflict($pos, $jqAlreadyRegistered) + { + $view = $this->getView(); + + $view->getAssetManager()->bundles['yiiunit\\framework\\web\\TestAssetBundle'] = [ + 'jsOptions' => [ + 'position' => $pos - 1, + ], + ]; + $view->getAssetManager()->bundles['yiiunit\\framework\\web\\TestJqueryAsset'] = [ + 'jsOptions' => [ + 'position' => $pos, + ], + ]; + + $this->assertEmpty($view->assetBundles); + if ($jqAlreadyRegistered) { + TestJqueryAsset::register($view); + } + $this->setExpectedException('yii\\base\\InvalidConfigException'); + TestAssetBundle::register($view); + } + + public function testCircularDependency() + { + $this->setExpectedException('yii\\base\\InvalidConfigException'); + TestAssetCircleA::register($this->getView()); + } +} + +class TestSimpleAsset extends AssetBundle +{ + public $basePath = '@testWebRoot/js'; + public $baseUrl = '@testWeb/js'; + public $js = [ + 'jquery.js', + ]; +} + +class TestAssetBundle extends AssetBundle +{ + public $basePath = '@testWebRoot/files'; + public $baseUrl = '@testWeb/files'; + public $css = [ + 'cssFile.css', + ]; + public $js = [ + 'jsFile.js', + ]; + public $depends = [ + 'yiiunit\\framework\\web\\TestJqueryAsset' + ]; +} + +class TestJqueryAsset extends AssetBundle +{ + public $basePath = '@testWebRoot/js'; + public $baseUrl = '@testWeb/js'; + public $js = [ + 'jquery.js', + ]; + public $depends = [ + 'yiiunit\\framework\\web\\TestAssetLevel3' + ]; +} + +class TestAssetLevel3 extends AssetBundle +{ + public $basePath = '@testWebRoot/js'; + public $baseUrl = '@testWeb/js'; +} + +class TestAssetCircleA extends AssetBundle +{ + public $basePath = '@testWebRoot/js'; + public $baseUrl = '@testWeb/js'; + public $js = [ + 'jquery.js', + ]; + public $depends = [ + 'yiiunit\\framework\\web\\TestAssetCircleB' + ]; +} + +class TestAssetCircleB extends AssetBundle +{ + public $basePath = '@testWebRoot/js'; + public $baseUrl = '@testWeb/js'; + public $js = [ + 'jquery.js', + ]; + public $depends = [ + 'yiiunit\\framework\\web\\TestAssetCircleA' + ]; +} \ No newline at end of file diff --git a/tests/unit/framework/web/AssetConverterTest.php b/tests/unit/framework/web/AssetConverterTest.php new file mode 100644 index 0000000..54669b3 --- /dev/null +++ b/tests/unit/framework/web/AssetConverterTest.php @@ -0,0 +1,42 @@ +<?php +/** + * @author Carsten Brandt <mail@cebe.cc> + */ + +namespace yiiunit\framework\web; +use yii\web\AssetConverter; + +/** + * @group web + */ +class AssetConverterTest extends \yiiunit\TestCase +{ + protected function setUp() + { + parent::setUp(); + $this->mockApplication(); + } + + + public function testConvert() + { + $tmpPath = \Yii::$app->runtimePath . '/assetConverterTest'; + if (!is_dir($tmpPath)) { + mkdir($tmpPath, 0777, true); + } + file_put_contents($tmpPath . '/test.php', <<<EOF +<?php + +echo "Hello World!\n"; +echo "Hello Yii!"; +EOF + ); + + $converter = new AssetConverter(); + $converter->commands['php'] = ['txt', 'php {from} > {to}']; + $this->assertEquals('test.txt', $converter->convert('test.php', $tmpPath)); + + $this->assertTrue(file_exists($tmpPath . '/test.txt'), 'Failed asserting that asset output file exists.'); + $this->assertEquals("Hello World!\nHello Yii!", file_get_contents($tmpPath . '/test.txt')); + } +} \ No newline at end of file diff --git a/tests/unit/framework/web/CacheSessionTest.php b/tests/unit/framework/web/CacheSessionTest.php index e740596..ae73868 100644 --- a/tests/unit/framework/web/CacheSessionTest.php +++ b/tests/unit/framework/web/CacheSessionTest.php @@ -31,9 +31,6 @@ class CacheSessionTest extends \yiiunit\TestCase public function testInvalidCache() { $this->setExpectedException('yii\base\InvalidConfigException'); - - $session = new CacheSession(array( - 'cache' => 'invalid', - )); + $session = new CacheSession(['cache' => 'invalid']); } } diff --git a/tests/unit/framework/web/ResponseTest.php b/tests/unit/framework/web/ResponseTest.php index 2a9b4bf..6077945 100644 --- a/tests/unit/framework/web/ResponseTest.php +++ b/tests/unit/framework/web/ResponseTest.php @@ -34,11 +34,11 @@ class ResponseTest extends \yiiunit\TestCase { // TODO test more cases for range requests and check for rfc compatibility // http://www.w3.org/Protocols/rfc2616/rfc2616.txt - return array( - array('0-5', '0-5', 6, '12ёж'), - array('2-', '2-66', 65, 'ёжик3456798áèabcdefghijklmnopqrstuvwxyz!"§$%&/(ёжик)=?'), - array('-12', '55-66', 12, '(ёжик)=?'), - ); + return [ + ['0-5', '0-5', 6, '12ёж'], + ['2-', '2-66', 65, 'ёжик3456798áèabcdefghijklmnopqrstuvwxyz!"§$%&/(ёжик)=?'], + ['-12', '55-66', 12, '(ёжик)=?'], + ]; } /** @@ -66,12 +66,12 @@ class ResponseTest extends \yiiunit\TestCase { // TODO test more cases for range requests and check for rfc compatibility // http://www.w3.org/Protocols/rfc2616/rfc2616.txt - return array( - array('1-2,3-5,6-10'), // multiple range request not supported - array('5-1'), // last-byte-pos value is less than its first-byte-pos value - array('-100000'), // last-byte-pos bigger then content length - array('10000-'), // first-byte-pos bigger then content length - ); + return [ + ['1-2,3-5,6-10'], // multiple range request not supported + ['5-1'], // last-byte-pos value is less than its first-byte-pos value + ['-100000'], // last-byte-pos bigger then content length + ['10000-'], // first-byte-pos bigger then content length + ]; } /** diff --git a/tests/unit/framework/web/UrlManagerTest.php b/tests/unit/framework/web/UrlManagerTest.php index a77a66d..2c3d458 100644 --- a/tests/unit/framework/web/UrlManagerTest.php +++ b/tests/unit/framework/web/UrlManagerTest.php @@ -19,205 +19,203 @@ class UrlManagerTest extends TestCase public function testCreateUrl() { // default setting with '/' as base url - $manager = new UrlManager(array( + $manager = new UrlManager([ 'baseUrl' => '/', 'cache' => null, - )); + ]); $url = $manager->createUrl('post/view'); $this->assertEquals('?r=post/view', $url); - $url = $manager->createUrl('post/view', array('id' => 1, 'title' => 'sample post')); + $url = $manager->createUrl('post/view', ['id' => 1, 'title' => 'sample post']); $this->assertEquals('?r=post/view&id=1&title=sample+post', $url); // default setting with '/test/' as base url - $manager = new UrlManager(array( + $manager = new UrlManager([ 'baseUrl' => '/test/', 'cache' => null, - )); - $url = $manager->createUrl('post/view', array('id' => 1, 'title' => 'sample post')); + ]); + $url = $manager->createUrl('post/view', ['id' => 1, 'title' => 'sample post']); $this->assertEquals('/test?r=post/view&id=1&title=sample+post', $url); // pretty URL without rules - $manager = new UrlManager(array( + $manager = new UrlManager([ 'enablePrettyUrl' => true, 'baseUrl' => '/', 'cache' => null, - )); - $url = $manager->createUrl('post/view', array('id' => 1, 'title' => 'sample post')); + ]); + $url = $manager->createUrl('post/view', ['id' => 1, 'title' => 'sample post']); $this->assertEquals('/post/view?id=1&title=sample+post', $url); - $manager = new UrlManager(array( + $manager = new UrlManager([ 'enablePrettyUrl' => true, 'baseUrl' => '/test/', 'cache' => null, - )); - $url = $manager->createUrl('post/view', array('id' => 1, 'title' => 'sample post')); + ]); + $url = $manager->createUrl('post/view', ['id' => 1, 'title' => 'sample post']); $this->assertEquals('/test/post/view?id=1&title=sample+post', $url); - $manager = new UrlManager(array( + $manager = new UrlManager([ 'enablePrettyUrl' => true, 'baseUrl' => '/test/index.php', 'cache' => null, - )); - $url = $manager->createUrl('post/view', array('id' => 1, 'title' => 'sample post')); + ]); + $url = $manager->createUrl('post/view', ['id' => 1, 'title' => 'sample post']); $this->assertEquals('/test/index.php/post/view?id=1&title=sample+post', $url); // todo: test showScriptName // pretty URL with rules - $manager = new UrlManager(array( + $manager = new UrlManager([ 'enablePrettyUrl' => true, 'cache' => null, - 'rules' => array( - array( + 'rules' => [ + [ 'pattern' => 'post/<id>/<title>', 'route' => 'post/view', - ), - ), + ], + ], 'baseUrl' => '/', - )); - $url = $manager->createUrl('post/view', array('id' => 1, 'title' => 'sample post')); + ]); + $url = $manager->createUrl('post/view', ['id' => 1, 'title' => 'sample post']); $this->assertEquals('/post/1/sample+post', $url); - $url = $manager->createUrl('post/index', array('page' => 1)); + $url = $manager->createUrl('post/index', ['page' => 1]); $this->assertEquals('/post/index?page=1', $url); // pretty URL with rules and suffix - $manager = new UrlManager(array( + $manager = new UrlManager([ 'enablePrettyUrl' => true, 'cache' => null, - 'rules' => array( - array( + 'rules' => [ + [ 'pattern' => 'post/<id>/<title>', 'route' => 'post/view', - ), - ), + ], + ], 'baseUrl' => '/', 'suffix' => '.html', - )); - $url = $manager->createUrl('post/view', array('id' => 1, 'title' => 'sample post')); + ]); + $url = $manager->createUrl('post/view', ['id' => 1, 'title' => 'sample post']); $this->assertEquals('/post/1/sample+post.html', $url); - $url = $manager->createUrl('post/index', array('page' => 1)); + $url = $manager->createUrl('post/index', ['page' => 1]); $this->assertEquals('/post/index.html?page=1', $url); // pretty URL with rules that have host info - $manager = new UrlManager(array( + $manager = new UrlManager([ 'enablePrettyUrl' => true, 'cache' => null, - 'rules' => array( - array( + 'rules' => [ + [ 'pattern' => 'post/<id>/<title>', 'route' => 'post/view', 'host' => 'http://<lang:en|fr>.example.com', - ), - ), + ], + ], 'baseUrl' => '/test', - )); - $url = $manager->createUrl('post/view', array('id' => 1, 'title' => 'sample post', 'lang' => 'en')); + ]); + $url = $manager->createUrl('post/view', ['id' => 1, 'title' => 'sample post', 'lang' => 'en']); $this->assertEquals('http://en.example.com/test/post/1/sample+post', $url); - $url = $manager->createUrl('post/index', array('page' => 1)); + $url = $manager->createUrl('post/index', ['page' => 1]); $this->assertEquals('/test/post/index?page=1', $url); } public function testCreateAbsoluteUrl() { - $manager = new UrlManager(array( + $manager = new UrlManager([ 'baseUrl' => '/', 'hostInfo' => 'http://www.example.com', 'cache' => null, - )); - $url = $manager->createAbsoluteUrl('post/view', array('id' => 1, 'title' => 'sample post')); + ]); + $url = $manager->createAbsoluteUrl('post/view', ['id' => 1, 'title' => 'sample post']); $this->assertEquals('http://www.example.com?r=post/view&id=1&title=sample+post', $url); } public function testParseRequest() { - $manager = new UrlManager(array( - 'cache' => null, - )); + $manager = new UrlManager(['cache' => null]); $request = new Request; // default setting without 'r' param unset($_GET['r']); $result = $manager->parseRequest($request); - $this->assertEquals(array('', array()), $result); + $this->assertEquals(['', []], $result); // default setting with 'r' param $_GET['r'] = 'site/index'; $result = $manager->parseRequest($request); - $this->assertEquals(array('site/index', array()), $result); + $this->assertEquals(['site/index', []], $result); // default setting with 'r' param as an array - $_GET['r'] = array('site/index'); + $_GET['r'] = ['site/index']; $result = $manager->parseRequest($request); - $this->assertEquals(array('', array()), $result); + $this->assertEquals(['', []], $result); // pretty URL without rules - $manager = new UrlManager(array( + $manager = new UrlManager([ 'enablePrettyUrl' => true, 'cache' => null, - )); + ]); // empty pathinfo $request->pathInfo = ''; $result = $manager->parseRequest($request); - $this->assertEquals(array('', array()), $result); + $this->assertEquals(['', []], $result); // normal pathinfo $request->pathInfo = 'site/index'; $result = $manager->parseRequest($request); - $this->assertEquals(array('site/index', array()), $result); + $this->assertEquals(['site/index', []], $result); // pathinfo with module $request->pathInfo = 'module/site/index'; $result = $manager->parseRequest($request); - $this->assertEquals(array('module/site/index', array()), $result); + $this->assertEquals(['module/site/index', []], $result); // pathinfo with trailing slashes $request->pathInfo = '/module/site/index/'; $result = $manager->parseRequest($request); - $this->assertEquals(array('module/site/index/', array()), $result); + $this->assertEquals(['module/site/index/', []], $result); // pretty URL rules - $manager = new UrlManager(array( + $manager = new UrlManager([ 'enablePrettyUrl' => true, 'cache' => null, - 'rules' => array( - array( + 'rules' => [ + [ 'pattern' => 'post/<id>/<title>', 'route' => 'post/view', - ), - ), - )); + ], + ], + ]); // matching pathinfo $request->pathInfo = 'post/123/this+is+sample'; $result = $manager->parseRequest($request); - $this->assertEquals(array('post/view', array('id' => '123', 'title' => 'this+is+sample')), $result); + $this->assertEquals(['post/view', ['id' => '123', 'title' => 'this+is+sample']], $result); // trailing slash is significant $request->pathInfo = 'post/123/this+is+sample/'; $result = $manager->parseRequest($request); - $this->assertEquals(array('post/123/this+is+sample/', array()), $result); + $this->assertEquals(['post/123/this+is+sample/', []], $result); // empty pathinfo $request->pathInfo = ''; $result = $manager->parseRequest($request); - $this->assertEquals(array('', array()), $result); + $this->assertEquals(['', []], $result); // normal pathinfo $request->pathInfo = 'site/index'; $result = $manager->parseRequest($request); - $this->assertEquals(array('site/index', array()), $result); + $this->assertEquals(['site/index', []], $result); // pathinfo with module $request->pathInfo = 'module/site/index'; $result = $manager->parseRequest($request); - $this->assertEquals(array('module/site/index', array()), $result); + $this->assertEquals(['module/site/index', []], $result); // pretty URL rules - $manager = new UrlManager(array( + $manager = new UrlManager([ 'enablePrettyUrl' => true, 'suffix' => '.html', 'cache' => null, - 'rules' => array( - array( + 'rules' => [ + [ 'pattern' => 'post/<id>/<title>', 'route' => 'post/view', - ), - ), - )); + ], + ], + ]); // matching pathinfo $request->pathInfo = 'post/123/this+is+sample.html'; $result = $manager->parseRequest($request); - $this->assertEquals(array('post/view', array('id' => '123', 'title' => 'this+is+sample')), $result); + $this->assertEquals(['post/view', ['id' => '123', 'title' => 'this+is+sample']], $result); // matching pathinfo without suffix $request->pathInfo = 'post/123/this+is+sample'; $result = $manager->parseRequest($request); @@ -225,33 +223,33 @@ class UrlManagerTest extends TestCase // empty pathinfo $request->pathInfo = ''; $result = $manager->parseRequest($request); - $this->assertEquals(array('', array()), $result); + $this->assertEquals(['', []], $result); // normal pathinfo $request->pathInfo = 'site/index.html'; $result = $manager->parseRequest($request); - $this->assertEquals(array('site/index', array()), $result); + $this->assertEquals(['site/index', []], $result); // pathinfo without suffix $request->pathInfo = 'site/index'; $result = $manager->parseRequest($request); $this->assertFalse($result); // strict parsing - $manager = new UrlManager(array( + $manager = new UrlManager([ 'enablePrettyUrl' => true, 'enableStrictParsing' => true, 'suffix' => '.html', 'cache' => null, - 'rules' => array( - array( + 'rules' => [ + [ 'pattern' => 'post/<id>/<title>', 'route' => 'post/view', - ), - ), - )); + ], + ], + ]); // matching pathinfo $request->pathInfo = 'post/123/this+is+sample.html'; $result = $manager->parseRequest($request); - $this->assertEquals(array('post/view', array('id' => '123', 'title' => 'this+is+sample')), $result); + $this->assertEquals(['post/view', ['id' => '123', 'title' => 'this+is+sample']], $result); // unmatching pathinfo $request->pathInfo = 'site/index.html'; $result = $manager->parseRequest($request); @@ -263,48 +261,48 @@ class UrlManagerTest extends TestCase $request = new Request; // pretty URL rules - $manager = new UrlManager(array( + $manager = new UrlManager([ 'enablePrettyUrl' => true, 'showScriptName' => false, 'cache' => null, - 'rules' => array( + 'rules' => [ 'PUT,POST post/<id>/<title>' => 'post/create', 'DELETE post/<id>' => 'post/delete', 'post/<id>/<title>' => 'post/view', 'POST/GET' => 'post/get', - ), - )); + ], + ]); // matching pathinfo GET request $_SERVER['REQUEST_METHOD'] = 'GET'; $request->pathInfo = 'post/123/this+is+sample'; $result = $manager->parseRequest($request); - $this->assertEquals(array('post/view', array('id' => '123', 'title' => 'this+is+sample')), $result); + $this->assertEquals(['post/view', ['id' => '123', 'title' => 'this+is+sample']], $result); // matching pathinfo PUT/POST request $_SERVER['REQUEST_METHOD'] = 'PUT'; $request->pathInfo = 'post/123/this+is+sample'; $result = $manager->parseRequest($request); - $this->assertEquals(array('post/create', array('id' => '123', 'title' => 'this+is+sample')), $result); + $this->assertEquals(['post/create', ['id' => '123', 'title' => 'this+is+sample']], $result); $_SERVER['REQUEST_METHOD'] = 'POST'; $request->pathInfo = 'post/123/this+is+sample'; $result = $manager->parseRequest($request); - $this->assertEquals(array('post/create', array('id' => '123', 'title' => 'this+is+sample')), $result); + $this->assertEquals(['post/create', ['id' => '123', 'title' => 'this+is+sample']], $result); // no wrong matching $_SERVER['REQUEST_METHOD'] = 'POST'; $request->pathInfo = 'POST/GET'; $result = $manager->parseRequest($request); - $this->assertEquals(array('post/get', array()), $result); + $this->assertEquals(['post/get', []], $result); // createUrl should ignore REST rules - $this->mockApplication(array( - 'components' => array( - 'request' => array( + $this->mockApplication([ + 'components' => [ + 'request' => [ 'hostInfo' => 'http://localhost/', 'baseUrl' => '/app' - ) - ) - ), \yii\web\Application::className()); - $this->assertEquals('/app/post/delete?id=123', $manager->createUrl('post/delete', array('id' => 123))); + ] + ] + ], \yii\web\Application::className()); + $this->assertEquals('/app/post/delete?id=123', $manager->createUrl('post/delete', ['id' => 123])); $this->destroyApplication(); unset($_SERVER['REQUEST_METHOD']); diff --git a/tests/unit/framework/web/UrlRuleTest.php b/tests/unit/framework/web/UrlRuleTest.php index 0a0def4..39fa9bd 100644 --- a/tests/unit/framework/web/UrlRuleTest.php +++ b/tests/unit/framework/web/UrlRuleTest.php @@ -14,7 +14,7 @@ class UrlRuleTest extends TestCase { public function testCreateUrl() { - $manager = new UrlManager(array('cache' => null)); + $manager = new UrlManager(['cache' => null]); $suites = $this->getTestsForCreateUrl(); foreach ($suites as $i => $suite) { list ($name, $config, $tests) = $suite; @@ -29,8 +29,8 @@ class UrlRuleTest extends TestCase public function testParseRequest() { - $manager = new UrlManager(array('cache' => null)); - $request = new Request(array('hostInfo' => 'http://en.example.com')); + $manager = new UrlManager(['cache' => null]); + $request = new Request(['hostInfo' => 'http://en.example.com']); $suites = $this->getTestsForParseRequest(); foreach ($suites as $i => $suite) { list ($name, $config, $tests) = $suite; @@ -38,12 +38,12 @@ class UrlRuleTest extends TestCase foreach ($tests as $j => $test) { $request->pathInfo = $test[0]; $route = $test[1]; - $params = isset($test[2]) ? $test[2] : array(); + $params = isset($test[2]) ? $test[2] : []; $result = $rule->parseRequest($manager, $request); if ($route === false) { $this->assertFalse($result, "Test#$i-$j: $name"); } else { - $this->assertEquals(array($route, $params), $result, "Test#$i-$j: $name"); + $this->assertEquals([$route, $params], $result, "Test#$i-$j: $name"); } } } @@ -58,293 +58,293 @@ class UrlRuleTest extends TestCase // route // params // expected output - return array( - array( + return [ + [ 'empty pattern', - array( + [ 'pattern' => '', 'route' => 'post/index', - ), - array( - array('post/index', array(), ''), - array('comment/index', array(), false), - array('post/index', array('page' => 1), '?page=1'), - ), - ), - array( + ], + [ + ['post/index', [], ''], + ['comment/index', [], false], + ['post/index', ['page' => 1], '?page=1'], + ], + ], + [ 'without param', - array( + [ 'pattern' => 'posts', 'route' => 'post/index', - ), - array( - array('post/index', array(), 'posts'), - array('comment/index', array(), false), - array('post/index', array('page' => 1), 'posts?page=1'), - ), - ), - array( + ], + [ + ['post/index', [], 'posts'], + ['comment/index', [], false], + ['post/index', ['page' => 1], 'posts?page=1'], + ], + ], + [ 'parsing only', - array( + [ 'pattern' => 'posts', 'route' => 'post/index', 'mode' => UrlRule::PARSING_ONLY, - ), - array( - array('post/index', array(), false), - ), - ), - array( + ], + [ + ['post/index', [], false], + ], + ], + [ 'with param', - array( + [ 'pattern' => 'post/<page>', 'route' => 'post/index', - ), - array( - array('post/index', array(), false), - array('comment/index', array(), false), - array('post/index', array('page' => 1), 'post/1'), - array('post/index', array('page' => 1, 'tag' => 'a'), 'post/1?tag=a'), - ), - ), - array( + ], + [ + ['post/index', [], false], + ['comment/index', [], false], + ['post/index', ['page' => 1], 'post/1'], + ['post/index', ['page' => 1, 'tag' => 'a'], 'post/1?tag=a'], + ], + ], + [ 'with param requirement', - array( + [ 'pattern' => 'post/<page:\d+>', 'route' => 'post/index', - ), - array( - array('post/index', array('page' => 'abc'), false), - array('post/index', array('page' => 1), 'post/1'), - array('post/index', array('page' => 1, 'tag' => 'a'), 'post/1?tag=a'), - ), - ), - array( + ], + [ + ['post/index', ['page' => 'abc'], false], + ['post/index', ['page' => 1], 'post/1'], + ['post/index', ['page' => 1, 'tag' => 'a'], 'post/1?tag=a'], + ], + ], + [ 'with multiple params', - array( + [ 'pattern' => 'post/<page:\d+>-<tag>', 'route' => 'post/index', - ), - array( - array('post/index', array('page' => '1abc'), false), - array('post/index', array('page' => 1), false), - array('post/index', array('page' => 1, 'tag' => 'a'), 'post/1-a'), - ), - ), - array( + ], + [ + ['post/index', ['page' => '1abc'], false], + ['post/index', ['page' => 1], false], + ['post/index', ['page' => 1, 'tag' => 'a'], 'post/1-a'], + ], + ], + [ 'with optional param', - array( + [ 'pattern' => 'post/<page:\d+>/<tag>', 'route' => 'post/index', - 'defaults' => array('page' => 1), - ), - array( - array('post/index', array('page' => 1), false), - array('post/index', array('page' => '1abc', 'tag' => 'a'), false), - array('post/index', array('page' => 1, 'tag' => 'a'), 'post/a'), - array('post/index', array('page' => 2, 'tag' => 'a'), 'post/2/a'), - ), - ), - array( + 'defaults' => ['page' => 1], + ], + [ + ['post/index', ['page' => 1], false], + ['post/index', ['page' => '1abc', 'tag' => 'a'], false], + ['post/index', ['page' => 1, 'tag' => 'a'], 'post/a'], + ['post/index', ['page' => 2, 'tag' => 'a'], 'post/2/a'], + ], + ], + [ 'with optional param not in pattern', - array( + [ 'pattern' => 'post/<tag>', 'route' => 'post/index', - 'defaults' => array('page' => 1), - ), - array( - array('post/index', array('page' => 1), false), - array('post/index', array('page' => '1abc', 'tag' => 'a'), false), - array('post/index', array('page' => 2, 'tag' => 'a'), false), - array('post/index', array('page' => 1, 'tag' => 'a'), 'post/a'), - ), - ), - array( + 'defaults' => ['page' => 1], + ], + [ + ['post/index', ['page' => 1], false], + ['post/index', ['page' => '1abc', 'tag' => 'a'], false], + ['post/index', ['page' => 2, 'tag' => 'a'], false], + ['post/index', ['page' => 1, 'tag' => 'a'], 'post/a'], + ], + ], + [ 'multiple optional params', - array( + [ 'pattern' => 'post/<page:\d+>/<tag>/<sort:yes|no>', 'route' => 'post/index', - 'defaults' => array('page' => 1, 'sort' => 'yes'), - ), - array( - array('post/index', array('page' => 1), false), - array('post/index', array('page' => '1abc', 'tag' => 'a'), false), - array('post/index', array('page' => 1, 'tag' => 'a', 'sort' => 'YES'), false), - array('post/index', array('page' => 1, 'tag' => 'a', 'sort' => 'yes'), 'post/a'), - array('post/index', array('page' => 2, 'tag' => 'a', 'sort' => 'yes'), 'post/2/a'), - array('post/index', array('page' => 2, 'tag' => 'a', 'sort' => 'no'), 'post/2/a/no'), - array('post/index', array('page' => 1, 'tag' => 'a', 'sort' => 'no'), 'post/a/no'), - ), - ), - array( + 'defaults' => ['page' => 1, 'sort' => 'yes'], + ], + [ + ['post/index', ['page' => 1], false], + ['post/index', ['page' => '1abc', 'tag' => 'a'], false], + ['post/index', ['page' => 1, 'tag' => 'a', 'sort' => 'YES'], false], + ['post/index', ['page' => 1, 'tag' => 'a', 'sort' => 'yes'], 'post/a'], + ['post/index', ['page' => 2, 'tag' => 'a', 'sort' => 'yes'], 'post/2/a'], + ['post/index', ['page' => 2, 'tag' => 'a', 'sort' => 'no'], 'post/2/a/no'], + ['post/index', ['page' => 1, 'tag' => 'a', 'sort' => 'no'], 'post/a/no'], + ], + ], + [ 'optional param and required param separated by dashes', - array( + [ 'pattern' => 'post/<page:\d+>-<tag>', 'route' => 'post/index', - 'defaults' => array('page' => 1), - ), - array( - array('post/index', array('page' => 1), false), - array('post/index', array('page' => '1abc', 'tag' => 'a'), false), - array('post/index', array('page' => 1, 'tag' => 'a'), 'post/-a'), - array('post/index', array('page' => 2, 'tag' => 'a'), 'post/2-a'), - ), - ), - array( + 'defaults' => ['page' => 1], + ], + [ + ['post/index', ['page' => 1], false], + ['post/index', ['page' => '1abc', 'tag' => 'a'], false], + ['post/index', ['page' => 1, 'tag' => 'a'], 'post/-a'], + ['post/index', ['page' => 2, 'tag' => 'a'], 'post/2-a'], + ], + ], + [ 'optional param at the end', - array( + [ 'pattern' => 'post/<tag>/<page:\d+>', 'route' => 'post/index', - 'defaults' => array('page' => 1), - ), - array( - array('post/index', array('page' => 1), false), - array('post/index', array('page' => '1abc', 'tag' => 'a'), false), - array('post/index', array('page' => 1, 'tag' => 'a'), 'post/a'), - array('post/index', array('page' => 2, 'tag' => 'a'), 'post/a/2'), - ), - ), - array( + 'defaults' => ['page' => 1], + ], + [ + ['post/index', ['page' => 1], false], + ['post/index', ['page' => '1abc', 'tag' => 'a'], false], + ['post/index', ['page' => 1, 'tag' => 'a'], 'post/a'], + ['post/index', ['page' => 2, 'tag' => 'a'], 'post/a/2'], + ], + ], + [ 'consecutive optional params', - array( + [ 'pattern' => 'post/<page:\d+>/<tag>', 'route' => 'post/index', - 'defaults' => array('page' => 1, 'tag' => 'a'), - ), - array( - array('post/index', array('page' => 1), false), - array('post/index', array('page' => '1abc', 'tag' => 'a'), false), - array('post/index', array('page' => 1, 'tag' => 'a'), 'post'), - array('post/index', array('page' => 2, 'tag' => 'a'), 'post/2'), - array('post/index', array('page' => 1, 'tag' => 'b'), 'post/b'), - array('post/index', array('page' => 2, 'tag' => 'b'), 'post/2/b'), - ), - ), - array( + 'defaults' => ['page' => 1, 'tag' => 'a'], + ], + [ + ['post/index', ['page' => 1], false], + ['post/index', ['page' => '1abc', 'tag' => 'a'], false], + ['post/index', ['page' => 1, 'tag' => 'a'], 'post'], + ['post/index', ['page' => 2, 'tag' => 'a'], 'post/2'], + ['post/index', ['page' => 1, 'tag' => 'b'], 'post/b'], + ['post/index', ['page' => 2, 'tag' => 'b'], 'post/2/b'], + ], + ], + [ 'consecutive optional params separated by dash', - array( + [ 'pattern' => 'post/<page:\d+>-<tag>', 'route' => 'post/index', - 'defaults' => array('page' => 1, 'tag' => 'a'), - ), - array( - array('post/index', array('page' => 1), false), - array('post/index', array('page' => '1abc', 'tag' => 'a'), false), - array('post/index', array('page' => 1, 'tag' => 'a'), 'post/-'), - array('post/index', array('page' => 1, 'tag' => 'b'), 'post/-b'), - array('post/index', array('page' => 2, 'tag' => 'a'), 'post/2-'), - array('post/index', array('page' => 2, 'tag' => 'b'), 'post/2-b'), - ), - ), - array( + 'defaults' => ['page' => 1, 'tag' => 'a'], + ], + [ + ['post/index', ['page' => 1], false], + ['post/index', ['page' => '1abc', 'tag' => 'a'], false], + ['post/index', ['page' => 1, 'tag' => 'a'], 'post/-'], + ['post/index', ['page' => 1, 'tag' => 'b'], 'post/-b'], + ['post/index', ['page' => 2, 'tag' => 'a'], 'post/2-'], + ['post/index', ['page' => 2, 'tag' => 'b'], 'post/2-b'], + ], + ], + [ 'route has parameters', - array( + [ 'pattern' => '<controller>/<action>', 'route' => '<controller>/<action>', - 'defaults' => array(), - ), - array( - array('post/index', array('page' => 1), 'post/index?page=1'), - array('module/post/index', array(), false), - ), - ), - array( + 'defaults' => [], + ], + [ + ['post/index', ['page' => 1], 'post/index?page=1'], + ['module/post/index', [], false], + ], + ], + [ 'route has parameters with regex', - array( + [ 'pattern' => '<controller:post|comment>/<action>', 'route' => '<controller>/<action>', - 'defaults' => array(), - ), - array( - array('post/index', array('page' => 1), 'post/index?page=1'), - array('comment/index', array('page' => 1), 'comment/index?page=1'), - array('test/index', array('page' => 1), false), - array('post', array(), false), - array('module/post/index', array(), false), - array('post/index', array('controller' => 'comment'), 'post/index?controller=comment'), - ), - ), - array( + 'defaults' => [], + ], + [ + ['post/index', ['page' => 1], 'post/index?page=1'], + ['comment/index', ['page' => 1], 'comment/index?page=1'], + ['test/index', ['page' => 1], false], + ['post', [], false], + ['module/post/index', [], false], + ['post/index', ['controller' => 'comment'], 'post/index?controller=comment'], + ], + ], + [ 'route has default parameter', - array( + [ 'pattern' => '<controller:post|comment>/<action>', 'route' => '<controller>/<action>', - 'defaults' => array('action' => 'index'), - ), - array( - array('post/view', array('page' => 1), 'post/view?page=1'), - array('comment/view', array('page' => 1), 'comment/view?page=1'), - array('test/view', array('page' => 1), false), - array('test/index', array('page' => 1), false), - array('post/index', array('page' => 1), 'post?page=1'), - ), - ), - array( + 'defaults' => ['action' => 'index'], + ], + [ + ['post/view', ['page' => 1], 'post/view?page=1'], + ['comment/view', ['page' => 1], 'comment/view?page=1'], + ['test/view', ['page' => 1], false], + ['test/index', ['page' => 1], false], + ['post/index', ['page' => 1], 'post?page=1'], + ], + ], + [ 'empty pattern with suffix', - array( + [ 'pattern' => '', 'route' => 'post/index', 'suffix' => '.html', - ), - array( - array('post/index', array(), ''), - array('comment/index', array(), false), - array('post/index', array('page' => 1), '?page=1'), - ), - ), - array( + ], + [ + ['post/index', [], ''], + ['comment/index', [], false], + ['post/index', ['page' => 1], '?page=1'], + ], + ], + [ 'regular pattern with suffix', - array( + [ 'pattern' => 'posts', 'route' => 'post/index', 'suffix' => '.html', - ), - array( - array('post/index', array(), 'posts.html'), - array('comment/index', array(), false), - array('post/index', array('page' => 1), 'posts.html?page=1'), - ), - ), - array( + ], + [ + ['post/index', [], 'posts.html'], + ['comment/index', [], false], + ['post/index', ['page' => 1], 'posts.html?page=1'], + ], + ], + [ 'empty pattern with slash suffix', - array( + [ 'pattern' => '', 'route' => 'post/index', 'suffix' => '/', - ), - array( - array('post/index', array(), ''), - array('comment/index', array(), false), - array('post/index', array('page' => 1), '?page=1'), - ), - ), - array( + ], + [ + ['post/index', [], ''], + ['comment/index', [], false], + ['post/index', ['page' => 1], '?page=1'], + ], + ], + [ 'regular pattern with slash suffix', - array( + [ 'pattern' => 'posts', 'route' => 'post/index', 'suffix' => '/', - ), - array( - array('post/index', array(), 'posts/'), - array('comment/index', array(), false), - array('post/index', array('page' => 1), 'posts/?page=1'), - ), - ), - array( + ], + [ + ['post/index', [], 'posts/'], + ['comment/index', [], false], + ['post/index', ['page' => 1], 'posts/?page=1'], + ], + ], + [ 'with host info', - array( + [ 'pattern' => 'post/<page:\d+>/<tag>', 'route' => 'post/index', - 'defaults' => array('page' => 1), + 'defaults' => ['page' => 1], 'host' => 'http://<lang:en|fr>.example.com', - ), - array( - array('post/index', array('page' => 1, 'tag' => 'a'), false), - array('post/index', array('page' => 1, 'tag' => 'a', 'lang' => 'en'), 'http://en.example.com/post/a'), - ), - ), - ); + ], + [ + ['post/index', ['page' => 1, 'tag' => 'a'], false], + ['post/index', ['page' => 1, 'tag' => 'a', 'lang' => 'en'], 'http://en.example.com/post/a'], + ], + ], + ]; } protected function getTestsForParseRequest() @@ -356,292 +356,303 @@ class UrlRuleTest extends TestCase // pathInfo // expected route, or false if the rule doesn't apply // expected params, or not set if empty - return array( - array( + return [ + [ 'empty pattern', - array( + [ 'pattern' => '', 'route' => 'post/index', - ), - array( - array('', 'post/index'), - array('a', false), - ), - ), - array( + ], + [ + ['', 'post/index'], + ['a', false], + ], + ], + [ 'without param', - array( + [ 'pattern' => 'posts', 'route' => 'post/index', - ), - array( - array('posts', 'post/index'), - array('a', false), - ), - ), - array( + ], + [ + ['posts', 'post/index'], + ['a', false], + ], + ], + [ + 'with dot', // https://github.com/yiisoft/yii/issues/2945 + [ + 'pattern' => 'posts.html', + 'route' => 'post/index', + ], + [ + ['posts.html', 'post/index'], + ['postsahtml', false], + ], + ], + [ 'creation only', - array( + [ 'pattern' => 'posts', 'route' => 'post/index', 'mode' => UrlRule::CREATION_ONLY, - ), - array( - array('posts', false), - ), - ), - array( + ], + [ + ['posts', false], + ], + ], + [ 'with param', - array( + [ 'pattern' => 'post/<page>', 'route' => 'post/index', - ), - array( - array('post/1', 'post/index', array('page' => '1')), - array('post/a', 'post/index', array('page' => 'a')), - array('post', false), - array('posts', false), - ), - ), - array( + ], + [ + ['post/1', 'post/index', ['page' => '1']], + ['post/a', 'post/index', ['page' => 'a']], + ['post', false], + ['posts', false], + ], + ], + [ 'with param requirement', - array( + [ 'pattern' => 'post/<page:\d+>', 'route' => 'post/index', - ), - array( - array('post/1', 'post/index', array('page' => '1')), - array('post/a', false), - array('post/1/a', false), - ), - ), - array( + ], + [ + ['post/1', 'post/index', ['page' => '1']], + ['post/a', false], + ['post/1/a', false], + ], + ], + [ 'with multiple params', - array( + [ 'pattern' => 'post/<page:\d+>-<tag>', 'route' => 'post/index', - ), - array( - array('post/1-a', 'post/index', array('page' => '1', 'tag' => 'a')), - array('post/a', false), - array('post/1', false), - array('post/1/a', false), - ), - ), - array( + ], + [ + ['post/1-a', 'post/index', ['page' => '1', 'tag' => 'a']], + ['post/a', false], + ['post/1', false], + ['post/1/a', false], + ], + ], + [ 'with optional param', - array( + [ 'pattern' => 'post/<page:\d+>/<tag>', 'route' => 'post/index', - 'defaults' => array('page' => 1), - ), - array( - array('post/1/a', 'post/index', array('page' => '1', 'tag' => 'a')), - array('post/2/a', 'post/index', array('page' => '2', 'tag' => 'a')), - array('post/a', 'post/index', array('page' => '1', 'tag' => 'a')), - array('post/1', 'post/index', array('page' => '1', 'tag' => '1')), - ), - ), - array( + 'defaults' => ['page' => 1], + ], + [ + ['post/1/a', 'post/index', ['page' => '1', 'tag' => 'a']], + ['post/2/a', 'post/index', ['page' => '2', 'tag' => 'a']], + ['post/a', 'post/index', ['page' => '1', 'tag' => 'a']], + ['post/1', 'post/index', ['page' => '1', 'tag' => '1']], + ], + ], + [ 'with optional param not in pattern', - array( + [ 'pattern' => 'post/<tag>', 'route' => 'post/index', - 'defaults' => array('page' => 1), - ), - array( - array('post/a', 'post/index', array('page' => '1', 'tag' => 'a')), - array('post/1', 'post/index', array('page' => '1', 'tag' => '1')), - array('post', false), - ), - ), - array( + 'defaults' => ['page' => 1], + ], + [ + ['post/a', 'post/index', ['page' => '1', 'tag' => 'a']], + ['post/1', 'post/index', ['page' => '1', 'tag' => '1']], + ['post', false], + ], + ], + [ 'multiple optional params', - array( + [ 'pattern' => 'post/<page:\d+>/<tag>/<sort:yes|no>', 'route' => 'post/index', - 'defaults' => array('page' => 1, 'sort' => 'yes'), - ), - array( - array('post/1/a/yes', 'post/index', array('page' => '1', 'tag' => 'a', 'sort' => 'yes')), - array('post/1/a/no', 'post/index', array('page' => '1', 'tag' => 'a', 'sort' => 'no')), - array('post/2/a/no', 'post/index', array('page' => '2', 'tag' => 'a', 'sort' => 'no')), - array('post/2/a', 'post/index', array('page' => '2', 'tag' => 'a', 'sort' => 'yes')), - array('post/a/no', 'post/index', array('page' => '1', 'tag' => 'a', 'sort' => 'no')), - array('post/a', 'post/index', array('page' => '1', 'tag' => 'a', 'sort' => 'yes')), - array('post', false), - ), - ), - array( + 'defaults' => ['page' => 1, 'sort' => 'yes'], + ], + [ + ['post/1/a/yes', 'post/index', ['page' => '1', 'tag' => 'a', 'sort' => 'yes']], + ['post/1/a/no', 'post/index', ['page' => '1', 'tag' => 'a', 'sort' => 'no']], + ['post/2/a/no', 'post/index', ['page' => '2', 'tag' => 'a', 'sort' => 'no']], + ['post/2/a', 'post/index', ['page' => '2', 'tag' => 'a', 'sort' => 'yes']], + ['post/a/no', 'post/index', ['page' => '1', 'tag' => 'a', 'sort' => 'no']], + ['post/a', 'post/index', ['page' => '1', 'tag' => 'a', 'sort' => 'yes']], + ['post', false], + ], + ], + [ 'optional param and required param separated by dashes', - array( + [ 'pattern' => 'post/<page:\d+>-<tag>', 'route' => 'post/index', - 'defaults' => array('page' => 1), - ), - array( - array('post/1-a', 'post/index', array('page' => '1', 'tag' => 'a')), - array('post/2-a', 'post/index', array('page' => '2', 'tag' => 'a')), - array('post/-a', 'post/index', array('page' => '1', 'tag' => 'a')), - array('post/a', false), - array('post-a', false), - ), - ), - array( + 'defaults' => ['page' => 1], + ], + [ + ['post/1-a', 'post/index', ['page' => '1', 'tag' => 'a']], + ['post/2-a', 'post/index', ['page' => '2', 'tag' => 'a']], + ['post/-a', 'post/index', ['page' => '1', 'tag' => 'a']], + ['post/a', false], + ['post-a', false], + ], + ], + [ 'optional param at the end', - array( + [ 'pattern' => 'post/<tag>/<page:\d+>', 'route' => 'post/index', - 'defaults' => array('page' => 1), - ), - array( - array('post/a/1', 'post/index', array('page' => '1', 'tag' => 'a')), - array('post/a/2', 'post/index', array('page' => '2', 'tag' => 'a')), - array('post/a', 'post/index', array('page' => '1', 'tag' => 'a')), - array('post/2', 'post/index', array('page' => '1', 'tag' => '2')), - array('post', false), - ), - ), - array( + 'defaults' => ['page' => 1], + ], + [ + ['post/a/1', 'post/index', ['page' => '1', 'tag' => 'a']], + ['post/a/2', 'post/index', ['page' => '2', 'tag' => 'a']], + ['post/a', 'post/index', ['page' => '1', 'tag' => 'a']], + ['post/2', 'post/index', ['page' => '1', 'tag' => '2']], + ['post', false], + ], + ], + [ 'consecutive optional params', - array( + [ 'pattern' => 'post/<page:\d+>/<tag>', 'route' => 'post/index', - 'defaults' => array('page' => 1, 'tag' => 'a'), - ), - array( - array('post/2/b', 'post/index', array('page' => '2', 'tag' => 'b')), - array('post/2', 'post/index', array('page' => '2', 'tag' => 'a')), - array('post', 'post/index', array('page' => '1', 'tag' => 'a')), - array('post/b', 'post/index', array('page' => '1', 'tag' => 'b')), - array('post//b', false), - ), - ), - array( + 'defaults' => ['page' => 1, 'tag' => 'a'], + ], + [ + ['post/2/b', 'post/index', ['page' => '2', 'tag' => 'b']], + ['post/2', 'post/index', ['page' => '2', 'tag' => 'a']], + ['post', 'post/index', ['page' => '1', 'tag' => 'a']], + ['post/b', 'post/index', ['page' => '1', 'tag' => 'b']], + ['post//b', false], + ], + ], + [ 'consecutive optional params separated by dash', - array( + [ 'pattern' => 'post/<page:\d+>-<tag>', 'route' => 'post/index', - 'defaults' => array('page' => 1, 'tag' => 'a'), - ), - array( - array('post/2-b', 'post/index', array('page' => '2', 'tag' => 'b')), - array('post/2-', 'post/index', array('page' => '2', 'tag' => 'a')), - array('post/-b', 'post/index', array('page' => '1', 'tag' => 'b')), - array('post/-', 'post/index', array('page' => '1', 'tag' => 'a')), - array('post', false), - ), - ), - array( + 'defaults' => ['page' => 1, 'tag' => 'a'], + ], + [ + ['post/2-b', 'post/index', ['page' => '2', 'tag' => 'b']], + ['post/2-', 'post/index', ['page' => '2', 'tag' => 'a']], + ['post/-b', 'post/index', ['page' => '1', 'tag' => 'b']], + ['post/-', 'post/index', ['page' => '1', 'tag' => 'a']], + ['post', false], + ], + ], + [ 'route has parameters', - array( + [ 'pattern' => '<controller>/<action>', 'route' => '<controller>/<action>', - 'defaults' => array(), - ), - array( - array('post/index', 'post/index'), - array('module/post/index', false), - ), - ), - array( + 'defaults' => [], + ], + [ + ['post/index', 'post/index'], + ['module/post/index', false], + ], + ], + [ 'route has parameters with regex', - array( + [ 'pattern' => '<controller:post|comment>/<action>', 'route' => '<controller>/<action>', - 'defaults' => array(), - ), - array( - array('post/index', 'post/index'), - array('comment/index', 'comment/index'), - array('test/index', false), - array('post', false), - array('module/post/index', false), - ), - ), - array( + 'defaults' => [], + ], + [ + ['post/index', 'post/index'], + ['comment/index', 'comment/index'], + ['test/index', false], + ['post', false], + ['module/post/index', false], + ], + ], + [ 'route has default parameter', - array( + [ 'pattern' => '<controller:post|comment>/<action>', 'route' => '<controller>/<action>', - 'defaults' => array('action' => 'index'), - ), - array( - array('post/view', 'post/view'), - array('comment/view', 'comment/view'), - array('test/view', false), - array('post', 'post/index'), - array('posts', false), - array('test', false), - array('index', false), - ), - ), - array( + 'defaults' => ['action' => 'index'], + ], + [ + ['post/view', 'post/view'], + ['comment/view', 'comment/view'], + ['test/view', false], + ['post', 'post/index'], + ['posts', false], + ['test', false], + ['index', false], + ], + ], + [ 'empty pattern with suffix', - array( + [ 'pattern' => '', 'route' => 'post/index', 'suffix' => '.html', - ), - array( - array('', 'post/index'), - array('.html', false), - array('a.html', false), - ), - ), - array( + ], + [ + ['', 'post/index'], + ['.html', false], + ['a.html', false], + ], + ], + [ 'regular pattern with suffix', - array( + [ 'pattern' => 'posts', 'route' => 'post/index', 'suffix' => '.html', - ), - array( - array('posts.html', 'post/index'), - array('posts', false), - array('posts.HTML', false), - array('a.html', false), - array('a', false), - ), - ), - array( + ], + [ + ['posts.html', 'post/index'], + ['posts', false], + ['posts.HTML', false], + ['a.html', false], + ['a', false], + ], + ], + [ 'empty pattern with slash suffix', - array( + [ 'pattern' => '', 'route' => 'post/index', 'suffix' => '/', - ), - array( - array('', 'post/index'), - array('a', false), - ), - ), - array( + ], + [ + ['', 'post/index'], + ['a', false], + ], + ], + [ 'regular pattern with slash suffix', - array( + [ 'pattern' => 'posts', 'route' => 'post/index', 'suffix' => '/', - ), - array( - array('posts/', 'post/index'), - array('posts', false), - array('a', false), - ), - ), - array( + ], + [ + ['posts/', 'post/index'], + ['posts', false], + ['a', false], + ], + ], + [ 'with host info', - array( + [ 'pattern' => 'post/<page:\d+>', 'route' => 'post/index', 'host' => 'http://<lang:en|fr>.example.com', - ), - array( - array('post/1', 'post/index', array('page' => '1', 'lang' => 'en')), - array('post/a', false), - array('post/1/a', false), - ), - ), - ); + ], + [ + ['post/1', 'post/index', ['page' => '1', 'lang' => 'en']], + ['post/a', false], + ['post/1/a', false], + ], + ], + ]; } } diff --git a/tests/unit/framework/web/XmlResponseFormatterTest.php b/tests/unit/framework/web/XmlResponseFormatterTest.php index e97962a..800de66 100644 --- a/tests/unit/framework/web/XmlResponseFormatterTest.php +++ b/tests/unit/framework/web/XmlResponseFormatterTest.php @@ -62,13 +62,13 @@ class XmlResponseFormatterTest extends \yiiunit\TestCase public function formatScalarDataProvider() { - return array( - array(null, "<response></response>\n"), - array(1, "<response>1</response>\n"), - array('abc', "<response>abc</response>\n"), - array(true, "<response>1</response>\n"), - array("<>", "<response><></response>\n"), - ); + return [ + [null, "<response></response>\n"], + [1, "<response>1</response>\n"], + ['abc', "<response>abc</response>\n"], + [true, "<response>1</response>\n"], + ["<>", "<response><></response>\n"], + ]; } /** @@ -86,26 +86,26 @@ class XmlResponseFormatterTest extends \yiiunit\TestCase public function formatArrayDataProvider() { - return array( - array(array(), "<response/>\n"), - array(array(1, 'abc'), "<response><item>1</item><item>abc</item></response>\n"), - array(array( + return [ + [[], "<response/>\n"], + [[1, 'abc'], "<response><item>1</item><item>abc</item></response>\n"], + [[ 'a' => 1, 'b' => 'abc', - ), "<response><a>1</a><b>abc</b></response>\n"), - array(array( + ], "<response><a>1</a><b>abc</b></response>\n"], + [[ 1, 'abc', - array(2, 'def'), + [2, 'def'], true, - ), "<response><item>1</item><item>abc</item><item><item>2</item><item>def</item></item><item>1</item></response>\n"), - array(array( + ], "<response><item>1</item><item>abc</item><item><item>2</item><item>def</item></item><item>1</item></response>\n"], + [[ 'a' => 1, 'b' => 'abc', - 'c' => array(2, '<>'), + 'c' => [2, '<>'], true, - ), "<response><a>1</a><b>abc</b><c><item>2</item><item><></item></c><item>1</item></response>\n"), - ); + ], "<response><a>1</a><b>abc</b><c><item>2</item><item><></item></c><item>1</item></response>\n"], + ]; } /** @@ -123,16 +123,16 @@ class XmlResponseFormatterTest extends \yiiunit\TestCase public function formatObjectDataProvider() { - return array( - array(new Post(123, 'abc'), "<response><Post><id>123</id><title>abc</title></Post></response>\n"), - array(array( + return [ + [new Post(123, 'abc'), "<response><Post><id>123</id><title>abc</title></Post></response>\n"], + [[ new Post(123, 'abc'), new Post(456, 'def'), - ), "<response><Post><id>123</id><title>abc</title></Post><Post><id>456</id><title>def</title></Post></response>\n"), - array(array( + ], "<response><Post><id>123</id><title>abc</title></Post><Post><id>456</id><title>def</title></Post></response>\n"], + [[ new Post(123, '<>'), 'a' => new Post(456, 'def'), - ), "<response><Post><id>123</id><title><></title></Post><a><Post><id>456</id><title>def</title></Post></a></response>\n"), - ); + ], "<response><Post><id>123</id><title><></title></Post><a><Post><id>456</id><title>def</title></Post></a></response>\n"], + ]; } } diff --git a/tests/web/app/protected/config/main.php b/tests/web/app/protected/config/main.php index d5be5de..0b67a5f 100644 --- a/tests/web/app/protected/config/main.php +++ b/tests/web/app/protected/config/main.php @@ -1,3 +1,3 @@ <?php -return array(); +return []; diff --git a/tests/web/app/protected/controllers/SiteController.php b/tests/web/app/protected/controllers/SiteController.php index 89a4086..b79f3a6 100644 --- a/tests/web/app/protected/controllers/SiteController.php +++ b/tests/web/app/protected/controllers/SiteController.php @@ -12,15 +12,15 @@ class DefaultController extends \yii\web\Controller public function actionForm() { echo Html::beginForm(); - echo Html::checkboxList('test', array( + echo Html::checkboxList('test', [ 'value 1' => 'item 1', 'value 2' => 'item 2', 'value 3' => 'item 3', - ), isset($_POST['test']) ? $_POST['test'] : null, + ], isset($_POST['test']) ? $_POST['test'] : null, function ($index, $label, $name, $value, $checked) { return Html::label( $label . ' ' . Html::checkbox($name, $value, $checked), - null, array('class' => 'inline checkbox') + null, ['class' => 'inline checkbox'] ); }); echo Html::submitButton();