diff --git a/.gitignore b/.gitignore index 832a890..13fcf4a 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,7 @@ nbproject Thumbs.db # composer vendor dir -/yii/vendor \ No newline at end of file +/yii/vendor + +# composer itself is not needed +composer.phar diff --git a/apps/bootstrap/protected/.htaccess b/apps/bootstrap/protected/.htaccess index e019832..8d2f256 100644 --- a/apps/bootstrap/protected/.htaccess +++ b/apps/bootstrap/protected/.htaccess @@ -1 +1 @@ -deny from all +deny from all diff --git a/apps/bootstrap/protected/commands/HelloController.php b/apps/bootstrap/protected/commands/HelloController.php new file mode 100644 index 0000000..16f5f74 --- /dev/null +++ b/apps/bootstrap/protected/commands/HelloController.php @@ -0,0 +1,29 @@ + + * @since 2.0 + */ +class HelloController extends Controller +{ + /** + * This command echos what you have entered as the message. + * @param string $message the message to be echoed. + */ + public function actionIndex($message = 'hello world') + { + echo $message; + } +} \ No newline at end of file diff --git a/apps/bootstrap/protected/config/console.php b/apps/bootstrap/protected/config/console.php new file mode 100644 index 0000000..df96023 --- /dev/null +++ b/apps/bootstrap/protected/config/console.php @@ -0,0 +1,26 @@ + 'bootstrap-console', + 'basePath' => dirname(__DIR__), + 'preload' => array('log'), + 'controllerPath' => dirname(__DIR__) . '/commands', + 'controllerNamespace' => 'app\commands', + 'modules' => array( + ), + 'components' => array( + 'cache' => array( + 'class' => 'yii\caching\FileCache', + ), + 'log' => array( + 'class' => 'yii\logging\Router', + 'targets' => array( + array( + 'class' => 'yii\logging\FileTarget', + 'levels' => array('error', 'warning'), + ), + ), + ), + ), + 'params' => require(__DIR__ . '/params.php'), +); diff --git a/apps/bootstrap/protected/config/main.php b/apps/bootstrap/protected/config/main.php index f19dead..b5980da 100644 --- a/apps/bootstrap/protected/config/main.php +++ b/apps/bootstrap/protected/config/main.php @@ -1,13 +1,14 @@ 'hello', + 'id' => 'bootstrap', 'basePath' => dirname(__DIR__), 'preload' => array('log'), + 'controllerNamespace' => 'app\controllers', 'modules' => array( - 'debug' => array( - 'class' => 'yii\debug\Module', - ) +// 'debug' => array( +// 'class' => 'yii\debug\Module', +// ) ), 'components' => array( 'cache' => array( @@ -23,14 +24,15 @@ return array( 'log' => array( 'class' => 'yii\logging\Router', 'targets' => array( - 'file' => array( + array( 'class' => 'yii\logging\FileTarget', 'levels' => array('error', 'warning'), ), +// array( +// 'class' => 'yii\logging\DebugTarget', +// ) ), ), ), - 'params' => array( - 'adminEmail' => 'admin@example.com', - ), + 'params' => require(__DIR__ . '/params.php'), ); diff --git a/apps/bootstrap/protected/config/params.php b/apps/bootstrap/protected/config/params.php new file mode 100644 index 0000000..1e197d0 --- /dev/null +++ b/apps/bootstrap/protected/config/params.php @@ -0,0 +1,5 @@ + 'admin@example.com', +); \ No newline at end of file diff --git a/apps/bootstrap/protected/controllers/SiteController.php b/apps/bootstrap/protected/controllers/SiteController.php index b06ed06..534bc59 100644 --- a/apps/bootstrap/protected/controllers/SiteController.php +++ b/apps/bootstrap/protected/controllers/SiteController.php @@ -1,5 +1,7 @@ registerAssetBundle('app'); -widget('yii\debug\Toolbar'); ?> + endPage(); ?> diff --git a/apps/bootstrap/protected/views/site/contact.php b/apps/bootstrap/protected/views/site/contact.php index a632345..e740d0f 100644 --- a/apps/bootstrap/protected/views/site/contact.php +++ b/apps/bootstrap/protected/views/site/contact.php @@ -23,7 +23,7 @@ $this->params['breadcrumbs'][] = $this->title; If you have business inquiries or other questions, please fill out the following form to contact us. Thank you.

-beginWidget(ActiveForm::className(), array( + array('class' => 'form-horizontal'), 'fieldConfig' => array('inputOptions' => array('class' => 'input-xlarge')), )); ?> @@ -33,14 +33,14 @@ $this->params['breadcrumbs'][] = $this->title; field($model, 'body')->textArea(array('rows' => 6)); ?> field($model, 'verifyCode'); - echo $field->begin(); - echo $field->label(); - $this->widget(Captcha::className()); - echo Html::activeTextInput($model, 'verifyCode', array('class' => 'input-medium')); - echo $field->error(); - echo $field->end(); + echo $field->begin() + . $field->label() + . Captcha::widget() + . Html::activeTextInput($model, 'verifyCode', array('class' => 'input-medium')) + . $field->error() + . $field->end(); ?>
'btn btn-primary')); ?>
-endWidget(); ?> + diff --git a/apps/bootstrap/protected/views/site/login.php b/apps/bootstrap/protected/views/site/login.php index 65dc7d1..f676b98 100644 --- a/apps/bootstrap/protected/views/site/login.php +++ b/apps/bootstrap/protected/views/site/login.php @@ -14,11 +14,11 @@ $this->params['breadcrumbs'][] = $this->title;

Please fill out the following fields to login:

-beginWidget(ActiveForm::className(), array('options' => array('class' => 'form-horizontal'))); ?> + array('class' => 'form-horizontal'))); ?> field($model, 'username')->textInput(); ?> field($model, 'password')->passwordInput(); ?> field($model, 'rememberMe')->checkbox(); ?>
'btn btn-primary')); ?>
-endWidget(); ?> + diff --git a/apps/bootstrap/protected/yii b/apps/bootstrap/protected/yii new file mode 100644 index 0000000..df58e58 --- /dev/null +++ b/apps/bootstrap/protected/yii @@ -0,0 +1,23 @@ +#!/usr/bin/env php +run(); \ No newline at end of file diff --git a/yii/yiic.bat b/apps/bootstrap/protected/yii.bat similarity index 92% rename from yii/yiic.bat rename to apps/bootstrap/protected/yii.bat index c63fc81..18bb296 100644 --- a/yii/yiic.bat +++ b/apps/bootstrap/protected/yii.bat @@ -17,6 +17,6 @@ set YII_PATH=%~dp0 if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe -"%PHP_COMMAND%" "%YII_PATH%yiic" %* +"%PHP_COMMAND%" "%YII_PATH%yii" %* @endlocal \ No newline at end of file diff --git a/build/build b/build/build index 95b51e4..f8b21a6 100755 --- a/build/build +++ b/build/build @@ -11,7 +11,7 @@ // fcgi doesn't have STDIN defined by default defined('STDIN') or define('STDIN', fopen('php://stdin', 'r')); -require(__DIR__ . '/../framework/Yii.php'); +require(__DIR__ . '/../yii/Yii.php'); $id = 'yiic-build'; $basePath = __DIR__; diff --git a/build/controllers/LocaleController.php b/build/controllers/LocaleController.php index d471c0d..1f9827e 100644 --- a/build/controllers/LocaleController.php +++ b/build/controllers/LocaleController.php @@ -93,7 +93,7 @@ class LocaleController extends Controller /** * Plural rules. * - * This file is automatically generated by the "yiic locale/plural" command under the "build" folder. + * This file is automatically generated by the "yii locale/plural" command under the "build" folder. * Do not modify it directly. * * The original plural rule data used for generating this file has the following copyright terms: diff --git a/composer.json b/composer.json index e9c3927..1e8ae83 100644 --- a/composer.json +++ b/composer.json @@ -69,11 +69,30 @@ "bin": [ "yii/yiic" ], + "repositories": [ + { + "type": "package", + "package": { + "name": "bestiejs/punycode.js", + "version": "1.2.1", + "dist": { + "url": "https://github.com/bestiejs/punycode.js/archive/1.2.1.zip", + "type": "zip" + }, + "source": { + "url": "https://github.com/bestiejs/punycode.js.git", + "type": "git", + "reference": "tags/1.2.1" + } + } + } + ], "require": { "php": ">=5.3.0", "michelf/php-markdown": "1.3", "twig/twig": "1.12.*", "smarty/smarty": "3.1.*", - "ezyang/htmlpurifier": "v4.5.0" + "ezyang/htmlpurifier": "v4.5.0", + "bestiejs/punycode.js": "1.2.1" } } diff --git a/composer.lock b/composer.lock index 1cae3d4..0c9457d 100644 --- a/composer.lock +++ b/composer.lock @@ -3,9 +3,25 @@ "This file locks the dependencies of your project to a known state", "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file" ], - "hash": "7d46ce9c4d8d5f4ecae1611ea8f0b49c", + "hash": "a8f949e337a229a4cfb41496a0071ef6", "packages": [ { + "name": "bestiejs/punycode.js", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/bestiejs/punycode.js.git", + "reference": "tags/1.2.1" + }, + "dist": { + "type": "zip", + "url": "https://github.com/bestiejs/punycode.js/archive/master.zip", + "reference": null, + "shasum": null + }, + "type": "library" + }, + { "name": "ezyang/htmlpurifier", "version": "v4.5.0", "source": { diff --git a/docs/api/db/ActiveRecord.md b/docs/api/db/ActiveRecord.md index 4e82793..d8bedb4 100644 --- a/docs/api/db/ActiveRecord.md +++ b/docs/api/db/ActiveRecord.md @@ -446,3 +446,7 @@ $customers = Customer::find()->olderThan(50)->all(); The parameters should follow after the `$query` parameter when defining the scope method, and they can take default values like shown above. + +### Atomic operations and scenarios + +TBD diff --git a/docs/guide/upgrade-from-v1.md b/docs/guide/upgrade-from-v1.md index d35e6e0..50e6977 100644 --- a/docs/guide/upgrade-from-v1.md +++ b/docs/guide/upgrade-from-v1.md @@ -209,6 +209,26 @@ if (isset($_POST['Post'])) { ``` +Widgets +------- + +Using a widget is more straightforward in 2.0. You mainly use the `begin()`, `end()` and `widget()` +methods of the `Widget` class. For example, + +```php +// $this refers to the View object +// Note that you have to "echo" the result to display it +echo \yii\widgets\Menu::widget(array('items' => $items)); + +// $this refers to the View object +$form = \yii\widgets\ActiveForm::begin($this); +... form inputs here ... +\yii\widgets\ActiveForm::end(); +``` + +Previously in 1.1, you would have to enter the widget class names as strings via the `beginWidget()`, +`endWidget()` and `widget()` methods of `CBaseController`. The approach above gets better IDE support. + Themes ------ @@ -249,10 +269,6 @@ Message translation is still supported, but managed via the "i18n" application c The component manages a set of message sources, which allows you to use different message sources based on message categories. For more information, see the class documentation for `I18N`. -The message translation method is changed by merging the message category into the message being -translated. For example, `Yii::t('yii|message to be translated')`. - - Action Filters -------------- @@ -309,13 +325,13 @@ is a container consisting of a label, an input, and an error message. It is repr as an `ActiveField` object. Using fields, you can build a form more cleanly than before: ```php -beginWidget('yii\widgets\ActiveForm'); ?> + field($model, 'username')->textInput(); ?> field($model, 'password')->passwordInput(); ?>
-endWidget(); ?> + ``` diff --git a/docs/internals/ar.md b/docs/internals/ar.md index c493269..d59ba6a 100644 --- a/docs/internals/ar.md +++ b/docs/internals/ar.md @@ -1,15 +1,32 @@ ActiveRecord ============ +Scenarios +--------- + +Possible scenario formats supported by ActiveRecord: + +```php +public function scenarios() +{ + return array( + // attributes array, all operations won't be wrapped with transaction + 'scenario1' => array('attribute1', 'attribute2'), + + // insert and update operations will be wrapped with transaction, delete won't be wrapped + 'scenario2' => array( + 'attributes' => array('attribute1', 'attribute2'), + 'atomic' => array(self::OP_INSERT, self::OP_UPDATE), + ), + ); +} +``` + Query ----- ### Basic Queries - - ### Relational Queries ### Scopes - - diff --git a/tests/unit/framework/YiiBaseTest.php b/tests/unit/framework/YiiBaseTest.php index bc31c0a..e256b2b 100644 --- a/tests/unit/framework/YiiBaseTest.php +++ b/tests/unit/framework/YiiBaseTest.php @@ -45,6 +45,9 @@ class YiiBaseTest extends TestCase Yii::setAlias('@yii', null); $this->assertFalse(Yii::getAlias('@yii', false)); $this->assertEquals('/yii/gii/file', Yii::getAlias('@yii/gii/file')); + + Yii::setAlias('@some/alias', '/www'); + $this->assertEquals('/www', Yii::getAlias('@some/alias')); } public function testGetVersion() diff --git a/tests/unit/framework/base/DictionaryTest.php b/tests/unit/framework/base/DictionaryTest.php deleted file mode 100644 index 9c47c29..0000000 --- a/tests/unit/framework/base/DictionaryTest.php +++ /dev/null @@ -1,209 +0,0 @@ -dictionary = new Dictionary; - $this->item1 = new MapItem; - $this->item2 = new MapItem; - $this->item3 = new MapItem; - $this->dictionary->add('key1', $this->item1); - $this->dictionary->add('key2', $this->item2); - } - - protected function tearDown() - { - parent::tearDown(); - $this->dictionary = null; - $this->item1 = null; - $this->item2 = null; - $this->item3 = null; - } - - public function testConstruct() - { - $a = array(1, 2, 'key3' => 3); - $dictionary = new Dictionary($a); - $this->assertEquals(3, $dictionary->getCount()); - $dictionary2=new Dictionary($this->dictionary); - $this->assertEquals(2, $dictionary2->getCount()); - } - - public function testGetCount() - { - $this->assertEquals(2, $this->dictionary->getCount()); - } - - public function testGetKeys() - { - $keys = $this->dictionary->getKeys(); - $this->assertEquals(2, count($keys)); - $this->assertEquals('key1', $keys[0]); - $this->assertEquals('key2', $keys[1]); - } - - public function testAdd() - { - $this->dictionary->add('key3', $this->item3); - $this->assertEquals(3, $this->dictionary->getCount()); - $this->assertTrue($this->dictionary->has('key3')); - - $this->dictionary[] = 'test'; - } - - public function testRemove() - { - $this->dictionary->remove('key1'); - $this->assertEquals(1, $this->dictionary->getCount()); - $this->assertTrue(!$this->dictionary->has('key1')); - $this->assertTrue($this->dictionary->remove('unknown key') === null); - } - - public function testRemoveAll() - { - $this->dictionary->add('key3', $this->item3); - $this->dictionary->removeAll(); - $this->assertEquals(0, $this->dictionary->getCount()); - $this->assertTrue(!$this->dictionary->has('key1') && !$this->dictionary->has('key2')); - - $this->dictionary->add('key3', $this->item3); - $this->dictionary->removeAll(true); - $this->assertEquals(0, $this->dictionary->getCount()); - $this->assertTrue(!$this->dictionary->has('key1') && !$this->dictionary->has('key2')); - } - - public function testHas() - { - $this->assertTrue($this->dictionary->has('key1')); - $this->assertTrue($this->dictionary->has('key2')); - $this->assertFalse($this->dictionary->has('key3')); - } - - public function testFromArray() - { - $array = array('key3' => $this->item3, 'key4' => $this->item1); - $this->dictionary->copyFrom($array); - - $this->assertEquals(2, $this->dictionary->getCount()); - $this->assertEquals($this->item3, $this->dictionary['key3']); - $this->assertEquals($this->item1, $this->dictionary['key4']); - - $this->setExpectedException('yii\base\InvalidParamException'); - $this->dictionary->copyFrom($this); - } - - public function testMergeWith() - { - $a = array('a' => 'v1', 'v2', array('2'), 'c' => array('3', 'c' => 'a')); - $b = array('v22', 'a' => 'v11', array('2'), 'c' => array('c' => '3', 'a')); - $c = array('a' => 'v11', 'v2', array('2'), 'c' => array('3', 'c' => '3', 'a'), 'v22', array('2')); - $dictionary = new Dictionary($a); - $dictionary2 = new Dictionary($b); - $dictionary->mergeWith($dictionary2); - $this->assertTrue($dictionary->toArray() === $c); - - $array = array('key2' => $this->item1, 'key3' => $this->item3); - $this->dictionary->mergeWith($array, false); - $this->assertEquals(3, $this->dictionary->getCount()); - $this->assertEquals($this->item1, $this->dictionary['key2']); - $this->assertEquals($this->item3, $this->dictionary['key3']); - $this->setExpectedException('yii\base\InvalidParamException'); - $this->dictionary->mergeWith($this, false); - } - - public function testRecursiveMergeWithTraversable(){ - $dictionary = new Dictionary(); - $obj = new \ArrayObject(array( - 'k1' => $this->item1, - 'k2' => $this->item2, - 'k3' => new \ArrayObject(array( - 'k4' => $this->item3, - )) - )); - $dictionary->mergeWith($obj, true); - - $this->assertEquals(3, $dictionary->getCount()); - $this->assertEquals($this->item1, $dictionary['k1']); - $this->assertEquals($this->item2, $dictionary['k2']); - $this->assertEquals($this->item3, $dictionary['k3']['k4']); - } - - public function testArrayRead() - { - $this->assertEquals($this->item1, $this->dictionary['key1']); - $this->assertEquals($this->item2, $this->dictionary['key2']); - $this->assertEquals(null, $this->dictionary['key3']); - } - - public function testArrayWrite() - { - $this->dictionary['key3'] = $this->item3; - $this->assertEquals(3, $this->dictionary->getCount()); - $this->assertEquals($this->item3, $this->dictionary['key3']); - - $this->dictionary['key1'] = $this->item3; - $this->assertEquals(3, $this->dictionary->getCount()); - $this->assertEquals($this->item3, $this->dictionary['key1']); - - unset($this->dictionary['key2']); - $this->assertEquals(2, $this->dictionary->getCount()); - $this->assertTrue(!$this->dictionary->has('key2')); - - unset($this->dictionary['unknown key']); - } - - public function testArrayForeach() - { - $n = 0; - $found = 0; - foreach ($this->dictionary as $index => $item) { - $n++; - if ($index === 'key1' && $item === $this->item1) { - $found++; - } - if ($index === 'key2' && $item === $this->item2) { - $found++; - } - } - $this->assertTrue($n == 2 && $found == 2); - } - - public function testArrayMisc() - { - $this->assertEquals($this->dictionary->Count, count($this->dictionary)); - $this->assertTrue(isset($this->dictionary['key1'])); - $this->assertFalse(isset($this->dictionary['unknown key'])); - } - - public function testToArray() - { - $dictionary = new Dictionary(array('key' => 'value')); - $this->assertEquals(array('key' => 'value'), $dictionary->toArray()); - } - - public function testIteratorCurrent() - { - $dictionary = new Dictionary(array('key1' => 'value1', 'key2' => 'value2')); - $val = $dictionary->getIterator()->current(); - $this->assertEquals('value1', $val); - } -} diff --git a/tests/unit/framework/base/ModelTest.php b/tests/unit/framework/base/ModelTest.php index cf6f09f..c292af7 100644 --- a/tests/unit/framework/base/ModelTest.php +++ b/tests/unit/framework/base/ModelTest.php @@ -12,7 +12,7 @@ use yiiunit\data\base\InvalidRulesModel; */ class ModelTest extends TestCase { - public function testGetAttributeLalel() + public function testGetAttributeLabel() { $speaker = new Speaker(); $this->assertEquals('First Name', $speaker->getAttributeLabel('firstName')); diff --git a/tests/unit/framework/base/VectorTest.php b/tests/unit/framework/base/VectorTest.php deleted file mode 100644 index 6d6bd23..0000000 --- a/tests/unit/framework/base/VectorTest.php +++ /dev/null @@ -1,230 +0,0 @@ -vector = new Vector; - $this->item1 = new ListItem; - $this->item2 = new ListItem; - $this->item3 = new ListItem; - $this->vector->add($this->item1); - $this->vector->add($this->item2); - } - - protected function tearDown() - { - parent::tearDown(); - $this->vector = null; - $this->item1 = null; - $this->item2 = null; - $this->item3 = null; - } - - public function testConstruct() - { - $a = array(1, 2, 3); - $vector = new Vector($a); - $this->assertEquals(3, $vector->getCount()); - $vector2 = new Vector($this->vector); - $this->assertEquals(2, $vector2->getCount()); - } - - public function testItemAt() - { - $a = array(1, 2, null, 4); - $vector = new Vector($a); - $this->assertEquals(1, $vector->itemAt(0)); - $this->assertEquals(2, $vector->itemAt(1)); - $this->assertNull($vector->itemAt(2)); - $this->assertEquals(4, $vector->itemAt(3)); - } - - public function testGetCount() - { - $this->assertEquals(2, $this->vector->getCount()); - $this->assertEquals(2, $this->vector->Count); - } - - public function testAdd() - { - $this->vector->add(null); - $this->vector->add($this->item3); - $this->assertEquals(4, $this->vector->getCount()); - $this->assertEquals(3, $this->vector->indexOf($this->item3)); - } - - public function testInsertAt() - { - $this->vector->insertAt(0, $this->item3); - $this->assertEquals(3, $this->vector->getCount()); - $this->assertEquals(2, $this->vector->indexOf($this->item2)); - $this->assertEquals(0, $this->vector->indexOf($this->item3)); - $this->assertEquals(1, $this->vector->indexOf($this->item1)); - $this->setExpectedException('yii\base\InvalidParamException'); - $this->vector->insertAt(4, $this->item3); - } - - public function testRemove() - { - $this->vector->remove($this->item1); - $this->assertEquals(1, $this->vector->getCount()); - $this->assertEquals(-1, $this->vector->indexOf($this->item1)); - $this->assertEquals(0, $this->vector->indexOf($this->item2)); - - $this->assertEquals(false, $this->vector->remove($this->item1)); - - } - - public function testRemoveAt() - { - $this->vector->add($this->item3); - $this->vector->removeAt(1); - $this->assertEquals(-1, $this->vector->indexOf($this->item2)); - $this->assertEquals(1, $this->vector->indexOf($this->item3)); - $this->assertEquals(0, $this->vector->indexOf($this->item1)); - $this->setExpectedException('yii\base\InvalidParamException'); - $this->vector->removeAt(2); - } - - public function testRemoveAll() - { - $this->vector->add($this->item3); - $this->vector->removeAll(); - $this->assertEquals(0, $this->vector->getCount()); - $this->assertEquals(-1, $this->vector->indexOf($this->item1)); - $this->assertEquals(-1, $this->vector->indexOf($this->item2)); - - $this->vector->add($this->item3); - $this->vector->removeAll(true); - $this->assertEquals(0, $this->vector->getCount()); - $this->assertEquals(-1, $this->vector->indexOf($this->item1)); - $this->assertEquals(-1, $this->vector->indexOf($this->item2)); - } - - public function testHas() - { - $this->assertTrue($this->vector->has($this->item1)); - $this->assertTrue($this->vector->has($this->item2)); - $this->assertFalse($this->vector->has($this->item3)); - } - - public function testIndexOf() - { - $this->assertEquals(0, $this->vector->indexOf($this->item1)); - $this->assertEquals(1, $this->vector->indexOf($this->item2)); - $this->assertEquals(-1, $this->vector->indexOf($this->item3)); - } - - public function testFromArray() - { - $array = array($this->item3, $this->item1); - $this->vector->copyFrom($array); - $this->assertTrue(count($array) == 2 && $this->vector[0] === $this->item3 && $this->vector[1] === $this->item1); - $this->setExpectedException('yii\base\InvalidParamException'); - $this->vector->copyFrom($this); - } - - public function testMergeWith() - { - $array = array($this->item3, $this->item1); - $this->vector->mergeWith($array); - $this->assertTrue($this->vector->getCount() == 4 && $this->vector[0] === $this->item1 && - $this->vector[3] === $this->item1); - - $a = array(1); - $vector = new Vector($a); - $this->vector->mergeWith($vector); - $this->assertTrue($this->vector->getCount() == 5 && $this->vector[0] === $this->item1 && - $this->vector[3] === $this->item1 && $this->vector[4] === 1); - - $this->setExpectedException('yii\base\InvalidParamException'); - $this->vector->mergeWith($this); - } - - public function testToArray() - { - $array = $this->vector->toArray(); - $this->assertTrue(count($array) == 2 && $array[0] === $this->item1 && $array[1] === $this->item2); - } - - public function testArrayRead() - { - $this->assertTrue($this->vector[0] === $this->item1); - $this->assertTrue($this->vector[1] === $this->item2); - $this->setExpectedException('yii\base\InvalidParamException'); - $a = $this->vector[2]; - } - - public function testGetIterator() - { - $n = 0; - $found = 0; - foreach ($this->vector as $index => $item) { - foreach ($this->vector as $a => $b) { - // test of iterator - } - $n++; - if ($index === 0 && $item === $this->item1) { - $found++; - } - if ($index === 1 && $item === $this->item2) { - $found++; - } - } - $this->assertTrue($n == 2 && $found == 2); - } - - public function testArrayMisc() - { - $this->assertEquals($this->vector->Count, count($this->vector)); - $this->assertTrue(isset($this->vector[1])); - $this->assertFalse(isset($this->vector[2])); - } - - public function testOffsetSetAdd() - { - $vector = new Vector(array(1, 2, 3)); - $vector->offsetSet(null, 4); - $this->assertEquals(array(1, 2, 3, 4), $vector->toArray()); - } - - public function testOffsetSetReplace() - { - $vector = new Vector(array(1, 2, 3)); - $vector->offsetSet(1, 4); - $this->assertEquals(array(1, 4, 3), $vector->toArray()); - } - - public function testOffsetUnset() - { - $vector = new Vector(array(1, 2, 3)); - $vector->offsetUnset(1); - $this->assertEquals(array(1, 3), $vector->toArray()); - } - - public function testIteratorCurrent() - { - $vector = new Vector(array('value1', 'value2')); - $val = $vector->getIterator()->current(); - $this->assertEquals('value1', $val); - } -} diff --git a/tests/unit/framework/caching/ApcCacheTest.php b/tests/unit/framework/caching/ApcCacheTest.php index 20a1cc8..c059554 100644 --- a/tests/unit/framework/caching/ApcCacheTest.php +++ b/tests/unit/framework/caching/ApcCacheTest.php @@ -31,7 +31,8 @@ class ApcCacheTest extends CacheTest return $this->_cacheInstance; } - // TODO there seems to be a problem with APC returning cached value even if it is expired. - // TODO makes test fail on PHP 5.3.10-1ubuntu3.6 with Suhosin-Patch (cli) -- cebe - // TODO http://drupal.org/node/1278292 + public function testExpire() + { + $this->markTestSkipped("APC keys are expiring only on the next request."); + } } diff --git a/tests/unit/framework/helpers/ArrayHelperTest.php b/tests/unit/framework/helpers/ArrayHelperTest.php index 8c83278..60a129f 100644 --- a/tests/unit/framework/helpers/ArrayHelperTest.php +++ b/tests/unit/framework/helpers/ArrayHelperTest.php @@ -3,6 +3,8 @@ namespace yiiunit\framework\helpers; use yii\helpers\ArrayHelper; +use yii\helpers\VarDumper; +use yii\web\Sort; class ArrayHelperTest extends \yii\test\TestCase { @@ -54,16 +56,51 @@ class ArrayHelperTest extends \yii\test\TestCase array('name' => 'A', 'age' => 1), ); - ArrayHelper::multisort($array, array('name', 'age'), SORT_ASC, array(SORT_STRING, SORT_REGULAR)); + ArrayHelper::multisort($array, array('name', 'age'), false, array(SORT_STRING, SORT_REGULAR)); $this->assertEquals(array('name' => 'A', 'age' => 1), $array[0]); $this->assertEquals(array('name' => 'B', 'age' => 4), $array[1]); $this->assertEquals(array('name' => 'a', 'age' => 3), $array[2]); $this->assertEquals(array('name' => 'b', 'age' => 2), $array[3]); - ArrayHelper::multisort($array, array('name', 'age'), SORT_ASC, array(SORT_STRING, SORT_REGULAR), false); + ArrayHelper::multisort($array, array('name', 'age'), false, array(SORT_STRING, SORT_REGULAR), false); $this->assertEquals(array('name' => 'A', 'age' => 1), $array[0]); $this->assertEquals(array('name' => 'a', 'age' => 3), $array[1]); $this->assertEquals(array('name' => 'b', 'age' => 2), $array[2]); $this->assertEquals(array('name' => 'B', 'age' => 4), $array[3]); } + + public function testMultisortUseSort() + { + // single key + $sort = new Sort(); + $sort->attributes = array('name', 'age'); + $sort->defaults = array('name' => Sort::ASC); + $orders = $sort->getOrders(); + + $array = array( + array('name' => 'b', 'age' => 3), + array('name' => 'a', 'age' => 1), + array('name' => 'c', 'age' => 2), + ); + ArrayHelper::multisort($array, array_keys($orders), array_values($orders)); + $this->assertEquals(array('name' => 'a', 'age' => 1), $array[0]); + $this->assertEquals(array('name' => 'b', 'age' => 3), $array[1]); + $this->assertEquals(array('name' => 'c', 'age' => 2), $array[2]); + + // multiple keys + $sort = new Sort(); + $sort->attributes = array('name', 'age'); + $sort->defaults = array('name' => Sort::ASC, 'age' => Sort::DESC); + $orders = $sort->getOrders(); + + $array = array( + array('name' => 'b', 'age' => 3), + array('name' => 'a', 'age' => 2), + array('name' => 'a', 'age' => 1), + ); + ArrayHelper::multisort($array, array_keys($orders), array_values($orders)); + $this->assertEquals(array('name' => 'a', 'age' => 2), $array[0]); + $this->assertEquals(array('name' => 'a', 'age' => 1), $array[1]); + $this->assertEquals(array('name' => 'b', 'age' => 3), $array[2]); + } } diff --git a/tests/unit/framework/web/ResponseTest.php b/tests/unit/framework/web/ResponseTest.php new file mode 100644 index 0000000..b3d9080 --- /dev/null +++ b/tests/unit/framework/web/ResponseTest.php @@ -0,0 +1,86 @@ +reset(); + } + + protected function reset() + { + static::$headers = array(); + static::$httpResponseCode = 200; + } + + public function ranges() + { + // TODO test more cases for range requests and check for rfc compatibility + // http://www.w3.org/Protocols/rfc2616/rfc2616.txt + return array( + array('0-5', '0-5', 6, '12ёж'), + array('2-', '2-66', 65, 'ёжик3456798áèabcdefghijklmnopqrstuvwxyz!"§$%&/(ёжик)=?'), + array('-12', '55-66', 12, '(ёжик)=?'), + ); + } + + /** + * @dataProvider ranges + */ + public function testSendFileRanges($rangeHeader, $expectedHeader, $length, $expectedFile) + { + $content = $this->generateTestFileContent(); + + $_SERVER['HTTP_RANGE'] = 'bytes=' . $rangeHeader; + $sent = $this->runSendFile('testFile.txt', $content, null); + $this->assertEquals($expectedFile, $sent); + $this->assertTrue(in_array('HTTP/1.1 206 Partial Content', static::$headers)); + $this->assertTrue(in_array('Accept-Ranges: bytes', static::$headers)); + $this->assertArrayHasKey('Content-Range: bytes ' . $expectedHeader . '/' . StringHelper::strlen($content), array_flip(static::$headers)); + $this->assertTrue(in_array('Content-Type: text/plain', static::$headers)); + $this->assertTrue(in_array('Content-Length: ' . $length, static::$headers)); + } + + protected function generateTestFileContent() + { + return '12ёжик3456798áèabcdefghijklmnopqrstuvwxyz!"§$%&/(ёжик)=?'; + } + + protected function runSendFile($fileName, $content, $mimeType) + { + ob_start(); + ob_implicit_flush(false); + $response = new Response(); + $response->sendFile($fileName, $content, $mimeType, false); + $file = ob_get_clean(); + return $file; + } +} \ No newline at end of file diff --git a/upgrade.md b/upgrade.md index 6dd89b7..470e375 100644 --- a/upgrade.md +++ b/upgrade.md @@ -35,8 +35,7 @@ Upgrading from v1.1.x from `Object` and supports events and behaviors. Behaviors declared in `Component::behaviors()` are attached on demand. -- `CList` is renamed to `Vector`, and `CMap` is renamed to `Dictionary`. - Other collection classes are dropped in favor of SPL classes. +- All collection classes are dropped in favor of SPL classes. - `CFormModel` is removed. Please use `yii\base\Model` instead. diff --git a/yii/YiiBase.php b/yii/YiiBase.php index 1a3f50c..ad48e52 100644 --- a/yii/YiiBase.php +++ b/yii/YiiBase.php @@ -287,7 +287,11 @@ class YiiBase if ($path !== null) { $path = strncmp($path, '@', 1) ? rtrim($path, '\\/') : static::getAlias($path); if (!isset(self::$aliases[$root])) { - self::$aliases[$root] = $path; + if ($pos === false) { + self::$aliases[$root] = $path; + } else { + self::$aliases[$root] = array($alias => $path); + } } elseif (is_string(self::$aliases[$root])) { if ($pos === false) { self::$aliases[$root] = $path; @@ -579,10 +583,9 @@ class YiiBase /** * Translates a message to the specified language. * - * The translation will be conducted according to the message category and the target language. - * To specify the category of the message, prefix the message with the category name and separate it - * with "|". For example, "app|hello world". If the category is not specified, the default category "app" - * will be used. The actual message translation is done by a [[\yii\i18n\MessageSource|message source]]. + * This is a shortcut method of [[\yii\i18n\I18N::translate()]]. + * + * The translation will be conducted according to the message category and the target language will be used. * * In case when a translated message has different plural forms (separated by "|"), this method * will also attempt to choose an appropriate one according to a given numeric value which is @@ -595,20 +598,18 @@ class YiiBase * For more details on how plural rules are applied, please refer to: * [[http://www.unicode.org/cldr/charts/supplemental/language_plural_rules.html]] * + * @param string $category the message category. * @param string $message the message to be translated. * @param array $params the parameters that will be used to replace the corresponding placeholders in the message. * @param string $language the language code (e.g. `en_US`, `en`). If this is null, the current * [[\yii\base\Application::language|application language]] will be used. * @return string the translated message. */ - public static function t($message, $params = array(), $language = null) + public static function t($category, $message, $params = array(), $language = null) { if (self::$app !== null) { - return self::$app->getI18N()->translate($message, $params, $language); + return self::$app->getI18N()->translate($category, $message, $params, $language); } else { - if (strpos($message, '|') !== false && preg_match('/^([\w\-\\/\.\\\\]+)\|(.*)/', $message, $matches)) { - $message = $matches[2]; - } return is_array($params) ? strtr($message, $params) : $message; } } diff --git a/yii/assets.php b/yii/assets.php index 7ee177d..746bf30 100644 --- a/yii/assets.php +++ b/yii/assets.php @@ -26,7 +26,7 @@ return array( 'js' => array( 'yii.activeForm.js', ), - 'depends' => array('yii', 'yii/validation'), + 'depends' => array('yii'), ), 'yii/captcha' => array( 'sourcePath' => __DIR__ . '/assets', @@ -42,4 +42,10 @@ return array( ), 'depends' => array('yii'), ), + 'punycode' => array( + 'sourcePath' => __DIR__ . '/vendor/bestiejs/punycode.js', + 'js' => array( + 'punycode.min.js', + ), + ), ); diff --git a/yii/assets/yii.activeForm.js b/yii/assets/yii.activeForm.js index d987879..483df96 100644 --- a/yii/assets/yii.activeForm.js +++ b/yii/assets/yii.activeForm.js @@ -57,7 +57,7 @@ // whether to perform validation when a change is detected on the input validateOnChange: false, // whether to perform validation when the user is typing. - validateOnType: false, + validateOnType: false, // number of milliseconds that the validation should be delayed when a user is typing in the input field. validationDelay: 200, // whether to enable AJAX-based validation. diff --git a/yii/assets/yii.captcha.js b/yii/assets/yii.captcha.js index 9211edb..7dfe3cc 100644 --- a/yii/assets/yii.captcha.js +++ b/yii/assets/yii.captcha.js @@ -51,8 +51,8 @@ dataType: 'json', cache: false, success: function(data) { - $e.attr('src', data['url']); - $('body').data(settings.hashKey, [data['hash1'], data['hash2']]); + $e.attr('src', data.url); + $('body').data(settings.hashKey, [data.hash1, data.hash2]); } }); }, diff --git a/yii/assets/yii.debug.js b/yii/assets/yii.debug.js index 4e32d89..e0d30f6 100644 --- a/yii/assets/yii.debug.js +++ b/yii/assets/yii.debug.js @@ -18,7 +18,7 @@ yii.debug = (function ($) { //dataType: 'json', success: function(data) { var $e = $('#' + id); - $e.html(data); + $e.html(data).show(); } }); } diff --git a/yii/assets/yii.validation.js b/yii/assets/yii.validation.js index 5fa8492..2748a74 100644 --- a/yii/assets/yii.validation.js +++ b/yii/assets/yii.validation.js @@ -110,9 +110,19 @@ yii.validation = (function ($) { return; } - var valid = value.match(options.pattern) && (!options.allowName || value.match(options.fullPattern)); + var valid = true; + + if (options.enableIDN) { + var regexp = /^(.*)@(.*)$/, + matches = regexp.exec(value); + if (matches === null) { + valid = false; + } else { + value = punycode.toASCII(matches[1]) + '@' + punycode.toASCII(matches[2]); + } + } - if (!valid) { + if (!valid || !(value.match(options.pattern) && (!options.allowName || value.match(options.fullPattern)))) { messages.push(options.message); } }, @@ -126,7 +136,19 @@ yii.validation = (function ($) { value = options.defaultScheme + '://' + value; } - if (!value.match(options.pattern)) { + var valid = true; + + if (options.enableIDN) { + var regexp = /^([^:]+):\/\/([^\/]+)(.*)$/, + matches = regexp.exec(value); + if (matches === null) { + valid = false; + } else { + value = matches[1] + '://' + punycode.toASCII(matches[2]) + matches[3]; + } + } + + if (!valid || !value.match(options.pattern)) { messages.push(options.message); } }, diff --git a/yii/base/Controller.php b/yii/base/Controller.php index 6b6c926..0639e2b 100644 --- a/yii/base/Controller.php +++ b/yii/base/Controller.php @@ -183,7 +183,7 @@ class Controller extends Component } if (!empty($missing)) { - throw new InvalidRequestException(Yii::t('yii|Missing required parameters: {params}', array( + throw new InvalidRequestException(Yii::t('yii', 'Missing required parameters: {params}', array( '{params}' => implode(', ', $missing), ))); } @@ -410,6 +410,7 @@ class Controller extends Component * Returns the view object that can be used to render views or view files. * The [[render()]], [[renderPartial()]] and [[renderFile()]] methods will use * this view object to implement the actual view rendering. + * If not set, it will default to the "view" application component. * @return View the view object that can be used to render views or view files. */ public function getView() diff --git a/yii/base/Dictionary.php b/yii/base/Dictionary.php deleted file mode 100644 index 0a4741e..0000000 --- a/yii/base/Dictionary.php +++ /dev/null @@ -1,297 +0,0 @@ - $value) // traverse the items in the dictionary - * $n = count($dictionary); // returns the number of items in the dictionary - * ~~~ - * - * @property integer $count the number of items in the dictionary - * @property array $keys The keys in the dictionary - * - * @author Qiang Xue - * @since 2.0 - */ -class Dictionary extends Object implements \IteratorAggregate, \ArrayAccess, \Countable -{ - /** - * @var array internal data storage - */ - private $_d = array(); - - /** - * Constructor. - * Initializes the dictionary with an array or an iterable object. - * @param mixed $data the initial data to be populated into the dictionary. - * This can be an array or an iterable object. - * @param array $config name-value pairs that will be used to initialize the object properties - * @throws Exception if data is not well formed (neither an array nor an iterable object) - */ - public function __construct($data = array(), $config = array()) - { - if (!empty($data)) { - $this->copyFrom($data); - } - parent::__construct($config); - } - - /** - * Returns an iterator for traversing the items in the dictionary. - * This method is required by the SPL interface `IteratorAggregate`. - * It will be implicitly called when you use `foreach` to traverse the dictionary. - * @return DictionaryIterator an iterator for traversing the items in the dictionary. - */ - public function getIterator() - { - return new DictionaryIterator($this->_d); - } - - /** - * Returns the number of items in the dictionary. - * This method is required by the SPL `Countable` interface. - * It will be implicitly called when you use `count($dictionary)`. - * @return integer number of items in the dictionary. - */ - public function count() - { - return $this->getCount(); - } - - /** - * Returns the number of items in the dictionary. - * @return integer the number of items in the dictionary - */ - public function getCount() - { - return count($this->_d); - } - - /** - * Returns the keys stored in the dictionary. - * @return array the key list - */ - public function getKeys() - { - return array_keys($this->_d); - } - - /** - * Returns the item with the specified key. - * @param mixed $key the key - * @return mixed the element with the specified key. - * Null if the key cannot be found in the dictionary. - */ - public function itemAt($key) - { - return isset($this->_d[$key]) ? $this->_d[$key] : null; - } - - /** - * Adds an item into the dictionary. - * Note, if the specified key already exists, the old value will be overwritten. - * @param mixed $key key - * @param mixed $value value - * @throws Exception if the dictionary is read-only - */ - public function add($key, $value) - { - if ($key === null) { - $this->_d[] = $value; - } else { - $this->_d[$key] = $value; - } - } - - /** - * Removes an item from the dictionary by its key. - * @param mixed $key the key of the item to be removed - * @return mixed the removed value, null if no such key exists. - * @throws Exception if the dictionary is read-only - */ - public function remove($key) - { - if (isset($this->_d[$key])) { - $value = $this->_d[$key]; - unset($this->_d[$key]); - return $value; - } else { // the value is null - unset($this->_d[$key]); - return null; - } - } - - /** - * Removes all items from the dictionary. - * @param boolean $safeClear whether to clear every item by calling [[remove]]. - * Defaults to false, meaning all items in the dictionary will be cleared directly - * without calling [[remove]]. - */ - public function removeAll($safeClear = false) - { - if ($safeClear) { - foreach (array_keys($this->_d) as $key) { - $this->remove($key); - } - } else { - $this->_d = array(); - } - } - - /** - * Returns a value indicating whether the dictionary contains the specified key. - * @param mixed $key the key - * @return boolean whether the dictionary contains an item with the specified key - */ - public function has($key) - { - return isset($this->_d[$key]) || array_key_exists($key, $this->_d); - } - - /** - * Returns the dictionary as a PHP array. - * @return array the list of items in array - */ - public function toArray() - { - return $this->_d; - } - - /** - * Copies iterable data into the dictionary. - * Note, existing data in the dictionary will be cleared first. - * @param mixed $data the data to be copied from, must be an array or an object implementing `Traversable` - * @throws InvalidParamException if data is neither an array nor an iterator. - */ - public function copyFrom($data) - { - if (is_array($data) || $data instanceof \Traversable) { - if (!empty($this->_d)) { - $this->removeAll(); - } - if ($data instanceof self) { - $data = $data->_d; - } - foreach ($data as $key => $value) { - $this->add($key, $value); - } - } else { - throw new InvalidParamException('Data must be either an array or an object implementing Traversable.'); - } - } - - /** - * Merges iterable data into the dictionary. - * - * Existing elements in the dictionary will be overwritten if their keys are the same as those in the source. - * If the merge is recursive, the following algorithm is performed: - * - * - the dictionary data is saved as $a, and the source data is saved as $b; - * - if $a and $b both have an array indexed at the same string key, the arrays will be merged using this algorithm; - * - any integer-indexed elements in $b will be appended to $a; - * - any string-indexed elements in $b will overwrite elements in $a with the same index; - * - * @param array|\Traversable $data the data to be merged with. It must be an array or object implementing Traversable - * @param boolean $recursive whether the merging should be recursive. - * @throws InvalidParamException if data is neither an array nor an object implementing `Traversable`. - */ - public function mergeWith($data, $recursive = true) - { - if (is_array($data) || $data instanceof \Traversable) { - if ($data instanceof self) { - $data = $data->_d; - } - if ($recursive) { - if ($data instanceof \Traversable) { - $d = array(); - foreach ($data as $key => $value) { - $d[$key] = $value; - } - $this->_d = ArrayHelper::merge($this->_d, $d); - } else { - $this->_d = ArrayHelper::merge($this->_d, $data); - } - } else { - foreach ($data as $key => $value) { - $this->add($key, $value); - } - } - } else { - throw new InvalidParamException('The data to be merged with must be an array or an object implementing Traversable.'); - } - } - - /** - * Returns whether there is an element at the specified offset. - * This method is required by the SPL interface `ArrayAccess`. - * It is implicitly called when you use something like `isset($dictionary[$offset])`. - * This is equivalent to [[contains]]. - * @param mixed $offset the offset to check on - * @return boolean - */ - public function offsetExists($offset) - { - return $this->has($offset); - } - - /** - * Returns the element at the specified offset. - * This method is required by the SPL interface `ArrayAccess`. - * It is implicitly called when you use something like `$value = $dictionary[$offset];`. - * This is equivalent to [[itemAt]]. - * @param mixed $offset the offset to retrieve element. - * @return mixed the element at the offset, null if no element is found at the offset - */ - public function offsetGet($offset) - { - return $this->itemAt($offset); - } - - /** - * Sets the element at the specified offset. - * This method is required by the SPL interface `ArrayAccess`. - * It is implicitly called when you use something like `$dictionary[$offset] = $item;`. - * If the offset is null, the new item will be appended to the dictionary. - * Otherwise, the existing item at the offset will be replaced with the new item. - * This is equivalent to [[add]]. - * @param mixed $offset the offset to set element - * @param mixed $item the element value - */ - public function offsetSet($offset, $item) - { - $this->add($offset, $item); - } - - /** - * Unsets the element at the specified offset. - * This method is required by the SPL interface `ArrayAccess`. - * It is implicitly called when you use something like `unset($dictionary[$offset])`. - * This is equivalent to [[remove]]. - * @param mixed $offset the offset to unset element - */ - public function offsetUnset($offset) - { - $this->remove($offset); - } -} diff --git a/yii/base/DictionaryIterator.php b/yii/base/DictionaryIterator.php deleted file mode 100644 index 0d15bb0..0000000 --- a/yii/base/DictionaryIterator.php +++ /dev/null @@ -1,92 +0,0 @@ - - * @since 2.0 - */ -class DictionaryIterator implements \Iterator -{ - /** - * @var array the data to be iterated through - */ - private $_d; - /** - * @var array list of keys in the map - */ - private $_keys; - /** - * @var mixed current key - */ - private $_key; - - /** - * Constructor. - * @param array $data the data to be iterated through - */ - public function __construct(&$data) - { - $this->_d = &$data; - $this->_keys = array_keys($data); - $this->_key = reset($this->_keys); - } - - /** - * Rewinds the index of the current item. - * This method is required by the SPL interface `Iterator`. - */ - public function rewind() - { - $this->_key = reset($this->_keys); - } - - /** - * Returns the key of the current array element. - * This method is required by the SPL interface `Iterator`. - * @return mixed the key of the current array element - */ - public function key() - { - return $this->_key; - } - - /** - * Returns the current array element. - * This method is required by the SPL interface `Iterator`. - * @return mixed the current array element - */ - public function current() - { - return $this->_d[$this->_key]; - } - - /** - * Moves the internal pointer to the next element. - * This method is required by the SPL interface `Iterator`. - */ - public function next() - { - $this->_key = next($this->_keys); - } - - /** - * Returns whether there is an element at current position. - * This method is required by the SPL interface `Iterator`. - * @return boolean whether there is an item at current position. - */ - public function valid() - { - return $this->_key !== false; - } -} diff --git a/yii/base/ErrorException.php b/yii/base/ErrorException.php index b41e9ed..8e1977a 100644 --- a/yii/base/ErrorException.php +++ b/yii/base/ErrorException.php @@ -90,20 +90,20 @@ class ErrorException extends Exception public function getName() { $names = array( - E_ERROR => Yii::t('yii|Fatal Error'), - E_PARSE => Yii::t('yii|Parse Error'), - E_CORE_ERROR => Yii::t('yii|Core Error'), - E_COMPILE_ERROR => Yii::t('yii|Compile Error'), - E_USER_ERROR => Yii::t('yii|User Error'), - E_WARNING => Yii::t('yii|Warning'), - E_CORE_WARNING => Yii::t('yii|Core Warning'), - E_COMPILE_WARNING => Yii::t('yii|Compile Warning'), - E_USER_WARNING => Yii::t('yii|User Warning'), - E_STRICT => Yii::t('yii|Strict'), - E_NOTICE => Yii::t('yii|Notice'), - E_RECOVERABLE_ERROR => Yii::t('yii|Recoverable Error'), - E_DEPRECATED => Yii::t('yii|Deprecated'), + E_ERROR => Yii::t('yii', 'Fatal Error'), + E_PARSE => Yii::t('yii', 'Parse Error'), + E_CORE_ERROR => Yii::t('yii', 'Core Error'), + E_COMPILE_ERROR => Yii::t('yii', 'Compile Error'), + E_USER_ERROR => Yii::t('yii', 'User Error'), + E_WARNING => Yii::t('yii', 'Warning'), + E_CORE_WARNING => Yii::t('yii', 'Core Warning'), + E_COMPILE_WARNING => Yii::t('yii', 'Compile Warning'), + E_USER_WARNING => Yii::t('yii', 'User Warning'), + E_STRICT => Yii::t('yii', 'Strict'), + E_NOTICE => Yii::t('yii', 'Notice'), + E_RECOVERABLE_ERROR => Yii::t('yii', 'Recoverable Error'), + E_DEPRECATED => Yii::t('yii', 'Deprecated'), ); - return isset($names[$this->getCode()]) ? $names[$this->getCode()] : Yii::t('yii|Error'); + return isset($names[$this->getCode()]) ? $names[$this->getCode()] : Yii::t('yii', 'Error'); } } diff --git a/yii/base/ErrorHandler.php b/yii/base/ErrorHandler.php index 44c2ca0..8340723 100644 --- a/yii/base/ErrorHandler.php +++ b/yii/base/ErrorHandler.php @@ -75,8 +75,11 @@ class ErrorHandler extends Component \Yii::$app->runAction($this->errorAction); } elseif (\Yii::$app instanceof \yii\web\Application) { if (!headers_sent()) { - $errorCode = $exception instanceof HttpException ? $exception->statusCode : 500; - header("HTTP/1.0 $errorCode " . get_class($exception)); + if ($exception instanceof HttpException) { + header('HTTP/1.0 ' . $exception->statusCode . ' ' . $exception->getName()); + } else { + header('HTTP/1.0 500 ' . get_class($exception)); + } } if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') { \Yii::$app->renderException($exception); diff --git a/yii/base/Exception.php b/yii/base/Exception.php index 7d26bd0..956f17b 100644 --- a/yii/base/Exception.php +++ b/yii/base/Exception.php @@ -20,6 +20,6 @@ class Exception extends \Exception */ public function getName() { - return \Yii::t('yii|Exception'); + return \Yii::t('yii', 'Exception'); } } diff --git a/yii/base/HttpException.php b/yii/base/HttpException.php index 55e0531..2b014f7 100644 --- a/yii/base/HttpException.php +++ b/yii/base/HttpException.php @@ -103,7 +103,7 @@ class HttpException extends UserException if (isset($httpCodes[$this->statusCode])) { return $httpCodes[$this->statusCode]; } else { - return \Yii::t('yii|Error'); + return \Yii::t('yii', 'Error'); } } } diff --git a/yii/base/InvalidCallException.php b/yii/base/InvalidCallException.php index 9aefe14..9a146d4 100644 --- a/yii/base/InvalidCallException.php +++ b/yii/base/InvalidCallException.php @@ -20,7 +20,7 @@ class InvalidCallException extends Exception */ public function getName() { - return \Yii::t('yii|Invalid Call'); + return \Yii::t('yii', 'Invalid Call'); } } diff --git a/yii/base/InvalidConfigException.php b/yii/base/InvalidConfigException.php index 389737c..c617381 100644 --- a/yii/base/InvalidConfigException.php +++ b/yii/base/InvalidConfigException.php @@ -20,7 +20,7 @@ class InvalidConfigException extends Exception */ public function getName() { - return \Yii::t('yii|Invalid Configuration'); + return \Yii::t('yii', 'Invalid Configuration'); } } diff --git a/yii/base/InvalidParamException.php b/yii/base/InvalidParamException.php index a8c96fd..0262051 100644 --- a/yii/base/InvalidParamException.php +++ b/yii/base/InvalidParamException.php @@ -20,7 +20,7 @@ class InvalidParamException extends Exception */ public function getName() { - return \Yii::t('yii|Invalid Parameter'); + return \Yii::t('yii', 'Invalid Parameter'); } } diff --git a/yii/base/InvalidRequestException.php b/yii/base/InvalidRequestException.php index 6663e29..f4806ce 100644 --- a/yii/base/InvalidRequestException.php +++ b/yii/base/InvalidRequestException.php @@ -20,7 +20,7 @@ class InvalidRequestException extends UserException */ public function getName() { - return \Yii::t('yii|Invalid Request'); + return \Yii::t('yii', 'Invalid Request'); } } diff --git a/yii/base/InvalidRouteException.php b/yii/base/InvalidRouteException.php index 6d2256e..a573636 100644 --- a/yii/base/InvalidRouteException.php +++ b/yii/base/InvalidRouteException.php @@ -20,7 +20,7 @@ class InvalidRouteException extends UserException */ public function getName() { - return \Yii::t('yii|Invalid Route'); + return \Yii::t('yii', 'Invalid Route'); } } diff --git a/yii/base/Model.php b/yii/base/Model.php index 8348d97..98901bf 100644 --- a/yii/base/Model.php +++ b/yii/base/Model.php @@ -7,6 +7,8 @@ namespace yii\base; +use ArrayObject; +use ArrayIterator; use yii\helpers\StringHelper; use yii\validators\RequiredValidator; use yii\validators\Validator; @@ -30,7 +32,7 @@ use yii\validators\Validator; * You may directly use Model to store model data, or extend it with customization. * You may also customize Model by attaching [[ModelBehavior|model behaviors]]. * - * @property Vector $validators All the validators declared in the model. + * @property ArrayObject $validators All the validators declared in the model. * @property array $activeValidators The validators applicable to the current [[scenario]]. * @property array $errors Errors for all attributes or the specified attribute. Empty array is returned if no error. * @property array $attributes Attribute values (name => value). @@ -56,7 +58,7 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess */ private $_errors; /** - * @var Vector vector of validators + * @var ArrayObject list of validators */ private $_validators; /** @@ -300,15 +302,15 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess * This method differs from [[getActiveValidators()]] in that the latter * only returns the validators applicable to the current [[scenario]]. * - * Because this method returns a [[Vector]] object, you may + * Because this method returns an ArrayObject object, you may * manipulate it by inserting or removing validators (useful in model behaviors). * For example, * * ~~~ - * $model->validators->add($newValidator); + * $model->validators[] = $newValidator; * ~~~ * - * @return Vector all the validators declared in the model. + * @return ArrayObject all the validators declared in the model. */ public function getValidators() { @@ -340,18 +342,18 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess /** * Creates validator objects based on the validation rules specified in [[rules()]]. * Unlike [[getValidators()]], each time this method is called, a new list of validators will be returned. - * @return Vector validators + * @return ArrayObject validators * @throws InvalidConfigException if any validation rule configuration is invalid */ public function createValidators() { - $validators = new Vector; + $validators = new ArrayObject; foreach ($this->rules() as $rule) { if ($rule instanceof Validator) { - $validators->add($rule); + $validators->append($rule); } elseif (is_array($rule) && isset($rule[0], $rule[1])) { // attributes, validator type $validator = Validator::createValidator($rule[1], $this, $rule[0], array_slice($rule, 2)); - $validators->add($validator); + $validators->append($validator); } else { throw new InvalidConfigException('Invalid validation rule: a rule must specify both attribute names and validator type.'); } @@ -590,18 +592,22 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess /** * Returns the attribute names that are safe to be massively assigned in the current scenario. - * @return array safe attribute names + * @return string[] safe attribute names */ public function safeAttributes() { $scenario = $this->getScenario(); $scenarios = $this->scenarios(); + if (!isset($scenarios[$scenario])) { + return array(); + } $attributes = array(); - if (isset($scenarios[$scenario])) { - foreach ($scenarios[$scenario] as $attribute) { - if ($attribute[0] !== '!') { - $attributes[] = $attribute; - } + if (isset($scenarios[$scenario]['attributes']) && is_array($scenarios[$scenario]['attributes'])) { + $scenarios[$scenario] = $scenarios[$scenario]['attributes']; + } + foreach ($scenarios[$scenario] as $attribute) { + if ($attribute[0] !== '!') { + $attributes[] = $attribute; } } return $attributes; @@ -609,34 +615,37 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess /** * Returns the attribute names that are subject to validation in the current scenario. - * @return array safe attribute names + * @return string[] safe attribute names */ public function activeAttributes() { $scenario = $this->getScenario(); $scenarios = $this->scenarios(); - if (isset($scenarios[$scenario])) { - $attributes = $scenarios[$this->getScenario()]; - foreach ($attributes as $i => $attribute) { - if ($attribute[0] === '!') { - $attributes[$i] = substr($attribute, 1); - } - } - return $attributes; - } else { + if (!isset($scenarios[$scenario])) { return array(); } + if (isset($scenarios[$scenario]['attributes']) && is_array($scenarios[$scenario]['attributes'])) { + $attributes = $scenarios[$scenario]['attributes']; + } else { + $attributes = $scenarios[$scenario]; + } + foreach ($attributes as $i => $attribute) { + if ($attribute[0] === '!') { + $attributes[$i] = substr($attribute, 1); + } + } + return $attributes; } /** * Returns an iterator for traversing the attributes in the model. * This method is required by the interface IteratorAggregate. - * @return DictionaryIterator an iterator for traversing the items in the list. + * @return ArrayIterator an iterator for traversing the items in the list. */ public function getIterator() { $attributes = $this->getAttributes(); - return new DictionaryIterator($attributes); + return new ArrayIterator($attributes); } /** diff --git a/yii/base/Module.php b/yii/base/Module.php index b1597e2..f2e67a9 100644 --- a/yii/base/Module.php +++ b/yii/base/Module.php @@ -449,7 +449,7 @@ abstract class Module extends Component public function getComponent($id, $load = true) { if (isset($this->_components[$id])) { - if ($this->_components[$id] instanceof Component) { + if ($this->_components[$id] instanceof Object) { return $this->_components[$id]; } elseif ($load) { Yii::trace("Loading component: $id", __METHOD__); diff --git a/yii/base/NotSupportedException.php b/yii/base/NotSupportedException.php index 2f08891..8a93e14 100644 --- a/yii/base/NotSupportedException.php +++ b/yii/base/NotSupportedException.php @@ -20,7 +20,7 @@ class NotSupportedException extends Exception */ public function getName() { - return \Yii::t('yii|Not Supported'); + return \Yii::t('yii', 'Not Supported'); } } diff --git a/yii/base/Theme.php b/yii/base/Theme.php index a60d56e..ca1efcd 100644 --- a/yii/base/Theme.php +++ b/yii/base/Theme.php @@ -25,7 +25,22 @@ use yii\helpers\FileHelper; * then the themed version for a view file `/www/views/site/index.php` will be * `/www/themes/basic/site/index.php`. * - * @property string $baseUrl the base URL for this theme. This is mainly used by [[getUrl()]]. + * To use a theme, you should configure the [[View::theme|theme]] property of the "view" application + * component like the following: + * + * ~~~ + * 'view' => array( + * 'theme' => array( + * 'basePath' => '@wwwroot/themes/basic', + * 'baseUrl' => '@www/themes/basic', + * ), + * ), + * ~~~ + * + * The above configuration specifies a theme located under the "themes/basic" directory of the Web folder + * that contains the entry script of the application. If your theme is designed to handle modules, + * you may configure the [[pathMap]] property like described above. + * * * @author Qiang Xue * @since 2.0 diff --git a/yii/base/UnknownClassException.php b/yii/base/UnknownClassException.php index ac44746..e4a682a 100644 --- a/yii/base/UnknownClassException.php +++ b/yii/base/UnknownClassException.php @@ -20,7 +20,7 @@ class UnknownClassException extends Exception */ public function getName() { - return \Yii::t('yii|Unknown Class'); + return \Yii::t('yii', 'Unknown Class'); } } diff --git a/yii/base/UnknownMethodException.php b/yii/base/UnknownMethodException.php index 440e76e..d8cea34 100644 --- a/yii/base/UnknownMethodException.php +++ b/yii/base/UnknownMethodException.php @@ -20,7 +20,7 @@ class UnknownMethodException extends Exception */ public function getName() { - return \Yii::t('yii|Unknown Method'); + return \Yii::t('yii', 'Unknown Method'); } } diff --git a/yii/base/UnknownPropertyException.php b/yii/base/UnknownPropertyException.php index 5ec3814..b8e93c5 100644 --- a/yii/base/UnknownPropertyException.php +++ b/yii/base/UnknownPropertyException.php @@ -20,7 +20,7 @@ class UnknownPropertyException extends Exception */ public function getName() { - return \Yii::t('yii|Unknown Property'); + return \Yii::t('yii', 'Unknown Property'); } } diff --git a/yii/base/Vector.php b/yii/base/Vector.php deleted file mode 100644 index ae35cca..0000000 --- a/yii/base/Vector.php +++ /dev/null @@ -1,341 +0,0 @@ - $item) // traverse each item in the vector - * $n = count($vector); // count the number of items - * ~~~ - * - * Note that if you plan to extend Vector by performing additional operations - * with each addition or removal of an item (e.g. performing type check), - * please make sure you override [[insertAt()]] and [[removeAt()]]. - * - * @property integer $count the number of items in the vector - * - * @author Qiang Xue - * @since 2.0 - */ -class Vector extends Object implements \IteratorAggregate, \ArrayAccess, \Countable -{ - /** - * @var array internal data storage - */ - private $_d = array(); - /** - * @var integer number of items - */ - private $_c = 0; - - /** - * Constructor. - * Initializes the vector with an array or an iterable object. - * @param mixed $data the initial data to be populated into the vector. - * This can be an array or an iterable object. - * @param array $config name-value pairs that will be used to initialize the object properties - * @throws Exception if data is not well formed (neither an array nor an iterable object) - */ - public function __construct($data = array(), $config = array()) - { - if (!empty($data)) { - $this->copyFrom($data); - } - parent::__construct($config); - } - - /** - * Returns an iterator for traversing the items in the vector. - * This method is required by the SPL interface `IteratorAggregate`. - * It will be implicitly called when you use `foreach` to traverse the vector. - * @return VectorIterator an iterator for traversing the items in the vector. - */ - public function getIterator() - { - return new VectorIterator($this->_d); - } - - /** - * Returns the number of items in the vector. - * This method is required by the SPL `Countable` interface. - * It will be implicitly called when you use `count($vector)`. - * @return integer number of items in the vector. - */ - public function count() - { - return $this->getCount(); - } - - /** - * Returns the number of items in the vector. - * @return integer the number of items in the vector - */ - public function getCount() - { - return $this->_c; - } - - /** - * Returns the item at the specified index. - * @param integer $index the index of the item - * @return mixed the item at the index - * @throws InvalidParamException if the index is out of range - */ - public function itemAt($index) - { - if (isset($this->_d[$index])) { - return $this->_d[$index]; - } elseif ($index >= 0 && $index < $this->_c) { // in case the value is null - return $this->_d[$index]; - } else { - throw new InvalidParamException('Index out of range: ' . $index); - } - } - - /** - * Appends an item at the end of the vector. - * @param mixed $item new item - * @return integer the zero-based index at which the item is added - * @throws Exception if the vector is read-only. - */ - public function add($item) - { - $this->insertAt($this->_c, $item); - return $this->_c - 1; - } - - /** - * Inserts an item at the specified position. - * Original item at the position and the following items will be moved - * one step towards the end. - * @param integer $index the specified position. - * @param mixed $item new item to be inserted into the vector - * @throws InvalidParamException if the index specified is out of range, or the vector is read-only. - */ - public function insertAt($index, $item) - { - if ($index === $this->_c) { - $this->_d[$this->_c++] = $item; - } elseif ($index >= 0 && $index < $this->_c) { - array_splice($this->_d, $index, 0, array($item)); - $this->_c++; - } else { - throw new InvalidParamException('Index out of range: ' . $index); - } - } - - /** - * Removes an item from the vector. - * The vector will search for the item, and the first item found - * will be removed from the vector. - * @param mixed $item the item to be removed. - * @return mixed the index at which the item is being removed, or false - * if the item cannot be found in the vector. - * @throws Exception if the vector is read only. - */ - public function remove($item) - { - if (($index = $this->indexOf($item)) >= 0) { - $this->removeAt($index); - return $index; - } else { - return false; - } - } - - /** - * Removes an item at the specified position. - * @param integer $index the index of the item to be removed. - * @return mixed the removed item. - * @throws InvalidParamException if the index is out of range, or the vector is read only. - */ - public function removeAt($index) - { - if ($index >= 0 && $index < $this->_c) { - $this->_c--; - if ($index === $this->_c) { - return array_pop($this->_d); - } else { - $item = $this->_d[$index]; - array_splice($this->_d, $index, 1); - return $item; - } - } else { - throw new InvalidParamException('Index out of range: ' . $index); - } - } - - /** - * Removes all items from the vector. - * @param boolean $safeClear whether to clear every item by calling [[removeAt]]. - * Defaults to false, meaning all items in the vector will be cleared directly - * without calling [[removeAt]]. - */ - public function removeAll($safeClear = false) - { - if ($safeClear) { - for ($i = $this->_c - 1; $i >= 0; --$i) { - $this->removeAt($i); - } - } else { - $this->_d = array(); - $this->_c = 0; - } - } - - /** - * Returns a value indicating whether the vector contains the specified item. - * Note that the search is based on strict PHP comparison. - * @param mixed $item the item - * @return boolean whether the vector contains the item - */ - public function has($item) - { - return $this->indexOf($item) >= 0; - } - - /** - * Returns the index of the specified item in the vector. - * The index is zero-based. If the item is not found in the vector, -1 will be returned. - * Note that the search is based on strict PHP comparison. - * @param mixed $item the item - * @return integer the index of the item in the vector (0 based), -1 if not found. - */ - public function indexOf($item) - { - $index = array_search($item, $this->_d, true); - return $index === false ? -1 : $index; - } - - /** - * Returns the vector as a PHP array. - * @return array the items in the vector. - */ - public function toArray() - { - return $this->_d; - } - - /** - * Copies iterable data into the vector. - * Note, existing data in the vector will be cleared first. - * @param mixed $data the data to be copied from, must be an array or an object implementing `Traversable` - * @throws InvalidParamException if data is neither an array nor an object implementing `Traversable`. - */ - public function copyFrom($data) - { - if (is_array($data) || $data instanceof \Traversable) { - if ($this->_c > 0) { - $this->removeAll(); - } - if ($data instanceof self) { - $data = $data->_d; - } - foreach ($data as $item) { - $this->add($item); - } - } else { - throw new InvalidParamException('Data must be either an array or an object implementing Traversable.'); - } - } - - /** - * Merges iterable data into the vector. - * New items will be appended to the end of the existing items. - * @param array|\Traversable $data the data to be merged with. It must be an array or object implementing Traversable - * @throws InvalidParamException if data is neither an array nor an object implementing `Traversable`. - */ - public function mergeWith($data) - { - if (is_array($data) || ($data instanceof \Traversable)) { - if ($data instanceof Vector) { - $data = $data->_d; - } - foreach ($data as $item) { - $this->add($item); - } - } else { - throw new InvalidParamException('The data to be merged with must be an array or an object implementing Traversable.'); - } - } - - /** - * Returns a value indicating whether there is an item at the specified offset. - * This method is required by the SPL interface `ArrayAccess`. - * It is implicitly called when you use something like `isset($vector[$offset])`. - * @param integer $offset the offset to be checked - * @return boolean whether there is an item at the specified offset. - */ - public function offsetExists($offset) - { - return $offset >= 0 && $offset < $this->_c; - } - - /** - * Returns the item at the specified offset. - * This method is required by the SPL interface `ArrayAccess`. - * It is implicitly called when you use something like `$value = $vector[$offset];`. - * This is equivalent to [[itemAt]]. - * @param integer $offset the offset to retrieve item. - * @return mixed the item at the offset - * @throws Exception if the offset is out of range - */ - public function offsetGet($offset) - { - return $this->itemAt($offset); - } - - /** - * Sets the item at the specified offset. - * This method is required by the SPL interface `ArrayAccess`. - * It is implicitly called when you use something like `$vector[$offset] = $item;`. - * If the offset is null or equal to the number of the existing items, - * the new item will be appended to the vector. - * Otherwise, the existing item at the offset will be replaced with the new item. - * @param integer $offset the offset to set item - * @param mixed $item the item value - * @throws Exception if the offset is out of range, or the vector is read only. - */ - public function offsetSet($offset, $item) - { - if ($offset === null || $offset === $this->_c) { - $this->insertAt($this->_c, $item); - } else { - $this->removeAt($offset); - $this->insertAt($offset, $item); - } - } - - /** - * Unsets the item at the specified offset. - * This method is required by the SPL interface `ArrayAccess`. - * It is implicitly called when you use something like `unset($vector[$offset])`. - * This is equivalent to [[removeAt]]. - * @param integer $offset the offset to unset item - * @throws Exception if the offset is out of range, or the vector is read only. - */ - public function offsetUnset($offset) - { - $this->removeAt($offset); - } -} diff --git a/yii/base/VectorIterator.php b/yii/base/VectorIterator.php deleted file mode 100644 index f83d42d..0000000 --- a/yii/base/VectorIterator.php +++ /dev/null @@ -1,92 +0,0 @@ - - * @since 2.0 - */ -class VectorIterator implements \Iterator -{ - /** - * @var array the data to be iterated through - */ - private $_d; - /** - * @var integer index of the current item - */ - private $_i; - /** - * @var integer count of the data items - */ - private $_c; - - /** - * Constructor. - * @param array $data the data to be iterated through - */ - public function __construct(&$data) - { - $this->_d = &$data; - $this->_i = 0; - $this->_c = count($this->_d); - } - - /** - * Rewinds the index of the current item. - * This method is required by the SPL interface `Iterator`. - */ - public function rewind() - { - $this->_i = 0; - } - - /** - * Returns the key of the current item. - * This method is required by the SPL interface `Iterator`. - * @return integer the key of the current item - */ - public function key() - { - return $this->_i; - } - - /** - * Returns the current item. - * This method is required by the SPL interface `Iterator`. - * @return mixed the current item - */ - public function current() - { - return $this->_d[$this->_i]; - } - - /** - * Moves the internal pointer to the next item. - * This method is required by the SPL interface `Iterator`. - */ - public function next() - { - $this->_i++; - } - - /** - * Returns a value indicating whether there is an item at current position. - * This method is required by the SPL interface `Iterator`. - * @return boolean whether there is an item at current position. - */ - public function valid() - { - return $this->_i < $this->_c; - } -} diff --git a/yii/base/View.php b/yii/base/View.php index 67519c0..a00b22c 100644 --- a/yii/base/View.php +++ b/yii/base/View.php @@ -11,6 +11,9 @@ use Yii; use yii\base\Application; use yii\helpers\FileHelper; use yii\helpers\Html; +use yii\widgets\Block; +use yii\widgets\ContentDecorator; +use yii\widgets\FragmentCache; /** * View represents a view object in the MVC pattern. @@ -108,12 +111,6 @@ class View extends Component */ public $blocks; /** - * @var Widget[] the widgets that are currently being rendered (not ended). This property - * is maintained by [[beginWidget()]] and [[endWidget()]] methods. Do not modify it directly. - * @internal - */ - public $widgetStack = array(); - /** * @var array a list of currently active fragment cache widgets. This property * is used internally to implement the content caching feature. Do not modify it directly. * @internal @@ -174,6 +171,9 @@ class View extends Component { parent::init(); if (is_array($this->theme)) { + if (!isset($this->theme['class'])) { + $this->theme['class'] = 'yii\base\Theme'; + } $this->theme = Yii::createObject($this->theme); } } @@ -364,93 +364,19 @@ class View extends Component } /** - * Creates a widget. - * This method will use [[Yii::createObject()]] to create the widget. - * @param string $class the widget class name or path alias - * @param array $properties the initial property values of the widget. - * @return Widget the newly created widget instance - */ - public function createWidget($class, $properties = array()) - { - $properties['class'] = $class; - if (!isset($properties['view'])) { - $properties['view'] = $this; - } - return Yii::createObject($properties); - } - - /** - * Creates and runs a widget. - * Compared with [[createWidget()]], this method does one more thing: it will - * run the widget after it is created. - * @param string $class the widget class name or path alias - * @param array $properties the initial property values of the widget. - * @param boolean $captureOutput whether to capture the output of the widget and return it as a string - * @return string|Widget if $captureOutput is true, the output of the widget will be returned; - * otherwise the widget object will be returned. - */ - public function widget($class, $properties = array(), $captureOutput = false) - { - if ($captureOutput) { - ob_start(); - ob_implicit_flush(false); - $widget = $this->createWidget($class, $properties); - $widget->run(); - return ob_get_clean(); - } else { - $widget = $this->createWidget($class, $properties); - $widget->run(); - return $widget; - } - } - - /** - * Begins a widget. - * This method is similar to [[createWidget()]] except that it will expect a matching - * [[endWidget()]] call after this. - * @param string $class the widget class name or path alias - * @param array $properties the initial property values of the widget. - * @return Widget the widget instance - */ - public function beginWidget($class, $properties = array()) - { - $widget = $this->createWidget($class, $properties); - $this->widgetStack[] = $widget; - return $widget; - } - - /** - * Ends a widget. - * Note that the rendering result of the widget is directly echoed out. - * If you want to capture the rendering result of a widget, you may use - * [[createWidget()]] and [[Widget::run()]]. - * @return Widget the widget instance - * @throws InvalidCallException if [[beginWidget()]] and [[endWidget()]] calls are not properly nested - */ - public function endWidget() - { - $widget = array_pop($this->widgetStack); - if ($widget instanceof Widget) { - $widget->run(); - return $widget; - } else { - throw new InvalidCallException("Unmatched beginWidget() and endWidget() calls."); - } - } - - /** * Begins recording a block. - * This method is a shortcut to beginning [[yii\widgets\Block]] + * This method is a shortcut to beginning [[Block]] * @param string $id the block ID. * @param boolean $renderInPlace whether to render the block content in place. * Defaults to false, meaning the captured block will not be displayed. - * @return \yii\widgets\Block the Block widget instance + * @return Block the Block widget instance */ public function beginBlock($id, $renderInPlace = false) { - return $this->beginWidget('yii\widgets\Block', array( + return Block::begin(array( 'id' => $id, 'renderInPlace' => $renderInPlace, + 'view' => $this, )); } @@ -459,7 +385,7 @@ class View extends Component */ public function endBlock() { - $this->endWidget(); + Block::end(); } /** @@ -476,14 +402,15 @@ class View extends Component * @param string $viewFile the view file that will be used to decorate the content enclosed by this widget. * This can be specified as either the view file path or path alias. * @param array $params the variables (name => value) to be extracted and made available in the decorative view. - * @return \yii\widgets\ContentDecorator the ContentDecorator widget instance - * @see \yii\widgets\ContentDecorator + * @return ContentDecorator the ContentDecorator widget instance + * @see ContentDecorator */ public function beginContent($viewFile, $params = array()) { - return $this->beginWidget('yii\widgets\ContentDecorator', array( + return ContentDecorator::begin(array( 'viewFile' => $viewFile, 'params' => $params, + 'view' => $this, )); } @@ -492,7 +419,7 @@ class View extends Component */ public function endContent() { - $this->endWidget(); + ContentDecorator::end(); } /** @@ -510,15 +437,16 @@ class View extends Component * ~~~ * * @param string $id a unique ID identifying the fragment to be cached. - * @param array $properties initial property values for [[\yii\widgets\FragmentCache]] + * @param array $properties initial property values for [[FragmentCache]] * @return boolean whether you should generate the content for caching. * False if the cached version is available. */ public function beginCache($id, $properties = array()) { $properties['id'] = $id; - /** @var $cache \yii\widgets\FragmentCache */ - $cache = $this->beginWidget('yii\widgets\FragmentCache', $properties); + $properties['view'] = $this; + /** @var $cache FragmentCache */ + $cache = FragmentCache::begin($properties); if ($cache->getCachedContent() !== false) { $this->endCache(); return false; @@ -532,7 +460,7 @@ class View extends Component */ public function endCache() { - $this->endWidget(); + FragmentCache::end(); } diff --git a/yii/base/Widget.php b/yii/base/Widget.php index c0c524f..0c948e9 100644 --- a/yii/base/Widget.php +++ b/yii/base/Widget.php @@ -8,7 +8,6 @@ namespace yii\base; use Yii; -use yii\helpers\FileHelper; /** * Widget is the base class for widgets. @@ -19,20 +18,74 @@ use yii\helpers\FileHelper; class Widget extends Component { /** - * @var View the view object that is used to create this widget. - * This property is automatically set by [[View::createWidget()]]. - * This property is required by [[render()]] and [[renderFile()]]. + * @var integer a counter used to generate [[id]] for widgets. + * @internal */ - public $view; + public static $_counter = 0; /** - * @var string id of the widget. + * @var Widget[] the widgets that are currently being rendered (not ended). This property + * is maintained by [[begin()]] and [[end()]] methods. + * @internal */ - private $_id; + public static $_stack = array(); + + + /** + * Begins a widget. + * This method creates an instance of the calling class. It will apply the configuration + * to the created instance. A matching [[end()]] call should be called later. + * @param array $config name-value pairs that will be used to initialize the object properties + * @return Widget the newly created widget instance + */ + public static function begin($config = array()) + { + $config['class'] = get_called_class(); + /** @var Widget $widget */ + $widget = Yii::createObject($config); + self::$_stack[] = $widget; + return $widget; + } + + /** + * Ends a widget. + * Note that the rendering result of the widget is directly echoed out. + * @return Widget the widget instance that is ended. + * @throws InvalidCallException if [[begin()]] and [[end()]] calls are not properly nested + */ + public static function end() + { + if (!empty(self::$_stack)) { + $widget = array_pop(self::$_stack); + if (get_class($widget) === get_called_class()) { + $widget->run(); + return $widget; + } else { + throw new InvalidCallException("Expecting end() of " . get_class($widget) . ", found " . get_called_class()); + } + } else { + throw new InvalidCallException("Unexpected " . get_called_class() . '::end() call. A matching begin() is not found.'); + } + } + /** - * @var integer a counter used to generate IDs for widgets. + * Creates a widget instance and runs it. + * The widget rendering result is returned by this method. + * @param array $config name-value pairs that will be used to initialize the object properties + * @return string the rendering result of the widget. */ - private static $_counter = 0; + public static function widget($config = array()) + { + ob_start(); + ob_implicit_flush(false); + /** @var Widget $widget */ + $config['class'] = get_called_class(); + $widget = Yii::createObject($config); + $widget->run(); + return ob_get_clean(); + } + private $_id; + /** * Returns the ID of the widget. * @param boolean $autoGenerate whether to generate an ID if it is not set previously @@ -55,6 +108,32 @@ class Widget extends Component $this->_id = $value; } + private $_view; + + /** + * Returns the view object that can be used to render views or view files. + * The [[render()]] and [[renderFile()]] methods will use + * this view object to implement the actual view rendering. + * If not set, it will default to the "view" application component. + * @return View the view object that can be used to render views or view files. + */ + public function getView() + { + if ($this->_view === null) { + $this->_view = Yii::$app->getView(); + } + return $this->_view; + } + + /** + * Sets the view object to be used by this widget. + * @param View $view the view object that can be used to render views or view files. + */ + public function setView($view) + { + $this->_view = $view; + } + /** * Executes the widget. */ @@ -84,7 +163,7 @@ class Widget extends Component public function render($view, $params = array()) { $viewFile = $this->findViewFile($view); - return $this->view->renderFile($viewFile, $params, $this); + return $this->getView()->renderFile($viewFile, $params, $this); } /** @@ -96,7 +175,7 @@ class Widget extends Component */ public function renderFile($file, $params = array()) { - return $this->view->renderFile($file, $params, $this); + return $this->getView()->renderFile($file, $params, $this); } /** diff --git a/yii/behaviors/AutoTimestamp.php b/yii/behaviors/AutoTimestamp.php new file mode 100644 index 0000000..944ee05 --- /dev/null +++ b/yii/behaviors/AutoTimestamp.php @@ -0,0 +1,105 @@ + array( + * 'class' => 'yii\behaviors\AutoTimestamp', + * ), + * ); + * } + * ~~~ + * + * By default, the attribute for keeping the creation time is named as "create_time", and the attribute + * for updating time is "update_time". You may customize the names via [[createAttribute]] and [[updateAttribute]]. + * + * @author Qiang Xue + * @since 2.0 + */ +class AutoTimestamp extends Behavior +{ + /** + * @var string The name of the attribute to store the creation time. Set to null to not + * use a timestamp for the creation attribute. Defaults to 'create_time' + */ + public $createAttribute = 'create_time'; + /** + * @var string The name of the attribute to store the modification time. Set to null to not + * use a timestamp for the update attribute. Defaults to 'update_time' + */ + public $updateAttribute = 'update_time'; + /** + * @var \Closure|Expression The expression that will be used for generating the timestamp. + * This can be either an anonymous function that returns the timestamp value, + * or an [[Expression]] object representing a DB expression (e.g. `new Expression('NOW()')`). + * If not set, it will use the value of `time()` to fill the attributes. + */ + public $timestamp; + + + /** + * Declares event handlers for the [[owner]]'s events. + * @return array events (array keys) and the corresponding event handler methods (array values). + */ + public function events() + { + return array( + ActiveRecord::EVENT_BEFORE_INSERT => 'beforeInsert', + ActiveRecord::EVENT_BEFORE_UPDATE => 'beforeUpdate', + ); + } + + /** + * This is the event handler for the "beforeInsert" event of the associated AR object. + */ + public function beforeInsert() + { + if ($this->createAttribute !== null) { + $this->owner->{$this->createAttribute} = $this->evaluateTimestamp($this->createAttribute); + } + } + + /** + * This is the event handler for the "beforeUpdate" event of the associated AR object. + */ + public function beforeUpdate() + { + if ($this->updateAttribute !== null) { + $this->owner->{$this->updateAttribute} = $this->evaluateTimestamp($this->updateAttribute); + } + } + + /** + * Gets the appropriate timestamp depending on the column type $attribute is + * @param string $attribute attribute name + * @return mixed the timestamp value + */ + protected function evaluateTimestamp($attribute) + { + if ($this->timestamp instanceof Expression) { + return $this->timestamp; + } elseif ($this->timestamp !== null) { + return call_user_func($this->timestamp); + } else { + return time(); + } + } +} diff --git a/yii/console/Application.php b/yii/console/Application.php index 2f28cac..31580e8 100644 --- a/yii/console/Application.php +++ b/yii/console/Application.php @@ -30,7 +30,7 @@ use yii\base\InvalidRouteException; * To run the console application, enter the following on the command line: * * ~~~ - * yiic [--param1=value1 --param2 ...] + * yii [--param1=value1 --param2 ...] * ~~~ * * where `` refers to a controller route in the form of `ModuleID/ControllerID/ActionID` @@ -42,7 +42,7 @@ use yii\base\InvalidRouteException; * To use this command, simply type: * * ~~~ - * yiic help + * yii help * ~~~ * * @author Qiang Xue @@ -94,7 +94,7 @@ class Application extends \yii\base\Application list ($route, $params) = $request->resolve(); return $this->runAction($route, $params); } else { - throw new Exception(\Yii::t('yii|This script must be run from the command line.')); + throw new Exception(\Yii::t('yii', 'This script must be run from the command line.')); } } @@ -113,7 +113,7 @@ class Application extends \yii\base\Application try { return parent::runAction($route, $params); } catch (InvalidRouteException $e) { - throw new Exception(\Yii::t('yii|Unknown command "{command}".', array('{command}' => $route))); + throw new Exception(\Yii::t('yii', 'Unknown command "{command}".', array('{command}' => $route))); } } diff --git a/yii/console/Controller.php b/yii/console/Controller.php index c07d92d..fe32daa 100644 --- a/yii/console/Controller.php +++ b/yii/console/Controller.php @@ -18,10 +18,10 @@ use yii\helpers\Console; * * A controller consists of one or several actions known as sub-commands. * Users call a console command by specifying the corresponding route which identifies a controller action. - * The `yiic` program is used when calling a console command, like the following: + * The `yii` program is used when calling a console command, like the following: * * ~~~ - * yiic [--param1=value1 --param2 ...] + * yii [--param1=value1 --param2 ...] * ~~~ * * @author Qiang Xue @@ -91,7 +91,7 @@ class Controller extends \yii\base\Controller $args = isset($params[Request::ANONYMOUS_PARAMS]) ? $params[Request::ANONYMOUS_PARAMS] : array(); unset($params[Request::ANONYMOUS_PARAMS]); if (!empty($params)) { - throw new Exception(Yii::t('yii|Unknown options: {params}', array( + throw new Exception(Yii::t('yii', 'Unknown options: {params}', array( '{params}' => implode(', ', array_keys($params)), ))); } @@ -115,7 +115,7 @@ class Controller extends \yii\base\Controller } if (!empty($missing)) { - throw new Exception(Yii::t('yii|Missing required arguments: {params}', array( + throw new Exception(Yii::t('yii', 'Missing required arguments: {params}', array( '{params}' => implode(', ', $missing), ))); } diff --git a/yii/console/Exception.php b/yii/console/Exception.php index cb10c19..9e9003e 100644 --- a/yii/console/Exception.php +++ b/yii/console/Exception.php @@ -22,7 +22,7 @@ class Exception extends UserException */ public function getName() { - return \Yii::t('yii|Error'); + return \Yii::t('yii', 'Error'); } } diff --git a/yii/console/Request.php b/yii/console/Request.php index ed477e9..d1a6aa6 100644 --- a/yii/console/Request.php +++ b/yii/console/Request.php @@ -27,7 +27,7 @@ class Request extends \yii\base\Request public function resolve() { $rawParams = $this->getRawParams(); - array_shift($rawParams); // the 1st argument is the yiic script name + array_shift($rawParams); // the 1st argument is the yii script name if (isset($rawParams[0])) { $route = $rawParams[0]; diff --git a/yii/console/controllers/AssetController.php b/yii/console/controllers/AssetController.php index aab489b..14bc2da 100644 --- a/yii/console/controllers/AssetController.php +++ b/yii/console/controllers/AssetController.php @@ -261,7 +261,7 @@ class AssetController extends Controller file_put_contents($bundleFile, <<forward('help/index', array('-args' => array('cache/flush'))); + $caches = array(); + $components = Yii::$app->getComponents(); + foreach ($components as $name => $component) { + if ($component instanceof Cache) { + $caches[$name] = get_class($component); + } elseif (is_array($component) && isset($component['class']) && strpos($component['class'], 'Cache') !== false) { + $caches[$name] = $component['class']; + } + } + if (!empty($caches)) { + echo "The following caches can be flushed:\n\n"; + foreach ($caches as $name => $class) { + echo " * $name: $class\n"; + } + } else { + echo "No cache is used.\n"; + } } /** @@ -33,7 +53,7 @@ class CacheController extends Controller public function actionFlush($component = 'cache') { /** @var $cache Cache */ - $cache = \Yii::$app->getComponent($component); + $cache = Yii::$app->getComponent($component); if (!$cache || !$cache instanceof Cache) { throw new Exception('Application component "'.$component.'" is not defined or not a cache.'); } diff --git a/yii/console/controllers/HelpController.php b/yii/console/controllers/HelpController.php index 6a66fd0..c40ea66 100644 --- a/yii/console/controllers/HelpController.php +++ b/yii/console/controllers/HelpController.php @@ -25,7 +25,7 @@ use yii\helpers\StringHelper; * This command can be used as follows on command line: * * ~~~ - * yiic help [command name] + * yii help [command name] * ~~~ * * In the above, if the command name is not provided, all @@ -41,8 +41,8 @@ class HelpController extends Controller * about a particular command. For example, * * ~~~ - * yiic help # list available commands - * yiic help message # display help info about "message" + * yii help # list available commands + * yii help message # display help info about "message" * ~~~ * * @param string $command The name of the command to show help about. @@ -55,7 +55,7 @@ class HelpController extends Controller if ($command !== null) { $result = Yii::$app->createController($command); if ($result === false) { - throw new Exception(Yii::t('yii|No help for unknown command "{command}".', array( + throw new Exception(Yii::t('yii', 'No help for unknown command "{command}".', array( '{command}' => $command, ))); } @@ -148,7 +148,7 @@ class HelpController extends Controller echo "* $command\n"; } echo "\nTo see the help of each command, enter:\n"; - echo "\n yiic help \n\n"; + echo "\n yii help \n\n"; } else { echo "\nNo commands are found.\n"; } @@ -188,7 +188,7 @@ class HelpController extends Controller echo "\n"; } echo "\n\nTo see the detailed information about individual sub-commands, enter:\n"; - echo "\n yiic help \n\n"; + echo "\n yii help \n\n"; } } @@ -239,7 +239,7 @@ class HelpController extends Controller { $action = $controller->createAction($actionID); if ($action === null) { - throw new Exception(Yii::t('yii|No help for unknown sub-command "{command}".', array( + throw new Exception(Yii::t('yii', 'No help for unknown sub-command "{command}".', array( '{command}' => rtrim($controller->getUniqueId() . '/' . $actionID, '/'), ))); } @@ -259,9 +259,9 @@ class HelpController extends Controller echo "\nUSAGE\n\n"; if ($action->id === $controller->defaultAction) { - echo 'yiic ' . $controller->getUniqueId(); + echo 'yii ' . $controller->getUniqueId(); } else { - echo "yiic " . $action->getUniqueId(); + echo "yii " . $action->getUniqueId(); } list ($required, $optional) = $this->getArgHelps($method, isset($tags['param']) ? $tags['param'] : array()); if (!empty($required)) { diff --git a/yii/console/controllers/MessageController.php b/yii/console/controllers/MessageController.php index 0ad0486..418062a 100644 --- a/yii/console/controllers/MessageController.php +++ b/yii/console/controllers/MessageController.php @@ -194,7 +194,7 @@ class MessageController extends Controller /** * Message translations. * - * This file is automatically generated by 'yiic message' command. + * This file is automatically generated by 'yii message' command. * It contains the localizable messages extracted from source code. * You may modify this file by translating the extracted messages. * diff --git a/yii/console/controllers/MigrateController.php b/yii/console/controllers/MigrateController.php index fb06c66..0acc672 100644 --- a/yii/console/controllers/MigrateController.php +++ b/yii/console/controllers/MigrateController.php @@ -42,13 +42,13 @@ use yii\helpers\ArrayHelper; * * ~~~ * # creates a new migration named 'create_user_table' - * yiic migrate/create create_user_table + * yii migrate/create create_user_table * * # applies ALL new migrations - * yiic migrate + * yii migrate * * # reverts the last applied migration - * yiic migrate/down + * yii migrate/down * ~~~ * * @author Qiang Xue @@ -135,8 +135,8 @@ class MigrateController extends Controller * For example, * * ~~~ - * yiic migrate # apply all new migrations - * yiic migrate 3 # apply the first 3 new migrations + * yii migrate # apply all new migrations + * yii migrate 3 # apply the first 3 new migrations * ~~~ * * @param integer $limit the number of new migrations to be applied. If 0, it means @@ -184,8 +184,8 @@ class MigrateController extends Controller * For example, * * ~~~ - * yiic migrate/down # revert the last migration - * yiic migrate/down 3 # revert the last 3 migrations + * yii migrate/down # revert the last migration + * yii migrate/down 3 # revert the last 3 migrations * ~~~ * * @param integer $limit the number of migrations to be reverted. Defaults to 1, @@ -231,8 +231,8 @@ class MigrateController extends Controller * them again. For example, * * ~~~ - * yiic migrate/redo # redo the last applied migration - * yiic migrate/redo 3 # redo the last 3 applied migrations + * yii migrate/redo # redo the last applied migration + * yii migrate/redo 3 # redo the last 3 applied migrations * ~~~ * * @param integer $limit the number of migrations to be redone. Defaults to 1, @@ -284,8 +284,8 @@ class MigrateController extends Controller * them again. For example, * * ~~~ - * yiic migrate/to 101129_185401 # using timestamp - * yiic migrate/to m101129_185401_create_user_table # using full name + * yii migrate/to 101129_185401 # using timestamp + * yii migrate/to m101129_185401_create_user_table # using full name * ~~~ * * @param string $version the version name that the application should be migrated to. @@ -332,8 +332,8 @@ class MigrateController extends Controller * No actual migration will be performed. * * ~~~ - * yiic migrate/mark 101129_185401 # using timestamp - * yiic migrate/mark m101129_185401_create_user_table # using full name + * yii migrate/mark 101129_185401 # using timestamp + * yii migrate/mark m101129_185401_create_user_table # using full name * ~~~ * * @param string $version the version at which the migration history should be marked. @@ -398,9 +398,9 @@ class MigrateController extends Controller * so far. For example, * * ~~~ - * yiic migrate/history # showing the last 10 migrations - * yiic migrate/history 5 # showing the last 5 migrations - * yiic migrate/history 0 # showing the whole history + * yii migrate/history # showing the last 10 migrations + * yii migrate/history 5 # showing the last 5 migrations + * yii migrate/history 0 # showing the whole history * ~~~ * * @param integer $limit the maximum number of migrations to be displayed. @@ -432,9 +432,9 @@ class MigrateController extends Controller * For example, * * ~~~ - * yiic migrate/new # showing the first 10 new migrations - * yiic migrate/new 5 # showing the first 5 new migrations - * yiic migrate/new 0 # showing all new migrations + * yii migrate/new # showing the first 10 new migrations + * yii migrate/new 5 # showing the first 5 new migrations + * yii migrate/new 0 # showing all new migrations * ~~~ * * @param integer $limit the maximum number of new migrations to be displayed. @@ -469,7 +469,7 @@ class MigrateController extends Controller * skeleton by filling up the actual migration logic. * * ~~~ - * yiic migrate/create create_user_table + * yii migrate/create create_user_table * ~~~ * * @param string $name the name of the new migration. This should only contain diff --git a/yii/db/ActiveRecord.php b/yii/db/ActiveRecord.php index 41ce84c..2b7d2b8 100644 --- a/yii/db/ActiveRecord.php +++ b/yii/db/ActiveRecord.php @@ -74,6 +74,22 @@ class ActiveRecord extends Model const EVENT_AFTER_DELETE = 'afterDelete'; /** + * Represents insert ActiveRecord operation. This constant is used for specifying set of atomic operations + * for particular scenario in the [[scenarios()]] method. + */ + const OP_INSERT = 'insert'; + /** + * Represents update ActiveRecord operation. This constant is used for specifying set of atomic operations + * for particular scenario in the [[scenarios()]] method. + */ + const OP_UPDATE = 'update'; + /** + * Represents delete ActiveRecord operation. This constant is used for specifying set of atomic operations + * for particular scenario in the [[scenarios()]] method. + */ + const OP_DELETE = 'delete'; + + /** * @var array attribute values indexed by attribute names */ private $_attributes = array(); @@ -664,10 +680,39 @@ class ActiveRecord extends Model * @param array $attributes list of attributes that need to be saved. Defaults to null, * meaning all attributes that are loaded from DB will be saved. * @return boolean whether the attributes are valid and the record is inserted successfully. + * @throws \Exception in case insert failed. */ public function insert($runValidation = true, $attributes = null) { - if ($runValidation && !$this->validate($attributes) || !$this->beforeSave(true)) { + if ($runValidation && !$this->validate($attributes)) { + return false; + } + $db = static::getDb(); + $transaction = $this->isOperationAtomic(self::OP_INSERT) && $db->getTransaction() === null ? $db->beginTransaction() : null; + try { + $result = $this->insertInternal($attributes); + if ($transaction !== null) { + if ($result === false) { + $transaction->rollback(); + } else { + $transaction->commit(); + } + } + } catch (\Exception $e) { + if ($transaction !== null) { + $transaction->rollback(); + } + throw $e; + } + return $result; + } + + /** + * @see ActiveRecord::insert() + */ + private function insertInternal($attributes = null) + { + if (!$this->beforeSave(true)) { return false; } $values = $this->getDirtyAttributes($attributes); @@ -678,22 +723,23 @@ class ActiveRecord extends Model } $db = static::getDb(); $command = $db->createCommand()->insert($this->tableName(), $values); - if ($command->execute()) { - $table = $this->getTableSchema(); - if ($table->sequenceName !== null) { - foreach ($table->primaryKey as $name) { - if (!isset($this->_attributes[$name])) { - $this->_oldAttributes[$name] = $this->_attributes[$name] = $db->getLastInsertID($table->sequenceName); - break; - } + if (!$command->execute()) { + return false; + } + $table = $this->getTableSchema(); + if ($table->sequenceName !== null) { + foreach ($table->primaryKey as $name) { + if (!isset($this->_attributes[$name])) { + $this->_oldAttributes[$name] = $this->_attributes[$name] = $db->getLastInsertID($table->sequenceName); + break; } } - foreach ($values as $name => $value) { - $this->_oldAttributes[$name] = $value; - } - $this->afterSave(true); - return true; } + foreach ($values as $name => $value) { + $this->_oldAttributes[$name] = $value; + } + $this->afterSave(true); + return true; } /** @@ -744,39 +790,67 @@ class ActiveRecord extends Model * or [[beforeSave()]] stops the updating process. * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data * being updated is outdated. + * @throws \Exception in case update failed. */ public function update($runValidation = true, $attributes = null) { - if ($runValidation && !$this->validate($attributes) || !$this->beforeSave(false)) { + if ($runValidation && !$this->validate($attributes)) { return false; } - $values = $this->getDirtyAttributes($attributes); - if (!empty($values)) { - $condition = $this->getOldPrimaryKey(true); - $lock = $this->optimisticLock(); - if ($lock !== null) { - if (!isset($values[$lock])) { - $values[$lock] = $this->$lock + 1; + $db = static::getDb(); + $transaction = $this->isOperationAtomic(self::OP_UPDATE) && $db->getTransaction() === null ? $db->beginTransaction() : null; + try { + $result = $this->updateInternal($attributes); + if ($transaction !== null) { + if ($result === false) { + $transaction->rollback(); + } else { + $transaction->commit(); } - $condition[$lock] = $this->$lock; } - // We do not check the return value of updateAll() because it's possible - // that the UPDATE statement doesn't change anything and thus returns 0. - $rows = $this->updateAll($values, $condition); - - if ($lock !== null && !$rows) { - throw new StaleObjectException('The object being updated is outdated.'); + } catch (\Exception $e) { + if ($transaction !== null) { + $transaction->rollback(); } + throw $e; + } + return $result; + } - foreach ($values as $name => $value) { - $this->_oldAttributes[$name] = $this->_attributes[$name]; + /** + * @see CActiveRecord::update() + * @throws StaleObjectException + */ + private function updateInternal($attributes = null) + { + if (!$this->beforeSave(false)) { + return false; + } + $values = $this->getDirtyAttributes($attributes); + if (empty($values)) { + return 0; + } + $condition = $this->getOldPrimaryKey(true); + $lock = $this->optimisticLock(); + if ($lock !== null) { + if (!isset($values[$lock])) { + $values[$lock] = $this->$lock + 1; } + $condition[$lock] = $this->$lock; + } + // We do not check the return value of updateAll() because it's possible + // that the UPDATE statement doesn't change anything and thus returns 0. + $rows = $this->updateAll($values, $condition); - $this->afterSave(false); - return $rows; - } else { - return 0; + if ($lock !== null && !$rows) { + throw new StaleObjectException('The object being updated is outdated.'); + } + + foreach ($values as $name => $value) { + $this->_oldAttributes[$name] = $this->_attributes[$name]; } + $this->afterSave(false); + return $rows; } /** @@ -826,27 +900,43 @@ class ActiveRecord extends Model * Note that it is possible the number of rows deleted is 0, even though the deletion execution is successful. * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data * being deleted is outdated. + * @throws \Exception in case delete failed. */ public function delete() { - if ($this->beforeDelete()) { - // we do not check the return value of deleteAll() because it's possible - // the record is already deleted in the database and thus the method will return 0 - $condition = $this->getOldPrimaryKey(true); - $lock = $this->optimisticLock(); - if ($lock !== null) { - $condition[$lock] = $this->$lock; + $db = static::getDb(); + $transaction = $this->isOperationAtomic(self::OP_DELETE) && $db->getTransaction() === null ? $db->beginTransaction() : null; + try { + $result = false; + if ($this->beforeDelete()) { + // we do not check the return value of deleteAll() because it's possible + // the record is already deleted in the database and thus the method will return 0 + $condition = $this->getOldPrimaryKey(true); + $lock = $this->optimisticLock(); + if ($lock !== null) { + $condition[$lock] = $this->$lock; + } + $result = $this->deleteAll($condition); + if ($lock !== null && !$result) { + throw new StaleObjectException('The object being deleted is outdated.'); + } + $this->_oldAttributes = null; + $this->afterDelete(); } - $rows = $this->deleteAll($condition); - if ($lock !== null && !$rows) { - throw new StaleObjectException('The object being deleted is outdated.'); + if ($transaction !== null) { + if ($result === false) { + $transaction->rollback(); + } else { + $transaction->commit(); + } } - $this->_oldAttributes = null; - $this->afterDelete(); - return $rows; - } else { - return false; + } catch (\Exception $e) { + if ($transaction !== null) { + $transaction->rollback(); + } + throw $e; } + return $result; } /** @@ -1336,4 +1426,19 @@ class ActiveRecord extends Model } return true; } + + /** + * @param string $operation possible values are ActiveRecord::INSERT, ActiveRecord::UPDATE and ActiveRecord::DELETE. + * @return boolean whether given operation is atomic. Currently active scenario is taken into account. + */ + private function isOperationAtomic($operation) + { + $scenario = $this->getScenario(); + $scenarios = $this->scenarios(); + if (isset($scenarios[$scenario], $scenario[$scenario]['atomic']) && is_array($scenarios[$scenario]['atomic'])) { + return in_array($operation, $scenarios[$scenario]['atomic']); + } else { + return false; + } + } } diff --git a/yii/db/Connection.php b/yii/db/Connection.php index c1e65f9..5aa222a 100644 --- a/yii/db/Connection.php +++ b/yii/db/Connection.php @@ -7,6 +7,8 @@ namespace yii\db; +use PDO; +use Yii; use yii\base\Component; use yii\base\InvalidConfigException; use yii\base\NotSupportedException; @@ -128,7 +130,7 @@ class Connection extends Component */ public $attributes; /** - * @var \PDO the PHP PDO instance associated with this DB connection. + * @var PDO the PHP PDO instance associated with this DB connection. * This property is mainly managed by [[open()]] and [[close()]] methods. * When a DB connection is active, this property will represent a PDO instance; * otherwise, it will be null. @@ -229,7 +231,7 @@ class Connection extends Component /** * @var array mapping between PDO driver names and [[Schema]] classes. * The keys of the array are PDO driver names while the values the corresponding - * schema class name or configuration. Please refer to [[\Yii::createObject()]] for + * schema class name or configuration. Please refer to [[Yii::createObject()]] for * details on how to specify a configuration. * * This property is mainly used by [[getSchema()]] when fetching the database schema information. @@ -313,12 +315,12 @@ class Connection extends Component throw new InvalidConfigException('Connection::dsn cannot be empty.'); } try { - \Yii::trace('Opening DB connection: ' . $this->dsn, __METHOD__); + Yii::trace('Opening DB connection: ' . $this->dsn, __METHOD__); $this->pdo = $this->createPdoInstance(); $this->initConnection(); } catch (\PDOException $e) { - \Yii::error("Failed to open DB connection ({$this->dsn}): " . $e->getMessage(), __METHOD__); + Yii::error("Failed to open DB connection ({$this->dsn}): " . $e->getMessage(), __METHOD__); $message = YII_DEBUG ? 'Failed to open DB connection: ' . $e->getMessage() : 'Failed to open DB connection.'; throw new Exception($message, $e->errorInfo, (int)$e->getCode()); } @@ -332,7 +334,7 @@ class Connection extends Component public function close() { if ($this->pdo !== null) { - \Yii::trace('Closing DB connection: ' . $this->dsn, __METHOD__); + Yii::trace('Closing DB connection: ' . $this->dsn, __METHOD__); $this->pdo = null; $this->_schema = null; $this->_transaction = null; @@ -344,11 +346,11 @@ class Connection extends Component * This method is called by [[open]] to establish a DB connection. * The default implementation will create a PHP PDO instance. * You may override this method if the default PDO needs to be adapted for certain DBMS. - * @return \PDO the pdo instance + * @return PDO the pdo instance */ protected function createPdoInstance() { - $pdoClass = '\PDO'; + $pdoClass = 'PDO'; if (($pos = strpos($this->dsn, ':')) !== false) { $driver = strtolower(substr($this->dsn, 0, $pos)); if ($driver === 'mssql' || $driver === 'dblib' || $driver === 'sqlsrv') { @@ -367,9 +369,9 @@ class Connection extends Component */ protected function initConnection() { - $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); - if ($this->emulatePrepare !== null && constant('\PDO::ATTR_EMULATE_PREPARES')) { - $this->pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, $this->emulatePrepare); + $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + if ($this->emulatePrepare !== null && constant('PDO::ATTR_EMULATE_PREPARES')) { + $this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, $this->emulatePrepare); } if ($this->charset !== null && in_array($this->getDriverName(), array('pgsql', 'mysql', 'mysqli'))) { $this->pdo->exec('SET NAMES ' . $this->pdo->quote($this->charset)); @@ -428,7 +430,7 @@ class Connection extends Component } else { $driver = $this->getDriverName(); if (isset($this->schemaMap[$driver])) { - $this->_schema = \Yii::createObject($this->schemaMap[$driver]); + $this->_schema = Yii::createObject($this->schemaMap[$driver]); $this->_schema->db = $this; return $this->_schema; } else { @@ -536,7 +538,7 @@ class Connection extends Component if (($pos = strpos($this->dsn, ':')) !== false) { return strtolower(substr($this->dsn, 0, $pos)); } else { - return strtolower($this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME)); + return strtolower($this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME)); } } @@ -551,7 +553,7 @@ class Connection extends Component */ public function getQuerySummary() { - $logger = \Yii::getLogger(); + $logger = Yii::getLogger(); $timings = $logger->getProfiling(array('yii\db\Command::query', 'yii\db\Command::execute')); $count = count($timings); $time = 0; diff --git a/yii/db/Exception.php b/yii/db/Exception.php index b7a60b4..9339211 100644 --- a/yii/db/Exception.php +++ b/yii/db/Exception.php @@ -39,6 +39,6 @@ class Exception extends \yii\base\Exception */ public function getName() { - return \Yii::t('yii|Database Exception'); + return \Yii::t('yii', 'Database Exception'); } } diff --git a/yii/db/Migration.php b/yii/db/Migration.php index 51e230b..774ac14 100644 --- a/yii/db/Migration.php +++ b/yii/db/Migration.php @@ -10,14 +10,14 @@ namespace yii\db; /** * Migration is the base class for representing a database migration. * - * Migration is designed to be used together with the "yiic migrate" command. + * Migration is designed to be used together with the "yii migrate" command. * * Each child class of Migration represents an individual database migration which * is identified by the child class name. * * Within each migration, the [[up()]] method should be overwritten to contain the logic * for "upgrading" the database; while the [[down()]] method for the "downgrading" - * logic. The "yiic migrate" command manages all available migrations in an application. + * logic. The "yii migrate" command manages all available migrations in an application. * * If the database supports transactions, you may also overwrite [[safeUp()]] and * [[safeDown()]] so that if anything wrong happens during the upgrading or downgrading, diff --git a/yii/db/StaleObjectException.php b/yii/db/StaleObjectException.php index 0a04bd3..dc88ceb 100644 --- a/yii/db/StaleObjectException.php +++ b/yii/db/StaleObjectException.php @@ -18,6 +18,6 @@ class StaleObjectException extends Exception */ public function getName() { - return \Yii::t('yii|Stale Object Exception'); + return \Yii::t('yii', 'Stale Object Exception'); } } diff --git a/yii/debug/Toolbar.php b/yii/debug/Toolbar.php index 84b55c8..c205277 100644 --- a/yii/debug/Toolbar.php +++ b/yii/debug/Toolbar.php @@ -17,18 +17,18 @@ use yii\helpers\Html; */ class Toolbar extends Widget { - public $debugAction = 'debug'; - public $enabled = YII_DEBUG; + public $debugAction = 'debug/default/toolbar'; public function run() { - if ($this->enabled) { + if (Yii::$app->hasModule('debug')) { $id = 'yii-debug-toolbar'; $url = Yii::$app->getUrlManager()->createUrl($this->debugAction, array( 'tag' => Yii::getLogger()->tag, )); - $this->view->registerJs("yii.debug.load('$id', '$url');"); - $this->view->registerAssetBundle('yii/debug'); + $view = $this->getView(); + $view->registerJs("yii.debug.load('$id', '$url');"); + $view->registerAssetBundle('yii/debug'); echo Html::tag('div', '', array( 'id' => $id, 'style' => 'display: none', diff --git a/yii/debug/controllers/DefaultController.php b/yii/debug/controllers/DefaultController.php index ca90920..4d686ee 100644 --- a/yii/debug/controllers/DefaultController.php +++ b/yii/debug/controllers/DefaultController.php @@ -7,6 +7,7 @@ namespace yii\debug\controllers; +use Yii; use yii\web\Controller; /** @@ -19,4 +20,15 @@ class DefaultController extends Controller { echo $tag; } + + public function actionToolbar($tag) + { + $file = Yii::$app->getRuntimePath() . "/debug/$tag.log"; + if (preg_match('/^[\w\-]+$/', $tag) && is_file($file)) { + $data = json_decode(file_get_contents($file), true); + echo $this->renderPartial('toolbar', $data); + } else { + echo "Unable to find debug data tagged with '$tag'."; + } + } } \ No newline at end of file diff --git a/yii/debug/views/default/toolbar.php b/yii/debug/views/default/toolbar.php new file mode 100644 index 0000000..0b08d4b --- /dev/null +++ b/yii/debug/views/default/toolbar.php @@ -0,0 +1,39 @@ + + +
+
+ +
+Peak memory: +
+ +
+Time spent: +
+ +
+
+ +
+
+ diff --git a/yii/helpers/base/ArrayHelper.php b/yii/helpers/base/ArrayHelper.php index c70e63d..cee39bc 100644 --- a/yii/helpers/base/ArrayHelper.php +++ b/yii/helpers/base/ArrayHelper.php @@ -263,8 +263,8 @@ class ArrayHelper * elements, a property name of the objects, or an anonymous function returning the values for comparison * purpose. The anonymous function signature should be: `function($item)`. * To sort by multiple keys, provide an array of keys here. - * @param boolean|array $ascending whether to sort in ascending or descending order. When - * sorting by multiple keys with different ascending orders, use an array of ascending flags. + * @param boolean|array $descending whether to sort in descending or ascending order. When + * sorting by multiple keys with different descending orders, use an array of descending flags. * @param integer|array $sortFlag the PHP sort flag. Valid values include * `SORT_REGULAR`, `SORT_NUMERIC`, `SORT_STRING` and `SORT_LOCALE_STRING`. * Please refer to [PHP manual](http://php.net/manual/en/function.sort.php) @@ -272,20 +272,20 @@ class ArrayHelper * @param boolean|array $caseSensitive whether to sort string in case-sensitive manner. This parameter * is used only when `$sortFlag` is `SORT_STRING`. * When sorting by multiple keys with different case sensitivities, use an array of boolean values. - * @throws InvalidParamException if the $ascending or $sortFlag parameters do not have + * @throws InvalidParamException if the $descending or $sortFlag parameters do not have * correct number of elements as that of $key. */ - public static function multisort(&$array, $key, $ascending = true, $sortFlag = SORT_REGULAR, $caseSensitive = true) + public static function multisort(&$array, $key, $descending = false, $sortFlag = SORT_REGULAR, $caseSensitive = true) { $keys = is_array($key) ? $key : array($key); if (empty($keys) || empty($array)) { return; } $n = count($keys); - if (is_scalar($ascending)) { - $ascending = array_fill(0, $n, $ascending); - } elseif (count($ascending) !== $n) { - throw new InvalidParamException('The length of $ascending parameter must be the same as that of $keys.'); + if (is_scalar($descending)) { + $descending = array_fill(0, $n, $descending); + } elseif (count($descending) !== $n) { + throw new InvalidParamException('The length of $descending parameter must be the same as that of $keys.'); } if (is_scalar($sortFlag)) { $sortFlag = array_fill(0, $n, $sortFlag); @@ -315,7 +315,7 @@ class ArrayHelper } else { $args[] = static::getColumn($array, $key); } - $args[] = $ascending[$i] ? SORT_ASC : SORT_DESC; + $args[] = $descending[$i] ? SORT_DESC : SORT_ASC; $args[] = $flag; } $args[] = &$array; diff --git a/yii/i18n/I18N.php b/yii/i18n/I18N.php index 1477fd4..a358683 100644 --- a/yii/i18n/I18N.php +++ b/yii/i18n/I18N.php @@ -75,26 +75,19 @@ class I18N extends Component * Translates a message to the specified language. * If the first parameter in `$params` is a number and it is indexed by 0, appropriate plural rules * will be applied to the translated message. + * @param string $category the message category. * @param string $message the message to be translated. * @param array $params the parameters that will be used to replace the corresponding placeholders in the message. * @param string $language the language code (e.g. `en_US`, `en`). If this is null, the current * [[\yii\base\Application::language|application language]] will be used. * @return string the translated message. */ - public function translate($message, $params = array(), $language = null) + public function translate($category, $message, $params = array(), $language = null) { if ($language === null) { $language = Yii::$app->language; } - // allow chars for category: word chars, ".", "-", "/", "\" - if (strpos($message, '|') !== false && preg_match('/^([\w\-\\/\.\\\\]+)\|(.*)/', $message, $matches)) { - $category = $matches[1]; - $message = $matches[2]; - } else { - $category = 'app'; - } - $message = $this->getMessageSource($category)->translate($category, $message, $language); if (!is_array($params)) { diff --git a/yii/i18n/data/plurals.php b/yii/i18n/data/plurals.php index 468f7e2..cb51307 100644 --- a/yii/i18n/data/plurals.php +++ b/yii/i18n/data/plurals.php @@ -2,7 +2,7 @@ /** * Plural rules. * - * This file is automatically generated by the "yiic locale/plural" command under the "build" folder. + * This file is automatically generated by the "yii locale/plural" command under the "build" folder. * Do not modify it directly. * * The original plural rule data used for generating this file has the following copyright terms: @@ -16,7 +16,7 @@ * @license http://www.yiiframework.com/license/ */ return array ( - 'ar' => + 'ar' => array ( 0 => '$n==0', 1 => '$n==1', @@ -24,569 +24,569 @@ return array ( 3 => 'in_array(fmod($n,100),range(3,10))', 4 => 'in_array(fmod($n,100),range(11,99))', ), - 'asa' => + 'asa' => array ( 0 => '$n==1', ), - 'af' => + 'af' => array ( 0 => '$n==1', ), - 'bem' => + 'bem' => array ( 0 => '$n==1', ), - 'bez' => + 'bez' => array ( 0 => '$n==1', ), - 'bg' => + 'bg' => array ( 0 => '$n==1', ), - 'bn' => + 'bn' => array ( 0 => '$n==1', ), - 'brx' => + 'brx' => array ( 0 => '$n==1', ), - 'ca' => + 'ca' => array ( 0 => '$n==1', ), - 'cgg' => + 'cgg' => array ( 0 => '$n==1', ), - 'chr' => + 'chr' => array ( 0 => '$n==1', ), - 'da' => + 'da' => array ( 0 => '$n==1', ), - 'de' => + 'de' => array ( 0 => '$n==1', ), - 'dv' => + 'dv' => array ( 0 => '$n==1', ), - 'ee' => + 'ee' => array ( 0 => '$n==1', ), - 'el' => + 'el' => array ( 0 => '$n==1', ), - 'en' => + 'en' => array ( 0 => '$n==1', ), - 'eo' => + 'eo' => array ( 0 => '$n==1', ), - 'es' => + 'es' => array ( 0 => '$n==1', ), - 'et' => + 'et' => array ( 0 => '$n==1', ), - 'eu' => + 'eu' => array ( 0 => '$n==1', ), - 'fi' => + 'fi' => array ( 0 => '$n==1', ), - 'fo' => + 'fo' => array ( 0 => '$n==1', ), - 'fur' => + 'fur' => array ( 0 => '$n==1', ), - 'fy' => + 'fy' => array ( 0 => '$n==1', ), - 'gl' => + 'gl' => array ( 0 => '$n==1', ), - 'gsw' => + 'gsw' => array ( 0 => '$n==1', ), - 'gu' => + 'gu' => array ( 0 => '$n==1', ), - 'ha' => + 'ha' => array ( 0 => '$n==1', ), - 'haw' => + 'haw' => array ( 0 => '$n==1', ), - 'he' => + 'he' => array ( 0 => '$n==1', ), - 'is' => + 'is' => array ( 0 => '$n==1', ), - 'it' => + 'it' => array ( 0 => '$n==1', ), - 'jmc' => + 'jmc' => array ( 0 => '$n==1', ), - 'kaj' => + 'kaj' => array ( 0 => '$n==1', ), - 'kcg' => + 'kcg' => array ( 0 => '$n==1', ), - 'kk' => + 'kk' => array ( 0 => '$n==1', ), - 'kl' => + 'kl' => array ( 0 => '$n==1', ), - 'ksb' => + 'ksb' => array ( 0 => '$n==1', ), - 'ku' => + 'ku' => array ( 0 => '$n==1', ), - 'lb' => + 'lb' => array ( 0 => '$n==1', ), - 'lg' => + 'lg' => array ( 0 => '$n==1', ), - 'mas' => + 'mas' => array ( 0 => '$n==1', ), - 'ml' => + 'ml' => array ( 0 => '$n==1', ), - 'mn' => + 'mn' => array ( 0 => '$n==1', ), - 'mr' => + 'mr' => array ( 0 => '$n==1', ), - 'nah' => + 'nah' => array ( 0 => '$n==1', ), - 'nb' => + 'nb' => array ( 0 => '$n==1', ), - 'nd' => + 'nd' => array ( 0 => '$n==1', ), - 'ne' => + 'ne' => array ( 0 => '$n==1', ), - 'nl' => + 'nl' => array ( 0 => '$n==1', ), - 'nn' => + 'nn' => array ( 0 => '$n==1', ), - 'no' => + 'no' => array ( 0 => '$n==1', ), - 'nr' => + 'nr' => array ( 0 => '$n==1', ), - 'ny' => + 'ny' => array ( 0 => '$n==1', ), - 'nyn' => + 'nyn' => array ( 0 => '$n==1', ), - 'om' => + 'om' => array ( 0 => '$n==1', ), - 'or' => + 'or' => array ( 0 => '$n==1', ), - 'pa' => + 'pa' => array ( 0 => '$n==1', ), - 'pap' => + 'pap' => array ( 0 => '$n==1', ), - 'ps' => + 'ps' => array ( 0 => '$n==1', ), - 'pt' => + 'pt' => array ( 0 => '$n==1', ), - 'rof' => + 'rof' => array ( 0 => '$n==1', ), - 'rm' => + 'rm' => array ( 0 => '$n==1', ), - 'rwk' => + 'rwk' => array ( 0 => '$n==1', ), - 'saq' => + 'saq' => array ( 0 => '$n==1', ), - 'seh' => + 'seh' => array ( 0 => '$n==1', ), - 'sn' => + 'sn' => array ( 0 => '$n==1', ), - 'so' => + 'so' => array ( 0 => '$n==1', ), - 'sq' => + 'sq' => array ( 0 => '$n==1', ), - 'ss' => + 'ss' => array ( 0 => '$n==1', ), - 'ssy' => + 'ssy' => array ( 0 => '$n==1', ), - 'st' => + 'st' => array ( 0 => '$n==1', ), - 'sv' => + 'sv' => array ( 0 => '$n==1', ), - 'sw' => + 'sw' => array ( 0 => '$n==1', ), - 'syr' => + 'syr' => array ( 0 => '$n==1', ), - 'ta' => + 'ta' => array ( 0 => '$n==1', ), - 'te' => + 'te' => array ( 0 => '$n==1', ), - 'teo' => + 'teo' => array ( 0 => '$n==1', ), - 'tig' => + 'tig' => array ( 0 => '$n==1', ), - 'tk' => + 'tk' => array ( 0 => '$n==1', ), - 'tn' => + 'tn' => array ( 0 => '$n==1', ), - 'ts' => + 'ts' => array ( 0 => '$n==1', ), - 'ur' => + 'ur' => array ( 0 => '$n==1', ), - 'wae' => + 'wae' => array ( 0 => '$n==1', ), - 've' => + 've' => array ( 0 => '$n==1', ), - 'vun' => + 'vun' => array ( 0 => '$n==1', ), - 'xh' => + 'xh' => array ( 0 => '$n==1', ), - 'xog' => + 'xog' => array ( 0 => '$n==1', ), - 'zu' => + 'zu' => array ( 0 => '$n==1', ), - 'ak' => + 'ak' => array ( 0 => '($n==0||$n==1)', ), - 'am' => + 'am' => array ( 0 => '($n==0||$n==1)', ), - 'bh' => + 'bh' => array ( 0 => '($n==0||$n==1)', ), - 'fil' => + 'fil' => array ( 0 => '($n==0||$n==1)', ), - 'tl' => + 'tl' => array ( 0 => '($n==0||$n==1)', ), - 'guw' => + 'guw' => array ( 0 => '($n==0||$n==1)', ), - 'hi' => + 'hi' => array ( 0 => '($n==0||$n==1)', ), - 'ln' => + 'ln' => array ( 0 => '($n==0||$n==1)', ), - 'mg' => + 'mg' => array ( 0 => '($n==0||$n==1)', ), - 'nso' => + 'nso' => array ( 0 => '($n==0||$n==1)', ), - 'ti' => + 'ti' => array ( 0 => '($n==0||$n==1)', ), - 'wa' => + 'wa' => array ( 0 => '($n==0||$n==1)', ), - 'ff' => + 'ff' => array ( 0 => '($n>=0&&$n<=2)&&$n!=2', ), - 'fr' => + 'fr' => array ( 0 => '($n>=0&&$n<=2)&&$n!=2', ), - 'kab' => + 'kab' => array ( 0 => '($n>=0&&$n<=2)&&$n!=2', ), - 'lv' => + 'lv' => array ( 0 => '$n==0', 1 => 'fmod($n,10)==1&&fmod($n,100)!=11', ), - 'iu' => + 'iu' => array ( 0 => '$n==1', 1 => '$n==2', ), - 'kw' => + 'kw' => array ( 0 => '$n==1', 1 => '$n==2', ), - 'naq' => + 'naq' => array ( 0 => '$n==1', 1 => '$n==2', ), - 'se' => + 'se' => array ( 0 => '$n==1', 1 => '$n==2', ), - 'sma' => + 'sma' => array ( 0 => '$n==1', 1 => '$n==2', ), - 'smi' => + 'smi' => array ( 0 => '$n==1', 1 => '$n==2', ), - 'smj' => + 'smj' => array ( 0 => '$n==1', 1 => '$n==2', ), - 'smn' => + 'smn' => array ( 0 => '$n==1', 1 => '$n==2', ), - 'sms' => + 'sms' => array ( 0 => '$n==1', 1 => '$n==2', ), - 'ga' => + 'ga' => array ( 0 => '$n==1', 1 => '$n==2', 2 => 'in_array($n,array(3,4,5,6))', 3 => 'in_array($n,array(7,8,9,10))', ), - 'ro' => + 'ro' => array ( 0 => '$n==1', 1 => '$n==0||$n!=1&&in_array(fmod($n,100),range(1,19))', ), - 'mo' => + 'mo' => array ( 0 => '$n==1', 1 => '$n==0||$n!=1&&in_array(fmod($n,100),range(1,19))', ), - 'lt' => + 'lt' => array ( 0 => 'fmod($n,10)==1&&!in_array(fmod($n,100),range(11,19))', 1 => 'in_array(fmod($n,10),range(2,9))&&!in_array(fmod($n,100),range(11,19))', ), - 'be' => + 'be' => array ( 0 => 'fmod($n,10)==1&&fmod($n,100)!=11', 1 => 'in_array(fmod($n,10),array(2,3,4))&&!in_array(fmod($n,100),array(12,13,14))', 2 => 'fmod($n,10)==0||in_array(fmod($n,10),array(5,6,7,8,9))||in_array(fmod($n,100),array(11,12,13,14))', ), - 'bs' => + 'bs' => array ( 0 => 'fmod($n,10)==1&&fmod($n,100)!=11', 1 => 'in_array(fmod($n,10),array(2,3,4))&&!in_array(fmod($n,100),array(12,13,14))', 2 => 'fmod($n,10)==0||in_array(fmod($n,10),array(5,6,7,8,9))||in_array(fmod($n,100),array(11,12,13,14))', ), - 'hr' => + 'hr' => array ( 0 => 'fmod($n,10)==1&&fmod($n,100)!=11', 1 => 'in_array(fmod($n,10),array(2,3,4))&&!in_array(fmod($n,100),array(12,13,14))', 2 => 'fmod($n,10)==0||in_array(fmod($n,10),array(5,6,7,8,9))||in_array(fmod($n,100),array(11,12,13,14))', ), - 'ru' => + 'ru' => array ( 0 => 'fmod($n,10)==1&&fmod($n,100)!=11', 1 => 'in_array(fmod($n,10),array(2,3,4))&&!in_array(fmod($n,100),array(12,13,14))', 2 => 'fmod($n,10)==0||in_array(fmod($n,10),array(5,6,7,8,9))||in_array(fmod($n,100),array(11,12,13,14))', ), - 'sh' => + 'sh' => array ( 0 => 'fmod($n,10)==1&&fmod($n,100)!=11', 1 => 'in_array(fmod($n,10),array(2,3,4))&&!in_array(fmod($n,100),array(12,13,14))', 2 => 'fmod($n,10)==0||in_array(fmod($n,10),array(5,6,7,8,9))||in_array(fmod($n,100),array(11,12,13,14))', ), - 'sr' => + 'sr' => array ( 0 => 'fmod($n,10)==1&&fmod($n,100)!=11', 1 => 'in_array(fmod($n,10),array(2,3,4))&&!in_array(fmod($n,100),array(12,13,14))', 2 => 'fmod($n,10)==0||in_array(fmod($n,10),array(5,6,7,8,9))||in_array(fmod($n,100),array(11,12,13,14))', ), - 'uk' => + 'uk' => array ( 0 => 'fmod($n,10)==1&&fmod($n,100)!=11', 1 => 'in_array(fmod($n,10),array(2,3,4))&&!in_array(fmod($n,100),array(12,13,14))', 2 => 'fmod($n,10)==0||in_array(fmod($n,10),array(5,6,7,8,9))||in_array(fmod($n,100),array(11,12,13,14))', ), - 'cs' => + 'cs' => array ( 0 => '$n==1', 1 => 'in_array($n,array(2,3,4))', ), - 'sk' => + 'sk' => array ( 0 => '$n==1', 1 => 'in_array($n,array(2,3,4))', ), - 'pl' => + 'pl' => array ( 0 => '$n==1', 1 => 'in_array(fmod($n,10),array(2,3,4))&&!in_array(fmod($n,100),array(12,13,14))', 2 => '$n!=1&&in_array(fmod($n,10),array(0,1))||in_array(fmod($n,10),array(5,6,7,8,9))||in_array(fmod($n,100),array(12,13,14))', ), - 'sl' => + 'sl' => array ( 0 => 'fmod($n,100)==1', 1 => 'fmod($n,100)==2', 2 => 'in_array(fmod($n,100),array(3,4))', ), - 'mt' => + 'mt' => array ( 0 => '$n==1', 1 => '$n==0||in_array(fmod($n,100),range(2,10))', 2 => 'in_array(fmod($n,100),range(11,19))', ), - 'mk' => + 'mk' => array ( 0 => 'fmod($n,10)==1&&$n!=11', ), - 'cy' => + 'cy' => array ( 0 => '$n==0', 1 => '$n==1', @@ -594,33 +594,33 @@ return array ( 3 => '$n==3', 4 => '$n==6', ), - 'lag' => + 'lag' => array ( 0 => '$n==0', 1 => '($n>=0&&$n<=2)&&$n!=0&&$n!=2', ), - 'shi' => + 'shi' => array ( 0 => '($n>=0&&$n<=1)', 1 => 'in_array($n,range(2,10))', ), - 'br' => + 'br' => array ( 0 => 'fmod($n,10)==1&&!in_array(fmod($n,100),array(11,71,91))', 1 => 'fmod($n,10)==2&&!in_array(fmod($n,100),array(12,72,92))', 2 => 'in_array(fmod($n,10),array(3,4,9))&&!in_array(fmod($n,100),array_merge(range(10,19),range(70,79),range(90,99)))', 3 => 'fmod($n,1000000)==0&&$n!=0', ), - 'ksh' => + 'ksh' => array ( 0 => '$n==0', 1 => '$n==1', ), - 'tzm' => + 'tzm' => array ( 0 => '($n==0||$n==1)||in_array($n,range(11,99))', ), - 'gv' => + 'gv' => array ( 0 => 'in_array(fmod($n,10),array(1,2))||fmod($n,20)==0', ), diff --git a/yii/logging/DebugTarget.php b/yii/logging/DebugTarget.php new file mode 100644 index 0000000..92a74d6 --- /dev/null +++ b/yii/logging/DebugTarget.php @@ -0,0 +1,91 @@ + + * @since 2.0 + */ +class DebugTarget extends Target +{ + public $maxLogFiles = 20; + + /** + * Exports log messages to a specific destination. + * Child classes must implement this method. + * @param array $messages the messages to be exported. See [[Logger::messages]] for the structure + * of each message. + */ + public function export($messages) + { + $path = Yii::$app->getRuntimePath() . '/debug'; + if (!is_dir($path)) { + mkdir($path); + } + $file = $path . '/' . Yii::getLogger()->getTag() . '.log'; + $data = array( + 'messages' => $messages, + '_SERVER' => $_SERVER, + '_GET' => $_GET, + '_POST' => $_POST, + '_COOKIE' => $_COOKIE, + '_FILES' => empty($_FILES) ? array() : $_FILES, + '_SESSION' => empty($_SESSION) ? array() : $_SESSION, + 'memory' => memory_get_peak_usage(), + 'time' => microtime(true) - YII_BEGIN_TIME, + ); + file_put_contents($file, json_encode($data)); + } + + /** + * Processes the given log messages. + * This method will filter the given messages with [[levels]] and [[categories]]. + * And if requested, it will also export the filtering result to specific medium (e.g. email). + * @param array $messages log messages to be processed. See [[Logger::messages]] for the structure + * of each message. + * @param boolean $final whether this method is called at the end of the current application + */ + public function collect($messages, $final) + { + if (Yii::$app->getModule('debug', false) !== null) { + return; + } + $this->messages = array_merge($this->messages, $this->filterMessages($messages)); + if ($final) { + $this->export($this->messages); + $this->gc(); + } + } + + protected function gc() + { + if (mt_rand(0, 10000) > 100) { + return; + } + $iterator = new \DirectoryIterator(Yii::$app->getRuntimePath() . '/debug'); + $files = array(); + foreach ($iterator as $file) { + if (preg_match('/^[\d\-]+\.log$/', $file->getFileName()) && $file->isFile()) { + $files[] = $file->getPathname(); + } + } + sort($files); + if (count($files) > $this->maxLogFiles) { + $n = count($files) - $this->maxLogFiles; + foreach ($files as $i => $file) { + if ($i < $n) { + unlink($file); + } else { + break; + } + } + } + } +} diff --git a/yii/logging/EmailTarget.php b/yii/logging/EmailTarget.php index bb02e34..94e2c00 100644 --- a/yii/logging/EmailTarget.php +++ b/yii/logging/EmailTarget.php @@ -48,7 +48,7 @@ class EmailTarget extends Target $body .= $this->formatMessage($message); } $body = wordwrap($body, 70); - $subject = $this->subject === null ? \Yii::t('yii|Application Log') : $this->subject; + $subject = $this->subject === null ? \Yii::t('yii', 'Application Log') : $this->subject; foreach ($this->emails as $email) { $this->sendEmail($subject, $body, $email, $this->sentFrom, $this->headers); } diff --git a/yii/logging/Logger.php b/yii/logging/Logger.php index b557c5e..788d946 100644 --- a/yii/logging/Logger.php +++ b/yii/logging/Logger.php @@ -82,11 +82,13 @@ class Logger extends Component * @var Router the log target router registered with this logger. */ public $router; + + /** - * @var string a tag that uniquely identifies the current request. This can be used - * to differentiate the log messages for different requests. + * @var string */ - public $tag; + private $_tag; + /** * Initializes the logger by registering [[flush()]] as a shutdown function. @@ -94,7 +96,6 @@ class Logger extends Component public function init() { parent::init(); - $this->tag = date('Ymd-His', microtime(true)); register_shutdown_function(array($this, 'flush'), true); } @@ -143,6 +144,25 @@ class Logger extends Component } /** + * @return string a tag that uniquely identifies the current request. + */ + public function getTag() + { + if ($this->_tag === null) { + $this->_tag = date('Ymd-His', microtime(true)); + } + return $this->_tag; + } + + /** + * @param string $tag a tag that uniquely identifies the current request. + */ + public function setTag($tag) + { + $this->_tag = $tag; + } + + /** * Returns the total elapsed time since the start of the current request. * This method calculates the difference between now and the timestamp * defined by constant `YII_BEGIN_TIME` which is evaluated at the beginning diff --git a/yii/logging/ProfileTarget.php b/yii/logging/ProfileTarget.php index 75447c9..f4522ac 100644 --- a/yii/logging/ProfileTarget.php +++ b/yii/logging/ProfileTarget.php @@ -59,7 +59,7 @@ class CProfileLogRoute extends CWebLogRoute if ($value === 'summary' || $value === 'callstack') $this->_report = $value; else - throw new CException(Yii::t('yii|CProfileLogRoute.report "{report}" is invalid. Valid values include "summary" and "callstack".', + throw new CException(Yii::t('yii', 'CProfileLogRoute.report "{report}" is invalid. Valid values include "summary" and "callstack".', array('{report}' => $value))); } @@ -106,7 +106,7 @@ class CProfileLogRoute extends CWebLogRoute $results[$last[4]] = array($token, $delta, count($stack)); } else { - throw new CException(Yii::t('yii|CProfileLogRoute found a mismatching code block "{token}". Make sure the calls to Yii::beginProfile() and Yii::endProfile() be properly nested.', + throw new CException(Yii::t('yii', 'CProfileLogRoute found a mismatching code block "{token}". Make sure the calls to Yii::beginProfile() and Yii::endProfile() be properly nested.', array('{token}' => $token))); } } @@ -149,7 +149,7 @@ class CProfileLogRoute extends CWebLogRoute else $results[$token] = array($token, 1, $delta, $delta, $delta); } else - throw new CException(Yii::t('yii|CProfileLogRoute found a mismatching code block "{token}". Make sure the calls to Yii::beginProfile() and Yii::endProfile() be properly nested.', + throw new CException(Yii::t('yii', 'CProfileLogRoute found a mismatching code block "{token}". Make sure the calls to Yii::beginProfile() and Yii::endProfile() be properly nested.', array('{token}' => $token))); } } diff --git a/yii/logging/Target.php b/yii/logging/Target.php index 311334d..fac8b53 100644 --- a/yii/logging/Target.php +++ b/yii/logging/Target.php @@ -7,6 +7,8 @@ namespace yii\logging; +use Yii; +use yii\base\Component; use yii\base\InvalidConfigException; /** @@ -25,7 +27,7 @@ use yii\base\InvalidConfigException; * @author Qiang Xue * @since 2.0 */ -abstract class Target extends \yii\base\Component +abstract class Target extends Component { /** * @var boolean whether to enable this log target. Defaults to true. @@ -67,7 +69,7 @@ abstract class Target extends \yii\base\Component /** * @var array the messages that are retrieved from the logger so far by this log target. */ - private $_messages = array(); + public $messages = array(); private $_levels = 0; @@ -89,14 +91,14 @@ abstract class Target extends \yii\base\Component */ public function collect($messages, $final) { - $this->_messages = array_merge($this->_messages, $this->filterMessages($messages)); - $count = count($this->_messages); + $this->messages = array_merge($this->messages, $this->filterMessages($messages)); + $count = count($this->messages); if ($count > 0 && ($final || $this->exportInterval > 0 && $count >= $this->exportInterval)) { if (($context = $this->getContextMessage()) !== '') { - $this->_messages[] = array($context, Logger::LEVEL_INFO, 'application', YII_BEGIN_TIME); + $this->messages[] = array($context, Logger::LEVEL_INFO, 'application', YII_BEGIN_TIME); } - $this->export($this->_messages); - $this->_messages = array(); + $this->export($this->messages); + $this->messages = array(); } } @@ -108,8 +110,9 @@ abstract class Target extends \yii\base\Component protected function getContextMessage() { $context = array(); - if ($this->logUser && ($user = \Yii::$app->getComponent('user', false)) !== null) { - $context[] = 'User: ' . $user->getName() . ' (ID: ' . $user->getId() . ')'; + if ($this->logUser && ($user = Yii::$app->getComponent('user', false)) !== null) { + /** @var $user \yii\web\User */ + $context[] = 'User: ' . $user->getId(); } foreach ($this->logVars as $name) { diff --git a/yii/logging/WebTarget.php b/yii/logging/WebTarget.php deleted file mode 100644 index c98fd9f..0000000 --- a/yii/logging/WebTarget.php +++ /dev/null @@ -1,61 +0,0 @@ - - * @since 2.0 - */ -class CWebLogRoute extends CLogRoute -{ - /** - * @var boolean whether the log should be displayed in FireBug instead of browser window. Defaults to false. - */ - public $showInFireBug = false; - - /** - * @var boolean whether the log should be ignored in FireBug for ajax calls. Defaults to true. - * This option should be used carefully, because an ajax call returns all output as a result data. - * For example if the ajax call expects a json type result any output from the logger will cause ajax call to fail. - */ - public $ignoreAjaxInFireBug = true; - - /** - * Displays the log messages. - * @param array $logs list of log messages - */ - public function processLogs($logs) - { - $this->render('log', $logs); - } - - /** - * Renders the view. - * @param string $view the view name (file name without extension). The file is assumed to be located under framework/data/views. - * @param array $data data to be passed to the view - */ - protected function render($view, $data) - { - $app = \Yii::$app; - $isAjax = $app->getRequest()->getIsAjaxRequest(); - - if ($this->showInFireBug) - { - if ($isAjax && $this->ignoreAjaxInFireBug) - return; - $view .= '-firebug'; - } elseif (!($app instanceof CWebApplication) || $isAjax) - return; - - $viewFile = YII_PATH . DIRECTORY_SEPARATOR . 'views' . DIRECTORY_SEPARATOR . $view . '.php'; - include($app->findLocalizedFile($viewFile, 'en')); - } -} diff --git a/yii/rbac/Assignment.php b/yii/rbac/Assignment.php index b3aa74f..065aaa5 100644 --- a/yii/rbac/Assignment.php +++ b/yii/rbac/Assignment.php @@ -16,97 +16,40 @@ use yii\base\Object; * Do not create a Assignment instance using the 'new' operator. * Instead, call [[Manager::assign()]]. * - * @property mixed $userId User ID (see [[User::id]]). - * @property string $itemName The authorization item name. - * @property string $bizRule The business rule associated with this assignment. - * @property mixed $data Additional data for this assignment. - * * @author Qiang Xue * @author Alexander Kochetov * @since 2.0 */ class Assignment extends Object { - private $_auth; - private $_userId; - private $_itemName; - private $_bizRule; - private $_data; - - /** - * Constructor. - * @param Manager $auth the authorization manager - * @param mixed $userId user ID (see [[User::id]]) - * @param string $itemName authorization item name - * @param string $bizRule the business rule associated with this assignment - * @param mixed $data additional data for this assignment - */ - public function __construct($auth, $userId, $itemName, $bizRule = null, $data = null) - { - $this->_auth = $auth; - $this->_userId = $userId; - $this->_itemName = $itemName; - $this->_bizRule = $bizRule; - $this->_data = $data; - } - /** - * @return mixed user ID (see [[User::id]]) + * @var Manager the auth manager of this item */ - public function getUserId() - { - return $this->_userId; - } - - /** - * @return string the authorization item name - */ - public function getItemName() - { - return $this->_itemName; - } - + public $manager; /** - * @return string the business rule associated with this assignment + * @var string the business rule associated with this assignment */ - public function getBizRule() - { - return $this->_bizRule; - } - + public $bizRule; /** - * @param string $value the business rule associated with this assignment + * @var mixed additional data for this assignment */ - public function setBizRule($value) - { - if ($this->_bizRule !== $value) { - $this->_bizRule = $value; - } - } - + public $data; /** - * @return mixed additional data for this assignment + * @var mixed user ID (see [[User::id]]). Do not modify this property after it is populated. + * To modify the user ID of an assignment, you must remove the assignment and create a new one. */ - public function getData() - { - return $this->_data; - } - + public $userId; /** - * @param mixed $value additional data for this assignment + * @return string the authorization item name. Do not modify this property after it is populated. + * To modify the item name of an assignment, you must remove the assignment and create a new one. */ - public function setData($value) - { - if ($this->_data !== $value) { - $this->_data = $value; - } - } + public $itemName; /** * Saves the changes to an authorization assignment. */ public function save() { - $this->_auth->saveAssignment($this); + $this->manager->saveAssignment($this); } } diff --git a/yii/rbac/DbManager.php b/yii/rbac/DbManager.php index 28b14d9..719ffa8 100644 --- a/yii/rbac/DbManager.php +++ b/yii/rbac/DbManager.php @@ -24,8 +24,6 @@ use yii\base\InvalidParamException; * the three tables used to store the authorization data by setting [[itemTable]], * [[itemChildTable]] and [[assignmentTable]]. * - * @property array $authItems The authorization items of the specific type. - * * @author Qiang Xue * @author Alexander Kochetov * @since 2.0 @@ -106,13 +104,13 @@ class DbManager extends Manager if (!isset($params['userId'])) { $params['userId'] = $userId; } - if ($this->executeBizRule($item->getBizRule(), $params, $item->getData())) { + if ($this->executeBizRule($item->bizRule, $params, $item->data)) { if (in_array($itemName, $this->defaultRoles)) { return true; } if (isset($assignments[$itemName])) { $assignment = $assignments[$itemName]; - if ($this->executeBizRule($assignment->getBizRule(), $params, $assignment->getData())) { + if ($this->executeBizRule($assignment->bizRule, $params, $assignment->data)) { return true; } } @@ -207,7 +205,7 @@ class DbManager extends Manager public function getItemChildren($names) { $query = new Query; - $rows = $query->select(array('name', 'type', 'description', 'bizrule', 'data')) + $rows = $query->select(array('name', 'type', 'description', 'biz_rule', 'data')) ->from(array($this->itemTable, $this->itemChildTable)) ->where(array('parent' => $names, 'name' => new Expression('child'))) ->createCommand($this->db) @@ -217,7 +215,14 @@ class DbManager extends Manager if (($data = @unserialize($row['data'])) === false) { $data = null; } - $children[$row['name']] = new Item($this, $row['name'], $row['type'], $row['description'], $row['bizrule'], $data); + $children[$row['name']] = new Item(array( + 'manager' => $this, + 'name' => $row['name'], + 'type' => $row['type'], + 'description' => $row['description'], + 'bizRule' => $row['biz_rule'], + 'data' => $data, + )); } return $children; } @@ -241,10 +246,16 @@ class DbManager extends Manager ->insert($this->assignmentTable, array( 'user_id' => $userId, 'item_name' => $itemName, - 'bizrule' => $bizRule, + 'biz_rule' => $bizRule, 'data' => serialize($data), )); - return new Assignment($this, $userId, $itemName, $bizRule, $data); + return new Assignment(array( + 'manager' => $this, + 'userId' => $userId, + 'itemName' => $itemName, + 'bizRule' => $bizRule, + 'data' => $data, + )); } /** @@ -293,7 +304,13 @@ class DbManager extends Manager if (($data = @unserialize($row['data'])) === false) { $data = null; } - return new Assignment($this, $row['user_id'], $row['item_name'], $row['bizrule'], $data); + return new Assignment(array( + 'manager' => $this, + 'userId' => $row['user_id'], + 'itemName' => $row['item_name'], + 'bizRule' => $row['biz_rule'], + 'data' => $data, + )); } else { return null; } @@ -317,7 +334,13 @@ class DbManager extends Manager if (($data = @unserialize($row['data'])) === false) { $data = null; } - $assignments[$row['item_name']] = new Assignment($this, $row['user_id'], $row['item_name'], $row['bizrule'], $data); + $assignments[$row['item_name']] = new Assignment(array( + 'manager' => $this, + 'userId' => $row['user_id'], + 'itemName' => $row['item_name'], + 'bizRule' => $row['biz_rule'], + 'data' => $data, + )); } return $assignments; } @@ -330,11 +353,11 @@ class DbManager extends Manager { $this->db->createCommand() ->update($this->assignmentTable, array( - 'bizrule' => $assignment->getBizRule(), - 'data' => serialize($assignment->getData()), + 'biz_rule' => $assignment->bizRule, + 'data' => serialize($assignment->data), ), array( - 'user_id' => $assignment->getUserId(), - 'item_name' => $assignment->getItemName(), + 'user_id' => $assignment->userId, + 'item_name' => $assignment->itemName, )); } @@ -357,12 +380,12 @@ class DbManager extends Manager ->where(array('type' => $type)) ->createCommand($this->db); } elseif ($type === null) { - $command = $query->select(array('name', 'type', 'description', 't1.bizrule', 't1.data')) + $command = $query->select(array('name', 'type', 'description', 't1.biz_rule', 't1.data')) ->from(array($this->itemTable . ' t1', $this->assignmentTable . ' t2')) ->where(array('user_id' => $userId, 'name' => new Expression('item_name'))) ->createCommand($this->db); } else { - $command = $query->select('name', 'type', 'description', 't1.bizrule', 't1.data') + $command = $query->select('name', 'type', 'description', 't1.biz_rule', 't1.data') ->from(array($this->itemTable . ' t1', $this->assignmentTable . ' t2')) ->where(array('user_id' => $userId, 'type' => $type, 'name' => new Expression('item_name'))) ->createCommand($this->db); @@ -372,7 +395,14 @@ class DbManager extends Manager if (($data = @unserialize($row['data'])) === false) { $data = null; } - $items[$row['name']] = new Item($this, $row['name'], $row['type'], $row['description'], $row['bizrule'], $data); + $items[$row['name']] = new Item(array( + 'manager' => $this, + 'name' => $row['name'], + 'type' => $row['type'], + 'description' => $row['description'], + 'bizRule' => $row['biz_rule'], + 'data' => $data, + )); } return $items; } @@ -399,10 +429,17 @@ class DbManager extends Manager 'name' => $name, 'type' => $type, 'description' => $description, - 'bizrule' => $bizRule, + 'biz_rule' => $bizRule, 'data' => serialize($data), )); - return new Item($this, $name, $type, $description, $bizRule, $data); + return new Item(array( + 'manager' => $this, + 'name' => $name, + 'type' => $type, + 'description' => $description, + 'bizRule' => $bizRule, + 'data' => $data, + )); } /** @@ -439,7 +476,14 @@ class DbManager extends Manager if (($data = @unserialize($row['data'])) === false) { $data = null; } - return new Item($this, $row['name'], $row['type'], $row['description'], $row['bizrule'], $data); + return new Item(array( + 'manager' => $this, + 'name' => $row['name'], + 'type' => $row['type'], + 'description' => $row['description'], + 'bizRule' => $row['biz_rule'], + 'data' => $data, + )); } else return null; } @@ -463,10 +507,10 @@ class DbManager extends Manager $this->db->createCommand() ->update($this->itemTable, array( 'name' => $item->getName(), - 'type' => $item->getType(), - 'description' => $item->getDescription(), - 'bizrule' => $item->getBizRule(), - 'data' => serialize($item->getData()), + 'type' => $item->type, + 'description' => $item->description, + 'biz_rule' => $item->bizRule, + 'data' => serialize($item->data), ), array( 'name' => $oldName === null ? $item->getName() : $oldName, )); diff --git a/yii/rbac/Item.php b/yii/rbac/Item.php index 57a7f7e..2504ad5 100644 --- a/yii/rbac/Item.php +++ b/yii/rbac/Item.php @@ -18,14 +18,6 @@ use yii\base\Object; * A user may be assigned one or several authorization items (called [[Assignment]] assignments). * He can perform an operation only when it is among his assigned items. * - * @property Manager $authManager The authorization manager. - * @property integer $type The authorization item type. This could be 0 (operation), 1 (task) or 2 (role). - * @property string $name The item name. - * @property string $description The item description. - * @property string $bizRule The business rule associated with this item. - * @property mixed $data The additional data associated with this item. - * @property array $children All child items of this item. - * * @author Qiang Xue * @author Alexander Kochetov * @since 2.0 @@ -36,32 +28,30 @@ class Item extends Object const TYPE_TASK = 1; const TYPE_ROLE = 2; - private $_auth; - private $_type; + /** + * @var Manager the auth manager of this item + */ + public $manager; + /** + * @var string the item description + */ + public $description; + /** + * @var string the business rule associated with this item + */ + public $bizRule; + /** + * @var mixed the additional data associated with this item + */ + public $data; + /** + * @var integer the authorization item type. This could be 0 (operation), 1 (task) or 2 (role). + */ + public $type; + private $_name; private $_oldName; - private $_description; - private $_bizRule; - private $_data; - /** - * Constructor. - * @param Manager $auth authorization manager - * @param string $name authorization item name - * @param integer $type authorization item type. This can be 0 (operation), 1 (task) or 2 (role). - * @param string $description the description - * @param string $bizRule the business rule associated with this item - * @param mixed $data additional data for this item - */ - public function __construct($auth, $name, $type, $description = '', $bizRule = null, $data = null) - { - $this->_type = (int)$type; - $this->_auth = $auth; - $this->_name = $name; - $this->_description = $description; - $this->_bizRule = $bizRule; - $this->_data = $data; - } /** * Checks to see if the specified item is within the hierarchy starting from this item. @@ -74,11 +64,11 @@ class Item extends Object public function checkAccess($itemName, $params = array()) { Yii::trace('Checking permission: ' . $this->_name, __METHOD__); - if ($this->_auth->executeBizRule($this->_bizRule, $params, $this->_data)) { + if ($this->manager->executeBizRule($this->bizRule, $params, $this->data)) { if ($this->_name == $itemName) { return true; } - foreach ($this->_auth->getItemChildren($this->_name) as $item) { + foreach ($this->manager->getItemChildren($this->_name) as $item) { if ($item->checkAccess($itemName, $params)) { return true; } @@ -88,22 +78,6 @@ class Item extends Object } /** - * @return Manager the authorization manager - */ - public function getManager() - { - return $this->_auth; - } - - /** - * @return integer the authorization item type. This could be 0 (operation), 1 (task) or 2 (role). - */ - public function getType() - { - return $this->_type; - } - - /** * @return string the item name */ public function getName() @@ -123,60 +97,6 @@ class Item extends Object } /** - * @return string the item description - */ - public function getDescription() - { - return $this->_description; - } - - /** - * @param string $value the item description - */ - public function setDescription($value) - { - if ($this->_description !== $value) { - $this->_description = $value; - } - } - - /** - * @return string the business rule associated with this item - */ - public function getBizRule() - { - return $this->_bizRule; - } - - /** - * @param string $value the business rule associated with this item - */ - public function setBizRule($value) - { - if ($this->_bizRule !== $value) { - $this->_bizRule = $value; - } - } - - /** - * @return mixed the additional data associated with this item - */ - public function getData() - { - return $this->_data; - } - - /** - * @param mixed $value the additional data associated with this item - */ - public function setData($value) - { - if ($this->_data !== $value) { - $this->_data = $value; - } - } - - /** * Adds a child item. * @param string $name the name of the child item * @return boolean whether the item is added successfully @@ -185,7 +105,7 @@ class Item extends Object */ public function addChild($name) { - return $this->_auth->addItemChild($this->_name, $name); + return $this->manager->addItemChild($this->_name, $name); } /** @@ -197,7 +117,7 @@ class Item extends Object */ public function removeChild($name) { - return $this->_auth->removeItemChild($this->_name, $name); + return $this->manager->removeItemChild($this->_name, $name); } /** @@ -208,7 +128,7 @@ class Item extends Object */ public function hasChild($name) { - return $this->_auth->hasItemChild($this->_name, $name); + return $this->manager->hasItemChild($this->_name, $name); } /** @@ -218,7 +138,7 @@ class Item extends Object */ public function getChildren() { - return $this->_auth->getItemChildren($this->_name); + return $this->manager->getItemChildren($this->_name); } /** @@ -233,7 +153,7 @@ class Item extends Object */ public function assign($userId, $bizRule = null, $data = null) { - return $this->_auth->assign($userId, $this->_name, $bizRule, $data); + return $this->manager->assign($userId, $this->_name, $bizRule, $data); } /** @@ -244,7 +164,7 @@ class Item extends Object */ public function revoke($userId) { - return $this->_auth->revoke($userId, $this->_name); + return $this->manager->revoke($userId, $this->_name); } /** @@ -255,7 +175,7 @@ class Item extends Object */ public function isAssigned($userId) { - return $this->_auth->isAssigned($userId, $this->_name); + return $this->manager->isAssigned($userId, $this->_name); } /** @@ -267,7 +187,7 @@ class Item extends Object */ public function getAssignment($userId) { - return $this->_auth->getAssignment($userId, $this->_name); + return $this->manager->getAssignment($userId, $this->_name); } /** @@ -275,7 +195,7 @@ class Item extends Object */ public function save() { - $this->_auth->saveItem($this, $this->_oldName); + $this->manager->saveItem($this, $this->_oldName); unset($this->_oldName); } } diff --git a/yii/rbac/Manager.php b/yii/rbac/Manager.php index adc9013..23ac1a8 100644 --- a/yii/rbac/Manager.php +++ b/yii/rbac/Manager.php @@ -161,7 +161,7 @@ abstract class Manager extends Component { static $types = array('operation', 'task', 'role'); if ($parentType < $childType) { - throw new InvalidParamException("Cannot add an item of type '$types[$childType]' to an item of type '$types[$parentType]'."); + throw new InvalidParamException("Cannot add an item of type '{$types[$childType]}' to an item of type '{$types[$parentType]}'."); } } diff --git a/yii/rbac/PhpManager.php b/yii/rbac/PhpManager.php index 8a4dbec..7a476e0 100644 --- a/yii/rbac/PhpManager.php +++ b/yii/rbac/PhpManager.php @@ -80,14 +80,14 @@ class PhpManager extends Manager if (!isset($params['userId'])) { $params['userId'] = $userId; } - if ($this->executeBizRule($item->getBizRule(), $params, $item->getData())) { + if ($this->executeBizRule($item->bizRule, $params, $item->data)) { if (in_array($itemName, $this->defaultRoles)) { return true; } if (isset($this->_assignments[$userId][$itemName])) { /** @var $assignment Assignment */ $assignment = $this->_assignments[$userId][$itemName]; - if ($this->executeBizRule($assignment->getBizRule(), $params, $assignment->getData())) { + if ($this->executeBizRule($assignment->bizRule, $params, $assignment->data)) { return true; } } @@ -117,7 +117,7 @@ class PhpManager extends Manager $child = $this->_items[$childName]; /** @var $item Item */ $item = $this->_items[$itemName]; - $this->checkItemChildType($item->getType(), $child->getType()); + $this->checkItemChildType($item->type, $child->type); if ($this->detectLoop($itemName, $childName)) { throw new InvalidCallException("Cannot add '$childName' as a child of '$itemName'. A loop has been detected."); } @@ -194,7 +194,13 @@ class PhpManager extends Manager } elseif (isset($this->_assignments[$userId][$itemName])) { throw new InvalidParamException("Authorization item '$itemName' has already been assigned to user '$userId'."); } else { - return $this->_assignments[$userId][$itemName] = new Assignment($this, $userId, $itemName, $bizRule, $data); + return $this->_assignments[$userId][$itemName] = new Assignment(array( + 'manager' => $this, + 'userId' => $userId, + 'itemName' => $itemName, + 'bizRule' => $bizRule, + 'data' => $data, + )); } } @@ -265,15 +271,15 @@ class PhpManager extends Manager if ($userId === null) { foreach ($this->_items as $name => $item) { /** @var $item Item */ - if ($item->getType() == $type) { + if ($item->type == $type) { $items[$name] = $item; } } } elseif (isset($this->_assignments[$userId])) { foreach ($this->_assignments[$userId] as $assignment) { /** @var $assignment Assignment */ - $name = $assignment->getItemName(); - if (isset($this->_items[$name]) && ($type === null || $this->_items[$name]->getType() == $type)) { + $name = $assignment->itemName; + if (isset($this->_items[$name]) && ($type === null || $this->_items[$name]->type == $type)) { $items[$name] = $this->_items[$name]; } } @@ -301,7 +307,14 @@ class PhpManager extends Manager if (isset($this->_items[$name])) { throw new Exception('Unable to add an item whose name is the same as an existing item.'); } - return $this->_items[$name] = new Item($this, $name, $type, $description, $bizRule, $data); + return $this->_items[$name] = new Item(array( + 'manager' => $this, + 'name' => $name, + 'type' => $type, + 'description' => $description, + 'bizRule' => $bizRule, + 'data' => $data, + )); } /** @@ -390,10 +403,10 @@ class PhpManager extends Manager foreach ($this->_items as $name => $item) { /** @var $item Item */ $items[$name] = array( - 'type' => $item->getType(), - 'description' => $item->getDescription(), - 'bizRule' => $item->getBizRule(), - 'data' => $item->getData(), + 'type' => $item->type, + 'description' => $item->description, + 'bizRule' => $item->bizRule, + 'data' => $item->data, ); if (isset($this->_children[$name])) { foreach ($this->_children[$name] as $child) { @@ -408,8 +421,8 @@ class PhpManager extends Manager /** @var $assignment Assignment */ if (isset($items[$name])) { $items[$name]['assignments'][$userId] = array( - 'bizRule' => $assignment->getBizRule(), - 'data' => $assignment->getData(), + 'bizRule' => $assignment->bizRule, + 'data' => $assignment->data, ); } } @@ -428,7 +441,14 @@ class PhpManager extends Manager $items = $this->loadFromFile($this->authFile); foreach ($items as $name => $item) { - $this->_items[$name] = new Item($this, $name, $item['type'], $item['description'], $item['bizRule'], $item['data']); + $this->_items[$name] = new Item(array( + 'manager' => $this, + 'name' => $name, + 'type' => $item['type'], + 'description' => $item['description'], + 'bizRule' => $item['bizRule'], + 'data' => $item['data'], + )); } foreach ($items as $name => $item) { @@ -441,8 +461,14 @@ class PhpManager extends Manager } if (isset($item['assignments'])) { foreach ($item['assignments'] as $userId => $assignment) { - $this->_assignments[$userId][$name] = new Assignment($this, $name, $userId, $assignment['bizRule'], $assignment['data']); - } + $this->_assignments[$userId][$name] = new Assignment(array( + 'manager' => $this, + 'userId' => $userId, + 'itemName' => $name, + 'bizRule' => $assignment['bizRule'], + 'data' => $assignment['data'], + )); + } } } } diff --git a/yii/rbac/schema-mssql.sql b/yii/rbac/schema-mssql.sql index a1170b1..923417a 100644 --- a/yii/rbac/schema-mssql.sql +++ b/yii/rbac/schema-mssql.sql @@ -18,9 +18,10 @@ create table [tbl_auth_item] [name] varchar(64) not null, [type] integer not null, [description] text, - [bizrule] text, + [biz_rule] text, [data] text, - primary key ([name]) + primary key ([name]), + key [type] ([type]) ); create table [tbl_auth_item_child] @@ -36,7 +37,7 @@ create table [tbl_auth_assignment] ( [item_name] varchar(64) not null, [user_id] varchar(64) not null, - [bizrule] text, + [biz_rule] text, [data] text, primary key ([item_name],[user_id]), foreign key ([item_name]) references [tbl_auth_item] ([name]) on delete cascade on update cascade diff --git a/yii/rbac/schema-mysql.sql b/yii/rbac/schema-mysql.sql index aa8015b..1530409 100644 --- a/yii/rbac/schema-mysql.sql +++ b/yii/rbac/schema-mysql.sql @@ -18,9 +18,10 @@ create table `tbl_auth_item` `name` varchar(64) not null, `type` integer not null, `description` text, - `bizrule` text, + `biz_rule` text, `data` text, - primary key (`name`) + primary key (`name`), + key `type` (`type`) ) engine InnoDB; create table `tbl_auth_item_child` @@ -36,7 +37,7 @@ create table `tbl_auth_assignment` ( `item_name` varchar(64) not null, `user_id` varchar(64) not null, - `bizrule` text, + `biz_rule` text, `data` text, primary key (`item_name`,`user_id`), foreign key (`item_name`) references `tbl_auth_item` (`name`) on delete cascade on update cascade diff --git a/yii/rbac/schema-oci.sql b/yii/rbac/schema-oci.sql index 16b7964..78dbb3d 100644 --- a/yii/rbac/schema-oci.sql +++ b/yii/rbac/schema-oci.sql @@ -18,9 +18,10 @@ create table "tbl_auth_item" "name" varchar(64) not null, "type" integer not null, "description" text, - "bizrule" text, + "biz_rule" text, "data" text, - primary key ("name") + primary key ("name"), + key "type" ("type") ); create table "tbl_auth_item_child" @@ -36,7 +37,7 @@ create table "tbl_auth_assignment" ( "item_name" varchar(64) not null, "user_id" varchar(64) not null, - "bizrule" text, + "biz_rule" text, "data" text, primary key ("item_name","user_id"), foreign key ("item_name") references "tbl_auth_item" ("name") on delete cascade on update cascade diff --git a/yii/rbac/schema-pgsql.sql b/yii/rbac/schema-pgsql.sql index 16b7964..78dbb3d 100644 --- a/yii/rbac/schema-pgsql.sql +++ b/yii/rbac/schema-pgsql.sql @@ -18,9 +18,10 @@ create table "tbl_auth_item" "name" varchar(64) not null, "type" integer not null, "description" text, - "bizrule" text, + "biz_rule" text, "data" text, - primary key ("name") + primary key ("name"), + key "type" ("type") ); create table "tbl_auth_item_child" @@ -36,7 +37,7 @@ create table "tbl_auth_assignment" ( "item_name" varchar(64) not null, "user_id" varchar(64) not null, - "bizrule" text, + "biz_rule" text, "data" text, primary key ("item_name","user_id"), foreign key ("item_name") references "tbl_auth_item" ("name") on delete cascade on update cascade diff --git a/yii/rbac/schema-sqlite.sql b/yii/rbac/schema-sqlite.sql index 668196e..2b79bbf 100644 --- a/yii/rbac/schema-sqlite.sql +++ b/yii/rbac/schema-sqlite.sql @@ -18,9 +18,10 @@ create table 'tbl_auth_item' "name" varchar(64) not null, "type" integer not null, "description" text, - "bizrule" text, + "biz_rule" text, "data" text, - primary key ("name") + primary key ("name"), + key "type" ("type") ); create table 'tbl_auth_item_child' @@ -36,7 +37,7 @@ create table 'tbl_auth_assignment' ( "item_name" varchar(64) not null, "user_id" varchar(64) not null, - "bizrule" text, + "biz_rule" text, "data" text, primary key ("item_name","user_id"), foreign key ("item_name") references 'tbl_auth_item' ("name") on delete cascade on update cascade diff --git a/yii/requirements/requirements.php b/yii/requirements/requirements.php index 96ae285..a703fde 100644 --- a/yii/requirements/requirements.php +++ b/yii/requirements/requirements.php @@ -7,9 +7,9 @@ return array( array( 'name' => 'PHP version', 'mandatory' => true, - 'condition' => version_compare(PHP_VERSION, '5.3.0', '>='), + 'condition' => version_compare(PHP_VERSION, '5.3.3', '>='), 'by' => 'Yii Framework', - 'memo' => 'PHP 5.3.0 or higher is required.', + 'memo' => 'PHP 5.3.3 or higher is required.', ), array( 'name' => 'Reflection extension', diff --git a/yii/requirements/views/web/css.php b/yii/requirements/views/web/css.php index bff20d1..4c62aed 100644 --- a/yii/requirements/views/web/css.php +++ b/yii/requirements/views/web/css.php @@ -1,93 +1,6157 @@ -body -{ - background: white; - font-family:'Lucida Grande',Verdana,Geneva,Lucida,Helvetica,Arial,sans-serif; - font-size:10pt; - font-weight:normal; + \ No newline at end of file diff --git a/yii/requirements/views/web/index.php b/yii/requirements/views/web/index.php index f5196c8..2f8da15 100644 --- a/yii/requirements/views/web/index.php +++ b/yii/requirements/views/web/index.php @@ -3,80 +3,68 @@ /* @var $summary array */ /* @var $requirements array[] */ ?> - - + + - - - -Yii Application Requirement Checker + + Yii Application Requirement Checker + renderViewFile(dirname(__FILE__).DIRECTORY_SEPARATOR.'css.php'); ?> - -
- - - -
-

Description

-

-This script checks if your server configuration meets the requirements -for running Yii application. -It checks if the server is running the right version of PHP, -if appropriate PHP extensions have been loaded, and if php.ini file settings are correct. -

+
+
+

Yii Application Requirement Checker

+
+
-

Conclusion

-

-0): ?> -Unfortunately your server configuration does not satisfy the requirements by this application. -0): ?> -Your server configuration satisfies the minimum requirements by this application. Please pay attention to the warnings listed below if your application will use the corresponding features. - -Congratulations! Your server configuration satisfies all requirements. - -

+
+

Description

+

+ This script checks if your server configuration meets the requirements + for running Yii application. + It checks if the server is running the right version of PHP, + if appropriate PHP extensions have been loaded, and if php.ini file settings are correct. +

-

Details

+

Conclusion

+ 0): ?> + Unfortunately your server configuration does not satisfy the requirements by this application. + 0): ?> + Your server configuration satisfies the minimum requirements by this application. Please pay attention to the warnings listed below if your application will use the corresponding features. + + Congratulations! Your server configuration satisfies all requirements. + - - - - - - - - - - -
NameResultRequired ByMemo
- - - - - - - -
+

Details

- - - - - - -
 passed failed warning
+ + + + + + + + + + +
NameResultRequired ByMemo
+ + + + + + + +
-
+
- +
-
+ +
\ No newline at end of file diff --git a/yii/validators/BooleanValidator.php b/yii/validators/BooleanValidator.php index 1420739..67a64f6 100644 --- a/yii/validators/BooleanValidator.php +++ b/yii/validators/BooleanValidator.php @@ -43,7 +43,7 @@ class BooleanValidator extends Validator { parent::init(); if ($this->message === null) { - $this->message = Yii::t('yii|{attribute} must be either "{true}" or "{false}".'); + $this->message = Yii::t('yii', '{attribute} must be either "{true}" or "{false}".'); } } @@ -79,9 +79,11 @@ class BooleanValidator extends Validator * Returns the JavaScript needed for performing client-side validation. * @param \yii\base\Model $object the data object being validated * @param string $attribute the name of the attribute to be validated. + * @param \yii\base\View $view the view object that is going to be used to render views or view files + * containing a model form with this validator applied. * @return string the client-side validation script. */ - public function clientValidateAttribute($object, $attribute) + public function clientValidateAttribute($object, $attribute, $view) { $options = array( 'trueValue' => $this->trueValue, @@ -100,6 +102,7 @@ class BooleanValidator extends Validator $options['strict'] = 1; } + $view->registerAssetBundle('yii/validation'); return 'yii.validation.boolean(value, messages, ' . json_encode($options) . ');'; } } diff --git a/yii/validators/CaptchaValidator.php b/yii/validators/CaptchaValidator.php index 2e58cf2..dbc263e 100644 --- a/yii/validators/CaptchaValidator.php +++ b/yii/validators/CaptchaValidator.php @@ -39,7 +39,7 @@ class CaptchaValidator extends Validator { parent::init(); if ($this->message === null) { - $this->message = Yii::t('yii|The verification code is incorrect.'); + $this->message = Yii::t('yii', 'The verification code is incorrect.'); } } @@ -91,9 +91,11 @@ class CaptchaValidator extends Validator * Returns the JavaScript needed for performing client-side validation. * @param \yii\base\Model $object the data object being validated * @param string $attribute the name of the attribute to be validated. + * @param \yii\base\View $view the view object that is going to be used to render views or view files + * containing a model form with this validator applied. * @return string the client-side validation script. */ - public function clientValidateAttribute($object, $attribute) + public function clientValidateAttribute($object, $attribute, $view) { $captcha = $this->getCaptchaAction(); $code = $captcha->getVerifyCode(false); @@ -111,6 +113,7 @@ class CaptchaValidator extends Validator $options['skipOnEmpty'] = 1; } + $view->registerAssetBundle('yii/validation'); return 'yii.validation.captcha(value, messages, ' . json_encode($options) . ');'; } } diff --git a/yii/validators/CompareValidator.php b/yii/validators/CompareValidator.php index 68504e5..b8e8a50 100644 --- a/yii/validators/CompareValidator.php +++ b/yii/validators/CompareValidator.php @@ -79,28 +79,28 @@ class CompareValidator extends Validator if ($this->message === null) { switch ($this->operator) { case '==': - $this->message = Yii::t('yii|{attribute} must be repeated exactly.'); + $this->message = Yii::t('yii', '{attribute} must be repeated exactly.'); break; case '===': - $this->message = Yii::t('yii|{attribute} must be repeated exactly.'); + $this->message = Yii::t('yii', '{attribute} must be repeated exactly.'); break; case '!=': - $this->message = Yii::t('yii|{attribute} must not be equal to "{compareValue}".'); + $this->message = Yii::t('yii', '{attribute} must not be equal to "{compareValue}".'); break; case '!==': - $this->message = Yii::t('yii|{attribute} must not be equal to "{compareValue}".'); + $this->message = Yii::t('yii', '{attribute} must not be equal to "{compareValue}".'); break; case '>': - $this->message = Yii::t('yii|{attribute} must be greater than "{compareValue}".'); + $this->message = Yii::t('yii', '{attribute} must be greater than "{compareValue}".'); break; case '>=': - $this->message = Yii::t('yii|{attribute} must be greater than or equal to "{compareValue}".'); + $this->message = Yii::t('yii', '{attribute} must be greater than or equal to "{compareValue}".'); break; case '<': - $this->message = Yii::t('yii|{attribute} must be less than "{compareValue}".'); + $this->message = Yii::t('yii', '{attribute} must be less than "{compareValue}".'); break; case '<=': - $this->message = Yii::t('yii|{attribute} must be less than or equal to "{compareValue}".'); + $this->message = Yii::t('yii', '{attribute} must be less than or equal to "{compareValue}".'); break; default: throw new InvalidConfigException("Unknown operator: {$this->operator}"); @@ -119,7 +119,7 @@ class CompareValidator extends Validator { $value = $object->$attribute; if (is_array($value)) { - $this->addError($object, $attribute, Yii::t('yii|{attribute} is invalid.')); + $this->addError($object, $attribute, Yii::t('yii', '{attribute} is invalid.')); return; } if ($this->compareValue !== null) { @@ -178,9 +178,11 @@ class CompareValidator extends Validator * @param \yii\base\Model $object the data object being validated * @param string $attribute the name of the attribute to be validated * @return string the client-side validation script + * @param \yii\base\View $view the view object that is going to be used to render views or view files + * containing a model form with this validator applied. * @throws InvalidConfigException if CompareValidator::operator is invalid */ - public function clientValidateAttribute($object, $attribute) + public function clientValidateAttribute($object, $attribute, $view) { $options = array('operator' => $this->operator); @@ -203,6 +205,7 @@ class CompareValidator extends Validator '{compareValue}' => $compareValue, ))); + $view->registerAssetBundle('yii/validation'); return 'yii.validation.compare(value, messages, ' . json_encode($options) . ');'; } } diff --git a/yii/validators/DateValidator.php b/yii/validators/DateValidator.php index 7c3b181..7370b78 100644 --- a/yii/validators/DateValidator.php +++ b/yii/validators/DateValidator.php @@ -38,7 +38,7 @@ class DateValidator extends Validator { parent::init(); if ($this->message === null) { - $this->message = Yii::t('yii|The format of {attribute} is invalid.'); + $this->message = Yii::t('yii', 'The format of {attribute} is invalid.'); } } diff --git a/yii/validators/EmailValidator.php b/yii/validators/EmailValidator.php index 949b3f9..4297f12 100644 --- a/yii/validators/EmailValidator.php +++ b/yii/validators/EmailValidator.php @@ -47,6 +47,12 @@ class EmailValidator extends Validator * Defaults to false. */ public $checkPort = false; + /** + * @var boolean whether validation process should take into account IDN (internationalized domain + * names). Defaults to false meaning that validation of emails containing IDN will always fail. + */ + public $enableIDN = false; + /** * Initializes the validator. @@ -55,7 +61,7 @@ class EmailValidator extends Validator { parent::init(); if ($this->message === null) { - $this->message = Yii::t('yii|{attribute} is not a valid email address.'); + $this->message = Yii::t('yii', '{attribute} is not a valid email address.'); } } @@ -81,10 +87,18 @@ class EmailValidator extends Validator public function validateValue($value) { // make sure string length is limited to avoid DOS attacks - $valid = is_string($value) && strlen($value) <= 254 - && (preg_match($this->pattern, $value) || $this->allowName && preg_match($this->fullPattern, $value)); + if (!is_string($value) || strlen($value) >= 255) { + return false; + } + if (($atPosition = strpos($value, '@')) === false) { + return false; + } + $domain = rtrim(substr($value, $atPosition + 1), '>'); + if ($this->enableIDN) { + $value = idn_to_ascii(ltrim(substr($value, 0, $atPosition), '<')) . '@' . idn_to_ascii($domain); + } + $valid = preg_match($this->pattern, $value) || $this->allowName && preg_match($this->fullPattern, $value); if ($valid) { - $domain = rtrim(substr($value, strpos($value, '@') + 1), '>'); if ($this->checkMX && function_exists('checkdnsrr')) { $valid = checkdnsrr($domain, 'MX'); } @@ -99,9 +113,11 @@ class EmailValidator extends Validator * Returns the JavaScript needed for performing client-side validation. * @param \yii\base\Model $object the data object being validated * @param string $attribute the name of the attribute to be validated. + * @param \yii\base\View $view the view object that is going to be used to render views or view files + * containing a model form with this validator applied. * @return string the client-side validation script. */ - public function clientValidateAttribute($object, $attribute) + public function clientValidateAttribute($object, $attribute, $view) { $options = array( 'pattern' => new JsExpression($this->pattern), @@ -111,11 +127,16 @@ class EmailValidator extends Validator '{attribute}' => $object->getAttributeLabel($attribute), '{value}' => $object->$attribute, ))), + 'enableIDN' => (boolean)$this->enableIDN, ); if ($this->skipOnEmpty) { $options['skipOnEmpty'] = 1; } + $view->registerAssetBundle('yii/validation'); + if ($this->enableIDN) { + $view->registerAssetBundle('punycode'); + } return 'yii.validation.email(value, messages, ' . Json::encode($options) . ');'; } } diff --git a/yii/validators/ExistValidator.php b/yii/validators/ExistValidator.php index 7aa434c..7c45491 100644 --- a/yii/validators/ExistValidator.php +++ b/yii/validators/ExistValidator.php @@ -45,7 +45,7 @@ class ExistValidator extends Validator { parent::init(); if ($this->message === null) { - $this->message = Yii::t('yii|{attribute} is invalid.'); + $this->message = Yii::t('yii', '{attribute} is invalid.'); } } diff --git a/yii/validators/FileValidator.php b/yii/validators/FileValidator.php index cc36d07..8a3ab9e 100644 --- a/yii/validators/FileValidator.php +++ b/yii/validators/FileValidator.php @@ -97,22 +97,22 @@ class FileValidator extends Validator { parent::init(); if ($this->message === null) { - $this->message = Yii::t('yii|File upload failed.'); + $this->message = Yii::t('yii', 'File upload failed.'); } if ($this->uploadRequired === null) { - $this->uploadRequired = Yii::t('yii|Please upload a file.'); + $this->uploadRequired = Yii::t('yii', 'Please upload a file.'); } if ($this->tooMany === null) { - $this->tooMany = Yii::t('yii|You can upload at most {limit} files.'); + $this->tooMany = Yii::t('yii', 'You can upload at most {limit} files.'); } if ($this->wrongType === null) { - $this->wrongType = Yii::t('yii|Only files with these extensions are allowed: {extensions}.'); + $this->wrongType = Yii::t('yii', 'Only files with these extensions are allowed: {extensions}.'); } if ($this->tooBig === null) { - $this->tooBig = Yii::t('yii|The file "{file}" is too big. Its size cannot exceed {limit} bytes.'); + $this->tooBig = Yii::t('yii', 'The file "{file}" is too big. Its size cannot exceed {limit} bytes.'); } if ($this->tooSmall === null) { - $this->tooSmall = Yii::t('yii|The file "{file}" is too small. Its size cannot be smaller than {limit} bytes.'); + $this->tooSmall = Yii::t('yii', 'The file "{file}" is too small. Its size cannot be smaller than {limit} bytes.'); } if (!is_array($this->types)) { $this->types = preg_split('/[\s,]+/', strtolower($this->types), -1, PREG_SPLIT_NO_EMPTY); diff --git a/yii/validators/InlineValidator.php b/yii/validators/InlineValidator.php index 8af5bbc..dd951aa 100644 --- a/yii/validators/InlineValidator.php +++ b/yii/validators/InlineValidator.php @@ -79,12 +79,14 @@ class InlineValidator extends Validator * * @param \yii\base\Model $object the data object being validated * @param string $attribute the name of the attribute to be validated. + * @param \yii\base\View $view the view object that is going to be used to render views or view files + * containing a model form with this validator applied. * @return string the client-side validation script. Null if the validator does not support * client-side validation. * @see enableClientValidation * @see \yii\web\ActiveForm::enableClientValidation */ - public function clientValidateAttribute($object, $attribute) + public function clientValidateAttribute($object, $attribute, $view) { if ($this->clientValidate !== null) { $method = $this->clientValidate; diff --git a/yii/validators/NumberValidator.php b/yii/validators/NumberValidator.php index 10f0e52..3bcc6ce 100644 --- a/yii/validators/NumberValidator.php +++ b/yii/validators/NumberValidator.php @@ -62,14 +62,14 @@ class NumberValidator extends Validator { parent::init(); if ($this->message === null) { - $this->message = $this->integerOnly ? Yii::t('yii|{attribute} must be an integer.') - : Yii::t('yii|{attribute} must be a number.'); + $this->message = $this->integerOnly ? Yii::t('yii', '{attribute} must be an integer.') + : Yii::t('yii', '{attribute} must be a number.'); } if ($this->min !== null && $this->tooSmall === null) { - $this->tooSmall = Yii::t('yii|{attribute} must be no less than {min}.'); + $this->tooSmall = Yii::t('yii', '{attribute} must be no less than {min}.'); } if ($this->max !== null && $this->tooBig === null) { - $this->tooBig = Yii::t('yii|{attribute} must be no greater than {max}.'); + $this->tooBig = Yii::t('yii', '{attribute} must be no greater than {max}.'); } } @@ -83,7 +83,7 @@ class NumberValidator extends Validator { $value = $object->$attribute; if (is_array($value)) { - $this->addError($object, $attribute, Yii::t('yii|{attribute} is invalid.')); + $this->addError($object, $attribute, Yii::t('yii', '{attribute} is invalid.')); return; } $pattern = $this->integerOnly ? $this->integerPattern : $this->numberPattern; @@ -114,9 +114,11 @@ class NumberValidator extends Validator * Returns the JavaScript needed for performing client-side validation. * @param \yii\base\Model $object the data object being validated * @param string $attribute the name of the attribute to be validated. + * @param \yii\base\View $view the view object that is going to be used to render views or view files + * containing a model form with this validator applied. * @return string the client-side validation script. */ - public function clientValidateAttribute($object, $attribute) + public function clientValidateAttribute($object, $attribute, $view) { $label = $object->getAttributeLabel($attribute); $value = $object->$attribute; @@ -149,6 +151,7 @@ class NumberValidator extends Validator $options['skipOnEmpty'] = 1; } + $view->registerAssetBundle('yii/validation'); return 'yii.validation.number(value, messages, ' . Json::encode($options) . ');'; } } diff --git a/yii/validators/RangeValidator.php b/yii/validators/RangeValidator.php index 2a9e15f..a915275 100644 --- a/yii/validators/RangeValidator.php +++ b/yii/validators/RangeValidator.php @@ -48,7 +48,7 @@ class RangeValidator extends Validator throw new InvalidConfigException('The "range" property must be set.'); } if ($this->message === null) { - $this->message = Yii::t('yii|{attribute} is invalid.'); + $this->message = Yii::t('yii', '{attribute} is invalid.'); } } @@ -81,9 +81,11 @@ class RangeValidator extends Validator * Returns the JavaScript needed for performing client-side validation. * @param \yii\base\Model $object the data object being validated * @param string $attribute the name of the attribute to be validated. + * @param \yii\base\View $view the view object that is going to be used to render views or view files + * containing a model form with this validator applied. * @return string the client-side validation script. */ - public function clientValidateAttribute($object, $attribute) + public function clientValidateAttribute($object, $attribute, $view) { $range = array(); foreach ($this->range as $value) { @@ -101,6 +103,7 @@ class RangeValidator extends Validator $options['skipOnEmpty'] = 1; } + $view->registerAssetBundle('yii/validation'); return 'yii.validation.range(value, messages, ' . json_encode($options) . ');'; } } diff --git a/yii/validators/RegularExpressionValidator.php b/yii/validators/RegularExpressionValidator.php index 23419b9..417f2bc 100644 --- a/yii/validators/RegularExpressionValidator.php +++ b/yii/validators/RegularExpressionValidator.php @@ -45,7 +45,7 @@ class RegularExpressionValidator extends Validator throw new InvalidConfigException('The "pattern" property must be set.'); } if ($this->message === null) { - $this->message = Yii::t('yii|{attribute} is invalid.'); + $this->message = Yii::t('yii', '{attribute} is invalid.'); } } @@ -79,10 +79,12 @@ class RegularExpressionValidator extends Validator * Returns the JavaScript needed for performing client-side validation. * @param \yii\base\Model $object the data object being validated * @param string $attribute the name of the attribute to be validated. + * @param \yii\base\View $view the view object that is going to be used to render views or view files + * containing a model form with this validator applied. * @return string the client-side validation script. * @throws InvalidConfigException if the "pattern" is not a valid regular expression */ - public function clientValidateAttribute($object, $attribute) + public function clientValidateAttribute($object, $attribute, $view) { $pattern = $this->pattern; $pattern = preg_replace('/\\\\x\{?([0-9a-fA-F]+)\}?/', '\u$1', $pattern); @@ -110,6 +112,7 @@ class RegularExpressionValidator extends Validator $options['skipOnEmpty'] = 1; } + $view->registerAssetBundle('yii/validation'); return 'yii.validation.regularExpression(value, messages, ' . Json::encode($options) . ');'; } } diff --git a/yii/validators/RequiredValidator.php b/yii/validators/RequiredValidator.php index 4c14a8d..aedbe05 100644 --- a/yii/validators/RequiredValidator.php +++ b/yii/validators/RequiredValidator.php @@ -57,8 +57,8 @@ class RequiredValidator extends Validator { parent::init(); if ($this->message === null) { - $this->message = $this->requiredValue === null ? Yii::t('yii|{attribute} cannot be blank.') - : Yii::t('yii|{attribute} must be "{requiredValue}".'); + $this->message = $this->requiredValue === null ? Yii::t('yii', '{attribute} cannot be blank.') + : Yii::t('yii', '{attribute} must be "{requiredValue}".'); } } @@ -102,9 +102,11 @@ class RequiredValidator extends Validator * Returns the JavaScript needed for performing client-side validation. * @param \yii\base\Model $object the data object being validated * @param string $attribute the name of the attribute to be validated. + * @param \yii\base\View $view the view object that is going to be used to render views or view files + * containing a model form with this validator applied. * @return string the client-side validation script. */ - public function clientValidateAttribute($object, $attribute) + public function clientValidateAttribute($object, $attribute, $view) { $options = array(); if ($this->requiredValue !== null) { @@ -124,6 +126,7 @@ class RequiredValidator extends Validator '{value}' => $object->$attribute, ))); + $view->registerAssetBundle('yii/validation'); return 'yii.validation.required(value, messages, ' . json_encode($options) . ');'; } } diff --git a/yii/validators/StringValidator.php b/yii/validators/StringValidator.php index 5d0fa1a..abe4634 100644 --- a/yii/validators/StringValidator.php +++ b/yii/validators/StringValidator.php @@ -65,16 +65,16 @@ class StringValidator extends Validator $this->encoding = Yii::$app->charset; } if ($this->message === null) { - $this->message = Yii::t('yii|{attribute} must be a string.'); + $this->message = Yii::t('yii', '{attribute} must be a string.'); } if ($this->min !== null && $this->tooShort === null) { - $this->tooShort = Yii::t('yii|{attribute} should contain at least {min} characters.'); + $this->tooShort = Yii::t('yii', '{attribute} should contain at least {min} characters.'); } if ($this->max !== null && $this->tooLong === null) { - $this->tooLong = Yii::t('yii|{attribute} should contain at most {max} characters.'); + $this->tooLong = Yii::t('yii', '{attribute} should contain at most {max} characters.'); } if ($this->is !== null && $this->notEqual === null) { - $this->notEqual = Yii::t('yii|{attribute} should contain {length} characters.'); + $this->notEqual = Yii::t('yii', '{attribute} should contain {length} characters.'); } } @@ -126,9 +126,11 @@ class StringValidator extends Validator * Returns the JavaScript needed for performing client-side validation. * @param \yii\base\Model $object the data object being validated * @param string $attribute the name of the attribute to be validated. + * @param \yii\base\View $view the view object that is going to be used to render views or view files + * containing a model form with this validator applied. * @return string the client-side validation script. */ - public function clientValidateAttribute($object, $attribute) + public function clientValidateAttribute($object, $attribute, $view) { $label = $object->getAttributeLabel($attribute); $value = $object->$attribute; @@ -168,6 +170,7 @@ class StringValidator extends Validator $options['skipOnEmpty'] = 1; } + $view->registerAssetBundle('yii/validation'); return 'yii.validation.string(value, messages, ' . json_encode($options) . ');'; } } diff --git a/yii/validators/UniqueValidator.php b/yii/validators/UniqueValidator.php index 7072ff4..a4d8bff 100644 --- a/yii/validators/UniqueValidator.php +++ b/yii/validators/UniqueValidator.php @@ -39,7 +39,7 @@ class UniqueValidator extends Validator { parent::init(); if ($this->message === null) { - $this->message = Yii::t('yii|{attribute} "{value}" has already been taken.'); + $this->message = Yii::t('yii', '{attribute} "{value}" has already been taken.'); } } @@ -55,7 +55,7 @@ class UniqueValidator extends Validator $value = $object->$attribute; if (is_array($value)) { - $this->addError($object, $attribute, Yii::t('yii|{attribute} is invalid.')); + $this->addError($object, $attribute, Yii::t('yii', '{attribute} is invalid.')); return; } diff --git a/yii/validators/UrlValidator.php b/yii/validators/UrlValidator.php index c418353..bbd8883 100644 --- a/yii/validators/UrlValidator.php +++ b/yii/validators/UrlValidator.php @@ -37,6 +37,12 @@ class UrlValidator extends Validator * contain the scheme part. **/ public $defaultScheme; + /** + * @var boolean whether validation process should take into account IDN (internationalized + * domain names). Defaults to false meaning that validation of URLs containing IDN will always + * fail. + */ + public $enableIDN = false; /** @@ -46,7 +52,7 @@ class UrlValidator extends Validator { parent::init(); if ($this->message === null) { - $this->message = Yii::t('yii|{attribute} is not a valid URL.'); + $this->message = Yii::t('yii', '{attribute} is not a valid URL.'); } } @@ -87,6 +93,12 @@ class UrlValidator extends Validator $pattern = $this->pattern; } + if ($this->enableIDN) { + $value = preg_replace_callback('/:\/\/([^\/]+)/', function($matches) { + return '://' . idn_to_ascii($matches[1]); + }, $value); + } + if (preg_match($pattern, $value)) { return true; } @@ -98,10 +110,12 @@ class UrlValidator extends Validator * Returns the JavaScript needed for performing client-side validation. * @param \yii\base\Model $object the data object being validated * @param string $attribute the name of the attribute to be validated. + * @param \yii\base\View $view the view object that is going to be used to render views or view files + * containing a model form with this validator applied. * @return string the client-side validation script. * @see \yii\Web\ActiveForm::enableClientValidation */ - public function clientValidateAttribute($object, $attribute) + public function clientValidateAttribute($object, $attribute, $view) { if (strpos($this->pattern, '{schemes}') !== false) { $pattern = str_replace('{schemes}', '(' . implode('|', $this->validSchemes) . ')', $this->pattern); @@ -115,6 +129,7 @@ class UrlValidator extends Validator '{attribute}' => $object->getAttributeLabel($attribute), '{value}' => $object->$attribute, ))), + 'enableIDN' => (boolean)$this->enableIDN, ); if ($this->skipOnEmpty) { $options['skipOnEmpty'] = 1; @@ -123,7 +138,10 @@ class UrlValidator extends Validator $options['defaultScheme'] = $this->defaultScheme; } + $view->registerAssetBundle('yii/validation'); + if ($this->enableIDN) { + $view->registerAssetBundle('punycode'); + } return 'yii.validation.url(value, messages, ' . Json::encode($options) . ');'; } } - diff --git a/yii/validators/Validator.php b/yii/validators/Validator.php index 677191b..6b103bf 100644 --- a/yii/validators/Validator.php +++ b/yii/validators/Validator.php @@ -211,11 +211,13 @@ abstract class Validator extends Component * * @param \yii\base\Model $object the data object being validated * @param string $attribute the name of the attribute to be validated. + * @param \yii\base\View $view the view object that is going to be used to render views or view files + * containing a model form with this validator applied. * @return string the client-side validation script. Null if the validator does not support * client-side validation. * @see \yii\web\ActiveForm::enableClientValidation */ - public function clientValidateAttribute($object, $attribute) + public function clientValidateAttribute($object, $attribute, $view) { return null; } diff --git a/yii/web/AccessControl.php b/yii/web/AccessControl.php index e890510..c4efd19 100644 --- a/yii/web/AccessControl.php +++ b/yii/web/AccessControl.php @@ -100,7 +100,7 @@ class AccessControl extends ActionFilter if ($user->getIsGuest()) { $user->loginRequired(); } else { - throw new HttpException(403, Yii::t('yii|You are not allowed to perform this action.')); + throw new HttpException(403, Yii::t('yii', 'You are not allowed to perform this action.')); } } } diff --git a/yii/web/CaptchaAction.php b/yii/web/CaptchaAction.php index e3d6eaa..cff2314 100644 --- a/yii/web/CaptchaAction.php +++ b/yii/web/CaptchaAction.php @@ -260,10 +260,6 @@ class CaptchaAction extends Action (int)($this->foreColor % 0x10000 / 0x100), $this->foreColor % 0x100); - if ($this->fontFile === null) { - $this->fontFile = dirname(__FILE__) . '/SpicyRice.ttf'; - } - $length = strlen($code); $box = imagettfbbox(30, 0, $this->fontFile, $code); $w = $box[4] - $box[0] + $this->offset * ($length - 1); @@ -302,10 +298,6 @@ class CaptchaAction extends Action $image = new \Imagick(); $image->newImage($this->width, $this->height, $backColor); - if ($this->fontFile === null) { - $this->fontFile = dirname(__FILE__) . '/SpicyRice.ttf'; - } - $draw = new \ImagickDraw(); $draw->setFont($this->fontFile); $draw->setFontSize(30); diff --git a/yii/web/CookieCollection.php b/yii/web/CookieCollection.php index c76926b..97726c7 100644 --- a/yii/web/CookieCollection.php +++ b/yii/web/CookieCollection.php @@ -8,7 +8,7 @@ namespace yii\web; use Yii; -use yii\base\DictionaryIterator; +use ArrayIterator; use yii\helpers\SecurityHelper; /** @@ -50,11 +50,11 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \ * Returns an iterator for traversing the cookies in the collection. * This method is required by the SPL interface `IteratorAggregate`. * It will be implicitly called when you use `foreach` to traverse the collection. - * @return DictionaryIterator an iterator for traversing the cookies in the collection. + * @return ArrayIterator an iterator for traversing the cookies in the collection. */ public function getIterator() { - return new DictionaryIterator($this->_cookies); + return new ArrayIterator($this->_cookies); } /** diff --git a/yii/web/Pagination.php b/yii/web/Pagination.php index 20241ef..52ff517 100644 --- a/yii/web/Pagination.php +++ b/yii/web/Pagination.php @@ -47,7 +47,7 @@ use Yii; * } * * // display pagination - * $this->widget('yii\widgets\LinkPager', array( + * LinkPager::widget(array( * 'pages' => $pages, * )); * ~~~ diff --git a/yii/web/Request.php b/yii/web/Request.php index d3f419b..2f4568a 100644 --- a/yii/web/Request.php +++ b/yii/web/Request.php @@ -72,7 +72,7 @@ class Request extends \yii\base\Request $_GET = array_merge($_GET, $params); return array($route, $_GET); } else { - throw new HttpException(404, Yii::t('yii|Page not found.')); + throw new HttpException(404, Yii::t('yii', 'Page not found.')); } } @@ -786,7 +786,7 @@ class Request extends \yii\base\Request } if (empty($token) || $cookies->getValue($this->csrfTokenName) !== $token) { - throw new HttpException(400, Yii::t('yii|Unable to verify your data submission.')); + throw new HttpException(400, Yii::t('yii', 'Unable to verify your data submission.')); } } } diff --git a/yii/web/Response.php b/yii/web/Response.php index 54b7f6e..954c999 100644 --- a/yii/web/Response.php +++ b/yii/web/Response.php @@ -8,8 +8,10 @@ namespace yii\web; use Yii; +use yii\base\HttpException; use yii\helpers\FileHelper; use yii\helpers\Html; +use yii\helpers\StringHelper; /** * @author Qiang Xue @@ -31,27 +33,76 @@ class Response extends \yii\base\Response * @param string $content content to be set. * @param string $mimeType mime type of the content. If null, it will be guessed automatically based on the given file name. * @param boolean $terminate whether to terminate the current application after calling this method - * @todo + * @throws \yii\base\HttpException when range request is not satisfiable. */ public function sendFile($fileName, $content, $mimeType = null, $terminate = true) { - if ($mimeType === null && ($mimeType = FileHelper::getMimeType($fileName)) === null) { + if ($mimeType === null && (($mimeType = FileHelper::getMimeTypeByExtension($fileName)) === null)) { $mimeType = 'application/octet-stream'; } + + $fileSize = StringHelper::strlen($content); + $contentStart = 0; + $contentEnd = $fileSize - 1; + + // tell the client that we accept range requests + header('Accept-Ranges: bytes'); + + if (isset($_SERVER['HTTP_RANGE'])) { + // client sent us a multibyte range, can not hold this one for now + if (strpos(',', $_SERVER['HTTP_RANGE']) !== false) { + header("Content-Range: bytes $contentStart-$contentEnd/$fileSize"); + throw new HttpException(416, 'Requested Range Not Satisfiable'); + } + + $range = str_replace('bytes=', '', $_SERVER['HTTP_RANGE']); + + // range requests starts from "-", so it means that data must be dumped the end point. + if ($range[0] === '-') { + $contentStart = $fileSize - substr($range, 1); + } else { + $range = explode('-', $range); + $contentStart = $range[0]; + $contentEnd = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $fileSize - 1; + } + + /* Check the range and make sure it's treated according to the specs. + * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html + */ + // End bytes can not be larger than $end. + $contentEnd = ($contentEnd > $fileSize) ? $fileSize : $contentEnd; + + // Validate the requested range and return an error if it's not correct. + $wrongContentStart = ($contentStart > $contentEnd || $contentStart > $fileSize - 1 || $contentStart < 0); + + if ($wrongContentStart) { + header("Content-Range: bytes $contentStart-$contentEnd/$fileSize"); + throw new HttpException(416, 'Requested Range Not Satisfiable'); + } + + header('HTTP/1.1 206 Partial Content'); + header("Content-Range: bytes $contentStart-$contentEnd/$fileSize"); + } else { + header('HTTP/1.1 200 OK'); + } + + $length = $contentEnd - $contentStart + 1; // Calculate new content length + header('Pragma: public'); header('Expires: 0'); header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); - header("Content-type: $mimeType"); - if (ob_get_length() === false) { - header('Content-Length: ' . (function_exists('mb_strlen') ? mb_strlen($content, '8bit') : strlen($content))); - } - header("Content-Disposition: attachment; filename=\"$fileName\""); + header('Content-Type: ' . $mimeType); + header('Content-Length: ' . $length); + header('Content-Disposition: attachment; filename="' . $fileName . '"'); header('Content-Transfer-Encoding: binary'); + $content = StringHelper::substr($content, $contentStart, $length); if ($terminate) { // clean up the application first because the file downloading could take long time // which may cause timeout of some resources (such as DB connection) - Yii::app()->end(0, false); + ob_start(); + Yii::$app->end(0, false); + ob_end_clean(); echo $content; exit(0); } else { diff --git a/yii/web/User.php b/yii/web/User.php index 7d8e300..79665ae 100644 --- a/yii/web/User.php +++ b/yii/web/User.php @@ -286,7 +286,7 @@ class User extends Component if ($this->loginUrl !== null) { Yii::$app->getResponse()->redirect($this->loginUrl); } else { - throw new HttpException(403, Yii::t('yii|Login Required')); + throw new HttpException(403, Yii::t('yii', 'Login Required')); } } diff --git a/yii/widgets/ActiveField.php b/yii/widgets/ActiveField.php index 55357a3..45faf9d 100644 --- a/yii/widgets/ActiveField.php +++ b/yii/widgets/ActiveField.php @@ -138,7 +138,7 @@ class ActiveField extends Component $validators = array(); foreach ($this->model->getActiveValidators($attribute) as $validator) { /** @var \yii\validators\Validator $validator */ - $js = $validator->clientValidateAttribute($this->model, $attribute); + $js = $validator->clientValidateAttribute($this->model, $attribute, $this->form->getView()); if ($validator->enableClientValidation && $js != '') { $validators[] = $js; } diff --git a/yii/widgets/ActiveForm.php b/yii/widgets/ActiveForm.php index 24451b9..25a2054 100644 --- a/yii/widgets/ActiveForm.php +++ b/yii/widgets/ActiveForm.php @@ -187,7 +187,7 @@ class ActiveForm extends Widget } } - $header = isset($options['header']) ? $options['header'] : '

' . Yii::t('yii|Please fix the following errors:') . '

'; + $header = isset($options['header']) ? $options['header'] : '

' . Yii::t('yii', 'Please fix the following errors:') . '

'; $footer = isset($options['footer']) ? $options['footer'] : ''; unset($options['header'], $options['footer']); diff --git a/yii/widgets/Breadcrumbs.php b/yii/widgets/Breadcrumbs.php index 22d09b3..9214f86 100644 --- a/yii/widgets/Breadcrumbs.php +++ b/yii/widgets/Breadcrumbs.php @@ -19,10 +19,11 @@ use yii\helpers\Html; * for the "Sample Post". He can click on "Sample Post" to view that page, or he can click on "Home" * to return to the homepage. * - * To use Breadcrumbs, you need to configure its [[links]] property, which specifiesthe links to be displayed. For example, + * To use Breadcrumbs, you need to configure its [[links]] property, which specifies the links to be displayed. For example, * * ~~~ - * $this->widget('yii\widgets\Breadcrumbs', array( + * // $this is the view object currently being used + * echo Breadcrumbs::widget(array( * 'links' => array( * array('label' => 'Sample Post', 'url' => array('post/edit', 'id' => 1)), * 'Edit', @@ -30,12 +31,13 @@ use yii\helpers\Html; * )); * ~~~ * - * Because breadcrumbs usually appears in nearly every page of a website, you may consider place it in a layout view. - * You can then use a view parameter (e.g. `$this->params['breadcrumbs']`) to configure the links in different + * Because breadcrumbs usually appears in nearly every page of a website, you may consider placing it in a layout view. + * You can use a view parameter (e.g. `$this->params['breadcrumbs']`) to configure the links in different * views. In the layout view, you assign this view parameter to the [[links]] property like the following: * * ~~~ - * $this->widget('yii\widgets\Breadcrumbs', array( + * // $this is the view object currently being used + * echo Breadcrumbs::widget(array( * 'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : array(), * )); * ~~~ @@ -101,7 +103,7 @@ class Breadcrumbs extends Widget $links = array(); if ($this->homeLink === null) { $links[] = $this->renderItem(array( - 'label' => Yii::t('yii|Home'), + 'label' => Yii::t('yii', 'Home'), 'url' => Yii::$app->homeUrl, ), $this->itemTemplate); } elseif ($this->homeLink !== false) { diff --git a/yii/widgets/LinkPager.php b/yii/widgets/LinkPager.php index 1651246..2510579 100644 --- a/yii/widgets/LinkPager.php +++ b/yii/widgets/LinkPager.php @@ -122,7 +122,7 @@ class LinkPager extends Widget { $buttons = array(); - $pageCount = $this->pagination->pageCount; + $pageCount = $this->pagination->getPageCount(); $currentPage = $this->pagination->getPage(); // first page diff --git a/yii/widgets/ListPager.php b/yii/widgets/ListPager.php index a68b624..7b16f7d 100644 --- a/yii/widgets/ListPager.php +++ b/yii/widgets/ListPager.php @@ -63,7 +63,7 @@ class ListPager extends Widget */ public function run() { - $pageCount = $this->pagination->pageCount; + $pageCount = $this->pagination->getPageCount(); $currentPage = $this->pagination->getPage(); $pages = array(); diff --git a/yii/widgets/Menu.php b/yii/widgets/Menu.php index b8f69e1..e76f11f 100644 --- a/yii/widgets/Menu.php +++ b/yii/widgets/Menu.php @@ -26,10 +26,11 @@ use yii\helpers\Html; * The following example shows how to use Menu: * * ~~~ - * $this->widget('yii\widgets\Menu', array( + * // $this is the view object currently being used + * echo Menu::widget(array( * 'items' => array( * // Important: you need to specify url as 'controller/action', - * // not just as 'controller' even if default acion is used. + * // not just as 'controller' even if default action is used. * array('label' => 'Home', 'url' => array('site/index')), * // 'Products' menu item will be selected as long as the route is 'product/index' * array('label' => 'Products', 'url' => array('product/index'), 'items' => array( diff --git a/yii/yiic.php b/yii/yii old mode 100644 new mode 100755 similarity index 89% rename from yii/yiic.php rename to yii/yii index 7cd0c40..54fa9de --- a/yii/yiic.php +++ b/yii/yii @@ -1,3 +1,4 @@ +#!/usr/bin/env php 'yiic', + 'id' => 'yii-console', 'basePath' => __DIR__ . '/console', 'controllerPath' => '@yii/console/controllers', )); -$application->run(); +$application->run(); \ No newline at end of file diff --git a/yii/yii.bat b/yii/yii.bat new file mode 100644 index 0000000..18bb296 --- /dev/null +++ b/yii/yii.bat @@ -0,0 +1,22 @@ +@echo off + +rem ------------------------------------------------------------- +rem Yii command line script for Windows. +rem +rem This is the bootstrap script for running yiic on Windows. +rem +rem @author Qiang Xue +rem @link http://www.yiiframework.com/ +rem @copyright Copyright © 2012 Yii Software LLC +rem @license http://www.yiiframework.com/license/ +rem ------------------------------------------------------------- + +@setlocal + +set YII_PATH=%~dp0 + +if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe + +"%PHP_COMMAND%" "%YII_PATH%yii" %* + +@endlocal \ No newline at end of file diff --git a/yii/yiic b/yii/yiic deleted file mode 100755 index d35d262..0000000 --- a/yii/yiic +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env php -