Commit 83a459cc by Alexander Makarov

Merge branch 'master'

parents e539482d 89f9f0e9
...@@ -22,7 +22,8 @@ install: ...@@ -22,7 +22,8 @@ install:
- tests/unit/data/travis/cubrid-setup.sh - tests/unit/data/travis/cubrid-setup.sh
# basic application: # basic application:
- composer install --dev --prefer-dist -d apps/basic - composer install --dev --prefer-dist -d apps/basic
- cd apps/basic && php vendor/bin/codecept build && cd ../.. - cd apps/basic && composer require --dev codeception/codeception:1.8.*@dev codeception/specify:* codeception/verify:*
- php vendor/bin/codecept build && cd ../..
- cd apps && php -S localhost:8080 & - cd apps && php -S localhost:8080 &
before_script: before_script:
......
<?php <?php
namespace backend\controllers; namespace backend\controllers;
use Yii; use Yii;
use yii\web\AccessControl;
use yii\web\Controller; use yii\web\Controller;
use common\models\LoginForm; use common\models\LoginForm;
/**
* Site controller
*/
class SiteController extends Controller class SiteController extends Controller
{ {
/**
* @inheritdoc
*/
public function behaviors() public function behaviors()
{ {
return [ return [
'access' => [ 'access' => [
'class' => \yii\web\AccessControl::className(), 'class' => AccessControl::className(),
'rules' => [ 'rules' => [
[ [
'actions' => ['login', 'error'], 'actions' => ['login', 'error'],
...@@ -28,6 +34,9 @@ class SiteController extends Controller ...@@ -28,6 +34,9 @@ class SiteController extends Controller
]; ];
} }
/**
* @inheritdoc
*/
public function actions() public function actions()
{ {
return [ return [
......
...@@ -5,7 +5,7 @@ use yii\widgets\ActiveForm; ...@@ -5,7 +5,7 @@ use yii\widgets\ActiveForm;
/** /**
* @var yii\web\View $this * @var yii\web\View $this
* @var yii\widgets\ActiveForm $form * @var yii\widgets\ActiveForm $form
* @var common\models\LoginForm $model * @var \common\models\LoginForm $model
*/ */
$this->title = 'Login'; $this->title = 'Login';
$this->params['breadcrumbs'][] = $this->title; $this->params['breadcrumbs'][] = $this->title;
......
...@@ -6,9 +6,5 @@ return [ ...@@ -6,9 +6,5 @@ return [
'cache' => [ 'cache' => [
'class' => 'yii\caching\FileCache', 'class' => 'yii\caching\FileCache',
], ],
'mail' => [
'class' => 'yii\swiftmailer\Mailer',
'viewPath' => '@common/mails',
],
], ],
]; ];
...@@ -2,4 +2,5 @@ ...@@ -2,4 +2,5 @@
return [ return [
'adminEmail' => 'admin@example.com', 'adminEmail' => 'admin@example.com',
'supportEmail' => 'support@example.com', 'supportEmail' => 'support@example.com',
'user.passwordResetTokenExpire' => 3600,
]; ];
<?php <?php
namespace common\models; namespace common\models;
use Yii;
use yii\base\Model; use yii\base\Model;
use Yii;
/** /**
* LoginForm is the model behind the login form. * Login form
*/ */
class LoginForm extends Model class LoginForm extends Model
{ {
...@@ -17,7 +16,7 @@ class LoginForm extends Model ...@@ -17,7 +16,7 @@ class LoginForm extends Model
private $_user = false; private $_user = false;
/** /**
* @return array the validation rules. * @inheritdoc
*/ */
public function rules() public function rules()
{ {
......
...@@ -6,8 +6,7 @@ use yii\helpers\Security; ...@@ -6,8 +6,7 @@ use yii\helpers\Security;
use yii\web\IdentityInterface; use yii\web\IdentityInterface;
/** /**
* Class User * User model
* @package common\models
* *
* @property integer $id * @property integer $id
* @property string $username * @property string $username
...@@ -19,19 +18,32 @@ use yii\web\IdentityInterface; ...@@ -19,19 +18,32 @@ use yii\web\IdentityInterface;
* @property integer $status * @property integer $status
* @property integer $created_at * @property integer $created_at
* @property integer $updated_at * @property integer $updated_at
* @property string $password write-only password
*/ */
class User extends ActiveRecord implements IdentityInterface class User extends ActiveRecord implements IdentityInterface
{ {
/**
* @var string the raw password. Used to collect password input and isn't saved in database
*/
public $password;
const STATUS_DELETED = 0; const STATUS_DELETED = 0;
const STATUS_ACTIVE = 10; const STATUS_ACTIVE = 10;
const ROLE_USER = 10; const ROLE_USER = 10;
public static function create($attributes)
{
/** @var User $user */
$user = new static();
$user->setAttributes($attributes);
$user->setPassword($attributes['password']);
$user->generateAuthKey();
if ($user->save()) {
return $user;
} else {
return null;
}
}
/**
* @inheritdoc
*/
public function behaviors() public function behaviors()
{ {
return [ return [
...@@ -46,10 +58,7 @@ class User extends ActiveRecord implements IdentityInterface ...@@ -46,10 +58,7 @@ class User extends ActiveRecord implements IdentityInterface
} }
/** /**
* Finds an identity by the given ID. * @inheritdoc
*
* @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) public static function findIdentity($id)
{ {
...@@ -60,7 +69,7 @@ class User extends ActiveRecord implements IdentityInterface ...@@ -60,7 +69,7 @@ class User extends ActiveRecord implements IdentityInterface
* Finds user by username * Finds user by username
* *
* @param string $username * @param string $username
* @return null|User * @return self
*/ */
public static function findByUsername($username) public static function findByUsername($username)
{ {
...@@ -68,7 +77,29 @@ class User extends ActiveRecord implements IdentityInterface ...@@ -68,7 +77,29 @@ class User extends ActiveRecord implements IdentityInterface
} }
/** /**
* @return int|string|array current user ID * Finds user by password reset token
*
* @param string $token password reset token
* @return self
*/
public static function findByPasswordResetToken($token)
{
$expire = \Yii::$app->params['user.passwordResetTokenExpire'];
$parts = explode('_', $token);
$timestamp = (int)end($parts);
if ($timestamp + $expire < time()) {
// token expired
return null;
}
return User::find([
'password_reset_token' => $token,
'status' => User::STATUS_ACTIVE,
]);
}
/**
* @inheritdoc
*/ */
public function getId() public function getId()
{ {
...@@ -76,7 +107,7 @@ class User extends ActiveRecord implements IdentityInterface ...@@ -76,7 +107,7 @@ class User extends ActiveRecord implements IdentityInterface
} }
/** /**
* @return string current user auth key * @inheritdoc
*/ */
public function getAuthKey() public function getAuthKey()
{ {
...@@ -84,8 +115,7 @@ class User extends ActiveRecord implements IdentityInterface ...@@ -84,8 +115,7 @@ class User extends ActiveRecord implements IdentityInterface
} }
/** /**
* @param string $authKey * @inheritdoc
* @return boolean if auth key is valid for current user
*/ */
public function validateAuthKey($authKey) public function validateAuthKey($authKey)
{ {
...@@ -93,6 +123,8 @@ class User extends ActiveRecord implements IdentityInterface ...@@ -93,6 +123,8 @@ class User extends ActiveRecord implements IdentityInterface
} }
/** /**
* Validates password
*
* @param string $password password to validate * @param string $password password to validate
* @return bool if password provided is valid for current user * @return bool if password provided is valid for current user
*/ */
...@@ -101,6 +133,43 @@ class User extends ActiveRecord implements IdentityInterface ...@@ -101,6 +133,43 @@ class User extends ActiveRecord implements IdentityInterface
return Security::validatePassword($password, $this->password_hash); return Security::validatePassword($password, $this->password_hash);
} }
/**
* Generates password hash from password and sets it to the model
*
* @param string $password
*/
public function setPassword($password)
{
$this->password_hash = Security::generatePasswordHash($password);
}
/**
* Generates "remember me" authentication key
*/
public function generateAuthKey()
{
$this->auth_key = Security::generateRandomKey();
}
/**
* Generates new password reset token
*/
public function generatePasswordResetToken()
{
$this->password_reset_token = Security::generateRandomKey() . '_' . time();
}
/**
* Removes password reset token
*/
public function removePasswordResetToken()
{
$this->password_reset_token = null;
}
/**
* @inheritdoc
*/
public function rules() public function rules()
{ {
return [ return [
...@@ -117,34 +186,7 @@ class User extends ActiveRecord implements IdentityInterface ...@@ -117,34 +186,7 @@ class User extends ActiveRecord implements IdentityInterface
['email', 'filter', 'filter' => 'trim'], ['email', 'filter', 'filter' => 'trim'],
['email', 'required'], ['email', 'required'],
['email', 'email'], ['email', 'email'],
['email', 'unique', 'message' => 'This email address has already been taken.', 'on' => 'signup'], ['email', 'unique'],
['email', 'exist', 'message' => 'There is no user with such email.', 'on' => 'requestPasswordResetToken'],
['password', 'required'],
['password', 'string', 'min' => 6],
];
}
public function scenarios()
{
return [
'signup' => ['username', 'email', 'password', '!status', '!role'],
'resetPassword' => ['password'],
'requestPasswordResetToken' => ['email'],
]; ];
} }
public function beforeSave($insert)
{
if (parent::beforeSave($insert)) {
if (($this->isNewRecord || $this->getScenario() === 'resetPassword') && !empty($this->password)) {
$this->password_hash = Security::generatePasswordHash($this->password);
}
if ($this->isNewRecord) {
$this->auth_key = Security::generateRandomKey();
}
return true;
}
return false;
}
} }
...@@ -16,7 +16,7 @@ class m130524_201442_init extends \yii\db\Migration ...@@ -16,7 +16,7 @@ class m130524_201442_init extends \yii\db\Migration
'username' => Schema::TYPE_STRING . ' NOT NULL', 'username' => Schema::TYPE_STRING . ' NOT NULL',
'auth_key' => Schema::TYPE_STRING . '(32) NOT NULL', 'auth_key' => Schema::TYPE_STRING . '(32) NOT NULL',
'password_hash' => Schema::TYPE_STRING . ' NOT NULL', 'password_hash' => Schema::TYPE_STRING . ' NOT NULL',
'password_reset_token' => Schema::TYPE_STRING . '(32)', 'password_reset_token' => Schema::TYPE_STRING,
'email' => Schema::TYPE_STRING . ' NOT NULL', 'email' => Schema::TYPE_STRING . ' NOT NULL',
'role' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 10', 'role' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 10',
......
...@@ -8,5 +8,10 @@ return [ ...@@ -8,5 +8,10 @@ return [
'password' => '', 'password' => '',
'charset' => 'utf8', 'charset' => 'utf8',
], ],
'mail' => [
'class' => 'yii\swiftmailer\Mailer',
'viewPath' => '@common/mails',
'useFileTransport' => true,
],
], ],
]; ];
...@@ -8,5 +8,9 @@ return [ ...@@ -8,5 +8,9 @@ return [
'password' => '', 'password' => '',
'charset' => 'utf8', 'charset' => 'utf8',
], ],
'mail' => [
'class' => 'yii\swiftmailer\Mailer',
'viewPath' => '@common/mails',
],
], ],
]; ];
<?php <?php
namespace frontend\controllers; namespace frontend\controllers;
use Yii;
use yii\web\Controller;
use common\models\LoginForm; use common\models\LoginForm;
use frontend\models\PasswordResetRequestForm;
use frontend\models\ResetPasswordForm;
use frontend\models\SignupForm;
use frontend\models\ContactForm; use frontend\models\ContactForm;
use common\models\User; use yii\base\InvalidParamException;
use yii\web\BadRequestHttpException; use yii\web\BadRequestHttpException;
use yii\helpers\Security; use yii\web\Controller;
use Yii;
/**
* Site controller
*/
class SiteController extends Controller class SiteController extends Controller
{ {
/**
* @inheritdoc
*/
public function behaviors() public function behaviors()
{ {
return [ return [
...@@ -34,6 +41,9 @@ class SiteController extends Controller ...@@ -34,6 +41,9 @@ class SiteController extends Controller
]; ];
} }
/**
* @inheritdoc
*/
public function actions() public function actions()
{ {
return [ return [
...@@ -59,7 +69,7 @@ class SiteController extends Controller ...@@ -59,7 +69,7 @@ class SiteController extends Controller
} }
$model = new LoginForm(); $model = new LoginForm();
if ($model->load($_POST) && $model->login()) { if ($model->load(Yii::$app->request->post()) && $model->login()) {
return $this->goBack(); return $this->goBack();
} else { } else {
return $this->render('login', [ return $this->render('login', [
...@@ -94,13 +104,15 @@ class SiteController extends Controller ...@@ -94,13 +104,15 @@ class SiteController extends Controller
public function actionSignup() public function actionSignup()
{ {
$model = new User(); $model = new SignupForm();
$model->setScenario('signup'); if ($model->load(Yii::$app->request->post())) {
if ($model->load($_POST) && $model->save()) { $user = $model->signup();
if (Yii::$app->getUser()->login($model)) { if ($user) {
if (Yii::$app->getUser()->login($user)) {
return $this->goHome(); return $this->goHome();
} }
} }
}
return $this->render('signup', [ return $this->render('signup', [
'model' => $model, 'model' => $model,
...@@ -109,16 +121,16 @@ class SiteController extends Controller ...@@ -109,16 +121,16 @@ class SiteController extends Controller
public function actionRequestPasswordReset() public function actionRequestPasswordReset()
{ {
$model = new User(); $model = new PasswordResetRequestForm();
$model->scenario = 'requestPasswordResetToken'; if ($model->load(Yii::$app->request->post())) {
if ($model->load($_POST) && $model->validate()) { if ($model->sendEmail()) {
if ($this->sendPasswordResetEmail($model->email)) {
Yii::$app->getSession()->setFlash('success', 'Check your email for further instructions.'); Yii::$app->getSession()->setFlash('success', 'Check your email for further instructions.');
return $this->goHome(); return $this->goHome();
} else { } else {
Yii::$app->getSession()->setFlash('error', 'There was an error sending email.'); Yii::$app->getSession()->setFlash('error', 'Sorry, we are unable to reset password for email provided.');
} }
} }
return $this->render('requestPasswordResetToken', [ return $this->render('requestPasswordResetToken', [
'model' => $model, 'model' => $model,
]); ]);
...@@ -126,21 +138,13 @@ class SiteController extends Controller ...@@ -126,21 +138,13 @@ class SiteController extends Controller
public function actionResetPassword($token) public function actionResetPassword($token)
{ {
if (empty($token) || is_array($token)) { try {
throw new BadRequestHttpException('Invalid password reset token.'); $model = new ResetPasswordForm($token);
} } catch (InvalidParamException $e) {
throw new BadRequestHttpException($e->getMessage());
$model = User::find([
'password_reset_token' => $token,
'status' => User::STATUS_ACTIVE,
]);
if ($model === null) {
throw new BadRequestHttpException('Wrong password reset token.');
} }
$model->scenario = 'resetPassword'; if ($model->load($_POST) && $model->resetPassword()) {
if ($model->load($_POST) && $model->save()) {
Yii::$app->getSession()->setFlash('success', 'New password was saved.'); Yii::$app->getSession()->setFlash('success', 'New password was saved.');
return $this->goHome(); return $this->goHome();
} }
...@@ -149,27 +153,4 @@ class SiteController extends Controller ...@@ -149,27 +153,4 @@ class SiteController extends Controller
'model' => $model, 'model' => $model,
]); ]);
} }
private function sendPasswordResetEmail($email)
{
$user = User::find([
'status' => User::STATUS_ACTIVE,
'email' => $email,
]);
if (!$user) {
return false;
}
$user->password_reset_token = Security::generateRandomKey();
if ($user->save(false)) {
return \Yii::$app->mail->compose('passwordResetToken', ['user' => $user])
->setFrom([\Yii::$app->params['supportEmail'] => \Yii::$app->name . ' robot'])
->setTo($email)
->setSubject('Password reset for ' . \Yii::$app->name)
->send();
}
return false;
}
} }
<?php
namespace frontend\models;
use common\models\User;
use yii\base\Model;
/**
* Password reset request form
*/
class PasswordResetRequestForm extends Model
{
public $email;
/**
* @inheritdoc
*/
public function rules()
{
return [
['email', 'filter', 'filter' => 'trim'],
['email', 'required'],
['email', 'email'],
['email', 'exist', 'targetClass' => '\common\models\User', 'message' => 'There is no user with such email.'],
];
}
/**
*
* @return boolean sends an email
*/
public function sendEmail()
{
/** @var User $user */
$user = User::find([
'status' => User::STATUS_ACTIVE,
'email' => $this->email,
]);
if (!$user) {
return false;
}
$user->generatePasswordResetToken();
if ($user->save()) {
return \Yii::$app->mail->compose('passwordResetToken', ['user' => $user])
->setFrom([\Yii::$app->params['supportEmail'] => \Yii::$app->name . ' robot'])
->setTo($this->email)
->setSubject('Password reset for ' . \Yii::$app->name)
->send();
}
return false;
}
}
\ No newline at end of file
<?php
namespace frontend\models;
use common\models\User;
use yii\base\InvalidParamException;
use yii\base\Model;
use Yii;
/**
* Password reset form
*/
class ResetPasswordForm extends Model
{
public $password;
/**
* @var \common\models\User
*/
private $_user;
/**
* Creates a form model given a token
*
* @param string $token
* @param array $config name-value pairs that will be used to initialize the object properties
* @throws \yii\base\InvalidParamException if token is empty or not valid
*/
public function __construct($token, $config = [])
{
if (empty($token) || !is_string($token)) {
throw new InvalidParamException('Password reset token cannot be blank.');
}
$this->_user = User::findByPasswordResetToken($token);
if (!$this->_user) {
throw new InvalidParamException('Wrong password reset token.');
}
parent::__construct($config);
}
/**
* @return array the validation rules.
*/
public function rules()
{
return [
['password', 'required'],
['password', 'string', 'min' => 6],
];
}
/**
* Resets password.
* @return boolean if password was reset.
*/
public function resetPassword()
{
$user = $this->_user;
$user->password = $this->password;
$user->removePasswordResetToken();
return $user->save();
}
}
\ No newline at end of file
<?php
namespace frontend\models;
use common\models\User;
use yii\base\Model;
use Yii;
/**
* Signup form
*/
class SignupForm extends Model
{
public $username;
public $email;
public $password;
/**
* @inheritdoc
*/
public function rules()
{
return [
['username', 'filter', 'filter' => 'trim'],
['username', 'required'],
['username', 'string', 'min' => 2, 'max' => 255],
['email', 'filter', 'filter' => 'trim'],
['email', 'required'],
['email', 'email'],
['email', 'unique', 'targetClass' => '\common\models\User', 'message' => 'This email address has already been taken.'],
['password', 'required'],
['password', 'string', 'min' => 6],
];
}
/**
* Signs user up.
* @return User saved model
*/
public function signup()
{
if ($this->validate()) {
return User::create($this->attributes);
}
return null;
}
}
\ No newline at end of file
...@@ -6,7 +6,7 @@ use yii\captcha\Captcha; ...@@ -6,7 +6,7 @@ use yii\captcha\Captcha;
/** /**
* @var yii\web\View $this * @var yii\web\View $this
* @var yii\widgets\ActiveForm $form * @var yii\widgets\ActiveForm $form
* @var frontend\models\ContactForm $model * @var \frontend\models\ContactForm $model
*/ */
$this->title = 'Contact'; $this->title = 'Contact';
$this->params['breadcrumbs'][] = $this->title; $this->params['breadcrumbs'][] = $this->title;
......
...@@ -5,7 +5,7 @@ use yii\widgets\ActiveForm; ...@@ -5,7 +5,7 @@ use yii\widgets\ActiveForm;
/** /**
* @var yii\web\View $this * @var yii\web\View $this
* @var yii\widgets\ActiveForm $form * @var yii\widgets\ActiveForm $form
* @var common\models\LoginForm $model * @var \common\models\LoginForm $model
*/ */
$this->title = 'Login'; $this->title = 'Login';
$this->params['breadcrumbs'][] = $this->title; $this->params['breadcrumbs'][] = $this->title;
......
...@@ -5,7 +5,7 @@ use yii\widgets\ActiveForm; ...@@ -5,7 +5,7 @@ use yii\widgets\ActiveForm;
/** /**
* @var yii\web\View $this * @var yii\web\View $this
* @var yii\widgets\ActiveForm $form * @var yii\widgets\ActiveForm $form
* @var common\models\User $model * @var \frontend\models\PasswordResetRequestForm $model
*/ */
$this->title = 'Request password reset'; $this->title = 'Request password reset';
$this->params['breadcrumbs'][] = $this->title; $this->params['breadcrumbs'][] = $this->title;
......
...@@ -5,7 +5,7 @@ use yii\widgets\ActiveForm; ...@@ -5,7 +5,7 @@ use yii\widgets\ActiveForm;
/** /**
* @var yii\web\View $this * @var yii\web\View $this
* @var yii\widgets\ActiveForm $form * @var yii\widgets\ActiveForm $form
* @var common\models\User $model * @var \frontend\models\ResetPasswordForm $model
*/ */
$this->title = 'Reset password'; $this->title = 'Reset password';
$this->params['breadcrumbs'][] = $this->title; $this->params['breadcrumbs'][] = $this->title;
......
...@@ -5,7 +5,7 @@ use yii\widgets\ActiveForm; ...@@ -5,7 +5,7 @@ use yii\widgets\ActiveForm;
/** /**
* @var yii\web\View $this * @var yii\web\View $this
* @var yii\widgets\ActiveForm $form * @var yii\widgets\ActiveForm $form
* @var common\models\User $model * @var \frontend\models\SignupForm $model
*/ */
$this->title = 'Signup'; $this->title = 'Signup';
$this->params['breadcrumbs'][] = $this->title; $this->params['breadcrumbs'][] = $this->title;
......
...@@ -16,7 +16,7 @@ namespace frontend\widgets; ...@@ -16,7 +16,7 @@ namespace frontend\widgets;
* - \Yii::$app->getSession()->setFlash('info', 'This is the message'); * - \Yii::$app->getSession()->setFlash('info', 'This is the message');
* *
* @author Kartik Visweswaran <kartikv2@gmail.com> * @author Kartik Visweswaran <kartikv2@gmail.com>
* @author Alexander Makarov <sam@rmcerative.ru> * @author Alexander Makarov <sam@rmcreative.ru>
*/ */
class Alert extends \yii\bootstrap\Widget class Alert extends \yii\bootstrap\Widget
{ {
......
...@@ -21,12 +21,14 @@ ...@@ -21,12 +21,14 @@
}, },
"require-dev": { "require-dev": {
"yiisoft/yii2-codeception": "*", "yiisoft/yii2-codeception": "*",
"codeception/codeception": "*",
"codeception/specify": "*",
"codeception/verify": "*",
"yiisoft/yii2-debug": "*", "yiisoft/yii2-debug": "*",
"yiisoft/yii2-gii": "*" "yiisoft/yii2-gii": "*"
}, },
"suggest": {
"codeception/codeception": "Codeception, 1.8.*@dev is currently works well with Yii.",
"codeception/specify": "BDD style code blocks for PHPUnit / Codeception",
"codeception/verify": "BDD Assertions for PHPUnit and Codeception"
},
"scripts": { "scripts": {
"post-create-project-cmd": [ "post-create-project-cmd": [
"yii\\composer\\Installer::setPermission" "yii\\composer\\Installer::setPermission"
......
...@@ -3,9 +3,14 @@ These tests are developed with [Codeception PHP Testing Framework](http://codece ...@@ -3,9 +3,14 @@ These tests are developed with [Codeception PHP Testing Framework](http://codece
After creating the basic application, follow these steps to prepare for the tests: After creating the basic application, follow these steps to prepare for the tests:
1. In the file `_bootstrap.php`, modify the definition of the constant `TEST_ENTRY_URL` so 1. Install additional composer packages:
```
php composer.phar require --dev "codeception/codeception: 1.8.*@dev" "codeception/specify: *" "codeception/verify: *"
```
2. In the file `_bootstrap.php`, modify the definition of the constant `TEST_ENTRY_URL` so
that it points to the correct entry script URL. that it points to the correct entry script URL.
2. Go to the application base directory and build the test suites: 3. Go to the application base directory and build the test suites:
``` ```
vendor/bin/codecept build vendor/bin/codecept build
......
...@@ -13,10 +13,10 @@ class ContactPage extends BasePage ...@@ -13,10 +13,10 @@ class ContactPage extends BasePage
*/ */
public function submit(array $contactData) public function submit(array $contactData)
{ {
$data = []; foreach ($contactData as $field => $value) {
foreach ($contactData as $name => $value) { $inputType = $field === 'body' ? 'textarea' : 'input';
$data["ContactForm[$name]"] = $value; $this->guy->fillField($inputType . '[name="ContactForm[' . $field . ']"]', $value);
} }
$this->guy->submitForm('#contact-form', $data); $this->guy->click('Submit', '#contact-form');
} }
} }
...@@ -14,9 +14,8 @@ class LoginPage extends BasePage ...@@ -14,9 +14,8 @@ class LoginPage extends BasePage
*/ */
public function login($username, $password) public function login($username, $password)
{ {
$this->guy->submitForm('#login-form', [ $this->guy->fillField('input[name="LoginForm[username]"]',$username);
'LoginForm[username]' => $username, $this->guy->fillField('input[name="LoginForm[password]"]',$password);
'LoginForm[password]' => $password, $this->guy->click('Login','#login-form');
]);
} }
} }
...@@ -15,6 +15,8 @@ modules: ...@@ -15,6 +15,8 @@ modules:
- PhpBrowser - PhpBrowser
# you can use WebDriver instead of PhpBrowser to test javascript and ajax. # you can use WebDriver instead of PhpBrowser to test javascript and ajax.
# This will require you to install selenium. See http://codeception.com/docs/04-AcceptanceTests#Selenium # This will require you to install selenium. See http://codeception.com/docs/04-AcceptanceTests#Selenium
# "restart" option is used by the WebDriver to start each time per test-file new session and cookies,
# it is useful if you want to login in your app in each test.
# - WebDriver # - WebDriver
config: config:
PhpBrowser: PhpBrowser:
...@@ -22,3 +24,4 @@ modules: ...@@ -22,3 +24,4 @@ modules:
# WebDriver: # WebDriver:
# url: 'http://localhost' # url: 'http://localhost'
# browser: firefox # browser: firefox
# restart: true
...@@ -93,7 +93,7 @@ ...@@ -93,7 +93,7 @@
"imagine/imagine": "required by yii2-imagine extension", "imagine/imagine": "required by yii2-imagine extension",
"smarty/smarty": "required by yii2-smarty extension", "smarty/smarty": "required by yii2-smarty extension",
"swiftmailer/swiftmailer": "required by yii2-swiftmailer extension", "swiftmailer/swiftmailer": "required by yii2-swiftmailer extension",
"twig/twig": "required by yii2-twig extension" "yiisoft/yii2-coding-standards": "you can use this package to check for code style issues when contributing to yii"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
......
...@@ -33,7 +33,7 @@ the installed application. You only need to do these once for all. ...@@ -33,7 +33,7 @@ the installed application. You only need to do these once for all.
``` ```
php /path/to/yii-application/init php /path/to/yii-application/init
``` ```
2. Create a new database and adjust the `components.db` configuration in `common/config/params-local.php` accordingly. 2. Create a new database and adjust the `components.db` configuration in `common/config/main-local.php` accordingly.
3. Apply migrations with console command `yii migrate`. 3. Apply migrations with console command `yii migrate`.
4. Set document roots of your Web server: 4. Set document roots of your Web server:
......
Database Fixtures Managing Fixtures
================= =================
// todo: this tutorial may be merged into test-fixture.md
Fixtures are important part of testing. Their main purpose is to populate you with data that needed by testing Fixtures are important part of testing. Their main purpose is to populate you with data that needed by testing
different cases. With this data using your tests becoming more efficient and useful. different cases. With this data using your tests becoming more efficient and useful.
Yii supports database fixtures via the `yii fixture` command line tool. This tool supports: Yii supports fixtures via the `yii fixture` command line tool. This tool supports:
* Applying new fixtures to database tables; * Loading fixtures to different storage such as: RDBMS, NoSQL, etc;
* Clearing, database tables (with sequences); * Unloading fixtures in different ways (usually it is clearing storage);
* Auto-generating fixtures and populating it with random data. * Auto-generating fixtures and populating it with random data.
Fixtures format Fixtures format
--------------- ---------------
Fixtures are just plain php files returning array. These files are usually stored under `@tests/unit/fixtures` path, but it Fixtures are objects with different methods and configurations, refer to official [documentation](https://github.com/yiisoft/yii2/blob/master/docs/guide/test-fixture.md) on them.
can be [configured](#configure-command-globally) in other way. Example of fixture file: Lets assume we have fixtures data to load:
``` ```
#users.php file under fixtures path #users.php file under fixtures data path, by default @tests\unit\fixtures\data
return [ return [
[ [
...@@ -36,61 +38,72 @@ return [ ...@@ -36,61 +38,72 @@ return [
], ],
]; ];
``` ```
If we are using fixture that loads data into database then these rows will be applied to `users` table. If we are using nosql fixtures, for example `mongodb`
This data will be loaded to the `users`, but before it will be loaded table `users` will be cleared: all data deleted, sequence reset. fixture, then this data will be applied to `users` mongodb collection. In order to learn about implementing various loading strategies and more, refer to official [documentation](https://github.com/yiisoft/yii2/blob/master/docs/guide/test-fixture.md).
Above fixture example was auto-generated by `yii2-faker` extension, read more about it in these [section](#auto-generating-fixtures). Above fixture example was auto-generated by `yii2-faker` extension, read more about it in these [section](#auto-generating-fixtures).
Fixture classes name should not be plural.
Applying fixtures Loading fixtures
----------------- ----------------
To apply fixture to the table, run the following command: Fixture classes should be suffixed by `Fixture` class. By default fixtures will be searched under `tests\unit\fixtures` namespace, you can
change this behavior with config or command options.
To load fixture, run the following command:
``` ```
yii fixture/apply <tbl_name> yii fixture/load <fixture_name>
``` ```
The required `tbl_name` parameter specifies a database table to which data will be loaded. You can load data to several tables at once. The required `fixture_name` parameter specifies a fixture name which data will be loaded. You can load several fixtures at once.
Below are correct formats of this command: Below are correct formats of this command:
``` ```
// apply fixtures to the "users" table of database // load `users` fixture
yii fixture/apply users yii fixture/load User
// same as above, because default action of "fixture" command is "apply" // same as above, because default action of "fixture" command is "load"
yii fixture users yii fixture User
// apply several fixtures to several tables. Note that there should not be any whitespace between ",", it should be one string. // load several fixtures. Note that there should not be any whitespace between ",", it should be one string.
yii fixture users,users_profiles yii fixture User,UserProfile
// apply all fixtures // load all fixtures
yii fixture/apply all yii fixture/load all
// same as above // same as above
yii fixture all yii fixture all
// apply fixtures to the table users, but fixtures will be taken from different path. // load fixtures, but for other database connection.
yii fixture users --fixturePath='@app/my/custom/path/to/fixtures' yii fixture User --db='customDbConnectionId'
// load fixtures, but search them in different namespace. By default namespace is: tests\unit\fixtures.
yii fixture User --namespace='alias\my\custom\namespace'
// apply fixtures to the table users, but for other database connection. // load global fixture `some\name\space\CustomFixture` before other fixtures will be loaded.
yii fixtures users --db='customDbConnectionId' // By default this option is set to `InitDbFixture` to disable/enable integrity checks. You can specify several
// global fixtures separated by comma.
yii fixture User --globalFixtures='some\name\space\Custom'
``` ```
Clearing tables Unloading fixtures
--------------- ------------------
To clear table, run the following command: To unload fixture, run the following command:
``` ```
// clear given table: delete all data and reset sequence. // unload Users fixture, by default it will clear fixture storage (for example "users" table, or "users" collection if this is mongodb fixture).
yii fixture/clear users yii fixture/unload User
// clear several tables. Note that there should not be any whitespace between ",", it should be one string. // Unload several fixtures. Note that there should not be any whitespace between ",", it should be one string.
yii fixture/clear users,users_profile yii fixture/unload User,UserProfile
// clear all tables of current connection in current schema // unload all fixtures
yii fixture/clear all yii fixture/unload all
``` ```
Same command options like: `db`, `namespace`, `globalFixtures` also can be applied to this command.
Configure Command Globally Configure Command Globally
-------------------------- --------------------------
While command line options allow us to configure the migration command While command line options allow us to configure the migration command
...@@ -100,9 +113,13 @@ different migration path as follows: ...@@ -100,9 +113,13 @@ different migration path as follows:
``` ```
'controllerMap' => [ 'controllerMap' => [
'fixture' => [ 'fixture' => [
'class' => 'yii\console\FixtureController', 'class' => 'yii\console\controllers\FixtureController',
'fixturePath' => '@app/my/custom/path/to/fixtures',
'db' => 'customDbConnectionId', 'db' => 'customDbConnectionId',
'namespace' => 'myalias\some\custom\namespace',
'globalFixtures' => [
'some\name\space\Foo',
'other\name\space\Bar'
],
], ],
] ]
``` ```
......
...@@ -91,6 +91,9 @@ to a console command. The method should return a list of public property names o ...@@ -91,6 +91,9 @@ to a console command. The method should return a list of public property names o
When running a command, you may specify the value of an option using the syntax `--OptionName=OptionValue`. When running a command, you may specify the value of an option using the syntax `--OptionName=OptionValue`.
This will assign `OptionValue` to the `OptionName` property of the controller class. This will assign `OptionValue` to the `OptionName` property of the controller class.
If the default value of an option is of array type, then if you set this option while running the command,
the option value will be converted into an array by splitting the input string by commas.
### Arguments ### Arguments
Besides options, a command can also receive arguments. The arguments will be passed as the parameters to the action Besides options, a command can also receive arguments. The arguments will be passed as the parameters to the action
......
...@@ -91,7 +91,7 @@ If controller is located inside a module its action internal route will be `modu ...@@ -91,7 +91,7 @@ If controller is located inside a module its action internal route will be `modu
In case module, controller or action specified isn't found Yii will return "not found" page and HTTP status code 404. In case module, controller or action specified isn't found Yii will return "not found" page and HTTP status code 404.
> Note: If controller name or action name contains camelCased words, internal route will use dashes i.e. for > Note: If module name, controller name or action name contains camelCased words, internal route will use dashes i.e. for
`DateTimeController::actionFastForward` route will be `date-time/fast-forward`. `DateTimeController::actionFastForward` route will be `date-time/fast-forward`.
### Defaults ### Defaults
......
Database basics Database basics
=============== ===============
Yii has a database access layer built on top of PHP's [PDO](http://www.php.net/manual/en/ref.pdo.php). It provides Yii has a database access layer built on top of PHP's [PDO](http://www.php.net/manual/en/book.pdo.php). It provides
uniform API and solves some inconsistencies between different DBMS. By default Yii supports the following DBMS: uniform API and solves some inconsistencies between different DBMS. By default Yii supports the following DBMS:
- [MySQL](http://www.mysql.com/) - [MySQL](http://www.mysql.com/)
......
Error Handling Error Handling
============== ==============
Error handling in Yii is different from plain PHP. First of all, all non-fatal errors are converted to exceptions so Error handling in Yii is different than handling errors in plain PHP. First of all, Yii will convert all non-fatal errors to *exceptions*. By doing so, you can gracefully handle them using `try`-`catch`. Second, even fatal errors in Yii are rendered in a nice way. This means that in debugging mode, you can trace the causes of fatal errors in order to more quickly identify the cause of the problem.
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 Rendering errors in a dedicated controller action
---------------------------------------- -------------------------------------------------
Default Yii error page is great for development mode and is OK for production if `YII_DEBUG` is turned off but you may The default Yii error page is great when developing a site, and is acceptable for production sites if `YII_DEBUG` is turned off in your bootstrap index.php file. But but you may want to customize the default error page to make it more suitable for your project.
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: The easiest way to create a custom error page it is to use a dedicated controller action for error rendering. First, you'll need to configure the `errorHandler` component in the application's configuration:
```php ```php
return [ return [
...@@ -22,7 +20,7 @@ return [ ...@@ -22,7 +20,7 @@ return [
], ],
``` ```
After it is done in case of error, Yii will launch `SiteController::actionError()`: With that configuration in place, whenever an error occurs, Yii will execute the "error" acction of the "Site" controller. That action should look for an exception and, if present, render the proper view file, passing along the exception:
```php ```php
public function actionError() public function actionError()
...@@ -33,8 +31,22 @@ public function actionError() ...@@ -33,8 +31,22 @@ public function actionError()
} }
``` ```
Since most of the time you need to adjust look and feel only, Yii provides `ErrorAction` class that can be used in Next, you would create the `views/site/error.php` file, which would make use of the exception. The exception object has the following properties:
controller instead of implementing action yourself:
* `code`: the HTTP status code (e.g. 403, 500)
* `type`: the error type (e.g. CHttpException, PHP Error)
* `message`: the error message
* `file`: the name of the PHP script file where the error occurs
* `line`: the line number of the code where the error occurs
* `trace`: the call stack of the error
* `source`: the context source code where the error occurs
[[Need to confirm the above for Yii 2.]]
Rendering errors without a dedicated controller action
------------------------------------------------------
Instead of creating a dedicated action within the Site controller, you could just indicate to Yii what class should be used to handle errors:
```php ```php
public function actions() public function actions()
...@@ -47,10 +59,10 @@ public function actions() ...@@ -47,10 +59,10 @@ public function actions()
} }
``` ```
After defining `actions` in `SiteController` as shown above you can create `views/site/error.php`. In the view there After associating the class with the error as in the above, define the `views/site/error.php` file, which will automatically be used. The view will be passed three variables:
are three variables available:
- `$name`: the error name - `$name`: the error name
- `$message`: the error message - `$message`: the error message
- `$exception`: the exception being handled - `$exception`: the exception being handled
The `$exception` object will have the same properties outlined above.
\ No newline at end of file
...@@ -3,36 +3,95 @@ Events ...@@ -3,36 +3,95 @@ Events
TBD, see also [Component.md](../api/base/Component.md). 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. [[ADD INTRODUCTION]]
Instead, you can use whatever event names. To attach a handler to an event, you should
use the `on` method now: Creating Event Handlers
-----------------------
In Yii 1, events were defined using the `onEventName` method syntax, such as `onBeforeSave`. This is no longer necessary in Yii 2, as event handling is now assigned using the `on` method. The method's first argument is the name of the event to watch for; the second is the handling method to be called when that event occurs:
```php ```php
$component->on($eventName, $handler); $component->on($eventName, $handler);
// To detach the handler, use: ```
[[LINK TO LIST OF EVENTS]]
The handler must be a valid PHP callback. This could be represented as:
* The name of a global function
* An array consisting of a model name and method name
* An array consisting of an object and a method name
* An anonymous function
```php
// Global function:
$component->on($eventName, 'functionName');
// Model and method names:
$component->on($eventName, ['Modelname', 'functionName']);
// Object and method name:
$component->on($eventName, [$obj, 'functionName']);
// Anonymous function:
$component->on($eventName, function($event) {
// Use $event.
});
```
As shown in the anonymous function example, the event handling function must be defined so that it takes one argument. This will be an [[Event]] object.
Removing Event Handlers
-----------------------
The correspondoing `off` method removes an event handler:
```php
// $component->off($eventName);
```
Yii supports the ability to associate multiple handlers with the same event. When using `off` as in the above, every handler is removed. To remove only a specific handler, provide that as the second argument to `off`:
```php
// $component->off($eventName, $handler); // $component->off($eventName, $handler);
``` ```
The `$handler` should be presented in the `off` method in the same way as was presented in `on` in order to remove it.
Event Parameters
----------------
When you attach a handler, you can now associate it with some parameters which can be later You can make your event handlers easier to work with and more powerful by passing additional values as parameters.
accessed via the event parameter by the handler:
```php ```php
$component->on($eventName, $handler, $params); $component->on($eventName, $handler, $params);
``` ```
The passed parameters will be available in the event handler through `$event->data`, which will be an array.
Because of this change, you can now use "global" events. Simply trigger and attach handlers to [[NEED TO CONFIRM THE ABOVE]]
an event of the application instance:
Global Events
-------------
Thanks to the change in Yii 2 as to how event handlers are created, you can now use "global" events. To create a global event, simply attach handlers to an event on the application instance:
```php ```php
Yii::$app->on($eventName, $handler); Yii::$app->on($eventName, $handler);
.... ```
// this will trigger the event and cause $handler to be invoked.
You can use the `trigger` method to trigger these events manually:
```php
// this will trigger the event and cause $handler to be invoked:
Yii::$app->trigger($eventName); 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: Class Events
------------
You can also attach event handlers to all instances of a class instead of individual instances. To do so, use the static `Event::on` method:
```php ```php
Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function ($event) { Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function ($event) {
......
...@@ -356,7 +356,8 @@ class Module extends \yii\base\Module ...@@ -356,7 +356,8 @@ class Module extends \yii\base\Module
} }
``` ```
In the example above we are using wildcard for matching and then filtering each category per needed file. In the example above we are using wildcard for matching and then filtering each category per needed file. Instead of using `fileMap` you can simply
use convention of category mapping to the same named file and use `Module::t('validation', 'your custom validation message')` or `Module::t('form', 'some form label')` directly.
###Translating widgets messages ###Translating widgets messages
...@@ -405,6 +406,8 @@ class Menu extends Widget ...@@ -405,6 +406,8 @@ class Menu extends Widget
} }
``` ```
Instead of using `fileMap` you can simply use convention of category mapping to the same named file and use `Menu::t('messages', 'new messages {messages}', ['{messages}' => 10])` directly.
> **Note**: For widgets you also can use i18n views, same rules as for controllers are applied to them too. > **Note**: For widgets you also can use i18n views, same rules as for controllers are applied to them too.
TBD: provided classes overview. TBD: provided classes overview.
...@@ -15,8 +15,8 @@ PHP 5.4.0 or greater. ...@@ -15,8 +15,8 @@ PHP 5.4.0 or greater.
For developers who want to use Yii, understanding object-oriented For developers who want to use Yii, understanding object-oriented
programming (OOP) is very helpful, because Yii is a pure OOP framework. 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) 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. so you should be familiar with how they work, too.
What is Yii Best for? What is Yii Best for?
...@@ -24,16 +24,16 @@ What is Yii Best for? ...@@ -24,16 +24,16 @@ What is Yii Best for?
Yii is a generic Web programming framework that can be used for developing 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 equipped with sophisticated caching mechanisms, Yii is especially suited
to high-traffic applications, such as portals, forums, content to high-traffic applications such as portals, forums, content
management systems (CMS), e-commerce projects, etc. management systems (CMS), e-commerce projects, and so on.
How does Yii Compare with Other Frameworks? How does Yii Compare with Other Frameworks?
------------------------------------------- -------------------------------------------
- Like most PHP frameworks, Yii is uses the MVC (Model-View-Controller) design approach. - Like most PHP frameworks, Yii is uses the MVC (Model-View-Controller) design approach.
- Yii is a fullstack framework providing many solutions and components, such as logging, session management, caching etc. - Yii is a fullstack framework providing many solutions and components, such as logging, session management, caching, etc.
- Yii strikes a good balance between simplicity and features. - Yii strikes a good balance between simplicity and features.
- Syntax and overall development usability are taken seriously by the Yii development team. - Syntax and overall development usability are taken seriously by the Yii development team.
- Performance is one of the key goals for the Yii framework. - Performance is one of the key goals for the Yii framework.
......
...@@ -116,7 +116,7 @@ use app\tests\fixtures\UserProfileFixture; ...@@ -116,7 +116,7 @@ use app\tests\fixtures\UserProfileFixture;
class UserProfileTest extends DbTestCase class UserProfileTest extends DbTestCase
{ {
protected function fixtures() public function fixtures()
{ {
return [ return [
'profiles' => UserProfileFixture::className(), 'profiles' => UserProfileFixture::className(),
...@@ -175,6 +175,43 @@ This means you only need to work with `@app/tests/fixtures/initdb.php` if you wa ...@@ -175,6 +175,43 @@ This means you only need to work with `@app/tests/fixtures/initdb.php` if you wa
before each test. You may otherwise simply focus on developing each individual test case and the corresponding fixtures. before each test. You may otherwise simply focus on developing each individual test case and the corresponding fixtures.
Organizing Fixture Classes and Data Files
-----------------------------------------
By default, fixture classes look for the corresponding data files under the `data` folder which is a sub-folder
of the folder containing the fixture class files. You can follow this convention when working with simple projects.
For big projects, chances are that you often need to switch different data files for the same fixture class for
different tests. We thus recommend that you organize the data files in a hierarchical way that is similar to
your class namespaces. For example,
```
# under folder tests\unit\fixtures
data\
components\
fixture_data_file1.php
fixture_data_file2.php
...
fixture_data_fileN.php
models\
fixture_data_file1.php
fixture_data_file2.php
...
fixture_data_fileN.php
# and so on
```
In this way you will avoid collision of fixture data files between tests and use them as you need.
> Note: In the example above fixture files are named only for example purpose. In real life you should name them
> according to which fixture class your fixture classes are extending from. For example, if you are extending
> from [[\yii\test\ActiveFixture]] for DB fixtures, you should use DB table names as the fixture data file names;
> If you are extending for [[\yii\mongodb\ActiveFixture]] for MongoDB fixtures, you should use collection names as the file names.
The similar hierarchy can be used to organize fixture class files. Instead of using `data` as the root directory, you may
want to use `fixtures` as the root directory to avoid conflict with the data files.
Summary Summary
------- -------
...@@ -186,5 +223,5 @@ of running unit tests related with DB: ...@@ -186,5 +223,5 @@ of running unit tests related with DB:
- Load fixtures: clean up the relevant DB tables and populate them with fixture data; - Load fixtures: clean up the relevant DB tables and populate them with fixture data;
- Perform the actual test; - Perform the actual test;
- Unload fixtures. - Unload fixtures.
3. Repeat 2 until all tests finish. 3. Repeat Step 2 until all tests finish.
...@@ -26,4 +26,4 @@ automatically re-extract messages keeping unchanged ones intact. ...@@ -26,4 +26,4 @@ automatically re-extract messages keeping unchanged ones intact.
In the translation file each array element represents the translation (value) of a message (key). If the value is empty, In the translation file each array element represents the translation (value) of a message (key). If the value is empty,
the message is considered as not translated. Messages that no longer need translation will have their translations the message is considered as not translated. Messages that no longer need translation will have their translations
enclosed between a pair of '@@' marks. Message string can be used with plural forms format. Check [i18n section enclosed between a pair of '@@' marks. Message string can be used with plural forms format. Check [i18n section
of the guide](i18n.md) for details. of the guide](../guide/i18n.md) for details.
...@@ -9,7 +9,6 @@ namespace yii\apidoc\models; ...@@ -9,7 +9,6 @@ namespace yii\apidoc\models;
use phpDocumentor\Reflection\FileReflector; use phpDocumentor\Reflection\FileReflector;
use yii\base\Component; use yii\base\Component;
use yii\base\Exception;
/** /**
* *
......
...@@ -11,7 +11,6 @@ use phpDocumentor\Reflection\DocBlock\Tag\ParamTag; ...@@ -11,7 +11,6 @@ use phpDocumentor\Reflection\DocBlock\Tag\ParamTag;
use phpDocumentor\Reflection\DocBlock\Tag\PropertyTag; use phpDocumentor\Reflection\DocBlock\Tag\PropertyTag;
use phpDocumentor\Reflection\DocBlock\Tag\ReturnTag; use phpDocumentor\Reflection\DocBlock\Tag\ReturnTag;
use phpDocumentor\Reflection\DocBlock\Tag\ThrowsTag; use phpDocumentor\Reflection\DocBlock\Tag\ThrowsTag;
use yii\base\Exception;
/** /**
* Represents API documentation information for a `function`. * Represents API documentation information for a `function`.
......
...@@ -63,7 +63,7 @@ class PropertyDoc extends BaseDoc ...@@ -63,7 +63,7 @@ class PropertyDoc extends BaseDoc
$this->defaultValue = PrettyPrinter::getRepresentationOfValue($reflector->getNode()->default); $this->defaultValue = PrettyPrinter::getRepresentationOfValue($reflector->getNode()->default);
} }
foreach($this->tags as $i => $tag) { foreach($this->tags as $tag) {
if ($tag instanceof VarTag) { if ($tag instanceof VarTag) {
$this->type = $tag->getType(); $this->type = $tag->getType();
$this->types = $tag->getTypes(); $this->types = $tag->getTypes();
......
...@@ -8,7 +8,6 @@ ...@@ -8,7 +8,6 @@
namespace yii\apidoc\models; namespace yii\apidoc\models;
use phpDocumentor\Reflection\DocBlock\Tag\AuthorTag; use phpDocumentor\Reflection\DocBlock\Tag\AuthorTag;
use yii\base\Exception;
use yii\helpers\StringHelper; use yii\helpers\StringHelper;
/** /**
......
...@@ -11,7 +11,6 @@ use yii\apidoc\models\Context; ...@@ -11,7 +11,6 @@ use yii\apidoc\models\Context;
use yii\console\Controller; use yii\console\Controller;
use Yii; use Yii;
use yii\helpers\Console; use yii\helpers\Console;
use yii\helpers\FileHelper;
/** /**
* *
...@@ -186,7 +185,7 @@ class Renderer extends \yii\apidoc\templates\html\Renderer ...@@ -186,7 +185,7 @@ class Renderer extends \yii\apidoc\templates\html\Renderer
protected function fixMarkdownLinks($content) protected function fixMarkdownLinks($content)
{ {
$content = preg_replace('/href\s*=\s*"([^"]+)\.md(#.*)?"/i', 'href="guide_\1.html\2"', $content); $content = preg_replace('/href\s*=\s*"([^"\/]+)\.md(#.*)?"/i', 'href="guide_\1.html\2"', $content);
return $content; return $content;
} }
} }
\ No newline at end of file
...@@ -6,7 +6,6 @@ ...@@ -6,7 +6,6 @@
*/ */
namespace yii\apidoc\templates\bootstrap\assets; namespace yii\apidoc\templates\bootstrap\assets;
use yii\web\JqueryAsset;
use yii\web\View; use yii\web\View;
/** /**
......
<?php <?php
use yii\apidoc\templates\bootstrap\SideNavWidget; use yii\apidoc\templates\bootstrap\SideNavWidget;
use yii\helpers\StringHelper;
/** /**
* @var yii\web\View $this * @var yii\web\View $this
......
<?php <?php
use yii\apidoc\templates\bootstrap\SideNavWidget;
use yii\bootstrap\Nav; use yii\bootstrap\Nav;
use yii\bootstrap\NavBar; use yii\bootstrap\NavBar;
use yii\helpers\Html; use yii\helpers\Html;
use yii\helpers\StringHelper;
use yii\widgets\Menu;
/** /**
* @var yii\web\View $this * @var yii\web\View $this
......
...@@ -6,11 +6,7 @@ ...@@ -6,11 +6,7 @@
*/ */
namespace yii\apidoc\templates\offline; namespace yii\apidoc\templates\offline;
use yii\apidoc\models\Context;
use yii\console\Controller;
use Yii; use Yii;
use yii\helpers\Console;
use yii\helpers\FileHelper;
/** /**
* *
......
...@@ -6,7 +6,6 @@ ...@@ -6,7 +6,6 @@
*/ */
namespace yii\apidoc\templates\offline\assets; namespace yii\apidoc\templates\offline\assets;
use yii\web\JqueryAsset;
use yii\web\View; use yii\web\View;
/** /**
......
...@@ -11,8 +11,6 @@ use yii\apidoc\models\TypeDoc; ...@@ -11,8 +11,6 @@ use yii\apidoc\models\TypeDoc;
use yii\console\Controller; use yii\console\Controller;
use Yii; use Yii;
use yii\helpers\Console; use yii\helpers\Console;
use yii\helpers\FileHelper;
use yii\helpers\StringHelper;
/** /**
* *
......
...@@ -315,7 +315,7 @@ class AuthAction extends Action ...@@ -315,7 +315,7 @@ class AuthAction extends Action
return Yii::$app->getResponse()->redirect($url); return Yii::$app->getResponse()->redirect($url);
} else { } else {
// Upgrade to access token. // Upgrade to access token.
$accessToken = $client->fetchAccessToken(); $client->fetchAccessToken();
return $this->authSuccess($client); return $this->authSuccess($client);
} }
} }
......
...@@ -29,6 +29,8 @@ class Dropdown extends Widget ...@@ -29,6 +29,8 @@ class Dropdown extends Widget
* - visible: boolean, optional, whether this menu item is visible. Defaults to true. * - visible: boolean, optional, whether this menu item is visible. Defaults to true.
* - linkOptions: array, optional, the HTML attributes of the item link. * - linkOptions: array, optional, the HTML attributes of the item link.
* - options: array, optional, the HTML attributes of the item. * - options: array, optional, the HTML attributes of the item.
* - items: array, optional, the submenu items. The structure is the same as this property.
* Note that Bootstrap doesn't support dropdown submenu. You have to add your own CSS styles to support it.
* *
* To insert divider use `<li role="presentation" class="divider"></li>`. * To insert divider use `<li role="presentation" class="divider"></li>`.
*/ */
...@@ -84,6 +86,10 @@ class Dropdown extends Widget ...@@ -84,6 +86,10 @@ class Dropdown extends Widget
$linkOptions = ArrayHelper::getValue($item, 'linkOptions', []); $linkOptions = ArrayHelper::getValue($item, 'linkOptions', []);
$linkOptions['tabindex'] = '-1'; $linkOptions['tabindex'] = '-1';
$content = Html::a($label, ArrayHelper::getValue($item, 'url', '#'), $linkOptions); $content = Html::a($label, ArrayHelper::getValue($item, 'url', '#'), $linkOptions);
if (!empty($item['items'])) {
$content .= $this->renderItems($item['items']);
Html::addCssClass($options, 'dropdown-submenu');
}
$lines[] = Html::tag('li', $content, $options); $lines[] = Html::tag('li', $content, $options);
} }
......
...@@ -18,7 +18,7 @@ class DbTestCase extends TestCase ...@@ -18,7 +18,7 @@ class DbTestCase extends TestCase
/** /**
* @inheritdoc * @inheritdoc
*/ */
protected function globalFixtures() public function globalFixtures()
{ {
return [ return [
InitDbFixture::className(), InitDbFixture::className(),
......
...@@ -5,6 +5,9 @@ namespace yii\codeception; ...@@ -5,6 +5,9 @@ namespace yii\codeception;
use Yii; use Yii;
use yii\base\InvalidConfigException; use yii\base\InvalidConfigException;
use Codeception\TestCase\Test; use Codeception\TestCase\Test;
use yii\base\UnknownMethodException;
use yii\base\UnknownPropertyException;
use yii\test\ActiveFixture;
use yii\test\FixtureTrait; use yii\test\FixtureTrait;
/** /**
...@@ -26,12 +29,52 @@ class TestCase extends Test ...@@ -26,12 +29,52 @@ class TestCase extends Test
public $appConfig = '@tests/unit/_config.php'; public $appConfig = '@tests/unit/_config.php';
/** /**
* Returns the value of an object property.
*
* Do not call this method directly as it is a PHP magic method that
* will be implicitly called when executing `$value = $object->property;`.
* @param string $name the property name
* @return mixed the property value
* @throws UnknownPropertyException if the property is not defined
*/
public function __get($name)
{
$fixture = $this->getFixture($name);
if ($fixture !== null) {
return $fixture;
} else {
throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name);
}
}
/**
* Calls the named method which is not a class method.
*
* Do not call this method directly as it is a PHP magic method that
* will be implicitly called when an unknown method is being invoked.
* @param string $name the method name
* @param array $params method parameters
* @throws UnknownMethodException when calling unknown method
* @return mixed the method return value
*/
public function __call($name, $params)
{
$fixture = $this->getFixture($name);
if ($fixture instanceof ActiveFixture) {
return $fixture->getModel(reset($params));
} else {
throw new UnknownMethodException('Unknown method: ' . get_class($this) . "::$name()");
}
}
/**
* @inheritdoc * @inheritdoc
*/ */
protected function setUp() protected function setUp()
{ {
parent::setUp(); parent::setUp();
$this->mockApplication(); $this->mockApplication();
$this->unloadFixtures();
$this->loadFixtures(); $this->loadFixtures();
} }
...@@ -40,7 +83,6 @@ class TestCase extends Test ...@@ -40,7 +83,6 @@ class TestCase extends Test
*/ */
protected function tearDown() protected function tearDown()
{ {
$this->unloadFixtures();
$this->destroyApplication(); $this->destroyApplication();
parent::tearDown(); parent::tearDown();
} }
......
...@@ -8,6 +8,7 @@ Yii Framework 2 debug extension Change Log ...@@ -8,6 +8,7 @@ Yii Framework 2 debug extension Change Log
- Bug #1504: Debug toolbar isn't loaded successfully in some environments when xdebug is enabled (qiangxue) - Bug #1504: Debug toolbar isn't loaded successfully in some environments when xdebug is enabled (qiangxue)
- Bug #1747: Fixed problems with displaying toolbar on small screens (cebe) - Bug #1747: Fixed problems with displaying toolbar on small screens (cebe)
- Bug #1827: Debugger toolbar is loaded twice if an action is calling `run()` to execute another action (qiangxue) - Bug #1827: Debugger toolbar is loaded twice if an action is calling `run()` to execute another action (qiangxue)
- Enh #2006: Added total queries count monitoring (o-rey, Ragazzo)
2.0.0 alpha, December 1, 2013 2.0.0 alpha, December 1, 2013
----------------------------- -----------------------------
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
*/ */
namespace yii\debug; namespace yii\debug;
use yii\web\AssetBundle; use yii\web\AssetBundle;
/** /**
......
...@@ -154,5 +154,4 @@ class LogTarget extends Target ...@@ -154,5 +154,4 @@ class LogTarget extends Target
# / 2 because messages are in couple (begin/end) # / 2 because messages are in couple (begin/end)
return count($profileLogs['messages']) / 2; return count($profileLogs['messages']) / 2;
} }
} }
...@@ -39,7 +39,7 @@ class DefaultController extends Controller ...@@ -39,7 +39,7 @@ class DefaultController extends Controller
public function actions() public function actions()
{ {
$actions = []; $actions = [];
foreach($this->module->panels as $panel) { foreach ($this->module->panels as $panel) {
$actions = array_merge($actions, $panel->actions); $actions = array_merge($actions, $panel->actions);
} }
return $actions; return $actions;
...@@ -50,7 +50,13 @@ class DefaultController extends Controller ...@@ -50,7 +50,13 @@ class DefaultController extends Controller
$searchModel = new Debug(); $searchModel = new Debug();
$dataProvider = $searchModel->search($_GET, $this->getManifest()); $dataProvider = $searchModel->search($_GET, $this->getManifest());
// load latest request
$tags = array_keys($this->getManifest());
$tag = reset($tags);
$this->loadData($tag);
return $this->render('index', [ return $this->render('index', [
'panels' => $this->module->panels,
'dataProvider' => $dataProvider, 'dataProvider' => $dataProvider,
'searchModel' => $searchModel, 'searchModel' => $searchModel,
]); ]);
...@@ -87,10 +93,6 @@ class DefaultController extends Controller ...@@ -87,10 +93,6 @@ class DefaultController extends Controller
]); ]);
} }
public function actionPhpinfo()
{
phpinfo();
}
private $_manifest; private $_manifest;
......
...@@ -82,7 +82,7 @@ class Debug extends Base ...@@ -82,7 +82,7 @@ class Debug extends Base
'ajax' => 'Ajax', 'ajax' => 'Ajax',
'url' => 'url', 'url' => 'url',
'statusCode' => 'Status code', 'statusCode' => 'Status code',
'sqlCount' => 'Total queries count', 'sqlCount' => 'Total queries',
]; ];
} }
......
...@@ -27,16 +27,6 @@ class ConfigPanel extends Panel ...@@ -27,16 +27,6 @@ class ConfigPanel extends Panel
} }
/** /**
* Returns Yii logo ready to use in `<img src="`
*
* @return string base64 representation of the image
*/
public static function getYiiLogo()
{
return 'data:image/png;base64,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';
}
/**
* @inheritdoc * @inheritdoc
*/ */
public function getSummary() public function getSummary()
...@@ -67,6 +57,23 @@ class ConfigPanel extends Panel ...@@ -67,6 +57,23 @@ class ConfigPanel extends Panel
} }
/** /**
* Returns the BODY contents of the phpinfo() output
*
* @return array
*/
public function getPhpInfo ()
{
ob_start();
phpinfo();
$pinfo = ob_get_contents();
ob_end_clean();
$phpinfo = preg_replace('%^.*<body>(.*)</body>.*$%ms', '$1', $pinfo);
$phpinfo = str_replace('<table ', '<table class="table table-condensed table-bordered table-striped table-hover"', $phpinfo);
return $phpinfo;
}
/**
* @inheritdoc * @inheritdoc
*/ */
public function save() public function save()
......
...@@ -21,6 +21,12 @@ use yii\debug\models\search\Db; ...@@ -21,6 +21,12 @@ use yii\debug\models\search\Db;
class DbPanel extends Panel class DbPanel extends Panel
{ {
/** /**
* @var integer the threshold for determining whether the request has involved
* critical number of DB queries. If the number of queries exceeds this number,
* the execution is considered taking critical number of DB queries.
*/
public $criticalQueryThreshold;
/**
* @var array db queries info extracted to array as models, to use with data provider. * @var array db queries info extracted to array as models, to use with data provider.
*/ */
private $_models; private $_models;
...@@ -121,7 +127,7 @@ class DbPanel extends Panel ...@@ -121,7 +127,7 @@ class DbPanel extends Panel
$this->_models = []; $this->_models = [];
$timings = $this->calculateTimings(); $timings = $this->calculateTimings();
foreach($timings as $seq => $dbTiming) { foreach ($timings as $seq => $dbTiming) {
$this->_models[] = [ $this->_models[] = [
'type' => $this->getQueryType($dbTiming['info']), 'type' => $this->getQueryType($dbTiming['info']),
'query' => $dbTiming['info'], 'query' => $dbTiming['info'],
...@@ -147,4 +153,15 @@ class DbPanel extends Panel ...@@ -147,4 +153,15 @@ class DbPanel extends Panel
preg_match('/^([a-zA-z]*)/', $timing, $matches); preg_match('/^([a-zA-z]*)/', $timing, $matches);
return count($matches) ? $matches[0] : ''; return count($matches) ? $matches[0] : '';
} }
/**
* Check if given queries count is critical according settings.
*
* @param integer $count queries count
* @return boolean
*/
public function isQueryCountCritical($count)
{
return (($this->criticalQueryThreshold !== null) && ($count > $this->criticalQueryThreshold));
}
} }
...@@ -78,7 +78,7 @@ class LogPanel extends Panel ...@@ -78,7 +78,7 @@ class LogPanel extends Panel
if ($this->_models === null || $refresh) { if ($this->_models === null || $refresh) {
$this->_models = []; $this->_models = [];
foreach($this->data['messages'] as $message) { foreach ($this->data['messages'] as $message) {
$this->_models[] = [ $this->_models[] = [
'message' => $message[0], 'message' => $message[0],
'level' => $message[1], 'level' => $message[1],
......
...@@ -86,7 +86,7 @@ class ProfilingPanel extends Panel ...@@ -86,7 +86,7 @@ class ProfilingPanel extends Panel
$this->_models = []; $this->_models = [];
$timings = Yii::$app->getLog()->calculateTimings($this->data['messages']); $timings = Yii::$app->getLog()->calculateTimings($this->data['messages']);
foreach($timings as $seq => $profileTiming) { foreach ($timings as $seq => $profileTiming) {
$this->_models[] = [ $this->_models[] = [
'duration' => $profileTiming['duration'] * 1000, // in milliseconds 'duration' => $profileTiming['duration'] * 1000, // in milliseconds
'category' => $profileTiming['category'], 'category' => $profileTiming['category'],
......
...@@ -9,15 +9,24 @@ use yii\data\ArrayDataProvider; ...@@ -9,15 +9,24 @@ use yii\data\ArrayDataProvider;
* @var array $manifest * @var array $manifest
* @var \yii\debug\models\search\Debug $searchModel * @var \yii\debug\models\search\Debug $searchModel
* @var ArrayDataProvider $dataProvider * @var ArrayDataProvider $dataProvider
* @var \yii\debug\Panel[] $panels
*/ */
$this->title = 'Yii Debugger'; $this->title = 'Yii Debugger';
?> ?>
<div class="default-index"> <div class="default-index">
<div id="yii-debug-toolbar" class="yii-debug-toolbar-top"> <div id="yii-debug-toolbar" class="yii-debug-toolbar-top">
<div class="yii-debug-toolbar-block title"> <div class="yii-debug-toolbar-block title">
<a href="#">
<img width="29" height="30" alt="" src="<?= \yii\debug\Module::getYiiLogo() ?>">
Yii Debugger Yii Debugger
</a>
</div> </div>
<?php foreach ($panels as $panel): ?>
<?= $panel->getSummary() ?>
<?php endforeach; ?>
</div> </div>
<div class="container"> <div class="container">
...@@ -32,7 +41,9 @@ echo GridView::widget([ ...@@ -32,7 +41,9 @@ echo GridView::widget([
'dataProvider' => $dataProvider, 'dataProvider' => $dataProvider,
'filterModel' => $searchModel, 'filterModel' => $searchModel,
'rowOptions' => function ($model, $key, $index, $grid) use ($searchModel) { 'rowOptions' => function ($model, $key, $index, $grid) use ($searchModel) {
if ($searchModel->isCodeCritical($model['statusCode'])) { $dbPanel = $this->context->module->panels['db'];
if ($searchModel->isCodeCritical($model['statusCode']) || $dbPanel->isQueryCountCritical($model['sqlCount'])) {
return ['class'=>'danger']; return ['class'=>'danger'];
} else { } else {
return []; return [];
...@@ -42,23 +53,36 @@ echo GridView::widget([ ...@@ -42,23 +53,36 @@ echo GridView::widget([
['class' => 'yii\grid\SerialColumn'], ['class' => 'yii\grid\SerialColumn'],
[ [
'attribute' => 'tag', 'attribute' => 'tag',
'value' => function ($data) 'value' => function ($data) {
{
return Html::a($data['tag'], ['view', 'tag' => $data['tag']]); return Html::a($data['tag'], ['view', 'tag' => $data['tag']]);
}, },
'format' => 'html', 'format' => 'html',
], ],
[ [
'attribute' => 'time', 'attribute' => 'time',
'value' => function ($data) use ($timeFormatter) 'value' => function ($data) use ($timeFormatter) {
{ return $timeFormatter->asDateTime($data['time'], 'short');
return $timeFormatter->asDateTime($data['time'], 'long');
}, },
], ],
'ip', 'ip',
[ [
'attribute' => 'sqlCount', 'attribute' => 'sqlCount',
'label' => 'Total queries count' 'label' => 'Total queries',
'value' => function ($data) {
$dbPanel = $this->context->module->panels['db'];
if ($dbPanel->isQueryCountCritical($data['sqlCount'])) {
$content = Html::tag('b', $data['sqlCount']) . ' ' . Html::tag('span', '', ['class' => 'glyphicon glyphicon-exclamation-sign']);
return Html::a($content, ['view', 'panel' => 'db', 'tag' => $data['tag']], [
'title' => 'Too many queries. Allowed count is ' . $dbPanel->criticalQueryThreshold,
]);
} else {
return $data['sqlCount'];
}
},
'format' => 'html',
], ],
[ [
'attribute' => 'method', 'attribute' => 'method',
...@@ -66,8 +90,7 @@ echo GridView::widget([ ...@@ -66,8 +90,7 @@ echo GridView::widget([
], ],
[ [
'attribute'=>'ajax', 'attribute'=>'ajax',
'value' => function ($data) 'value' => function ($data) {
{
return $data['ajax'] ? 'Yes' : 'No'; return $data['ajax'] ? 'Yes' : 'No';
}, },
'filter' => ['No', 'Yes'], 'filter' => ['No', 'Yes'],
......
...@@ -7,6 +7,7 @@ use yii\helpers\Html; ...@@ -7,6 +7,7 @@ use yii\helpers\Html;
$extensions = $panel->getExtensions(); $extensions = $panel->getExtensions();
?> ?>
<h1>Configuration</h1> <h1>Configuration</h1>
<?php <?php
echo $this->render('panels/config/table', [ echo $this->render('panels/config/table', [
'caption' => 'Application Configuration', 'caption' => 'Application Configuration',
...@@ -34,5 +35,6 @@ echo $this->render('panels/config/table', [ ...@@ -34,5 +35,6 @@ echo $this->render('panels/config/table', [
'Memcache' => $panel->data['php']['memcache'] ? 'Enabled' : 'Disabled', 'Memcache' => $panel->data['php']['memcache'] ? 'Enabled' : 'Disabled',
], ],
]); ]);
echo $panel->getPhpInfo();
?> ?>
\ No newline at end of file
<div><?= Html::a('Show phpinfo() »', ['phpinfo'], ['class' => 'btn btn-primary']) ?></div>
...@@ -8,10 +8,9 @@ use yii\helpers\Html; ...@@ -8,10 +8,9 @@ use yii\helpers\Html;
?> ?>
<div class="yii-debug-toolbar-block"> <div class="yii-debug-toolbar-block">
<a href="<?= $panel->getUrl() ?>"> <a href="<?= $panel->getUrl() ?>">
<img width="29" height="30" alt="" src="<?= $panel->getYiiLogo() ?>"> Yii
<span><?= $panel->data['application']['yii'] ?></span> <span class="label label-info"><?= $panel->data['application']['yii'] ?></span>
PHP
<span class="label label-info"><?= $panel->data['php']['version'] ?></span>
</a> </a>
</div> </div>
<div class="yii-debug-toolbar-block">
<?= Html::a('PHP ' . $panel->data['php']['version'], ['phpinfo'], ['title' => 'Show phpinfo()']) ?>
</div>
<div class="yii-debug-toolbar-block"> <div class="yii-debug-toolbar-block">
<a href="<?= $panel->getUrl() ?>" title="Total request processing time was <?= $time ?>">Time <span class="label"><?= $time ?></span></a> <a href="<?= $panel->getUrl() ?>" title="Total request processing time was <?= $time ?>">Time <span class="label"><?= $time ?></span></a>
</div>
<div class="yii-debug-toolbar-block">
<a href="<?= $panel->getUrl() ?>" title="Peak memory consumption">Memory <span class="label"><?= $memory ?></span></a> <a href="<?= $panel->getUrl() ?>" title="Peak memory consumption">Memory <span class="label"><?= $memory ?></span></a>
</div> </div>
...@@ -21,7 +21,5 @@ $statusText = Html::encode(isset(Response::$httpStatuses[$statusCode]) ? Respons ...@@ -21,7 +21,5 @@ $statusText = Html::encode(isset(Response::$httpStatuses[$statusCode]) ? Respons
?> ?>
<div class="yii-debug-toolbar-block"> <div class="yii-debug-toolbar-block">
<a href="<?= $panel->getUrl() ?>" title="Status code: <?= $statusCode ?> <?= $statusText ?>">Status <span class="label <?= $class ?>"><?= $statusCode ?></span></a> <a href="<?= $panel->getUrl() ?>" title="Status code: <?= $statusCode ?> <?= $statusText ?>">Status <span class="label <?= $class ?>"><?= $statusCode ?></span></a>
</div>
<div class="yii-debug-toolbar-block">
<a href="<?= $panel->getUrl() ?>">Action <span class="label"><?= $panel->data['action'] ?></span></a> <a href="<?= $panel->getUrl() ?>">Action <span class="label"><?= $panel->data['action'] ?></span></a>
</div> </div>
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
* @var string $tag * @var string $tag
* @var string $position * @var string $position
*/ */
use yii\helpers\Html;
use yii\debug\panels\ConfigPanel; use yii\debug\panels\ConfigPanel;
$minJs = <<<EOD $minJs = <<<EOD
...@@ -23,9 +24,17 @@ if (window.localStorage) { ...@@ -23,9 +24,17 @@ if (window.localStorage) {
} }
EOD; EOD;
$url = $panels['request']->getUrl(); $firstPanel = reset($panels);
$url = $firstPanel->getUrl();
?> ?>
<div id="yii-debug-toolbar" class="yii-debug-toolbar-<?= $position ?>"> <div id="yii-debug-toolbar" class="yii-debug-toolbar-<?= $position ?>">
<div class="yii-debug-toolbar-block title">
<a href="<?= Html::url(['index']) ?>">
<img width="29" height="30" alt="" src="<?= \yii\debug\Module::getYiiLogo() ?>">
Yii Debugger
</a>
</div>
<?php foreach ($panels as $panel): ?> <?php foreach ($panels as $panel): ?>
<?= $panel->getSummary() ?> <?= $panel->getSummary() ?>
<?php endforeach; ?> <?php endforeach; ?>
...@@ -33,7 +42,7 @@ $url = $panels['request']->getUrl(); ...@@ -33,7 +42,7 @@ $url = $panels['request']->getUrl();
</div> </div>
<div id="yii-debug-toolbar-min"> <div id="yii-debug-toolbar-min">
<a href="<?= $url ?>" title="Open Yii Debugger" id="yii-debug-toolbar-logo"> <a href="<?= $url ?>" title="Open Yii Debugger" id="yii-debug-toolbar-logo">
<img width="29" height="30" alt="" src="<?= ConfigPanel::getYiiLogo() ?>"> <img width="29" height="30" alt="" src="<?= \yii\debug\Module::getYiiLogo() ?>">
</a> </a>
<span class="yii-debug-toolbar-toggler" onclick="<?= $maxJs ?>"></span> <span class="yii-debug-toolbar-toggler" onclick="<?= $maxJs ?>"></span>
</div> </div>
...@@ -17,9 +17,14 @@ $this->title = 'Yii Debugger'; ...@@ -17,9 +17,14 @@ $this->title = 'Yii Debugger';
?> ?>
<div class="default-view"> <div class="default-view">
<div id="yii-debug-toolbar" class="yii-debug-toolbar-top"> <div id="yii-debug-toolbar" class="yii-debug-toolbar-top">
<div class="yii-debug-toolbar-block title"> <div class="yii-debug-toolbar-block title">
<?= Html::a('Yii Debugger', ['index'], ['title' => 'Back to main debug page']) ?> <a href="<?= Html::url(['index']) ?>">
<img width="29" height="30" alt="" src="<?= \yii\debug\Module::getYiiLogo() ?>">
Yii Debugger
</a>
</div> </div>
<?php foreach ($panels as $panel): ?> <?php foreach ($panels as $panel): ?>
<?= $panel->getSummary() ?> <?= $panel->getSummary() ?>
<?php endforeach; ?> <?php endforeach; ?>
...@@ -27,7 +32,7 @@ $this->title = 'Yii Debugger'; ...@@ -27,7 +32,7 @@ $this->title = 'Yii Debugger';
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-lg-2"> <div class="col-lg-2 col-md-2">
<div class="list-group"> <div class="list-group">
<?php <?php
foreach ($panels as $id => $panel) { foreach ($panels as $id => $panel) {
...@@ -39,7 +44,7 @@ $this->title = 'Yii Debugger'; ...@@ -39,7 +44,7 @@ $this->title = 'Yii Debugger';
?> ?>
</div> </div>
</div> </div>
<div class="col-lg-10"> <div class="col-lg-10 col-md-10">
<div class="callout callout-danger"> <div class="callout callout-danger">
<?php <?php
$count = 0; $count = 0;
......
...@@ -152,7 +152,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface ...@@ -152,7 +152,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface
} else { } else {
/** @var ActiveRecord $class */ /** @var ActiveRecord $class */
$class = $this->modelClass; $class = $this->modelClass;
$model = $class::create($result); $model = $class::instantiate($result);
$class::populateRecord($model, $result);
} }
if (!empty($this->with)) { if (!empty($this->with)) {
$models = [$model]; $models = [$model];
......
...@@ -94,7 +94,8 @@ class ActiveRecord extends BaseActiveRecord ...@@ -94,7 +94,8 @@ class ActiveRecord extends BaseActiveRecord
$command = static::getDb()->createCommand(); $command = static::getDb()->createCommand();
$result = $command->get(static::index(), static::type(), $primaryKey, $options); $result = $command->get(static::index(), static::type(), $primaryKey, $options);
if ($result['exists']) { if ($result['exists']) {
$model = static::create($result); $model = static::instantiate($result);
static::populateRecord($model, $result);
$model->afterFind(); $model->afterFind();
return $model; return $model;
} }
...@@ -123,7 +124,8 @@ class ActiveRecord extends BaseActiveRecord ...@@ -123,7 +124,8 @@ class ActiveRecord extends BaseActiveRecord
$models = []; $models = [];
foreach($result['docs'] as $doc) { foreach($result['docs'] as $doc) {
if ($doc['exists']) { if ($doc['exists']) {
$model = static::create($doc); $model = static::instantiate($doc);
static::populateRecord($model, $doc);
$model->afterFind(); $model->afterFind();
$models[] = $model; $models[] = $model;
} }
...@@ -264,22 +266,38 @@ class ActiveRecord extends BaseActiveRecord ...@@ -264,22 +266,38 @@ class ActiveRecord extends BaseActiveRecord
} }
/** /**
* Creates an active record object using a row of data. * @inheritdoc
* This method is called by [[ActiveQuery]] to populate the query results
* into Active Records. It is not meant to be used to create new records.
* @param array $row attribute values (name => value)
* @return ActiveRecord the newly created active record.
*/ */
public static function create($row) public static function populateRecord($record, $row)
{ {
$record = parent::create($row['_source']); parent::populateRecord($record, $row['_source']);
$pk = static::primaryKey()[0]; $pk = static::primaryKey()[0];
if ($pk === '_id') { if ($pk === '_id') {
$record->$pk = $row['_id']; $record->_id = $row['_id'];
} }
$record->_score = isset($row['_score']) ? $row['_score'] : null; $record->_score = isset($row['_score']) ? $row['_score'] : null;
$record->_version = isset($row['_version']) ? $row['_version'] : null; // TODO version should always be available... $record->_version = isset($row['_version']) ? $row['_version'] : null; // TODO version should always be available...
return $record; }
/**
* Creates an active record instance.
*
* This method is called together with [[populateRecord()]] by [[ActiveQuery]].
*
* You may override this method if the instance being created
* depends on the row data to be populated into the record.
* For example, by creating a record based on the value of a column,
* you may implement the so-called single-table inheritance mapping.
* @param array $row row data to be populated into the record.
* This array consists of the following keys:
* - `_source`: refers to the attributes of the record.
* - `_type`: the type this record is stored in.
* - `_index`: the index this record is stored in.
* @return static the newly created active record
*/
public static function instantiate($row)
{
return new static;
} }
/** /**
......
...@@ -5,9 +5,12 @@ Yii Framework 2 elasticsearch extension Change Log ...@@ -5,9 +5,12 @@ Yii Framework 2 elasticsearch extension Change Log
---------------------------- ----------------------------
- Bug #1993: afterFind event in AR is now called after relations have been populated (cebe, creocoder) - Bug #1993: afterFind event in AR is now called after relations have been populated (cebe, creocoder)
- Bug #2324: Fixed QueryBuilder bug when building a query with "query" option (mintao)
- Enh #1313: made index and type available in `ActiveRecord::instantiate()` to allow creating records based on elasticsearch type when doing cross index/type search (cebe)
- Enh #1382: Added a debug toolbar panel for elasticsearch (cebe) - Enh #1382: Added a debug toolbar panel for elasticsearch (cebe)
- Enh #1765: Added support for primary key path mapping, pk can now be part of the attributes when mapping is defined (cebe) - Enh #1765: Added support for primary key path mapping, pk can now be part of the attributes when mapping is defined (cebe)
- Chg #1765: Changed handling of ActiveRecord primary keys, removed getId(), use getPrimaryKey() instead (cebe) - Chg #1765: Changed handling of ActiveRecord primary keys, removed getId(), use getPrimaryKey() instead (cebe)
- Chg #2281: Renamed `ActiveRecord::create()` to `populateRecord()` and changed signature. This method will not call instantiate() anymore (cebe)
2.0.0 alpha, December 1, 2013 2.0.0 alpha, December 1, 2013
----------------------------- -----------------------------
......
...@@ -55,8 +55,10 @@ class QueryBuilder extends \yii\base\Object ...@@ -55,8 +55,10 @@ class QueryBuilder extends \yii\base\Object
$parts['from'] = (int) $query->offset; $parts['from'] = (int) $query->offset;
} }
if (empty($parts['query'])) { if (empty($query->query)) {
$parts['query'] = ["match_all" => (object)[]]; $parts['query'] = ["match_all" => (object)[]];
} else {
$parts['query'] = $query->query;
} }
$whereFilter = $this->buildCondition($query->where); $whereFilter = $this->buildCondition($query->where);
......
...@@ -9,8 +9,8 @@ namespace yii\faker; ...@@ -9,8 +9,8 @@ namespace yii\faker;
use Yii; use Yii;
use yii\console\Exception; use yii\console\Exception;
use yii\helpers\FileHelper;
use yii\helpers\Console; use yii\helpers\Console;
use yii\helpers\FileHelper;
/** /**
* This command manage fixtures creations based on given template. * This command manage fixtures creations based on given template.
...@@ -69,7 +69,7 @@ use yii\helpers\Console; ...@@ -69,7 +69,7 @@ use yii\helpers\Console;
* ~~~ * ~~~
* *
* In the code above "users" is template name, after this command run, new file named same as template * In the code above "users" is template name, after this command run, new file named same as template
* will be created under the `$fixturePath` folder. * will be created under the `$fixtureDataPath` folder.
* You can generate fixtures for all templates by specifying keyword "all" * You can generate fixtures for all templates by specifying keyword "all"
* *
* ~~~ * ~~~
...@@ -77,7 +77,7 @@ use yii\helpers\Console; ...@@ -77,7 +77,7 @@ use yii\helpers\Console;
* ~~~ * ~~~
* *
* This command will generate fixtures for all template files that are stored under $templatePath and * This command will generate fixtures for all template files that are stored under $templatePath and
* store fixtures under $fixturePath with file names same as templates names. * store fixtures under `$fixtureDataPath` with file names same as templates names.
* *
* You can specify how many fixtures per file you need by the second parameter. In the code below we generate * You can specify how many fixtures per file you need by the second parameter. In the code below we generate
* all fixtures and in each file there will be 3 rows (fixtures). * all fixtures and in each file there will be 3 rows (fixtures).
...@@ -95,8 +95,8 @@ use yii\helpers\Console; ...@@ -95,8 +95,8 @@ use yii\helpers\Console;
* //read templates from the other path * //read templates from the other path
* yii fixture/generate all --templatePath=@app/path/to/my/custom/templates * yii fixture/generate all --templatePath=@app/path/to/my/custom/templates
* *
* //generate fixtures into other folders, but be sure that this folders exists or you will get notice about that. * //generate fixtures into other folders
* yii fixture/generate all --fixturePath=@tests/unit/fixtures/subfolder1/subfolder2/subfolder3 * yii fixture/generate all --fixtureDataPath=@tests/unit/fixtures/subfolder1/subfolder2/subfolder3
* ~~~ * ~~~
* *
* You also can create your own data providers for custom tables fields, see Faker library guide for more info (https://github.com/fzaninotto/Faker); * You also can create your own data providers for custom tables fields, see Faker library guide for more info (https://github.com/fzaninotto/Faker);
...@@ -148,24 +148,24 @@ class FixtureController extends \yii\console\controllers\FixtureController ...@@ -148,24 +148,24 @@ class FixtureController extends \yii\console\controllers\FixtureController
*/ */
public $defaultAction = 'generate'; public $defaultAction = 'generate';
/** /**
* Alias to the template path, where all tables templates are stored. * @var string Alias to the template path, where all tables templates are stored.
* @var string
*/ */
public $templatePath = '@tests/unit/templates/fixtures'; public $templatePath = '@tests/unit/templates/fixtures';
/** /**
* Language to use when generating fixtures data. * @var string Alias to the fixture data path, where data files should be written.
* @var string */
public $fixtureDataPath = '@tests/unit/fixtures/data';
/**
* @var string Language to use when generating fixtures data.
*/ */
public $language; public $language;
/** /**
* Additional data providers that can be created by user and will be added to the Faker generator. * @var array Additional data providers that can be created by user and will be added to the Faker generator.
* More info in [Faker](https://github.com/fzaninotto/Faker.) library docs. * More info in [Faker](https://github.com/fzaninotto/Faker.) library docs.
* @var array
*/ */
public $providers = []; public $providers = [];
/** /**
* Faker generator instance * @var \Faker\Generator Faker generator instance
* @var \Faker\Generator
*/ */
private $_generator; private $_generator;
...@@ -177,7 +177,7 @@ class FixtureController extends \yii\console\controllers\FixtureController ...@@ -177,7 +177,7 @@ class FixtureController extends \yii\console\controllers\FixtureController
public function globalOptions() public function globalOptions()
{ {
return array_merge(parent::globalOptions(), [ return array_merge(parent::globalOptions(), [
'templatePath', 'language' 'templatePath', 'language', 'fixtureDataPath'
]); ]);
} }
...@@ -201,7 +201,7 @@ class FixtureController extends \yii\console\controllers\FixtureController ...@@ -201,7 +201,7 @@ class FixtureController extends \yii\console\controllers\FixtureController
public function actionGenerate(array $file, $times = 2) public function actionGenerate(array $file, $times = 2)
{ {
$templatePath = Yii::getAlias($this->templatePath); $templatePath = Yii::getAlias($this->templatePath);
$fixturePath = Yii::getAlias($this->fixturePath); $fixtureDataPath = Yii::getAlias($this->fixtureDataPath);
if ($this->needToGenerateAll($file[0])) { if ($this->needToGenerateAll($file[0])) {
$files = FileHelper::findFiles($templatePath, ['only' => ['*.php']]); $files = FileHelper::findFiles($templatePath, ['only' => ['*.php']]);
...@@ -233,9 +233,10 @@ class FixtureController extends \yii\console\controllers\FixtureController ...@@ -233,9 +233,10 @@ class FixtureController extends \yii\console\controllers\FixtureController
} }
$content = $this->exportFixtures($fixtures); $content = $this->exportFixtures($fixtures);
$filePath = realpath($fixturePath . '/' . $fixtureFileName); FileHelper::createDirectory($fixtureDataPath);
file_put_contents($filePath, $content); file_put_contents($fixtureDataPath . '/'. $fixtureFileName, $content);
$this->stdout("Fixture file was generated under: $filePath\n", Console::FG_GREEN);
$this->stdout("Fixture file was generated under: $fixtureDataPath\n", Console::FG_GREEN);
} }
} }
...@@ -357,9 +358,9 @@ class FixtureController extends \yii\console\controllers\FixtureController ...@@ -357,9 +358,9 @@ class FixtureController extends \yii\console\controllers\FixtureController
public function confirmGeneration($files) public function confirmGeneration($files)
{ {
$this->stdout("Fixtures will be generated under the path: \n", Console::FG_YELLOW); $this->stdout("Fixtures will be generated under the path: \n", Console::FG_YELLOW);
$this->stdout(realpath(Yii::getAlias($this->fixturePath)) . "\n\n", Console::FG_GREEN); $this->stdout("\t" . Yii::getAlias($this->fixtureDataPath) . "\n\n", Console::FG_GREEN);
$this->stdout("Templates will be taken from path: \n", Console::FG_YELLOW); $this->stdout("Templates will be taken from path: \n", Console::FG_YELLOW);
$this->stdout(realpath(Yii::getAlias($this->templatePath)) . "\n\n", Console::FG_GREEN); $this->stdout("\t" . Yii::getAlias($this->templatePath) . "\n\n", Console::FG_GREEN);
foreach ($files as $index => $fileName) { foreach ($files as $index => $fileName) {
$this->stdout(" " . ($index + 1) . ". " . basename($fileName) . "\n", Console::FG_GREEN); $this->stdout(" " . ($index + 1) . ". " . basename($fileName) . "\n", Console::FG_GREEN);
......
...@@ -95,13 +95,14 @@ php yii fixture/generate users ...@@ -95,13 +95,14 @@ php yii fixture/generate users
//also a short version of this command ("generate" action is default) //also a short version of this command ("generate" action is default)
php yii fixture users php yii fixture users
//to generate fixtures for several tables, use "," as a separator, for example: //to generate several fixtures data files, use "," as a separator, for example:
php yii fixture users,profile,some_other_table php yii fixture users,profile,some_other_name
``` ```
In the code above "users" is template name, after this command run, new file named same as template In the code above "users" is template name, after this command run, new file named same as template
will be created under the fixtures path (by default ```@tests/unit/fixtures```) folder. will be created under the fixtures path (by default ```@tests/unit/fixtures```) folder.
You can generate fixtures for all templates by specifying keyword ```all```. You can generate fixtures for all templates by specifying keyword ```all```. You dont need to worry about if data file
directory already created or not, if not - it will be created by these command.
```php ```php
php yii fixture/generate all php yii fixture/generate all
...@@ -124,8 +125,8 @@ php yii fixture/generate users 5 --language='ru_RU' ...@@ -124,8 +125,8 @@ php yii fixture/generate users 5 --language='ru_RU'
//read templates from the other path //read templates from the other path
php yii fixture/generate all --templatePath='@app/path/to/my/custom/templates' php yii fixture/generate all --templatePath='@app/path/to/my/custom/templates'
//generate fixtures into other folders, but be sure that this folders exists or you will get notice about that. //generate fixtures into other directory.
php yii fixture/generate all --fixturePath='@tests/unit/fixtures/subfolder1/subfolder2/subfolder3' php yii fixture/generate all --fixtureDataPath='@tests/acceptance/fixtures/data'
``` ```
You also can create your own data providers for custom tables fields, see [Faker]((https://github.com/fzaninotto/Faker)) library guide for more info; You also can create your own data providers for custom tables fields, see [Faker]((https://github.com/fzaninotto/Faker)) library guide for more info;
......
...@@ -5,10 +5,13 @@ Yii Framework 2 gii extension Change Log ...@@ -5,10 +5,13 @@ Yii Framework 2 gii extension Change Log
---------------------------- ----------------------------
- Bug #1405: fixed disambiguation of relation names generated by gii (qiangxue) - Bug #1405: fixed disambiguation of relation names generated by gii (qiangxue)
- Bug #1904: Fixed autocomplete to work with underscore inputs "_" (tonydspaniard)
- Bug #2298: Fixed the bug that Gii controller generator did not allow digit in the controller ID (qiangxue)
- Bug: fixed controller in crud template to avoid returning query in findModel() (cebe) - Bug: fixed controller in crud template to avoid returning query in findModel() (cebe)
- Enh #1624: generate rules for unique indexes (lucianobaraglia) - Enh #1624: generate rules for unique indexes (lucianobaraglia)
- Enh #1818: Do not display checkbox column if all rows are empty (johonunu) - Enh #1818: Do not display checkbox column if all rows are empty (johonunu)
- Enh #1897: diff markup is now copy paste friendly (samdark) - Enh #1897: diff markup is now copy paste friendly (samdark)
- Enh #2327: better visual representation of changed files, added header and refresh button to diff modal (thiagotalma)
2.0.0 alpha, December 1, 2013 2.0.0 alpha, December 1, 2013
----------------------------- -----------------------------
......
...@@ -35,9 +35,10 @@ yii.gii = (function ($) { ...@@ -35,9 +35,10 @@ yii.gii = (function ($) {
}; };
var initPreviewDiffLinks = function () { var initPreviewDiffLinks = function () {
$('.preview-code,.diff-code').on('click', function () { $('.preview-code, .diff-code, .modal-refresh').on('click', function () {
var $modal = $('#preview-modal'); var $modal = $('#preview-modal');
var $link = $(this); var $link = $(this);
$modal.find('.modal-refresh').attr('href', $link.prop('href'));
$modal.find('.modal-title').text($link.data('title')); $modal.find('.modal-title').text($link.data('title'));
$modal.find('.modal-body').html('Loading ...'); $modal.find('.modal-body').html('Loading ...');
$modal.modal('show'); $modal.modal('show');
...@@ -70,6 +71,15 @@ yii.gii = (function ($) { ...@@ -70,6 +71,15 @@ yii.gii = (function ($) {
}; };
return { return {
autocomplete: function (counter, data) {
var datum = new Bloodhound({
datumTokenizer: function(d){return Bloodhound.tokenizers.whitespace(d.word);},
queryTokenizer: Bloodhound.tokenizers.whitespace,
local: data
});
datum.initialize();
jQuery('.typeahead-'+counter).typeahead(null,{displayKey: 'word', source: datum.ttAdapter()});
},
init: function () { init: function () {
initHintBlocks(); initHintBlocks();
initStickyInputs(); initStickyInputs();
......
...@@ -63,7 +63,10 @@ class ActiveField extends \yii\widgets\ActiveField ...@@ -63,7 +63,10 @@ class ActiveField extends \yii\widgets\ActiveField
{ {
static $counter = 0; static $counter = 0;
$this->inputOptions['class'] .= ' typeahead-' . (++$counter); $this->inputOptions['class'] .= ' typeahead-' . (++$counter);
$this->form->getView()->registerJs("jQuery('.typeahead-{$counter}').typeahead({local: " . Json::encode($data) . "});"); foreach ($data as &$item) {
$item = array('word' => $item);
}
$this->form->getView()->registerJs("yii.gii.autocomplete($counter, " . Json::encode($data) . ");");
return $this; return $this;
} }
} }
...@@ -80,8 +80,8 @@ class Generator extends \yii\gii\Generator ...@@ -80,8 +80,8 @@ class Generator extends \yii\gii\Generator
return array_merge(parent::rules(), [ return array_merge(parent::rules(), [
[['controller', 'actions', 'baseClass', 'ns'], 'filter', 'filter' => 'trim'], [['controller', 'actions', 'baseClass', 'ns'], 'filter', 'filter' => 'trim'],
[['controller', 'baseClass'], 'required'], [['controller', 'baseClass'], 'required'],
[['controller'], 'match', 'pattern' => '/^[a-z\\-\\/]*$/', 'message' => 'Only a-z, dashes (-) and slashes (/) are allowed.'], [['controller'], 'match', 'pattern' => '/^[a-z][a-z0-9\\-\\/]*$/', 'message' => 'Only a-z, 0-9, dashes (-) and slashes (/) are allowed.'],
[['actions'], 'match', 'pattern' => '/^[a-z\\-,\\s]*$/', 'message' => 'Only a-z, dashes (-), spaces and commas are allowed.'], [['actions'], 'match', 'pattern' => '/^[a-z][a-z0-9\\-,\\s]*$/', 'message' => 'Only a-z, 0-9, dashes (-), spaces and commas are allowed.'],
[['baseClass'], 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes 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.'], [['ns'], 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'],
]); ]);
......
...@@ -252,7 +252,7 @@ class Generator extends \yii\gii\Generator ...@@ -252,7 +252,7 @@ class Generator extends \yii\gii\Generator
try { try {
$db = $this->getDbConnection(); $db = $this->getDbConnection();
$uniqueIndexes = $db->getSchema()->findUniqueIndexes($table); $uniqueIndexes = $db->getSchema()->findUniqueIndexes($table);
foreach ($uniqueIndexes as $indexName => $uniqueColumns) { foreach ($uniqueIndexes as $uniqueColumns) {
// Avoid validating auto incrementable columns // Avoid validating auto incrementable columns
if (!$this->isUniqueColumnAutoIncrementable($table, $uniqueColumns)) { if (!$this->isUniqueColumnAutoIncrementable($table, $uniqueColumns)) {
$attributesCount = count($uniqueColumns); $attributesCount = count($uniqueColumns);
......
...@@ -4,11 +4,9 @@ use yii\helpers\Html; ...@@ -4,11 +4,9 @@ use yii\helpers\Html;
/** /**
* @var \yii\web\View $this * @var \yii\web\View $this
* @var \yii\gii\Generator[] $generators * @var \yii\gii\Generator[] $generators
* @var \yii\gii\Generator $activeGenerator
* @var string $content * @var string $content
*/ */
$generators = Yii::$app->controller->module->generators; $generators = Yii::$app->controller->module->generators;
$activeGenerator = Yii::$app->controller->generator;
$this->title = 'Welcome to Gii'; $this->title = 'Welcome to Gii';
?> ?>
<div class="default-index"> <div class="default-index">
......
...@@ -33,7 +33,18 @@ use yii\gii\CodeFile; ...@@ -33,7 +33,18 @@ use yii\gii\CodeFile;
</thead> </thead>
<tbody> <tbody>
<?php foreach ($files as $file): ?> <?php foreach ($files as $file): ?>
<tr class="<?= $file->operation ?>"> <?php
if ($file->operation === CodeFile::OP_OVERWRITE) {
$trClass = 'warning';
} elseif ($file->operation === CodeFile::OP_SKIP) {
$trClass = 'active';
} elseif ($file->operation === CodeFile::OP_CREATE) {
$trClass = 'success';
} else {
$trClass = '';
}
?>
<tr class="<?= "$file->operation $trClass" ?>">
<td class="file"> <td class="file">
<?= Html::a(Html::encode($file->getRelativePath()), ['preview', 'file' => $file->id], ['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 if ($file->operation === CodeFile::OP_OVERWRITE): ?>
...@@ -70,7 +81,7 @@ use yii\gii\CodeFile; ...@@ -70,7 +81,7 @@ use yii\gii\CodeFile;
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button> <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">Modal title</h4> <h4><a class="modal-refresh glyphicon glyphicon-refresh" href="#"></a> <span class="modal-title">Modal title</span></h4>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p>Please wait ...</p> <p>Please wait ...</p>
......
...@@ -53,12 +53,12 @@ class ActiveFixture extends BaseActiveFixture ...@@ -53,12 +53,12 @@ class ActiveFixture extends BaseActiveFixture
/** /**
* Loads the fixture data. * Loads the fixture data.
* The default implementation will first reset the DB table and then populate it with the data * Data will be batch inserted into the given collection.
* returned by [[getData()]].
*/ */
public function load() public function load()
{ {
$this->resetCollection(); parent::load();
$data = $this->getData(); $data = $this->getData();
$this->getCollection()->batchInsert($data); $this->getCollection()->batchInsert($data);
foreach ($data as $alias => $row) { foreach ($data as $alias => $row) {
...@@ -66,6 +66,17 @@ class ActiveFixture extends BaseActiveFixture ...@@ -66,6 +66,17 @@ class ActiveFixture extends BaseActiveFixture
} }
} }
/**
* Unloads the fixture.
*
* The default implementation will clean up the colection by calling [[resetCollection()]].
*/
public function unload()
{
$this->resetCollection();
parent::unload();
}
protected function getCollection() protected function getCollection()
{ {
return $this->db->getCollection($this->getCollectionName()); return $this->db->getCollection($this->getCollectionName());
......
...@@ -81,7 +81,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface ...@@ -81,7 +81,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface
} else { } else {
/** @var ActiveRecord $class */ /** @var ActiveRecord $class */
$class = $this->modelClass; $class = $this->modelClass;
$model = $class::create($row); $model = $class::instantiate($row);
$class::populateRecord($model, $row);
} }
if (!empty($this->with)) { if (!empty($this->with)) {
$models = [$model]; $models = [$model];
......
...@@ -8,9 +8,7 @@ ...@@ -8,9 +8,7 @@
namespace yii\mongodb; namespace yii\mongodb;
use yii\base\InvalidConfigException; use yii\base\InvalidConfigException;
use yii\base\InvalidParamException;
use yii\db\BaseActiveRecord; use yii\db\BaseActiveRecord;
use yii\base\UnknownMethodException;
use yii\db\StaleObjectException; use yii\db\StaleObjectException;
use yii\helpers\Inflector; use yii\helpers\Inflector;
use yii\helpers\StringHelper; use yii\helpers\StringHelper;
......
...@@ -8,8 +8,6 @@ ...@@ -8,8 +8,6 @@
namespace yii\mongodb; namespace yii\mongodb;
use Yii; use Yii;
use yii\mongodb\Connection;
use yii\mongodb\Query;
use yii\base\InvalidConfigException; use yii\base\InvalidConfigException;
/** /**
......
...@@ -81,7 +81,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface ...@@ -81,7 +81,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface
} else { } else {
/** @var ActiveRecord $class */ /** @var ActiveRecord $class */
$class = $this->modelClass; $class = $this->modelClass;
$model = $class::create($row); $model = $class::instantiate($row);
$class::populateRecord($model, $row);
} }
if (!empty($this->with)) { if (!empty($this->with)) {
$models = [$model]; $models = [$model];
......
...@@ -112,7 +112,8 @@ class ActiveQuery extends \yii\base\Component implements ActiveQueryInterface ...@@ -112,7 +112,8 @@ class ActiveQuery extends \yii\base\Component implements ActiveQueryInterface
} else { } else {
/** @var ActiveRecord $class */ /** @var ActiveRecord $class */
$class = $this->modelClass; $class = $this->modelClass;
$model = $class::create($row); $model = $class::instantiate($row);
$class::populateRecord($model, $row);
} }
if (!empty($this->with)) { if (!empty($this->with)) {
$models = [$model]; $models = [$model];
...@@ -287,7 +288,7 @@ class ActiveQuery extends \yii\base\Component implements ActiveQueryInterface ...@@ -287,7 +288,7 @@ class ActiveQuery extends \yii\base\Component implements ActiveQueryInterface
if (count($this->where) == 1) { if (count($this->where) == 1) {
$pks = (array) reset($this->where); $pks = (array) reset($this->where);
} else { } else {
foreach($this->where as $column => $values) { foreach($this->where as $values) {
if (is_array($values)) { if (is_array($values)) {
// TODO support composite IN for composite PK // TODO support composite IN for composite PK
throw new NotSupportedException('Find by composite PK is not supported by redis ActiveRecord.'); throw new NotSupportedException('Find by composite PK is not supported by redis ActiveRecord.');
......
...@@ -6,6 +6,7 @@ Yii Framework 2 redis extension Change Log ...@@ -6,6 +6,7 @@ Yii Framework 2 redis extension Change Log
- Bug #1993: afterFind event in AR is now called after relations have been populated (cebe, creocoder) - Bug #1993: afterFind event in AR is now called after relations have been populated (cebe, creocoder)
- Enh #1773: keyPrefix property of Session and Cache is not restricted to alnum characters anymore (cebe) - Enh #1773: keyPrefix property of Session and Cache is not restricted to alnum characters anymore (cebe)
- Chg #2281: Renamed `ActiveRecord::create()` to `populateRecord()` and changed signature. This method will not call instantiate() anymore (cebe)
2.0.0 alpha, December 1, 2013 2.0.0 alpha, December 1, 2013
----------------------------- -----------------------------
......
...@@ -8,7 +8,6 @@ ...@@ -8,7 +8,6 @@
namespace yii\redis; namespace yii\redis;
use yii\base\Component; use yii\base\Component;
use yii\base\InvalidConfigException;
use yii\db\Exception; use yii\db\Exception;
use yii\helpers\Inflector; use yii\helpers\Inflector;
......
...@@ -336,7 +336,7 @@ EOF; ...@@ -336,7 +336,7 @@ EOF;
} }
$columnAlias = $this->addColumn($column, $columns); $columnAlias = $this->addColumn($column, $columns);
$parts = []; $parts = [];
foreach ($values as $i => $value) { foreach ($values as $value) {
if (is_array($value)) { if (is_array($value)) {
$value = isset($value[$column]) ? $value[$column] : null; $value = isset($value[$column]) ? $value[$column] : null;
} }
......
...@@ -139,7 +139,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface ...@@ -139,7 +139,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface
} else { } else {
/** @var $class ActiveRecord */ /** @var $class ActiveRecord */
$class = $this->modelClass; $class = $this->modelClass;
$model = $class::create($row); $model = $class::instantiate($row);
$class::populateRecord($model, $row);
} }
if (!empty($this->with)) { if (!empty($this->with)) {
$models = [$model]; $models = [$model];
......
...@@ -619,29 +619,17 @@ abstract class ActiveRecord extends BaseActiveRecord ...@@ -619,29 +619,17 @@ abstract class ActiveRecord extends BaseActiveRecord
} }
/** /**
* Creates an active record object using a row of data. * @inheritdoc
* This method is called by [[ActiveQuery]] to populate the query results
* into Active Records. It is not meant to be used to create new records.
* @param array $row attribute values (name => value)
* @return ActiveRecord the newly created active record.
*/ */
public static function create($row) public static function populateRecord($record, $row)
{ {
$record = static::instantiate($row);
$columns = static::getIndexSchema()->columns; $columns = static::getIndexSchema()->columns;
foreach ($row as $name => $value) { foreach ($row as $name => $value) {
if (isset($columns[$name])) { if (isset($columns[$name]) && $columns[$name]->isMva) {
$column = $columns[$name]; $row[$name] = explode(',', $value);
if ($column->isMva) {
$value = explode(',', $value);
}
$record->setAttribute($name, $value);
} else {
$record->$name = $value;
} }
} }
$record->setOldAttributes($record->getAttributes()); parent::populateRecord($record, $row);
return $record;
} }
/** /**
......
...@@ -7,6 +7,7 @@ Yii Framework 2 sphinx extension Change Log ...@@ -7,6 +7,7 @@ Yii Framework 2 sphinx extension Change Log
- Bug #1993: afterFind event in AR is now called after relations have been populated (cebe, creocoder) - Bug #1993: afterFind event in AR is now called after relations have been populated (cebe, creocoder)
- Bug #2160: SphinxQL does not support OFFSET (qiangxue, romeo7) - Bug #2160: SphinxQL does not support OFFSET (qiangxue, romeo7)
- Enh #1398: Refactor ActiveRecord to use BaseActiveRecord class of the framework (klimov-paul) - Enh #1398: Refactor ActiveRecord to use BaseActiveRecord class of the framework (klimov-paul)
- Chg #2281: Renamed `ActiveRecord::create()` to `populateRecord()` and changed signature. This method will not call instantiate() anymore (cebe)
2.0.0 alpha, December 1, 2013 2.0.0 alpha, December 1, 2013
----------------------------- -----------------------------
......
...@@ -8,7 +8,6 @@ ...@@ -8,7 +8,6 @@
namespace yii\sphinx; namespace yii\sphinx;
use yii\base\Object; use yii\base\Object;
use yii\base\InvalidParamException;
/** /**
* IndexSchema represents the metadata of a Sphinx index. * IndexSchema represents the metadata of a Sphinx index.
......
...@@ -146,7 +146,7 @@ class Schema extends Object ...@@ -146,7 +146,7 @@ class Schema extends Object
return $this->_indexes[$name] = $index; return $this->_indexes[$name] = $index;
} }
} }
return $this->_indexes[$name] = $index = $this->loadIndexSchema($realName); return $this->_indexes[$name] = $this->loadIndexSchema($realName);
} }
/** /**
......
...@@ -26,6 +26,7 @@ Yii Framework 2 Change Log ...@@ -26,6 +26,7 @@ Yii Framework 2 Change Log
- Bug #1710: OpenId auth client does not request required attributes correctly (klimov-paul) - Bug #1710: OpenId auth client does not request required attributes correctly (klimov-paul)
- Bug #1798: Fixed label attributes for array fields (zhuravljov) - Bug #1798: Fixed label attributes for array fields (zhuravljov)
- Bug #1800: Better check for `$_SERVER['HTTPS']` in `yii\web\Request::getIsSecureConnection()` (ginus, samdark) - Bug #1800: Better check for `$_SERVER['HTTPS']` in `yii\web\Request::getIsSecureConnection()` (ginus, samdark)
- Bug #1812: Hide potential warning message due to race condition occurring to `Session::regenerateID()` call (qiangxue)
- Bug #1827: Debugger toolbar is loaded twice if an action is calling `run()` to execute another action (qiangxue) - Bug #1827: Debugger toolbar is loaded twice if an action is calling `run()` to execute another action (qiangxue)
- Bug #1868: Added ability to exclude tables from FixtureController apply/clear actions. (Ragazzo) - Bug #1868: Added ability to exclude tables from FixtureController apply/clear actions. (Ragazzo)
- Bug #1869: Fixed tables clearing. `TRUNCATE` changed to `DELETE` to avoid postgresql tables checks (and truncating all tables) (Ragazzo) - Bug #1869: Fixed tables clearing. `TRUNCATE` changed to `DELETE` to avoid postgresql tables checks (and truncating all tables) (Ragazzo)
...@@ -41,6 +42,9 @@ Yii Framework 2 Change Log ...@@ -41,6 +42,9 @@ Yii Framework 2 Change Log
- Bug #2091: `QueryBuilder::buildInCondition()` fails to handle array not starting with index 0 (qiangxue) - Bug #2091: `QueryBuilder::buildInCondition()` fails to handle array not starting with index 0 (qiangxue)
- Bug #2160: SphinxQL does not support OFFSET (qiangxue, romeo7) - Bug #2160: SphinxQL does not support OFFSET (qiangxue, romeo7)
- Bug #2212: `yii\gridview\DataColumn` generates incorrect labels when used with nosql DB and there is no data (qiangxue) - Bug #2212: `yii\gridview\DataColumn` generates incorrect labels when used with nosql DB and there is no data (qiangxue)
- Bug #2298: Fixed the bug that Gii controller generator did not allow digit in the controller ID (qiangxue)
- Bug #2303: Fixed the bug that `yii\base\Theme::pathMap` did not support dynamic update with path aliases (qiangxue)
- Bug #2324: Fixed QueryBuilder bug when building a query with "query" option (mintao)
- Bug: Fixed `Call to a member function registerAssetFiles() on a non-object` in case of wrong `sourcePath` for an asset bundle (samdark) - Bug: Fixed `Call to a member function registerAssetFiles() on a non-object` in case of wrong `sourcePath` for an asset bundle (samdark)
- Bug: Fixed incorrect event name for `yii\jui\Spinner` (samdark) - Bug: Fixed incorrect event name for `yii\jui\Spinner` (samdark)
- Bug: Json::encode() did not handle objects that implement JsonSerializable interface correctly (cebe) - Bug: Json::encode() did not handle objects that implement JsonSerializable interface correctly (cebe)
...@@ -49,6 +53,7 @@ Yii Framework 2 Change Log ...@@ -49,6 +53,7 @@ Yii Framework 2 Change Log
- Bug: Fixed URL parsing so it's now properly giving 404 for URLs like `http://example.com//////site/about` (samdark) - Bug: Fixed URL parsing so it's now properly giving 404 for URLs like `http://example.com//////site/about` (samdark)
- Bug: Fixed `HelpController::getModuleCommands` issue where it attempts to scan a module's controller directory when it doesn't exist (jom) - Bug: Fixed `HelpController::getModuleCommands` issue where it attempts to scan a module's controller directory when it doesn't exist (jom)
- Bug: Fixed an issue with Filehelper and not accessable directories which resulted in endless loop (cebe) - Bug: Fixed an issue with Filehelper and not accessable directories which resulted in endless loop (cebe)
- Bug: Fixed `$model->load($data)` returned `true` if `$data` and `formName` were empty (samdark)
- Enh #46: Added Image extension based on [Imagine library](http://imagine.readthedocs.org) (tonydspaniard) - Enh #46: Added Image extension based on [Imagine library](http://imagine.readthedocs.org) (tonydspaniard)
- Enh #364: Improve Inflector::slug with `intl` transliteration. Improved transliteration char map. (tonydspaniard) - Enh #364: Improve Inflector::slug with `intl` transliteration. Improved transliteration char map. (tonydspaniard)
- Enh #797: Added support for validating multiple columns by `UniqueValidator` and `ExistValidator` (qiangxue) - Enh #797: Added support for validating multiple columns by `UniqueValidator` and `ExistValidator` (qiangxue)
...@@ -99,6 +104,7 @@ Yii Framework 2 Change Log ...@@ -99,6 +104,7 @@ Yii Framework 2 Change Log
- Enh #2132: Allow url of CSS and JS files registered in yii\web\View to be url alias (cebe) - Enh #2132: Allow url of CSS and JS files registered in yii\web\View to be url alias (cebe)
- Enh #2144: `Html` helper now supports rendering "data" attributes (qiangxue) - Enh #2144: `Html` helper now supports rendering "data" attributes (qiangxue)
- Enh #2156: `yii migrate` now automatically creates `migrations` directory if it does not exist (samdark) - Enh #2156: `yii migrate` now automatically creates `migrations` directory if it does not exist (samdark)
- Enh: Added support for using arrays as option values for console commands (qiangxue)
- Enh: Added `favicon.ico` and `robots.txt` to default application templates (samdark) - Enh: Added `favicon.ico` and `robots.txt` to default application templates (samdark)
- Enh: Added `Widget::autoIdPrefix` to support prefixing automatically generated widget IDs (qiangxue) - Enh: Added `Widget::autoIdPrefix` to support prefixing automatically generated widget IDs (qiangxue)
- Enh: Support for file aliases in console command 'message' (omnilight) - Enh: Support for file aliases in console command 'message' (omnilight)
...@@ -115,7 +121,9 @@ Yii Framework 2 Change Log ...@@ -115,7 +121,9 @@ Yii Framework 2 Change Log
- Enh: Added `yii\web\View::POS_LOAD` (qiangxue) - Enh: Added `yii\web\View::POS_LOAD` (qiangxue)
- Enh: Added `yii\web\Response::clearOutputBuffers()` (qiangxue) - Enh: Added `yii\web\Response::clearOutputBuffers()` (qiangxue)
- Enh: Improved `QueryBuilder::buildLimit()` to support big numbers (qiangxue) - Enh: Improved `QueryBuilder::buildLimit()` to support big numbers (qiangxue)
- Enh:#2211: Added typecast database types into php types (dizews)
- Enh #2240: Improved `yii\web\AssetManager::publish()`, `yii\web\AssetManager::getPublishedPath()` and `yii\web\AssetManager::getPublishedUrl()` to support aliases (vova07) - Enh #2240: Improved `yii\web\AssetManager::publish()`, `yii\web\AssetManager::getPublishedPath()` and `yii\web\AssetManager::getPublishedUrl()` to support aliases (vova07)
- Enh #2325: Adding support for the `X-HTTP-Method-Override` header in `yii\web\Request::getMethod()` (pawzar)
- Chg #1519: `yii\web\User::loginRequired()` now returns the `Response` object instead of exiting the application (qiangxue) - Chg #1519: `yii\web\User::loginRequired()` now returns the `Response` object instead of exiting the application (qiangxue)
- Chg #1586: `QueryBuilder::buildLikeCondition()` will now escape special characters and use percentage characters by default (qiangxue) - Chg #1586: `QueryBuilder::buildLikeCondition()` will now escape special characters and use percentage characters by default (qiangxue)
- Chg #1610: `Html::activeCheckboxList()` and `Html::activeRadioList()` will submit an empty string if no checkbox/radio is selected (qiangxue) - Chg #1610: `Html::activeCheckboxList()` and `Html::activeRadioList()` will submit an empty string if no checkbox/radio is selected (qiangxue)
...@@ -141,6 +149,7 @@ Yii Framework 2 Change Log ...@@ -141,6 +149,7 @@ Yii Framework 2 Change Log
- Chg #2175: QueryBuilder will now append UNION statements at the end of the primary SQL (qiangxue) - Chg #2175: QueryBuilder will now append UNION statements at the end of the primary SQL (qiangxue)
- Chg #2210: Mysql driver will now treat `tinyint(1)` as integer instead of boolean (qiangxue) - Chg #2210: Mysql driver will now treat `tinyint(1)` as integer instead of boolean (qiangxue)
- Chg #2248: Renamed `yii\base\Model::DEFAULT_SCENARIO` to `yii\base\Model::SCENARIO_DEFAULT` (samdark) - Chg #2248: Renamed `yii\base\Model::DEFAULT_SCENARIO` to `yii\base\Model::SCENARIO_DEFAULT` (samdark)
- Chg #2281: Renamed `ActiveRecord::create()` to `populateRecord()` and changed signature. This method will not call instantiate() anymore (cebe)
- Chg: Renamed `yii\jui\Widget::clientEventsMap` to `clientEventMap` (qiangxue) - Chg: Renamed `yii\jui\Widget::clientEventsMap` to `clientEventMap` (qiangxue)
- Chg: Renamed `ActiveRecord::getPopulatedRelations()` to `getRelatedRecords()` (qiangxue) - Chg: Renamed `ActiveRecord::getPopulatedRelations()` to `getRelatedRecords()` (qiangxue)
- Chg: Renamed `attributeName` and `className` to `targetAttribute` and `targetClass` for `UniqueValidator` and `ExistValidator` (qiangxue) - Chg: Renamed `attributeName` and `className` to `targetAttribute` and `targetClass` for `UniqueValidator` and `ExistValidator` (qiangxue)
...@@ -154,6 +163,7 @@ Yii Framework 2 Change Log ...@@ -154,6 +163,7 @@ Yii Framework 2 Change Log
- Chg: Renamed `yii\web\Request::acceptedLanguages` to `acceptableLanguages` (qiangxue) - Chg: Renamed `yii\web\Request::acceptedLanguages` to `acceptableLanguages` (qiangxue)
- Chg: Removed implementation of `Arrayable` from `yii\Object` (qiangxue) - Chg: Removed implementation of `Arrayable` from `yii\Object` (qiangxue)
- Chg: Renamed `ActiveRecordInterface::createActiveRelation()` to `createRelation()` (qiangxue) - Chg: Renamed `ActiveRecordInterface::createActiveRelation()` to `createRelation()` (qiangxue)
- Chg: The scripts in asset bundles are now registered in `View` at the end of `endBody()`. It was done in `endPage()` previously (qiangxue)
- New #66: [Auth client library](https://github.com/yiisoft/yii2-authclient) OpenId, OAuth1, OAuth2 clients (klimov-paul) - New #66: [Auth client library](https://github.com/yiisoft/yii2-authclient) OpenId, OAuth1, OAuth2 clients (klimov-paul)
- New #706: Added `yii\widgets\Pjax` and enhanced `GridView` to work with `Pjax` to support AJAX-update (qiangxue) - New #706: Added `yii\widgets\Pjax` and enhanced `GridView` to work with `Pjax` to support AJAX-update (qiangxue)
- New #1393: [Codeception testing framework integration](https://github.com/yiisoft/yii2-codeception) (Ragazzo) - New #1393: [Codeception testing framework integration](https://github.com/yiisoft/yii2-codeception) (Ragazzo)
......
...@@ -61,17 +61,23 @@ ...@@ -61,17 +61,23 @@
applyFilter: function () { applyFilter: function () {
var $grid = $(this); var $grid = $(this);
var settings = $grid.data('yiiGridView').settings; var settings = $grid.data('yiiGridView').settings;
var data = $(settings.filterSelector).serialize(); var data = {};
var url = settings.filterUrl; $.each($(settings.filterSelector).serializeArray(), function () {
if (url.indexOf('?') >= 0) { data[this.name] = this.value;
url += '&' + data; });
} else {
url += '?' + data; $.each(yii.getQueryParams(settings.filterUrl), function (name, value) {
if (data[name] === undefined) {
data[name] = value;
} }
});
var pos = settings.filterUrl.indexOf('?');
var url = pos < 0 ? settings.filterUrl : settings.filterUrl.substring(0, pos);
$grid.find('form.gridview-filter-form').remove(); $grid.find('form.gridview-filter-form').remove();
var $form = $('<form action="' + url + '" method="get" class="gridview-filter-form" style="display:none" data-pjax></form>').appendTo($grid); var $form = $('<form action="' + url + '" method="get" class="gridview-filter-form" style="display:none" data-pjax></form>').appendTo($grid);
$.each(yii.getQueryParams(url), function (name, value) { $.each(data, function (name, value) {
$form.append($('<input type="hidden" name="t" value="" />').attr('name', name).val(value)); $form.append($('<input type="hidden" name="t" value="" />').attr('name', name).val(value));
}); });
$form.submit(); $form.submit();
......
...@@ -721,7 +721,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab ...@@ -721,7 +721,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab
public function load($data, $formName = null) public function load($data, $formName = null)
{ {
$scope = $formName === null ? $this->formName() : $formName; $scope = $formName === null ? $this->formName() : $formName;
if ($scope == '') { if ($scope == '' && !empty($data)) {
$this->setAttributes($data); $this->setAttributes($data);
return true; return true;
} elseif (isset($data[$scope])) { } elseif (isset($data[$scope])) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment