Merge branch 'master' of git://github.com/yiisoft/yii2

This commit is contained in:
Qiang Xue
2015-02-08 22:28:09 -05:00
32 changed files with 180 additions and 48 deletions

View File

@ -0,0 +1,45 @@
Подготовка к разработке Yii2
============================
1. Создаём клон своего форка yii2 `git clone git@github.com:<ваше имя>/yii2.git`.
2. Переходим в папку репозитория `cd yii2`.
3. Запускаем `./build/build app/link basic` для установки composer зависимостей приложения basic.
*Эта команда установит сторонние пакеты composer как обычно, но создаст ссылку с репозитория yii2
на только что загуженный репозиторий. Таким образом у вас будет только один экземпляр кода.*
4. При необходимости делаем тоже самое для приложения advanced: `./build/build app/link advanced`
Внутри эта команда использует `composer update` для обновления кода.
5. Теперь у нас есть рабочая площадка для экспериментов с Yii 2.
Можно так же добавить репозиторий yii2 upstream для получения последних изменений:
```
git remote add upstream https://github.com/yiisoft/yii2.git
```
Пожалуйста ознакомьтесь с разделом «[рабочий процесс Git для разработчиков Yii 2](git-workflow.md
для получения подробной информации о создании pull request-ов.
Модульные тесты
---------------
Для запуска модульных тестов нужно установить composer пакеты для dev-репозитория.
В корневой директории делаем `composer update` для получения последней версии пакетов.
Теперь можно выполнить модульные тесты, запустив `phpunit`.
Можно ограничиться группой тестов, над которыми вы работаете. Например, следующая команда запустит тесты только для
валидаторов и redis `phpunit --group=validators,redis`.
Расширения
----------
Для работы над расширениями необходимо установить их в приложение. Добавляем их в `composer.json` как обычно. Например,
добавим `"yiisoft/yii2-redis": "*"` в секцию `require` для приложения basic.
Запускаем `./build/build app/link basic` для установки расширения, его зависимостей и создания символической
ссылки на `extensions/redis`. Теперь вы работаете с репозиторием yii2, а не с директорией vendor.
Функциональные и приёмочные тесты для приложений
------------------------------------------------
отрите `apps/advanced/tests/README.md` и `apps/basic/tests/README.md`, чтобы узнать о том как запускать
тесты Codeception.

View File

@ -575,7 +575,7 @@ class ActiveRecord extends BaseActiveRecord
{
$pkName = static::primaryKey()[0];
if (count($condition) == 1 && isset($condition[$pkName])) {
$primaryKeys = is_array($condition[$pkName]) ? $condition[$pkName] : [$condition[$pkName]];
$primaryKeys = (array)$condition[$pkName];
} else {
$primaryKeys = static::find()->where($condition)->column($pkName); // TODO check whether this works with default pk _id
}
@ -635,7 +635,7 @@ class ActiveRecord extends BaseActiveRecord
{
$pkName = static::primaryKey()[0];
if (count($condition) == 1 && isset($condition[$pkName])) {
$primaryKeys = is_array($condition[$pkName]) ? $condition[$pkName] : [$condition[$pkName]];
$primaryKeys = (array)$condition[$pkName];
} else {
$primaryKeys = static::find()->where($condition)->column($pkName); // TODO check whether this works with default pk _id
}
@ -771,7 +771,7 @@ class ActiveRecord extends BaseActiveRecord
{
$pkName = static::primaryKey()[0];
if (count($condition) == 1 && isset($condition[$pkName])) {
$primaryKeys = is_array($condition[$pkName]) ? $condition[$pkName] : [$condition[$pkName]];
$primaryKeys = (array)$condition[$pkName];
} else {
$primaryKeys = static::find()->where($condition)->column($pkName); // TODO check whether this works with default pk _id
}

View File

@ -35,6 +35,17 @@ use yii\helpers\Html;
* ]);
* ```
*
* You can also use this widget in an [[yii\widgets\ActiveForm|ActiveForm]] using the [[yii\widgets\ActiveField::widget()|widget()]]
* method, for example like this:
*
* ```php
* <?= $form->field($model, 'from_date')->widget(\yii\jui\AutoComplete::classname(), [
* 'clientOptions' => [
* 'source' => ['USA', 'RUS'],
* ],
* ]) ?>
* ```
*
* @see http://api.jqueryui.com/autocomplete/
* @author Alexander Kochetov <creocoder@gmail.com>
* @since 2.0

View File

@ -38,8 +38,19 @@ use yii\helpers\Json;
* ]);
* ```
*
* Note that empty values like empty strings and 0 will result in a date displayed as `1970-01-01`.
* So to make sure empty values result in an empty text field in the datepicker you need to add a
* You can also use this widget in an [[yii\widgets\ActiveForm|ActiveForm]] using the [[yii\widgets\ActiveField::widget()|widget()]]
* method, for example like this:
*
* ```php
* <?= $form->field($model, 'from_date')->widget(\yii\jui\DatePicker::classname(), [
* //'language' => 'ru',
* //'dateFormat' => 'yyyy-MM-dd',
* ]) ?>
* ```
*
* Note that and empty string (`''`) and `null` will result in an empty text field while `0` will be
* interpreted as a UNIX timestamp and result in a date displayed as `1970-01-01`.
* It is recommended to add a
* validation filter in your model that sets the value to `null` in case when no date has been entered:
*
* ```php

View File

@ -14,6 +14,15 @@ use yii\helpers\Html;
/**
* InputWidget is the base class for all jQuery UI input widgets.
*
* Classes extending from this widget can be used in an [[yii\widgets\ActiveForm|ActiveForm]]
* using the [[yii\widgets\ActiveField::widget()|widget()]] method, for example like this:
*
* ```php
* <?= $form->field($model, 'from_date')->widget('WidgetClassName', [
* // configure additional widget properties here
* ]) ?>
* ```
*
* @author Alexander Kochetov <creocoder@gmail.com>
* @since 2.0
*/

View File

@ -37,6 +37,18 @@ use yii\helpers\Html;
* ]);
* ```
*
* You can also use this widget in an [[yii\widgets\ActiveForm|ActiveForm]] using the [[yii\widgets\ActiveField::widget()|widget()]]
* method, for example like this:
*
* ```php
* <?= $form->field($model, 'from_date')->widget(\yii\jui\SliderInput::classname(), [
* 'clientOptions' => [
* 'min' => 1,
* 'max' => 10,
* ],
* ]) ?>
* ```
*
* @see http://api.jqueryui.com/slider/
* @author Alexander Makarov <sam@rmcreative.ru>
* @since 2.0

View File

@ -31,6 +31,15 @@ use yii\helpers\Html;
* ]);
* ```
*
* You can also use this widget in an [[yii\widgets\ActiveForm|ActiveForm]] using the [[yii\widgets\ActiveField::widget()|widget()]]
* method, for example like this:
*
* ```php
* <?= $form->field($model, 'from_date')->widget(\yii\jui\Spinner::classname(), [
* 'clientOptions' => ['step' => 2],
* ]) ?>
* ```
*
* @see http://api.jqueryui.com/spinner/
* @author Alexander Kochetov <creocoder@gmail.com>
* @since 2.0

View File

@ -213,9 +213,7 @@ class Collection extends Object
*/
public function createIndex($columns, $options = [])
{
if (!is_array($columns)) {
$columns = [$columns];
}
$columns = (array)$columns;
$keys = $this->normalizeIndexKeys($columns);
$token = $this->composeLogToken('createIndex', [$keys, $options]);
$options = array_merge(['w' => 1], $options);
@ -258,9 +256,7 @@ class Collection extends Object
*/
public function dropIndex($columns)
{
if (!is_array($columns)) {
$columns = [$columns];
}
$columns = (array)$columns;
$keys = $this->normalizeIndexKeys($columns);
$token = $this->composeLogToken('dropIndex', [$keys]);
Yii::info($token, __METHOD__);

View File

@ -17,6 +17,7 @@ Yii Framework 2 Change Log
- Enh #6697: Added `yii\helpers\Url::current()` method that allows adding or removing parameters from current URL (samdark, callmez)
- Enh #6852: Added `yii\helpers\BaseHtmlPurifier::helpers()` in order to be able to configure `HtmlPurifier` helper globally via subclassing (Alex-Code)
- Enh #6882: Added `yii\web\ErrorHandler::getTypeUrl()` in order to allow providing custom types/classes/methods URLs for subclasses (brandonkelly)
- Enh #6883: `yii\base\ErrorHandler::logException()` is now public (samdark)
- Enh #6896: Added `yii\log\FileTarget::$enableRotation` to allow disabling log rotation when external tools are configured for this (cebe)
- Enh #7008: Removed extra white space in GridView filter cell (uran1980)
- Enh #7051: Added support for preventing swapping values between different cookies (pavimus, qiangxue)

View File

@ -197,7 +197,7 @@ abstract class ErrorHandler extends Component
* Logs the given exception
* @param \Exception $exception the exception to be logged
*/
protected function logException($exception)
public function logException($exception)
{
$category = get_class($exception);
if ($exception instanceof HttpException) {

View File

@ -17,8 +17,8 @@ use yii\widgets\InputWidget;
/**
* Captcha renders a CAPTCHA image and an input field that takes user-entered verification code.
*
* Captcha is used together with [[CaptchaAction]] provide [CAPTCHA](http://en.wikipedia.org/wiki/Captcha)
* - a way of preventing Website spamming.
* Captcha is used together with [[CaptchaAction]] provide [CAPTCHA](http://en.wikipedia.org/wiki/Captcha) - a way
* of preventing Website spamming.
*
* The image element rendered by Captcha will display a CAPTCHA image generated by
* an action whose route is specified by [[captchaAction]]. This action must be an instance of [[CaptchaAction]].
@ -29,6 +29,32 @@ use yii\widgets\InputWidget;
* You may use [[\yii\captcha\CaptchaValidator]] to validate the user input matches
* the current CAPTCHA verification code.
*
* The following example shows how to use this widget with a model attribute:
*
* ```php
* echo Captcha::widget([
* 'model' => $model,
* 'attribute' => 'captcha',
* ]);
* ```
*
* The following example will use the name property instead:
*
* ```php
* echo Captcha::widget([
* 'name' => 'captcha',
* ]);
* ```
*
* You can also use this widget in an [[yii\widgets\ActiveForm|ActiveForm]] using the [[yii\widgets\ActiveField::widget()|widget()]]
* method, for example like this:
*
* ```php
* <?= $form->field($model, 'captcha')->widget(\yii\widgets\Captcha::classname(), [
* // configure additional widget properties here
* ]) ?>
* ```
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/

View File

@ -615,7 +615,7 @@ abstract class BaseMigrateController extends Controller
continue;
}
$path = $this->migrationPath . DIRECTORY_SEPARATOR . $file;
if (preg_match('/^(m(\d{6}_\d{6})_.*?)\.php$/', $file, $matches) && is_file($path) && !isset($applied[$matches[2]])) {
if (preg_match('/^(m(\d{6}_\d{6})_.*?)\.php$/', $file, $matches) && !isset($applied[$matches[2]]) && is_file($path)) {
$migrations[] = $matches[1];
}
}

View File

@ -113,7 +113,7 @@ class HelpController extends Controller
$class = new \ReflectionClass($controller);
foreach ($class->getMethods() as $method) {
$name = $method->getName();
if ($method->isPublic() && !$method->isStatic() && strpos($name, 'action') === 0 && $name !== 'actions') {
if ($name !== 'actions' && $method->isPublic() && !$method->isStatic() && strpos($name, 'action') === 0) {
$actions[] = Inflector::camel2id(substr($name, 6), '-', true);
}
}

View File

@ -248,10 +248,7 @@ class MessageController extends Controller
$this->stdout("Extracting messages from $coloredFileName...\n");
$subject = file_get_contents($fileName);
$messages = [];
if (!is_array($translator)) {
$translator = [$translator];
}
foreach ($translator as $currentTranslator) {
foreach ((array)$translator as $currentTranslator) {
$translatorTokens = token_get_all('<?php ' . $currentTranslator);
array_shift($translatorTokens);
@ -401,7 +398,7 @@ class MessageController extends Controller
}
ksort($existingMessages);
foreach ($existingMessages as $message => $translation) {
if (!isset($merged[$message]) && !isset($todo[$message]) && !$removeUnused) {
if (!$removeUnused && !isset($merged[$message]) && !isset($todo[$message])) {
if (!empty($translation) && strncmp($translation, '@@', 2) === 0 && substr_compare($translation, '@@', -2, 2) === 0) {
$todo[$message] = $translation;
} else {
@ -515,7 +512,7 @@ EOD;
// add obsolete unused messages
foreach ($existingMessages as $message => $translation) {
if (!isset($merged[$category . chr(4) . $message]) && !isset($todos[$category . chr(4) . $message]) && !$removeUnused) {
if (!$removeUnused && !isset($merged[$category . chr(4) . $message]) && !isset($todos[$category . chr(4) . $message])) {
if (!empty($translation) && substr($translation, 0, 2) === '@@' && substr($translation, -2) === '@@') {
$todos[$category . chr(4) . $message] = $translation;
} else {

View File

@ -232,7 +232,7 @@ class Pagination extends Object implements Linkable
$this->_pageSize = null;
} else {
$value = (int) $value;
if ($validatePageSize && count($this->pageSizeLimit) === 2 && isset($this->pageSizeLimit[0], $this->pageSizeLimit[1])) {
if ($validatePageSize && isset($this->pageSizeLimit[0], $this->pageSizeLimit[1]) && count($this->pageSizeLimit) === 2) {
if ($value < $this->pageSizeLimit[0]) {
$value = $this->pageSizeLimit[0];
} elseif ($value > $this->pageSizeLimit[1]) {

View File

@ -215,7 +215,7 @@ trait ActiveRelationTrait
$this->filterByModels($primaryModels);
}
if (count($primaryModels) === 1 && !$this->multiple) {
if (!$this->multiple && count($primaryModels) === 1) {
$model = $this->one();
foreach ($primaryModels as $i => $primaryModel) {
if ($primaryModel instanceof ActiveRecordInterface) {

View File

@ -980,7 +980,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
public function getPrimaryKey($asArray = false)
{
$keys = $this->primaryKey();
if (count($keys) === 1 && !$asArray) {
if (!$asArray && count($keys) === 1) {
return isset($this->_attributes[$keys[0]]) ? $this->_attributes[$keys[0]] : null;
} else {
$values = [];
@ -1014,7 +1014,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
if (empty($keys)) {
throw new Exception(get_class($this) . ' does not have a primary key. You should either define a primary key for the corresponding table or override the primaryKey() method.');
}
if (count($keys) === 1 && !$asArray) {
if (!$asArray && count($keys) === 1) {
return isset($this->_oldAttributes[$keys[0]]) ? $this->_oldAttributes[$keys[0]] : null;
} else {
$values = [];

View File

@ -189,7 +189,7 @@ class QueryBuilder extends \yii\base\Object
foreach ($rows as $row) {
$vs = [];
foreach ($row as $i => $value) {
if (!is_array($value) && isset($columns[$i]) && isset($columnSchemas[$columns[$i]])) {
if (isset($columns[$i], $columnSchemas[$columns[$i]]) && !is_array($value)) {
$value = $columnSchemas[$columns[$i]]->dbTypecast($value);
}
if (is_string($value)) {

View File

@ -87,9 +87,7 @@ class TableSchema extends Object
*/
public function fixPrimaryKey($keys)
{
if (!is_array($keys)) {
$keys = [$keys];
}
$keys = (array)$keys;
$this->primaryKey = $keys;
foreach ($this->columns as $column) {
$column->isPrimaryKey = false;

View File

@ -305,7 +305,7 @@ class BaseFileHelper
if (!is_dir($dir)) {
return;
}
if (!is_link($dir) || isset($options['traverseSymlinks']) && $options['traverseSymlinks']) {
if (isset($options['traverseSymlinks']) && $options['traverseSymlinks'] || !is_link($dir)) {
if (!($handle = opendir($dir))) {
return;
}
@ -424,7 +424,7 @@ class BaseFileHelper
}
}
if (!is_dir($path) && !empty($options['only'])) {
if (!empty($options['only']) && !is_dir($path)) {
if (($except = self::lastExcludeMatchingFromList($options['basePath'], $path, $options['only'])) !== null) {
// don't check PATTERN_NEGATIVE since those entries are not prefixed with !
return true;

View File

@ -109,8 +109,7 @@ class GettextMoFile extends GettextFile
$separatorPosition = strpos($id, chr(4));
if (($context && $separatorPosition !== false && strncmp($id, $context, $separatorPosition) === 0) ||
(!$context && $separatorPosition === false)) {
if ((!$context && $separatorPosition === false) || ($context && $separatorPosition !== false && strncmp($id, $context, $separatorPosition) === 0)) {
if ($separatorPosition !== false) {
$id = substr($id, $separatorPosition+1);
}

View File

@ -209,7 +209,7 @@ class Logger extends Component
$matched = empty($categories);
foreach ($categories as $category) {
$prefix = rtrim($category, '*');
if (strpos($timing['category'], $prefix) === 0 && ($timing['category'] === $category || $prefix !== $category)) {
if (($timing['category'] === $category || $prefix !== $category) && strpos($timing['category'], $prefix) === 0) {
$matched = true;
break;
}
@ -219,7 +219,7 @@ class Logger extends Component
foreach ($excludeCategories as $category) {
$prefix = rtrim($category, '*');
foreach ($timings as $i => $timing) {
if (strpos($timing['category'], $prefix) === 0 && ($timing['category'] === $category || $prefix !== $category)) {
if (($timing['category'] === $category || $prefix !== $category) && strpos($timing['category'], $prefix) === 0) {
$matched = false;
break;
}

View File

@ -213,7 +213,7 @@ abstract class Target extends Component
if ($matched) {
foreach ($except as $category) {
$prefix = rtrim($category, '*');
if (strpos($message[2], $prefix) === 0 && ($message[2] === $category || $prefix !== $category)) {
if (($message[2] === $category || $prefix !== $category) && strpos($message[2], $prefix) === 0) {
$matched = false;
break;
}

View File

@ -203,7 +203,7 @@ class DbManager extends BaseManager
*/
protected function updateItem($name, $item)
{
if (!$this->supportsCascadeUpdate() && $item->name !== $name) {
if ($item->name !== $name && !$this->supportsCascadeUpdate()) {
$this->db->createCommand()
->update($this->itemChildTable, ['parent' => $item->name], ['parent' => $name])
->execute();
@ -259,7 +259,7 @@ class DbManager extends BaseManager
*/
protected function updateRule($name, $rule)
{
if (!$this->supportsCascadeUpdate() && $rule->name !== $name) {
if ($rule->name !== $name && !$this->supportsCascadeUpdate()) {
$this->db->createCommand()
->update($this->itemTable, ['rule_name' => $rule->name], ['rule_name' => $name])
->execute();

View File

@ -9,7 +9,7 @@
/* @var $end integer */
/* @var $handler \yii\web\ErrorHandler */
?>
<li class="<?php if (!$handler->isCoreFile($file) || $index === 1) echo 'application'; ?> call-stack-item"
<li class="<?php if ($index === 1 || !$handler->isCoreFile($file)) echo 'application'; ?> call-stack-item"
data-line="<?= (int) ($line - $begin) ?>">
<div class="element-wrap">
<div class="element">

View File

@ -470,7 +470,7 @@ class AssetManager extends Component
if (!is_dir($dstDir)) {
symlink($src, $dstDir);
}
} elseif (!is_dir($dstDir) || !empty($options['forceCopy']) || (!isset($options['forceCopy']) && $this->forceCopy)) {
} elseif (!empty($options['forceCopy']) || ($this->forceCopy && !isset($options['forceCopy'])) || !is_dir($dstDir)) {
$opts = [
'dirMode' => $this->dirMode,
'fileMode' => $this->fileMode,

View File

@ -73,7 +73,7 @@ class Controller extends \yii\base\Controller
$name = $param->getName();
if (array_key_exists($name, $params)) {
if ($param->isArray()) {
$args[] = $actionParams[$name] = is_array($params[$name]) ? $params[$name] : [$params[$name]];
$args[] = $actionParams[$name] = (array)$params[$name];
} elseif (!is_array($params[$name])) {
$args[] = $actionParams[$name] = $params[$name];
} else {

View File

@ -84,7 +84,7 @@ class ErrorHandler extends \yii\base\ErrorHandler
$response->data = $result;
}
} elseif ($response->format === Response::FORMAT_HTML) {
if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest' || YII_ENV_TEST) {
if (YII_ENV_TEST || isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') {
// AJAX request
$response->data = '<pre>' . $this->htmlEncode($this->convertExceptionToString($exception)) . '</pre>';
} else {

View File

@ -205,7 +205,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
if ($this->_hasSessionId === null) {
$name = $this->getName();
$request = Yii::$app->getRequest();
if (ini_get('session.use_cookies') && !empty($_COOKIE[$name])) {
if (!empty($_COOKIE[$name]) && ini_get('session.use_cookies')) {
$this->_hasSessionId = true;
} elseif (!ini_get('session.use_only_cookies') && ini_get('session.use_trans_sid')) {
$this->_hasSessionId = $request->get($name) !== null;

View File

@ -121,7 +121,7 @@ abstract class BaseListView extends Widget
*/
public function run()
{
if ($this->dataProvider->getCount() > 0 || $this->showOnEmpty) {
if ($this->showOnEmpty || $this->dataProvider->getCount() > 0) {
$content = preg_replace_callback("/{\\w+}/", function ($matches) {
$content = $this->renderSection($matches[0]);

View File

@ -20,6 +20,15 @@ use yii\helpers\Html;
* or a name and a value. If the former, the name and the value will
* be generated automatically.
*
* Classes extending from this widget can be used in an [[yii\widgets\ActiveForm|ActiveForm]]
* using the [[yii\widgets\ActiveField::widget()|widget()]] method, for example like this:
*
* ```php
* <?= $form->field($model, 'from_date')->widget('WidgetClassName', [
* // configure additional widget properties here
* ]) ?>
* ```
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
@ -54,7 +63,7 @@ class InputWidget extends Widget
*/
public function init()
{
if (!$this->hasModel() && $this->name === null) {
if ($this->name === null && !$this->hasModel()) {
throw new InvalidConfigException("Either 'name', or 'model' and 'attribute' properties must be specified.");
}
if (!isset($this->options['id'])) {

View File

@ -29,6 +29,15 @@ use yii\web\View;
* ]);
* ```
*
* You can also use this widget in an [[yii\widgets\ActiveForm|ActiveForm]] using the [[yii\widgets\ActiveField::widget()|widget()]]
* method, for example like this:
*
* ```php
* <?= $form->field($model, 'from_date')->widget(\yii\jui\MaskedInput::classname(), [
* 'mask' => '999-999-9999',
* ]) ?>
* ```
*
* The masked text field is implemented based on the
* [jQuery input masked plugin](https://github.com/RobinHerbots/jquery.inputmask).
*
@ -143,8 +152,8 @@ class MaskedInput extends InputWidget
{
$options = $this->clientOptions;
foreach ($options as $key => $value) {
if (in_array($key, ['oncomplete', 'onincomplete', 'oncleared', 'onKeyUp', 'onKeyDown', 'onBeforeMask',
'onBeforePaste', 'onUnMask', 'isComplete', 'determineActiveMasksetIndex']) && !$value instanceof JsExpression
if (!$value instanceof JsExpression && in_array($key, ['oncomplete', 'onincomplete', 'oncleared', 'onKeyUp',
'onKeyDown', 'onBeforeMask', 'onBeforePaste', 'onUnMask', 'isComplete', 'determineActiveMasksetIndex'])
) {
$options[$key] = new JsExpression($value);
}