Browse Source

Merge branch 'master' of https://github.com/yiisoft/yii2 into jui-tabs-rework

tags/2.0.0-beta
Alexander Kochetov 12 years ago
parent
commit
814a259546
  1. 1
      apps/advanced/.gitignore
  2. 28
      apps/advanced/README.md
  3. 3
      apps/advanced/backstage/config/main.php
  4. 3
      apps/advanced/common/models/User.php
  5. 3
      apps/advanced/composer.json
  6. 164
      apps/advanced/composer.lock
  7. 3
      apps/advanced/console/config/main.php
  8. 2
      apps/advanced/console/migrations/m130524_201442_init.php
  9. 3
      apps/advanced/frontend/config/main.php
  10. 14
      apps/advanced/init
  11. 0
      apps/advanced/init.bat
  12. 1
      apps/basic/.gitignore
  13. 164
      apps/basic/composer.lock
  14. 2
      apps/basic/requirements.php
  15. 12
      apps/basic/views/site/contact.php
  16. 32
      apps/benchmark/LICENSE.md
  17. 56
      apps/benchmark/README.md
  18. 23
      apps/benchmark/composer.json
  19. 119
      apps/benchmark/composer.lock
  20. 18
      apps/benchmark/index.php
  21. 1
      apps/benchmark/protected/.htaccess
  22. 13
      apps/benchmark/protected/controllers/SiteController.php
  23. 2
      apps/benchmark/protected/vendor/.gitignore
  24. 32
      docs/guide/upgrade-from-v1.md
  25. 7
      framework/yii/assets.php
  26. 338
      framework/yii/assets/jquery.maskedinput.js
  27. 4
      framework/yii/assets/yii.validation.js
  28. 4
      framework/yii/base/ActionFilter.php
  29. 15
      framework/yii/base/Application.php
  30. 44
      framework/yii/base/Controller.php
  31. 292
      framework/yii/base/Formatter.php
  32. 26
      framework/yii/base/InvalidRequestException.php
  33. 2
      framework/yii/base/View.php
  34. 8
      framework/yii/behaviors/AutoTimestamp.php
  35. 2
      framework/yii/bootstrap/Carousel.php
  36. 2
      framework/yii/bootstrap/Collapse.php
  37. 138
      framework/yii/bootstrap/Nav.php
  38. 188
      framework/yii/bootstrap/NavBar.php
  39. 35
      framework/yii/caching/Cache.php
  40. 64
      framework/yii/console/controllers/AssetController.php
  41. 2
      framework/yii/console/controllers/MigrateController.php
  42. 2
      framework/yii/db/ActiveRecord.php
  43. 4
      framework/yii/db/Command.php
  44. 13
      framework/yii/db/Schema.php
  45. 8
      framework/yii/db/mssql/Schema.php
  46. 4
      framework/yii/helpers/base/Html.php
  47. 13
      framework/yii/helpers/base/StringHelper.php
  48. 232
      framework/yii/i18n/Formatter.php
  49. 31
      framework/yii/jui/Accordion.php
  50. 52
      framework/yii/jui/Draggable.php
  51. 52
      framework/yii/jui/Droppable.php
  52. 52
      framework/yii/jui/Resizable.php
  53. 116
      framework/yii/jui/Selectable.php
  54. 66
      framework/yii/jui/Spinner.php
  55. 47
      framework/yii/rbac/DbManager.php
  56. 2
      framework/yii/requirements/requirements.php
  57. 3
      framework/yii/validators/UniqueValidator.php
  58. 22
      framework/yii/views/errorHandler/callStackItem.php
  59. 108
      framework/yii/views/errorHandler/main.php
  60. 4
      framework/yii/web/CacheSession.php
  61. 44
      framework/yii/web/Controller.php
  62. 2
      framework/yii/web/UrlManager.php
  63. 90
      framework/yii/web/VerbFilter.php
  64. 12
      framework/yii/widgets/ActiveField.php
  65. 4
      framework/yii/widgets/ActiveForm.php
  66. 64
      framework/yii/widgets/Captcha.php
  67. 4
      framework/yii/widgets/FragmentCache.php
  68. 64
      framework/yii/widgets/InputWidget.php
  69. 136
      framework/yii/widgets/MaskedInput.php
  70. 179
      tests/unit/framework/base/FormatterTest.php
  71. 2
      tests/unit/framework/caching/CacheTestCase.php
  72. 78
      tests/unit/framework/console/controllers/AssetControllerTest.php
  73. 88
      tests/unit/framework/i18n/FormatterTest.php

1
apps/advanced/.gitignore vendored

@ -1,2 +1 @@
/yii /yii
composer.lock

28
apps/advanced/README.md

@ -67,32 +67,32 @@ If you do not have [Composer](http://getcomposer.org/), you may download it from
curl -s http://getcomposer.org/installer | php curl -s http://getcomposer.org/installer | php
~~~ ~~~
You can then install the Bootstrap Application using the following command: You can then install the application using the following command:
~~~ ~~~
php composer.phar create-project --stability=dev yiisoft/yii2-app-advanced yii-advanced php composer.phar create-project --stability=dev yiisoft/yii2-app-advanced yii-advanced
~~~ ~~~
Now you should be able to access:
- the frontend using the URL `http://localhost/yii-advanced/frontend/www/`
- the backstage using the URL `http://localhost/yii-advanced/backstage/www/`
assuming `yii-advanced` is directly under the document root of your Web server.
### Install from an Archive File ### Install from an Archive File
This is not currently available. We will provide it when Yii 2 is formally released. This is not currently available. We will provide it when Yii 2 is formally released.
GETTING STARTED GETTING STARTED
--------------- ---------------
After template application and its dependencies are downloaded you need to initialize it and set some config values to After you install the application, you have to conduct the following steps to initialize
match your application requirements. the installed application. You only need to do these once for all.
1. Execute `install` command selecting `dev` as environment. 1. Execute the `init` command and select `dev` as environment.
2. Set `id` value in `console/config/main.php`, `frontend/config/main.php`, `backstage/config/main.php`. 2. Create a new database. It is assumed that MySQL InnoDB is used. If not, adjust `console/migrations/m130524_201442_init.php`.
3. Create new database. It is assumed that MySQL InnoDB is used. If not, adjust `console/migrations/m130524_201442_init.php`. 3. In `common/config/params.php` set your database details in `components.db` values.
4. In `common/config/params.php` set your database details in `components.db` values.
Now you should be able to access:
- the frontend using the URL `http://localhost/yii-advanced/frontend/www/`
- the backstage using the URL `http://localhost/yii-advanced/backstage/www/`
assuming `yii-advanced` is directly under the document root of your Web server.

3
apps/advanced/backstage/config/main.php

@ -9,8 +9,9 @@ $params = array_merge(
); );
return array( return array(
'id' => 'change-me', 'id' => 'app-backend',
'basePath' => dirname(__DIR__), 'basePath' => dirname(__DIR__),
'vendorPath' => dirname(dirname(__DIR__)) . '/vendor',
'preload' => array('log'), 'preload' => array('log'),
'controllerNamespace' => 'backstage\controllers', 'controllerNamespace' => 'backstage\controllers',
'modules' => array( 'modules' => array(

3
apps/advanced/common/models/User.php

@ -37,8 +37,7 @@ class User extends ActiveRecord implements Identity
'timestamp' => array( 'timestamp' => array(
'class' => 'yii\behaviors\AutoTimestamp', 'class' => 'yii\behaviors\AutoTimestamp',
'attributes' => array( 'attributes' => array(
ActiveRecord::EVENT_BEFORE_INSERT => 'create_time', ActiveRecord::EVENT_BEFORE_INSERT => array('create_time', 'update_time'),
ActiveRecord::EVENT_BEFORE_INSERT => 'update_time',
ActiveRecord::EVENT_BEFORE_UPDATE => 'update_time', ActiveRecord::EVENT_BEFORE_UPDATE => 'update_time',
), ),
), ),

3
apps/advanced/composer.json

@ -36,9 +36,6 @@
"frontend/runtime", "frontend/runtime",
"frontend/www/assets" "frontend/www/assets"
],
"yii-install-executable": [
"yii"
] ]
} }
} }

164
apps/advanced/composer.lock generated

@ -0,0 +1,164 @@
{
"_readme": [
"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": "05f7bcd0e99931b52415eaeb62d54daf",
"packages": [
{
"name": "yiisoft/yii2",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/yiisoft/yii2-framework.git",
"reference": "2d93f20ba6044ac3f1957300c4ae85fd98ecf47d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/2d93f20ba6044ac3f1957300c4ae85fd98ecf47d",
"reference": "2d93f20ba6044ac3f1957300c4ae85fd98ecf47d",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"lib-pcre": "*",
"php": ">=5.3.7"
},
"suggest": {
"ezyang/htmlpurifier": "Required by HtmlPurifier.",
"michelf/php-markdown": "Required by Markdown.",
"smarty/smarty": "Required by SmartyViewRenderer.",
"twig/twig": "Required by TwigViewRenderer."
},
"type": "library",
"autoload": {
"psr-0": {
"yii\\": "/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Qiang Xue",
"email": "qiang.xue@gmail.com",
"homepage": "http://www.yiiframework.com/",
"role": "Founder and project lead"
},
{
"name": "Alexander Makarov",
"email": "sam@rmcreative.ru",
"homepage": "http://rmcreative.ru/",
"role": "Core framework development"
},
{
"name": "Maurizio Domba",
"homepage": "http://mdomba.info/",
"role": "Core framework development"
},
{
"name": "Carsten Brandt",
"email": "mail@cebe.cc",
"homepage": "http://cebe.cc/",
"role": "Core framework development"
},
{
"name": "Wei Zhuo",
"email": "weizhuo@gmail.com",
"role": "Project site maintenance and development"
},
{
"name": "Sebastián Thierer",
"email": "sebas@artfos.com",
"role": "Component development"
},
{
"name": "Jeffrey Winesett",
"email": "jefftulsa@gmail.com",
"role": "Documentation and marketing"
},
{
"name": "Timur Ruziev",
"email": "resurtm@gmail.com",
"homepage": "http://resurtm.com/",
"role": "Core framework development"
},
{
"name": "Paul Klimov",
"email": "klimov.paul@gmail.com",
"role": "Core framework development"
}
],
"description": "Yii2 Web Programming Framework",
"homepage": "http://www.yiiframework.com/",
"keywords": [
"framework",
"yii"
],
"time": "2013-05-29 02:55:14"
},
{
"name": "yiisoft/yii2-composer",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/yiisoft/yii2-composer.git",
"reference": "7ce4060faca940b836ab88de207638940a0a0568"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/yiisoft/yii2-composer/zipball/7ce4060faca940b836ab88de207638940a0a0568",
"reference": "7ce4060faca940b836ab88de207638940a0a0568",
"shasum": ""
},
"require": {
"yiisoft/yii2": "*"
},
"type": "library",
"autoload": {
"psr-0": {
"yii\\composer": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Qiang Xue",
"email": "qiang.xue@gmail.com",
"homepage": "http://www.yiiframework.com/",
"role": "Founder and project lead"
}
],
"description": "The composer integration for the Yii framework",
"keywords": [
"composer",
"install",
"update",
"yii"
],
"time": "2013-05-23 19:12:45"
}
],
"packages-dev": [
],
"aliases": [
],
"minimum-stability": "dev",
"stability-flags": {
"yiisoft/yii2": 20,
"yiisoft/yii2-composer": 20
},
"platform": {
"php": ">=5.3.0"
},
"platform-dev": [
]
}

3
apps/advanced/console/config/main.php

@ -9,8 +9,9 @@ $params = array_merge(
); );
return array( return array(
'id' => 'change-me', 'id' => 'app-console',
'basePath' => dirname(__DIR__), 'basePath' => dirname(__DIR__),
'vendorPath' => dirname(dirname(__DIR__)) . '/vendor',
'preload' => array('log'), 'preload' => array('log'),
'controllerNamespace' => 'console\controllers', 'controllerNamespace' => 'console\controllers',
'modules' => array( 'modules' => array(

2
apps/advanced/console/migrations/m130524_201442_init.php

@ -1,5 +1,7 @@
<?php <?php
use yii\db\Schema;
class m130524_201442_init extends \yii\db\Migration class m130524_201442_init extends \yii\db\Migration
{ {
public function up() public function up()

3
apps/advanced/frontend/config/main.php

@ -9,8 +9,9 @@ $params = array_merge(
); );
return array( return array(
'id' => 'change-me', 'id' => 'app-frontend',
'basePath' => dirname(__DIR__), 'basePath' => dirname(__DIR__),
'vendorPath' => dirname(dirname(__DIR__)) . '/vendor',
'preload' => array('log'), 'preload' => array('log'),
'controllerNamespace' => 'frontend\controllers', 'controllerNamespace' => 'frontend\controllers',
'modules' => array( 'modules' => array(

14
apps/advanced/install → apps/advanced/init

@ -4,27 +4,27 @@ $root = str_replace('\\', '/', __DIR__);
$envs = require("$root/environments/index.php"); $envs = require("$root/environments/index.php");
$envNames = array_keys($envs); $envNames = array_keys($envs);
echo "Yii Application Installation Tool v1.0\n\n"; echo "Yii Application Init Tool v1.0\n\n";
echo "Which environment do you want to install the application to?\n\n"; echo "Which environment do you want the application to be initialized in?\n\n";
foreach ($envNames as $i => $name) { foreach ($envNames as $i => $name) {
echo " [$i] $name\n"; echo " [$i] $name\n";
} }
echo "\n Your choice [0-" . (count($envs) - 1) . ', or "q" to quit] '; echo "\n Your choice [0-" . (count($envs) - 1) . ', or "q" to quit] ';
$answer = trim(fgets(STDIN)); $answer = trim(fgets(STDIN));
if (!ctype_digit($answer) || !isset($envNames[$answer])) { if (!ctype_digit($answer) || !isset($envNames[$answer])) {
echo "\n Quit installation.\n"; echo "\n Quit initialization.\n";
return; return;
} }
$env = $envs[$envNames[$answer]]; $env = $envs[$envNames[$answer]];
echo "\n Install the application under '{$envNames[$answer]}' environment? [yes|no] "; echo "\n Initialize the application under '{$envNames[$answer]}' environment? [yes|no] ";
$answer = trim(fgets(STDIN)); $answer = trim(fgets(STDIN));
if (strncasecmp($answer, 'y', 1)) { if (strncasecmp($answer, 'y', 1)) {
echo "\n Quit installation.\n"; echo "\n Quit initialization.\n";
return; return;
} }
echo "\n Start installation ...\n\n"; echo "\n Start initialization ...\n\n";
$files = getFileList("$root/environments/{$env['path']}"); $files = getFileList("$root/environments/{$env['path']}");
$all = false; $all = false;
foreach ($files as $file) { foreach ($files as $file) {
@ -47,7 +47,7 @@ if (isset($env['executable'])) {
} }
} }
echo "\n ... installation completed.\n\n"; echo "\n ... initialization completed.\n\n";
function getFileList($root, $basePath = '') function getFileList($root, $basePath = '')
{ {

0
apps/advanced/install.bat → apps/advanced/init.bat

1
apps/basic/.gitignore vendored

@ -1 +0,0 @@
composer.lock

164
apps/basic/composer.lock generated

@ -0,0 +1,164 @@
{
"_readme": [
"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": "0411dbbd774aa1c89256c77c68023940",
"packages": [
{
"name": "yiisoft/yii2",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/yiisoft/yii2-framework.git",
"reference": "2d93f20ba6044ac3f1957300c4ae85fd98ecf47d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/2d93f20ba6044ac3f1957300c4ae85fd98ecf47d",
"reference": "2d93f20ba6044ac3f1957300c4ae85fd98ecf47d",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"lib-pcre": "*",
"php": ">=5.3.7"
},
"suggest": {
"ezyang/htmlpurifier": "Required by HtmlPurifier.",
"michelf/php-markdown": "Required by Markdown.",
"smarty/smarty": "Required by SmartyViewRenderer.",
"twig/twig": "Required by TwigViewRenderer."
},
"type": "library",
"autoload": {
"psr-0": {
"yii\\": "/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Qiang Xue",
"email": "qiang.xue@gmail.com",
"homepage": "http://www.yiiframework.com/",
"role": "Founder and project lead"
},
{
"name": "Alexander Makarov",
"email": "sam@rmcreative.ru",
"homepage": "http://rmcreative.ru/",
"role": "Core framework development"
},
{
"name": "Maurizio Domba",
"homepage": "http://mdomba.info/",
"role": "Core framework development"
},
{
"name": "Carsten Brandt",
"email": "mail@cebe.cc",
"homepage": "http://cebe.cc/",
"role": "Core framework development"
},
{
"name": "Wei Zhuo",
"email": "weizhuo@gmail.com",
"role": "Project site maintenance and development"
},
{
"name": "Sebastián Thierer",
"email": "sebas@artfos.com",
"role": "Component development"
},
{
"name": "Jeffrey Winesett",
"email": "jefftulsa@gmail.com",
"role": "Documentation and marketing"
},
{
"name": "Timur Ruziev",
"email": "resurtm@gmail.com",
"homepage": "http://resurtm.com/",
"role": "Core framework development"
},
{
"name": "Paul Klimov",
"email": "klimov.paul@gmail.com",
"role": "Core framework development"
}
],
"description": "Yii2 Web Programming Framework",
"homepage": "http://www.yiiframework.com/",
"keywords": [
"framework",
"yii"
],
"time": "2013-05-29 02:55:14"
},
{
"name": "yiisoft/yii2-composer",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/yiisoft/yii2-composer.git",
"reference": "7ce4060faca940b836ab88de207638940a0a0568"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/yiisoft/yii2-composer/zipball/7ce4060faca940b836ab88de207638940a0a0568",
"reference": "7ce4060faca940b836ab88de207638940a0a0568",
"shasum": ""
},
"require": {
"yiisoft/yii2": "*"
},
"type": "library",
"autoload": {
"psr-0": {
"yii\\composer": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Qiang Xue",
"email": "qiang.xue@gmail.com",
"homepage": "http://www.yiiframework.com/",
"role": "Founder and project lead"
}
],
"description": "The composer integration for the Yii framework",
"keywords": [
"composer",
"install",
"update",
"yii"
],
"time": "2013-05-23 19:12:45"
}
],
"packages-dev": [
],
"aliases": [
],
"minimum-stability": "dev",
"stability-flags": {
"yiisoft/yii2": 20,
"yiisoft/yii2-composer": 20
},
"platform": {
"php": ">=5.3.0"
},
"platform-dev": [
]
}

2
apps/basic/requirements.php

@ -11,7 +11,7 @@
*/ */
// you may need to adjust this path to the correct Yii framework path // you may need to adjust this path to the correct Yii framework path
$frameworkPath = dirname(__FILE__) . '/vendor/yiisoft/yii2/yii.'; $frameworkPath = dirname(__FILE__) . '/vendor/yiisoft/yii2/yii';
if (!is_dir($frameworkPath)) { if (!is_dir($frameworkPath)) {
echo '<h1>Error</h1>'; echo '<h1>Error</h1>';

12
apps/basic/views/site/contact.php

@ -31,15 +31,9 @@ $this->params['breadcrumbs'][] = $this->title;
<?php echo $form->field($model, 'email')->textInput(); ?> <?php echo $form->field($model, 'email')->textInput(); ?>
<?php echo $form->field($model, 'subject')->textInput(); ?> <?php echo $form->field($model, 'subject')->textInput(); ?>
<?php echo $form->field($model, 'body')->textArea(array('rows' => 6)); ?> <?php echo $form->field($model, 'body')->textArea(array('rows' => 6)); ?>
<?php <?php echo $form->field($model, 'verifyCode')->widget(Captcha::className(), array(
$field = $form->field($model, 'verifyCode'); 'options' => array('class' => 'input-medium'),
echo $field->begin() )); ?>
. $field->label()
. Captcha::widget()
. Html::activeTextInput($model, 'verifyCode', array('class' => 'input-medium'))
. $field->error()
. $field->end();
?>
<div class="form-actions"> <div class="form-actions">
<?php echo Html::submitButton('Submit', null, null, array('class' => 'btn btn-primary')); ?> <?php echo Html::submitButton('Submit', null, null, array('class' => 'btn btn-primary')); ?>
</div> </div>

32
apps/benchmark/LICENSE.md

@ -0,0 +1,32 @@
The Yii framework is free software. It is released under the terms of
the following BSD License.
Copyright © 2008-2013 by Yii Software LLC (http://www.yiisoft.com)
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
* Neither the name of Yii Software LLC nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

56
apps/benchmark/README.md

@ -0,0 +1,56 @@
Yii 2 Benchmark Application
===========================
**NOTE** Yii 2 and the relevant applications and extensions are still under heavy
development. We may make significant changes without prior notices. Please do not
use them for production. Please consider using [Yii v1.1](https://github.com/yiisoft/yii)
if you have a project to be deployed for production soon.
Yii 2 Benchmark Application is an application built to demonstrate the minimal overhead
introduced by the Yii framework. The application contains a single page which only renders
the "hello world" string.
The application attempts to simulate the scenario in which you can achieve the best performance
when using Yii. It does so by assuming that both of the main application configuration and the page
content are cached in memory, and the application enables pretty URLs.
DIRECTORY STRUCTURE
-------------------
protected/ contains application source code
controllers/ contains Web controller classes
index.php the entry script
REQUIREMENTS
------------
The minimum requirement by Yii is that your Web server supports PHP 5.3.?.
INSTALLATION
------------
If you do not have [Composer](http://getcomposer.org/), you may download it from
[http://getcomposer.org/](http://getcomposer.org/) or run the following command on Linux/Unix/MacOS:
~~~
curl -s http://getcomposer.org/installer | php
~~~
You can then install the Bootstrap Application using the following command:
~~~
php composer.phar create-project --stability=dev yiisoft/yii2-app-benchmark yii-benchmark
~~~
Now you should be able to access the benchmark page using the URL
~~~
http://localhost/yii-benchmark/index.php/site/hello
~~~
In the above, we assume `yii-benchmark` is directly under the document root of your Web server.

23
apps/benchmark/composer.json

@ -0,0 +1,23 @@
{
"name": "yiisoft/yii2-app-benchmark",
"description": "Yii 2 Benchmark Application",
"keywords": ["yii", "framework", "benchmark", "application"],
"homepage": "http://www.yiiframework.com/",
"type": "project",
"license": "BSD-3-Clause",
"support": {
"issues": "https://github.com/yiisoft/yii2/issues?state=open",
"forum": "http://www.yiiframework.com/forum/",
"wiki": "http://www.yiiframework.com/wiki/",
"irc": "irc://irc.freenode.net/yii",
"source": "https://github.com/yiisoft/yii2"
},
"config": {
"vendor-dir": "protected/vendor"
},
"minimum-stability": "dev",
"require": {
"php": ">=5.3.0",
"yiisoft/yii2": "dev-master"
}
}

119
apps/benchmark/composer.lock generated

@ -0,0 +1,119 @@
{
"_readme": [
"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": "25a338a613af40ef87f2e280057f0d8c",
"packages": [
{
"name": "yiisoft/yii2",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/yiisoft/yii2-framework.git",
"reference": "2d93f20ba6044ac3f1957300c4ae85fd98ecf47d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/2d93f20ba6044ac3f1957300c4ae85fd98ecf47d",
"reference": "2d93f20ba6044ac3f1957300c4ae85fd98ecf47d",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"lib-pcre": "*",
"php": ">=5.3.7"
},
"suggest": {
"ezyang/htmlpurifier": "Required by HtmlPurifier.",
"michelf/php-markdown": "Required by Markdown.",
"smarty/smarty": "Required by SmartyViewRenderer.",
"twig/twig": "Required by TwigViewRenderer."
},
"type": "library",
"autoload": {
"psr-0": {
"yii\\": "/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Qiang Xue",
"email": "qiang.xue@gmail.com",
"homepage": "http://www.yiiframework.com/",
"role": "Founder and project lead"
},
{
"name": "Alexander Makarov",
"email": "sam@rmcreative.ru",
"homepage": "http://rmcreative.ru/",
"role": "Core framework development"
},
{
"name": "Maurizio Domba",
"homepage": "http://mdomba.info/",
"role": "Core framework development"
},
{
"name": "Carsten Brandt",
"email": "mail@cebe.cc",
"homepage": "http://cebe.cc/",
"role": "Core framework development"
},
{
"name": "Wei Zhuo",
"email": "weizhuo@gmail.com",
"role": "Project site maintenance and development"
},
{
"name": "Sebastián Thierer",
"email": "sebas@artfos.com",
"role": "Component development"
},
{
"name": "Jeffrey Winesett",
"email": "jefftulsa@gmail.com",
"role": "Documentation and marketing"
},
{
"name": "Timur Ruziev",
"email": "resurtm@gmail.com",
"homepage": "http://resurtm.com/",
"role": "Core framework development"
},
{
"name": "Paul Klimov",
"email": "klimov.paul@gmail.com",
"role": "Core framework development"
}
],
"description": "Yii2 Web Programming Framework",
"homepage": "http://www.yiiframework.com/",
"keywords": [
"framework",
"yii"
],
"time": "2013-05-29 02:55:14"
}
],
"packages-dev": [
],
"aliases": [
],
"minimum-stability": "dev",
"stability-flags": {
"yiisoft/yii2": 20
},
"platform": {
"php": ">=5.3.0"
},
"platform-dev": [
]
}

18
apps/benchmark/index.php

@ -0,0 +1,18 @@
<?php
defined('YII_DEBUG') or define('YII_DEBUG', false);
require(__DIR__ . '/protected/vendor/yiisoft/yii2/yii/Yii.php');
$config = array(
'id' => 'benchmark',
'basePath' => __DIR__ . '/protected',
'components' => array(
'urlManager' => array(
'enablePrettyUrl' => true,
),
)
);
$application = new yii\web\Application($config);
$application->run();

1
apps/benchmark/protected/.htaccess

@ -0,0 +1 @@
deny from all

13
apps/benchmark/protected/controllers/SiteController.php

@ -0,0 +1,13 @@
<?php
use yii\web\Controller;
class SiteController extends Controller
{
public $defaultAction = 'hello';
public function actionHello()
{
echo 'hello world';
}
}

2
apps/benchmark/protected/vendor/.gitignore vendored

@ -0,0 +1,2 @@
*
!.gitignore

32
docs/guide/upgrade-from-v1.md

@ -13,7 +13,7 @@ The most obvious change in Yii 2.0 is the use of namespaces. Almost every core c
is namespaced, e.g., `yii\web\Request`. The "C" prefix is no longer used in class names. is namespaced, e.g., `yii\web\Request`. The "C" prefix is no longer used in class names.
The naming of the namespaces follows the directory structure. For example, `yii\web\Request` The naming of the namespaces follows the directory structure. For example, `yii\web\Request`
indicates the corresponding class file is `web/Request.php` under the Yii framework folder. indicates the corresponding class file is `web/Request.php` under the Yii framework folder.
You can use any core class without explicitly include that class file, thanks to the Yii You can use any core class without explicitly including that class file, thanks to the Yii
class loader. class loader.
@ -117,17 +117,17 @@ supported in most places in the Yii core code. For example, `FileCache::cachePat
both a path alias and a normal directory path. both a path alias and a normal directory path.
Path alias is also closely related with class namespaces. It is recommended that a path Path alias is also closely related with class namespaces. It is recommended that a path
alias defined for each root namespace so that you can use Yii class autoloader without alias be defined for each root namespace so that you can use Yii the class autoloader without
any further configuration. For example, because `@yii` refers to the Yii installation directory, any further configuration. For example, because `@yii` refers to the Yii installation directory,
a class like `yii\web\Request` can be autoloaded by Yii. If you use a third party library a class like `yii\web\Request` can be autoloaded by Yii. If you use a third party library
such as Zend Framework, you may define a path alias `@Zend` which refers to its installation directory. such as Zend Framework, you may define a path alias `@Zend` which refers to its installation
And Yii will be able to autoload any class in this library. directory and Yii will be able to autoload any class in this library.
View View
---- ----
Yii 2.0 introduces a `View` class to represent the view part in the MVC pattern. Yii 2.0 introduces a `View` class to represent the view part of the MVC pattern.
It can be configured globally through the "view" application component. It is also It can be configured globally through the "view" application component. It is also
accessible in any view file via `$this`. This is one of the biggest changes compared to 1.1: accessible in any view file via `$this`. This is one of the biggest changes compared to 1.1:
**`$this` in a view file no longer refers to the controller or widget object.** **`$this` in a view file no longer refers to the controller or widget object.**
@ -159,7 +159,7 @@ extension for your Smarty views, or `twig` for Twig views. You may also configur
Models Models
------ ------
A model is now associated with a form name returned its `formName()` method. This is A model is now associated with a form name returned by its `formName()` method. This is
mainly used when using HTML forms to collect user inputs for a model. Previously in 1.1, mainly used when using HTML forms to collect user inputs for a model. Previously in 1.1,
this is usually hardcoded as the class name of the model. this is usually hardcoded as the class name of the model.
@ -235,7 +235,7 @@ Previously in 1.1, you would have to enter the widget class names as strings via
Themes Themes
------ ------
Theme works completely different in 2.0. It is now based on a path map to "translate" a source Themes work completely different in 2.0. They are now based on a path map to "translate" a source
view into a themed view. For example, if the path map for a theme is view into a themed view. For example, if the path map for a theme is
`array('/www/views' => '/www/themes/basic')`, then the themed version for a view file `array('/www/views' => '/www/themes/basic')`, then the themed version for a view file
`/www/views/site/index.php` will be `/www/themes/basic/site/index.php`. `/www/views/site/index.php` will be `/www/themes/basic/site/index.php`.
@ -250,7 +250,7 @@ application component.
Console Applications Console Applications
-------------------- --------------------
Console applications are now composed by controllers, too, like Web applications. In fact, Console applications are now composed by controllers, like Web applications. In fact,
console controllers and Web controllers share the same base controller class. console controllers and Web controllers share the same base controller class.
Each console controller is like `CConsoleCommand` in 1.1. It consists of one or several Each console controller is like `CConsoleCommand` in 1.1. It consists of one or several
@ -300,7 +300,7 @@ public function behaviors()
Assets Assets
------ ------
Yii 2.0 introduces a new concept called *asset bundle*. It is a bit similar to script Yii 2.0 introduces a new concept called *asset bundle*. It is similar to script
packages (managed by `CClientScript`) in 1.1, but with better support. packages (managed by `CClientScript`) in 1.1, but with better support.
An asset bundle is a collection of asset files (e.g. JavaScript files, CSS files, image files, etc.) An asset bundle is a collection of asset files (e.g. JavaScript files, CSS files, image files, etc.)
@ -315,7 +315,7 @@ Static Helpers
Yii 2.0 introduces many commonly used static helper classes, such as `Html`, `ArrayHelper`, Yii 2.0 introduces many commonly used static helper classes, such as `Html`, `ArrayHelper`,
`StringHelper`. These classes are designed to be easily extended. Note that static classes `StringHelper`. These classes are designed to be easily extended. Note that static classes
are usually hard to be extended because of the fixed class name references. But Yii 2.0 are usually hard to extend because of the fixed class name references. But Yii 2.0
introduces the class map (via `Yii::$classMap`) to overcome this difficulty. introduces the class map (via `Yii::$classMap`) to overcome this difficulty.
@ -343,7 +343,7 @@ Query Builder
In 1.1, query building is scattered among several classes, including `CDbCommand`, In 1.1, query building is scattered among several classes, including `CDbCommand`,
`CDbCriteria`, and `CDbCommandBuilder`. Yii 2.0 uses `Query` to represent a DB query `CDbCriteria`, and `CDbCommandBuilder`. Yii 2.0 uses `Query` to represent a DB query
and `QueryBuilder` to generate SQL statements from query objects. For example, and `QueryBuilder` to generate SQL statements from query objects. For example:
```php ```php
$query = new \yii\db\Query; $query = new \yii\db\Query;
@ -365,7 +365,7 @@ ActiveRecord
------------ ------------
ActiveRecord has undergone significant changes in Yii 2.0. The most important one ActiveRecord has undergone significant changes in Yii 2.0. The most important one
is about relational ActiveRecord query. In 1.1, you have to declare the relations is the relational ActiveRecord query. In 1.1, you have to declare the relations
in the `relations()` method. In 2.0, this is done via getter methods that return in the `relations()` method. In 2.0, this is done via getter methods that return
an `ActiveQuery` object. For example, the following method declares an "orders" relation: an `ActiveQuery` object. For example, the following method declares an "orders" relation:
@ -392,7 +392,7 @@ by filtering with the primary keys of the primary records.
Yii 2.0 no longer uses the `model()` method when performing queries. Instead, you Yii 2.0 no longer uses the `model()` method when performing queries. Instead, you
use the `find()` method like the following: use the `find()` method:
```php ```php
// to retrieve all *active* customers and order them by their ID: // to retrieve all *active* customers and order them by their ID:
@ -410,14 +410,14 @@ Therefore, you can use all query methods of `Query`.
Instead of returning ActiveRecord objects, you may call `ActiveQuery::asArray()` to Instead of returning ActiveRecord objects, you may call `ActiveQuery::asArray()` to
return results in terms of arrays. This is more efficient and is especially useful return results in terms of arrays. This is more efficient and is especially useful
when you need to return large number of records. For example, when you need to return a large number of records:
```php ```php
$customers = Customer::find()->asArray()->all(); $customers = Customer::find()->asArray()->all();
``` ```
By default, ActiveRecord now only saves dirty attributes. In 1.1, all attributes By default, ActiveRecord now only saves dirty attributes. In 1.1, all attributes
would be saved to database when you call `save()`, regardless they are changed or not, are saved to database when you call `save()`, regardless of having changed or not,
unless you explicitly list the attributes to save. unless you explicitly list the attributes to save.
@ -427,7 +427,7 @@ Auto-quoting Table and Column Names
Yii 2.0 supports automatic quoting of database table and column names. A name enclosed Yii 2.0 supports automatic quoting of database table and column names. A name enclosed
within double curly brackets is treated as a table name, and a name enclosed within within double curly brackets is treated as a table name, and a name enclosed within
double square brackets is treated as a column name. They will be quoted according to double square brackets is treated as a column name. They will be quoted according to
the database driver being used. For example, the database driver being used:
```php ```php
$command = $connection->createCommand('SELECT [[id]] FROM {{posts}}'); $command = $connection->createCommand('SELECT [[id]] FROM {{posts}}');

7
framework/yii/assets.php

@ -48,4 +48,11 @@ return array(
YII_DEBUG ? 'punycode/punycode.js' : 'punycode/punycode.min.js', YII_DEBUG ? 'punycode/punycode.js' : 'punycode/punycode.min.js',
), ),
), ),
'yii/maskedinput' => array(
'sourcePath' => __DIR__ . '/assets',
'js' => array(
'jquery.maskedinput.js',
),
'depends' => array('yii/jquery'),
),
); );

338
framework/yii/assets/jquery.maskedinput.js

@ -0,0 +1,338 @@
/*
Masked Input plugin for jQuery
Copyright (c) 2007-2013 Josh Bush (digitalbush.com)
Licensed under the MIT license (http://digitalbush.com/projects/masked-input-plugin/#license)
Version: 1.3.1
*/
(function($) {
function getPasteEvent() {
var el = document.createElement('input'),
name = 'onpaste';
el.setAttribute(name, '');
return (typeof el[name] === 'function')?'paste':'input';
}
var pasteEventName = getPasteEvent() + ".mask",
ua = navigator.userAgent,
iPhone = /iphone/i.test(ua),
android=/android/i.test(ua),
caretTimeoutId;
$.mask = {
//Predefined character definitions
definitions: {
'9': "[0-9]",
'a': "[A-Za-z]",
'*': "[A-Za-z0-9]"
},
dataName: "rawMaskFn",
placeholder: '_',
};
$.fn.extend({
//Helper Function for Caret positioning
caret: function(begin, end) {
var range;
if (this.length === 0 || this.is(":hidden")) {
return;
}
if (typeof begin == 'number') {
end = (typeof end === 'number') ? end : begin;
return this.each(function() {
if (this.setSelectionRange) {
this.setSelectionRange(begin, end);
} else if (this.createTextRange) {
range = this.createTextRange();
range.collapse(true);
range.moveEnd('character', end);
range.moveStart('character', begin);
range.select();
}
});
} else {
if (this[0].setSelectionRange) {
begin = this[0].selectionStart;
end = this[0].selectionEnd;
} else if (document.selection && document.selection.createRange) {
range = document.selection.createRange();
begin = 0 - range.duplicate().moveStart('character', -100000);
end = begin + range.text.length;
}
return { begin: begin, end: end };
}
},
unmask: function() {
return this.trigger("unmask");
},
mask: function(mask, settings) {
var input,
defs,
tests,
partialPosition,
firstNonMaskPos,
len;
if (!mask && this.length > 0) {
input = $(this[0]);
return input.data($.mask.dataName)();
}
settings = $.extend({
placeholder: $.mask.placeholder, // Load default placeholder
completed: null
}, settings);
defs = $.mask.definitions;
tests = [];
partialPosition = len = mask.length;
firstNonMaskPos = null;
$.each(mask.split(""), function(i, c) {
if (c == '?') {
len--;
partialPosition = i;
} else if (defs[c]) {
tests.push(new RegExp(defs[c]));
if (firstNonMaskPos === null) {
firstNonMaskPos = tests.length - 1;
}
} else {
tests.push(null);
}
});
return this.trigger("unmask").each(function() {
var input = $(this),
buffer = $.map(
mask.split(""),
function(c, i) {
if (c != '?') {
return defs[c] ? settings.placeholder : c;
}
}),
focusText = input.val();
function seekNext(pos) {
while (++pos < len && !tests[pos]);
return pos;
}
function seekPrev(pos) {
while (--pos >= 0 && !tests[pos]);
return pos;
}
function shiftL(begin,end) {
var i,
j;
if (begin<0) {
return;
}
for (i = begin, j = seekNext(end); i < len; i++) {
if (tests[i]) {
if (j < len && tests[i].test(buffer[j])) {
buffer[i] = buffer[j];
buffer[j] = settings.placeholder;
} else {
break;
}
j = seekNext(j);
}
}
writeBuffer();
input.caret(Math.max(firstNonMaskPos, begin));
}
function shiftR(pos) {
var i,
c,
j,
t;
for (i = pos, c = settings.placeholder; i < len; i++) {
if (tests[i]) {
j = seekNext(i);
t = buffer[i];
buffer[i] = c;
if (j < len && tests[j].test(t)) {
c = t;
} else {
break;
}
}
}
}
function keydownEvent(e) {
var k = e.which,
pos,
begin,
end;
//backspace, delete, and escape get special treatment
if (k === 8 || k === 46 || (iPhone && k === 127)) {
pos = input.caret();
begin = pos.begin;
end = pos.end;
if (end - begin === 0) {
begin=k!==46?seekPrev(begin):(end=seekNext(begin-1));
end=k===46?seekNext(end):end;
}
clearBuffer(begin, end);
shiftL(begin, end - 1);
e.preventDefault();
} else if (k == 27) {//escape
input.val(focusText);
input.caret(0, checkVal());
e.preventDefault();
}
}
function keypressEvent(e) {
var k = e.which,
pos = input.caret(),
p,
c,
next;
if (e.ctrlKey || e.altKey || e.metaKey || k < 32) {//Ignore
return;
} else if (k) {
if (pos.end - pos.begin !== 0){
clearBuffer(pos.begin, pos.end);
shiftL(pos.begin, pos.end-1);
}
p = seekNext(pos.begin - 1);
if (p < len) {
c = String.fromCharCode(k);
if (tests[p].test(c)) {
shiftR(p);
buffer[p] = c;
writeBuffer();
next = seekNext(p);
if(android){
setTimeout($.proxy($.fn.caret,input,next),0);
}else{
input.caret(next);
}
if (settings.completed && next >= len) {
settings.completed.call(input);
}
}
}
e.preventDefault();
}
}
function clearBuffer(start, end) {
var i;
for (i = start; i < end && i < len; i++) {
if (tests[i]) {
buffer[i] = settings.placeholder;
}
}
}
function writeBuffer() { input.val(buffer.join('')); }
function checkVal(allow) {
//try to place characters where they belong
var test = input.val(),
lastMatch = -1,
i,
c;
for (i = 0, pos = 0; i < len; i++) {
if (tests[i]) {
buffer[i] = settings.placeholder;
while (pos++ < test.length) {
c = test.charAt(pos - 1);
if (tests[i].test(c)) {
buffer[i] = c;
lastMatch = i;
break;
}
}
if (pos > test.length) {
break;
}
} else if (buffer[i] === test.charAt(pos) && i !== partialPosition) {
pos++;
lastMatch = i;
}
}
if (allow) {
writeBuffer();
} else if (lastMatch + 1 < partialPosition) {
input.val("");
clearBuffer(0, len);
} else {
writeBuffer();
input.val(input.val().substring(0, lastMatch + 1));
}
return (partialPosition ? i : firstNonMaskPos);
}
input.data($.mask.dataName,function(){
return $.map(buffer, function(c, i) {
return tests[i]&&c!=settings.placeholder ? c : null;
}).join('');
});
if (!input.attr("readonly"))
input
.one("unmask", function() {
input
.unbind(".mask")
.removeData($.mask.dataName);
})
.bind("focus.mask", function() {
clearTimeout(caretTimeoutId);
var pos,
moveCaret;
focusText = input.val();
pos = checkVal();
caretTimeoutId = setTimeout(function(){
writeBuffer();
if (pos == mask.length) {
input.caret(0, pos);
} else {
input.caret(pos);
}
}, 10);
})
.bind("blur.mask", function() {
checkVal();
if (input.val() != focusText)
input.change();
})
.bind("keydown.mask", keydownEvent)
.bind("keypress.mask", keypressEvent)
.bind(pasteEventName, function() {
setTimeout(function() {
var pos=checkVal(true);
input.caret(pos);
if (settings.completed && pos == input.val().length)
settings.completed.call(input);
}, 0);
});
checkVal(); //Perform initial check for existing values
});
}
});
})(jQuery);

4
framework/yii/assets/yii.validation.js

@ -87,8 +87,8 @@ yii.validation = (function ($) {
if (options.skipOnEmpty && isEmpty(value)) { if (options.skipOnEmpty && isEmpty(value)) {
return; return;
} }
var valid = !options.not && $.inArray(value, options.range) var valid = !options.not && $.inArray(value, options.range) > -1
|| options.not && !$.inArray(value, options.range); || options.not && $.inArray(value, options.range) == -1;
if (!valid) { if (!valid) {
messages.push(options.message); messages.push(options.message);

4
framework/yii/base/ActionFilter.php

@ -30,8 +30,8 @@ class ActionFilter extends Behavior
public function events() public function events()
{ {
return array( return array(
'beforeAction' => 'beforeFilter', Controller::EVENT_BEFORE_ACTION => 'beforeFilter',
'afterAction' => 'afterFilter', Controller::EVENT_AFTER_ACTION => 'afterFilter',
); );
} }

15
framework/yii/base/Application.php

@ -207,7 +207,8 @@ class Application extends Module
/** /**
* Returns the directory that stores vendor files. * Returns the directory that stores vendor files.
* @return string the directory that stores vendor files. Defaults to 'protected/vendor'. * @return string the directory that stores vendor files.
* Defaults to 'vendor' directory under applications [[basePath]].
*/ */
public function getVendorPath() public function getVendorPath()
{ {
@ -278,6 +279,15 @@ class Application extends Module
} }
/** /**
* Returns the formatter component.
* @return \yii\base\Formatter the formatter application component.
*/
public function getFormatter()
{
return $this->getComponent('formatter');
}
/**
* Returns the request component. * Returns the request component.
* @return \yii\web\Request|\yii\console\Request the request component * @return \yii\web\Request|\yii\console\Request the request component
*/ */
@ -332,6 +342,9 @@ class Application extends Module
'errorHandler' => array( 'errorHandler' => array(
'class' => 'yii\base\ErrorHandler', 'class' => 'yii\base\ErrorHandler',
), ),
'formatter' => array(
'class' => 'yii\base\Formatter',
),
'i18n' => array( 'i18n' => array(
'class' => 'yii\i18n\I18N', 'class' => 'yii\i18n\I18N',
), ),

44
framework/yii/base/Controller.php

@ -151,43 +151,13 @@ class Controller extends Component
/** /**
* Binds the parameters to the action. * Binds the parameters to the action.
* This method is invoked by [[Action]] when it begins to run with the given parameters. * This method is invoked by [[Action]] when it begins to run with the given parameters.
* This method will check the parameter names that the action requires and return
* the provided parameters according to the requirement. If there is any missing parameter,
* an exception will be thrown.
* @param Action $action the action to be bound with parameters * @param Action $action the action to be bound with parameters
* @param array $params the parameters to be bound to the action * @param array $params the parameters to be bound to the action
* @return array the valid parameters that the action can run with. * @return array the valid parameters that the action can run with.
* @throws InvalidRequestException if there are missing parameters.
*/ */
public function bindActionParams($action, $params) public function bindActionParams($action, $params)
{ {
if ($action instanceof InlineAction) { return array();
$method = new \ReflectionMethod($this, $action->actionMethod);
} else {
$method = new \ReflectionMethod($action, 'run');
}
$args = array();
$missing = array();
foreach ($method->getParameters() as $param) {
$name = $param->getName();
if (array_key_exists($name, $params)) {
$args[] = $params[$name];
unset($params[$name]);
} elseif ($param->isDefaultValueAvailable()) {
$args[] = $param->getDefaultValue();
} else {
$missing[] = $name;
}
}
if (!empty($missing)) {
throw new InvalidRequestException(Yii::t('yii', 'Missing required parameters: {params}', array(
'{params}' => implode(', ', $missing),
)));
}
return $args;
} }
/** /**
@ -272,18 +242,6 @@ class Controller extends Component
} }
/** /**
* Validates the parameter being bound to actions.
* This method is invoked when parameters are being bound to the currently requested action.
* Child classes may override this method to throw exceptions when there are missing and/or unknown parameters.
* @param Action $action the currently requested action
* @param array $missingParams the names of the missing parameters
* @param array $unknownParams the unknown parameters (name => value)
*/
public function validateActionParams($action, $missingParams, $unknownParams)
{
}
/**
* @return string the controller ID that is prefixed with the module ID (if any). * @return string the controller ID that is prefixed with the module ID (if any).
*/ */
public function getUniqueId() public function getUniqueId()

292
framework/yii/base/Formatter.php

@ -0,0 +1,292 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\base;
use Yii;
use DateTime;
use yii\helpers\HtmlPurifier;
use yii\helpers\Html;
/**
* Formatter provides a set of commonly used data formatting methods.
*
* The formatting methods provided by Formatter are all named in the form of `asXyz()`.
* The behavior of some of them may be configured via the properties of Formatter. For example,
* by configuring [[dateFormat]], one may control how [[asDate()]] formats the value into a date string.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Formatter extends Component
{
/**
* @var string the default format string to be used to format a date using PHP date() function.
*/
public $dateFormat = 'Y/m/d';
/**
* @var string the default format string to be used to format a time using PHP date() function.
*/
public $timeFormat = 'h:i:s A';
/**
* @var string the default format string to be used to format a date and time using PHP date() function.
*/
public $datetimeFormat = 'Y/m/d h:i:s A';
/**
* @var array the text to be displayed when formatting a boolean value. The first element corresponds
* to the text display for false, the second element for true. Defaults to <code>array('No', 'Yes')</code>.
*/
public $booleanFormat;
/**
* @var string the character displayed as the decimal point when formatting a number.
*/
public $decimalSeparator = '.';
/**
* @var string the character displayed as the thousands separator character when formatting a number.
*/
public $thousandSeparator = ',';
/**
* Initializes the component.
*/
public function init()
{
if (empty($this->booleanFormat)) {
$this->booleanFormat = array(Yii::t('yii', 'No'), Yii::t('yii', 'Yes'));
}
}
/**
* Formats the value as is without any formatting.
* This method simply returns back the parameter without any format.
* @param mixed $value the value to be formatted
* @return string the formatted result
*/
public function asRaw($value)
{
return $value;
}
/**
* Formats the value as an HTML-encoded plain text.
* @param mixed $value the value to be formatted
* @return string the formatted result
*/
public function asText($value)
{
return Html::encode($value);
}
/**
* Formats the value as an HTML-encoded plain text with newlines converted into breaks.
* @param mixed $value the value to be formatted
* @return string the formatted result
*/
public function asNtext($value)
{
return nl2br(Html::encode($value));
}
/**
* Formats the value as HTML-encoded text paragraphs.
* Each text paragraph is enclosed within a `<p>` tag.
* One or multiple consecutive empty lines divide two paragraphs.
* @param mixed $value the value to be formatted
* @return string the formatted result
*/
public function asParagraphs($value)
{
return str_replace('<p></p>', '',
'<p>' . preg_replace('/[\r\n]{2,}/', "</p>\n<p>", Html::encode($value)) . '</p>'
);
}
/**
* Formats the value as HTML text.
* The value will be purified using [[HtmlPurifier]] to avoid XSS attacks.
* Use [[asRaw()]] if you do not want any purification of the value.
* @param mixed $value the value to be formatted
* @param array|null $config the configuration for the HTMLPurifier class.
* @return string the formatted result
*/
public function asHtml($value, $config = null)
{
return HtmlPurifier::process($value, $config);
}
/**
* Formats the value as a mailto link.
* @param mixed $value the value to be formatted
* @return string the formatted result
*/
public function asEmail($value)
{
return Html::mailto($value);
}
/**
* Formats the value as an image tag.
* @param mixed $value the value to be formatted
* @return string the formatted result
*/
public function asImage($value)
{
return Html::img($value);
}
/**
* Formats the value as a hyperlink.
* @param mixed $value the value to be formatted
* @return string the formatted result
*/
public function asUrl($value)
{
$url = $value;
if (strpos($url, 'http://') !== 0 && strpos($url, 'https://') !== 0) {
$url = 'http://' . $url;
}
return Html::a(Html::encode($value), $url);
}
/**
* Formats the value as a boolean.
* @param mixed $value the value to be formatted
* @return string the formatted result
* @see booleanFormat
*/
public function asBoolean($value)
{
return $value ? $this->booleanFormat[1] : $this->booleanFormat[0];
}
/**
* Formats the value as a date.
* @param integer|string|DateTime $value the value to be formatted. The following
* types of value are supported:
*
* - an integer representing a UNIX timestamp
* - a string that can be parsed into a UNIX timestamp via `strtotime()`
* - a PHP DateTime object
*
* @param string $format the format used to convert the value into a date string.
* If null, [[dateFormat]] will be used. The format string should be one
* that can be recognized by the PHP `date()` function.
* @return string the formatted result
* @see dateFormat
*/
public function asDate($value, $format = null)
{
$value = $this->normalizeDatetimeValue($value);
return date($format === null ? $this->dateFormat : $format, $value);
}
/**
* Formats the value as a time.
* @param integer|string|DateTime $value the value to be formatted. The following
* types of value are supported:
*
* - an integer representing a UNIX timestamp
* - a string that can be parsed into a UNIX timestamp via `strtotime()`
* - a PHP DateTime object
*
* @param string $format the format used to convert the value into a date string.
* If null, [[timeFormat]] will be used. The format string should be one
* that can be recognized by the PHP `date()` function.
* @return string the formatted result
* @see timeFormat
*/
public function asTime($value, $format = null)
{
$value = $this->normalizeDatetimeValue($value);
return date($format === null ? $this->timeFormat : $format, $value);
}
/**
* Formats the value as a datetime.
* @param integer|string|DateTime $value the value to be formatted. The following
* types of value are supported:
*
* - an integer representing a UNIX timestamp
* - a string that can be parsed into a UNIX timestamp via `strtotime()`
* - a PHP DateTime object
*
* @param string $format the format used to convert the value into a date string.
* If null, [[datetimeFormat]] will be used. The format string should be one
* that can be recognized by the PHP `date()` function.
* @return string the formatted result
* @see datetimeFormat
*/
public function asDatetime($value, $format = null)
{
$value = $this->normalizeDatetimeValue($value);
return date($format === null ? $this->datetimeFormat : $format, $value);
}
/**
* Normalizes the given datetime value as one that can be taken by various date/time formatting methods.
* @param mixed $value the datetime value to be normalized.
* @return mixed the normalized datetime value
*/
protected function normalizeDatetimeValue($value)
{
if (is_string($value)) {
if (ctype_digit($value) || $value[0] === '-' && ctype_digit(substr($value, 1))) {
return (int)$value;
} else {
return strtotime($value);
}
} elseif ($value instanceof DateTime) {
return $value->getTimestamp();
} else {
return (int)$value;
}
}
/**
* Formats the value as an integer.
* @param mixed $value the value to be formatted
* @return string the formatting result.
*/
public function asInteger($value)
{
if (is_string($value) && preg_match('/^(-?\d+)/', $value, $matches)) {
return $matches[1];
} else {
$value = (int)$value;
return "$value";
}
}
/**
* Formats the value as a double number.
* Property [[decimalSeparator]] will be used to represent the decimal point.
* @param mixed $value the value to be formatted
* @param integer $decimals the number of digits after the decimal point
* @return string the formatting result.
* @see decimalSeparator
*/
public function asDouble($value, $decimals = 2)
{
return str_replace('.', $this->decimalSeparator, sprintf("%.{$decimals}f", $value));
}
/**
* Formats the value as a number with decimal and thousand separators.
* This method calls the PHP number_format() function to do the formatting.
* @param mixed $value the value to be formatted
* @param integer $decimals the number of digits after the decimal point
* @return string the formatted result
* @see decimalSeparator
* @see thousandSeparator
*/
public function asNumber($value, $decimals = 0)
{
return number_format($value, $decimals, $this->decimalSeparator, $this->thousandSeparator);
}
}

26
framework/yii/base/InvalidRequestException.php

@ -1,26 +0,0 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\base;
/**
* InvalidRequestException represents an exception caused by incorrect end user request.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class InvalidRequestException extends UserException
{
/**
* @return string the user-friendly name of this exception
*/
public function getName()
{
return \Yii::t('yii', 'Invalid Request');
}
}

2
framework/yii/base/View.php

@ -661,6 +661,8 @@ class View extends Component
/** /**
* Registers a JS file. * Registers a JS file.
* Please note that when this file depends on other JS files to be registered before,
* for example jQuery, you should use [[registerAssetBundle]] instead.
* @param string $url the JS file to be registered. * @param string $url the JS file to be registered.
* @param array $options the HTML attributes for the script tag. A special option * @param array $options the HTML attributes for the script tag. A special option
* named "position" is supported which specifies where the JS script tag should be inserted * named "position" is supported which specifies where the JS script tag should be inserted

8
framework/yii/behaviors/AutoTimestamp.php

@ -83,17 +83,17 @@ class AutoTimestamp extends Behavior
*/ */
public function updateTimestamp($attributes) public function updateTimestamp($attributes)
{ {
$timestamp = $this->evaluateTimestamp();
foreach ($attributes as $attribute) { foreach ($attributes as $attribute) {
$this->owner->$attribute = $this->evaluateTimestamp($attribute); $this->owner->$attribute = $timestamp;
} }
} }
/** /**
* Gets the appropriate timestamp for the specified attribute. * Gets the current timestamp.
* @param string $attribute attribute name
* @return mixed the timestamp value * @return mixed the timestamp value
*/ */
protected function evaluateTimestamp($attribute) protected function evaluateTimestamp()
{ {
if ($this->timestamp instanceof Expression) { if ($this->timestamp instanceof Expression) {
return $this->timestamp; return $this->timestamp;

2
framework/yii/bootstrap/Carousel.php

@ -8,7 +8,7 @@
namespace yii\bootstrap; namespace yii\bootstrap;
use yii\base\InvalidConfigException; use yii\base\InvalidConfigException;
use yii\helpers\base\ArrayHelper; use yii\helpers\ArrayHelper;
use yii\helpers\Html; use yii\helpers\Html;
/** /**

2
framework/yii/bootstrap/Collapse.php

@ -8,7 +8,7 @@
namespace yii\bootstrap; namespace yii\bootstrap;
use yii\base\InvalidConfigException; use yii\base\InvalidConfigException;
use yii\helpers\base\ArrayHelper; use yii\helpers\ArrayHelper;
use yii\helpers\Html; use yii\helpers\Html;
/** /**

138
framework/yii/bootstrap/Nav.php

@ -0,0 +1,138 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\bootstrap;
use yii\base\InvalidConfigException;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
/**
* Nav renders a nav HTML component.
*
* For example:
*
* ```php
* echo Nav::widget(array(
* 'items' => array(
* array(
* 'label' => 'Home',
* 'url' => '/',
* 'linkOptions' => array(...),
* 'active' => true,
* ),
* array(
* 'label' => 'Dropdown',
* 'items' => array(
* array(
* 'label' => 'DropdownA',
* 'url' => '#',
* ),
* array(
* 'label' => 'DropdownB',
* 'url' => '#',
* ),
* ),
* ),
* ),
* ));
* ```
*
* @see http://twitter.github.io/bootstrap/components.html#nav
* @author Antonio Ramirez <amigo.cobos@gmail.com>
* @since 2.0
*/
class Nav extends Widget
{
/**
* @var array list of items in the nav widget. Each array element represents a single
* menu item with the following structure:
*
* - label: string, required, the nav item label.
* - url: optional, the item's URL. Defaults to "#".
* - linkOptions: array, optional, the HTML attributes of the item's link.
* - options: array, optional, the HTML attributes of the item container (LI).
* - active: boolean, optional, whether the item should be on active state or not.
* - dropdown: array|string, optional, the configuration array for creating a [[Dropdown]] widget,
* or a string representing the dropdown menu. Note that Bootstrap does not support sub-dropdown menus.
*/
public $items = array();
/**
* @var boolean whether the nav items labels should be HTML-encoded.
*/
public $encodeLabels = true;
/**
* Initializes the widget.
*/
public function init()
{
parent::init();
$this->addCssClass($this->options, 'nav');
}
/**
* Renders the widget.
*/
public function run()
{
echo $this->renderItems();
$this->getView()->registerAssetBundle('yii/bootstrap');
}
/**
* Renders widget items.
*/
public function renderItems()
{
$items = array();
foreach ($this->items as $item) {
$items[] = $this->renderItem($item);
}
return Html::tag('ul', implode("\n", $items), $this->options);
}
/**
* Renders a widget's item.
* @param mixed $item the item to render.
* @return string the rendering result.
* @throws InvalidConfigException
*/
public function renderItem($item)
{
if (is_string($item)) {
return $item;
}
if (!isset($item['label'])) {
throw new InvalidConfigException("The 'label' option is required.");
}
$label = $this->encodeLabels ? Html::encode($item['label']) : $item['label'];
$options = ArrayHelper::getValue($item, 'options', array());
$dropdown = ArrayHelper::getValue($item, 'dropdown');
$url = Html::url(ArrayHelper::getValue($item, 'url', '#'));
$linkOptions = ArrayHelper::getValue($item, 'linkOptions', array());
if(ArrayHelper::getValue($item, 'active')) {
$this->addCssClass($options, 'active');
}
if ($dropdown !== null) {
$linkOptions['data-toggle'] = 'dropdown';
$this->addCssClass($options, 'dropdown');
$this->addCssClass($urlOptions, 'dropdown-toggle');
$label .= ' ' . Html::tag('b', '', array('class' => 'caret'));
if (is_array($dropdown)) {
$dropdown['clientOptions'] = false;
$dropdown = Dropdown::widget($dropdown);
}
}
return Html::tag('li', Html::a($label, $url, $linkOptions) . $dropdown, $options);
}
}

188
framework/yii/bootstrap/NavBar.php

@ -0,0 +1,188 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\bootstrap;
use yii\base\InvalidConfigException;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
/**
* NavBar renders a navbar HTML component.
*
* For example:
*
* ```php
* echo NavBar::widget(array(
* 'brandLabel' => 'NavBar Test',
* 'items' => array(
* // a Nav widget
* array(
* // defaults to Nav anyway.
* 'class' => 'yii\bootstrap\Nav',
* // widget configuration
* 'options' => array(
* 'items' => array(
* array(
* 'label' => 'Home',
* 'url' => '/',
* 'options' => array('class' => 'active'),
* ),
* array(
* 'label' => 'Dropdown',
* 'content' => new Dropdown(array(
* 'items' => array(
* array(
* 'label' => 'DropdownA',
* 'url' => '#',
* ),
* array(
* 'label' => 'DropdownB',
* 'url' => '#'
* ),
* )
* ),
* ),
* )
* ),
* ),
* // you can also use strings
* '<form class="navbar-search pull-left" action="">' .
* '<input type="text" class="search-query" placeholder="Search">' .
* '</form>',
* ),
* ));
* ```
*
* @see http://twitter.github.io/bootstrap/components.html#navbar
* @author Antonio Ramirez <amigo.cobos@gmail.com>
* @since 2.0
*/
class NavBar extends Widget
{
/**
* @var string the text of the brand.
* @see http://twitter.github.io/bootstrap/components.html#navbar
*/
public $brandLabel;
/**
* @param array|string $url the URL for the brand's hyperlink tag. This parameter will be processed by [[Html::url()]]
* and will be used for the "href" attribute of the brand link. Defaults to site root.
*/
public $brandUrl = '/';
/**
* @var array the HTML attributes of the brand link.
*/
public $brandOptions = array();
/**
* @var array list of menu items in the navbar widget. Each array element represents a single
* menu item with the following structure:
*
* ```php
* array(
* // optional, the menu item class type of the widget to render. Defaults to "Nav" widget.
* 'class' => 'Menu item class type',
* // required, the configuration options of the widget.
* 'options'=> array(...),
* ),
* // optionally, you can pass a string
* '<form class="navbar-search pull-left" action="">' .
* '<input type="text" class="search-query span2" placeholder="Search">' .
* '</form>',
* ```
*
* Optionally, you can also use a plain string instead of an array element.
*/
public $items = array();
/**
* Initializes the widget.
*/
public function init()
{
parent::init();
$this->clientOptions = false;
$this->addCssClass($this->options, 'navbar');
$this->addCssClass($this->brandOptions, 'brand');
}
/**
* Renders the widget.
*/
public function run()
{
echo Html::beginTag('div', $this->options);
echo $this->renderItems();
echo Html::endTag('div');
$this->getView()->registerAssetBundle(self::$responsive ? 'yii/bootstrap/responsive' : 'yii/bootstrap');
}
/**
* Renders the items.
* @return string the rendering items.
*/
protected function renderItems()
{
$items = array();
foreach ($this->items as $item) {
$items[] = $this->renderItem($item);
}
$contents = implode("\n", $items);
$brand = Html::a($this->brandLabel, $this->brandUrl, $this->brandOptions);
if (self::$responsive) {
$this->getView()->registerAssetBundle('yii/bootstrap/collapse');
$contents = Html::tag('div',
$this->renderToggleButton() .
$brand . "\n" .
Html::tag('div', $contents, array('class' => 'nav-collapse collapse navbar-collapse')),
array('class' => 'container'));
} else {
$contents = $brand . "\n" . $contents;
}
return Html::tag('div', $contents, array('class' => 'navbar-inner'));
}
/**
* Renders a item. The item can be a string, a custom class or a Nav widget (defaults if no class specified.
* @param mixed $item the item to render. If array, it is assumed the configuration of a widget being `class`
* required and if not specified, then defaults to `yii\bootstrap\Nav`.
* @return string the rendering result.
* @throws InvalidConfigException
*/
protected function renderItem($item)
{
if (is_string($item)) {
return $item;
}
$config = ArrayHelper::getValue($item, 'options', array());
$config['clientOptions'] = false;
$class = ArrayHelper::getValue($item, 'class', 'yii\bootstrap\Nav');
return $class::widget($config);
}
/**
* Renders collapsible toggle button.
* @return string the rendering toggle button.
*/
protected function renderToggleButton()
{
$items = array();
for ($i = 0; $i < 3; $i++) {
$items[] = Html::tag('span', '', array('class' => 'icon-bar'));
}
return Html::a(implode("\n", $items), null, array(
'class' => 'btn btn-navbar',
'data-toggle' => 'collapse',
'data-target' => 'div.navbar-collapse',
));
}
}

35
framework/yii/caching/Cache.php

@ -7,7 +7,9 @@
namespace yii\caching; namespace yii\caching;
use Yii;
use yii\base\Component; use yii\base\Component;
use yii\base\InvalidConfigException;
use yii\helpers\StringHelper; use yii\helpers\StringHelper;
/** /**
@ -52,10 +54,12 @@ use yii\helpers\StringHelper;
abstract class Cache extends Component implements \ArrayAccess abstract class Cache extends Component implements \ArrayAccess
{ {
/** /**
* @var string a string prefixed to every cache key so that it is unique. Defaults to null, meaning using * @var string a string prefixed to every cache key so that it is unique. If not set,
* the value of [[Application::id]] as the key prefix. You may set this property to be an empty string * it will use a prefix generated from [[Application::id]]. You may set this property to be an empty string
* if you don't want to use key prefix. It is recommended that you explicitly set this property to some * if you don't want to use key prefix. It is recommended that you explicitly set this property to some
* static value if the cached data needs to be shared among multiple applications. * static value if the cached data needs to be shared among multiple applications.
*
* To ensure interoperability, only use alphanumeric characters should be used.
*/ */
public $keyPrefix; public $keyPrefix;
/** /**
@ -78,18 +82,18 @@ abstract class Cache extends Component implements \ArrayAccess
{ {
parent::init(); parent::init();
if ($this->keyPrefix === null) { if ($this->keyPrefix === null) {
$this->keyPrefix = \Yii::$app->id; $this->keyPrefix = substr(md5(Yii::$app->id), 0, 5);
} elseif (!ctype_alnum($this->keyPrefix)) {
throw new InvalidConfigException(get_class($this) . '::keyPrefix should only contain alphanumeric characters.');
} }
} }
/** /**
* Builds a normalized cache key from a given key. * Builds a normalized cache key from a given key.
* *
* The generated key contains letters and digits only, and its length is no more than 32.
*
* If the given key is a string containing alphanumeric characters only and no more than 32 characters, * If the given key is a string containing alphanumeric characters only and no more than 32 characters,
* then the key will be returned back without change. Otherwise, a normalized key * then the key will be returned back prefixed with [[keyPrefix]]. Otherwise, a normalized key
* is generated by serializing the given key and applying MD5 hashing. * is generated by serializing the given key, applying MD5 hashing, and prefixing with [[keyPrefix]].
* *
* The following example builds a cache key using three parameters: * The following example builds a cache key using three parameters:
* *
@ -97,16 +101,17 @@ abstract class Cache extends Component implements \ArrayAccess
* $key = $cache->buildKey(array($className, $method, $id)); * $key = $cache->buildKey(array($className, $method, $id));
* ~~~ * ~~~
* *
* @param array|string $key the key to be normalized * @param mixed $key the key to be normalized
* @return string the generated cache key * @return string the generated cache key
*/ */
public function buildKey($key) public function buildKey($key)
{ {
if (is_string($key)) { if (is_string($key)) {
return ctype_alnum($key) && StringHelper::strlen($key) <= 32 ? $key : md5($key); $key = ctype_alnum($key) && StringHelper::strlen($key) <= 32 ? $key : md5($key);
} else { } else {
return md5(json_encode($key)); $key = md5(json_encode($key));
} }
return $this->keyPrefix . $key;
} }
/** /**
@ -117,7 +122,7 @@ abstract class Cache extends Component implements \ArrayAccess
*/ */
public function get($key) public function get($key)
{ {
$key = $this->keyPrefix . $this->buildKey($key); $key = $this->buildKey($key);
$value = $this->getValue($key); $value = $this->getValue($key);
if ($value === false || $this->serializer === false) { if ($value === false || $this->serializer === false) {
return $value; return $value;
@ -147,7 +152,7 @@ abstract class Cache extends Component implements \ArrayAccess
{ {
$keyMap = array(); $keyMap = array();
foreach ($keys as $key) { foreach ($keys as $key) {
$keyMap[$key] = $this->keyPrefix . $this->buildKey($key); $keyMap[$key] = $this->buildKey($key);
} }
$values = $this->getValues(array_values($keyMap)); $values = $this->getValues(array_values($keyMap));
$results = array(); $results = array();
@ -192,7 +197,7 @@ abstract class Cache extends Component implements \ArrayAccess
} elseif ($this->serializer !== false) { } elseif ($this->serializer !== false) {
$value = call_user_func($this->serializer[0], array($value, $dependency)); $value = call_user_func($this->serializer[0], array($value, $dependency));
} }
$key = $this->keyPrefix . $this->buildKey($key); $key = $this->buildKey($key);
return $this->setValue($key, $value, $expire); return $this->setValue($key, $value, $expire);
} }
@ -217,7 +222,7 @@ abstract class Cache extends Component implements \ArrayAccess
} elseif ($this->serializer !== false) { } elseif ($this->serializer !== false) {
$value = call_user_func($this->serializer[0], array($value, $dependency)); $value = call_user_func($this->serializer[0], array($value, $dependency));
} }
$key = $this->keyPrefix . $this->buildKey($key); $key = $this->buildKey($key);
return $this->addValue($key, $value, $expire); return $this->addValue($key, $value, $expire);
} }
@ -228,7 +233,7 @@ abstract class Cache extends Component implements \ArrayAccess
*/ */
public function delete($key) public function delete($key)
{ {
$key = $this->keyPrefix . $this->buildKey($key); $key = $this->buildKey($key);
return $this->deleteValue($key); return $this->deleteValue($key);
} }

64
framework/yii/console/controllers/AssetController.php

@ -517,17 +517,77 @@ EOD
*/ */
public function combineCssFiles($inputFiles, $outputFile) public function combineCssFiles($inputFiles, $outputFile)
{ {
// todo: adjust url() references in CSS files
$content = ''; $content = '';
foreach ($inputFiles as $file) { foreach ($inputFiles as $file) {
$content .= "/*** BEGIN FILE: $file ***/\n" $content .= "/*** BEGIN FILE: $file ***/\n"
. file_get_contents($file) . $this->adjustCssUrl(file_get_contents($file), dirname($file), dirname($outputFile))
. "/*** END FILE: $file ***/\n"; . "/*** END FILE: $file ***/\n";
} }
file_put_contents($outputFile, $content); file_put_contents($outputFile, $content);
} }
/** /**
* Adjusts CSS content allowing URL references pointing to the original resources.
* @param string $cssContent source CSS content.
* @param string $inputFilePath input CSS file name.
* @param string $outputFilePath output CSS file name.
* @return string adjusted CSS content.
*/
protected function adjustCssUrl($cssContent, $inputFilePath, $outputFilePath)
{
$sharedPathParts = array();
$inputFilePathParts = explode('/', $inputFilePath);
$inputFilePathPartsCount = count($inputFilePathParts);
$outputFilePathParts = explode('/', $outputFilePath);
$outputFilePathPartsCount = count($outputFilePathParts);
for ($i =0; $i < $inputFilePathPartsCount && $i < $outputFilePathPartsCount; $i++) {
if ($inputFilePathParts[$i] == $outputFilePathParts[$i]) {
$sharedPathParts[] = $inputFilePathParts[$i];
} else {
break;
}
}
$sharedPath = implode('/', $sharedPathParts);
$inputFileRelativePath = trim(str_replace($sharedPath, '', $inputFilePath), '/');
$outputFileRelativePath = trim(str_replace($sharedPath, '', $outputFilePath), '/');
$inputFileRelativePathParts = explode('/', $inputFileRelativePath);
$outputFileRelativePathParts = explode('/', $outputFileRelativePath);
$callback = function($matches) use ($inputFileRelativePathParts, $outputFileRelativePathParts) {
$fullMatch = $matches[0];
$inputUrl = $matches[1];
if (preg_match('/https?:\/\//is', $inputUrl)) {
return $fullMatch;
}
$outputUrlParts = array_fill(0, count($outputFileRelativePathParts), '..');
$outputUrlParts = array_merge($outputUrlParts, $inputFileRelativePathParts);
if (strpos($inputUrl, '/') !== false) {
$inputUrlParts = explode('/', $inputUrl);
foreach ($inputUrlParts as $key => $inputUrlPart) {
if ($inputUrlPart == '..') {
array_pop($outputUrlParts);
unset($inputUrlParts[$key]);
}
}
$outputUrlParts[] = implode('/', $inputUrlParts);
} else {
$outputUrlParts[] = $inputUrl;
}
$outputUrl = implode('/', $outputUrlParts);
return str_replace($inputUrl, $outputUrl, $fullMatch);
};
$cssContent = preg_replace_callback('/url\(["\']?([^"]*)["\']?\)/is', $callback, $cssContent);
return $cssContent;
}
/**
* Creates template of configuration file for [[actionCompress]]. * Creates template of configuration file for [[actionCompress]].
* @param string $configFile output file name. * @param string $configFile output file name.
*/ */

2
framework/yii/console/controllers/MigrateController.php

@ -574,7 +574,7 @@ class MigrateController extends Controller
*/ */
protected function getMigrationHistory($limit) protected function getMigrationHistory($limit)
{ {
if ($this->db->schema->getTableSchema($this->migrationTable) === null) { if ($this->db->schema->getTableSchema($this->migrationTable, true) === null) {
$this->createMigrationHistoryTable(); $this->createMigrationHistoryTable();
} }
$query = new Query; $query = new Query;

2
framework/yii/db/ActiveRecord.php

@ -1152,7 +1152,7 @@ class ActiveRecord extends Model
/** /**
* Creates an active record object using a row of data. * Creates an active record object using a row of data.
* This method is called by [[ActiveQuery]] to populate the query results * This method is called by [[ActiveQuery]] to populate the query results
* into Active Records. * into Active Records. It is not meant to be used to create new records.
* @param array $row attribute values (name => value) * @param array $row attribute values (name => value)
* @return ActiveRecord the newly created active record. * @return ActiveRecord the newly created active record.
*/ */

4
framework/yii/db/Command.php

@ -391,12 +391,12 @@ class Command extends \yii\base\Component
} }
if (isset($cache) && $cache instanceof Cache) { if (isset($cache) && $cache instanceof Cache) {
$cacheKey = $cache->buildKey(array( $cacheKey = array(
__CLASS__, __CLASS__,
$db->dsn, $db->dsn,
$db->username, $db->username,
$rawSql, $rawSql,
)); );
if (($result = $cache->get($cacheKey)) !== false) { if (($result = $cache->get($cacheKey)) !== false) {
Yii::trace('Query result served from cache', __METHOD__); Yii::trace('Query result served from cache', __METHOD__);
return $result; return $result;

13
framework/yii/db/Schema.php

@ -89,7 +89,7 @@ abstract class Schema extends \yii\base\Object
/** @var $cache Cache */ /** @var $cache Cache */
$cache = is_string($db->schemaCache) ? Yii::$app->getComponent($db->schemaCache) : $db->schemaCache; $cache = is_string($db->schemaCache) ? Yii::$app->getComponent($db->schemaCache) : $db->schemaCache;
if ($cache instanceof Cache) { if ($cache instanceof Cache) {
$key = $this->getCacheKey($cache, $name); $key = $this->getCacheKey($name);
if ($refresh || ($table = $cache->get($key)) === false) { if ($refresh || ($table = $cache->get($key)) === false) {
$table = $this->loadTableSchema($realName); $table = $this->loadTableSchema($realName);
if ($table !== null) { if ($table !== null) {
@ -104,18 +104,17 @@ abstract class Schema extends \yii\base\Object
/** /**
* Returns the cache key for the specified table name. * Returns the cache key for the specified table name.
* @param Cache $cache the cache component
* @param string $name the table name * @param string $name the table name
* @return string the cache key * @return mixed the cache key
*/ */
public function getCacheKey($cache, $name) public function getCacheKey($name)
{ {
return $cache->buildKey(array( return array(
__CLASS__, __CLASS__,
$this->db->dsn, $this->db->dsn,
$this->db->username, $this->db->username,
$name, $name,
)); );
} }
/** /**
@ -178,7 +177,7 @@ abstract class Schema extends \yii\base\Object
$cache = is_string($this->db->schemaCache) ? Yii::$app->getComponent($this->db->schemaCache) : $this->db->schemaCache; $cache = is_string($this->db->schemaCache) ? Yii::$app->getComponent($this->db->schemaCache) : $this->db->schemaCache;
if ($this->db->enableSchemaCache && $cache instanceof Cache) { if ($this->db->enableSchemaCache && $cache instanceof Cache) {
foreach ($this->_tables as $name => $table) { foreach ($this->_tables as $name => $table) {
$cache->delete($this->getCacheKey($cache, $name)); $cache->delete($this->getCacheKey($name));
} }
} }
$this->_tableNames = array(); $this->_tableNames = array();

8
framework/yii/db/mssql/Schema.php

@ -241,16 +241,12 @@ SQL;
} }
foreach ($columns as $column) { foreach ($columns as $column) {
$column = $this->loadColumnSchema($column); $column = $this->loadColumnSchema($column);
if (is_array($table->primaryKey)) { foreach ($table->primaryKey as $primaryKey) {
foreach ($table->primaryKey as $primaryKeyColumn) { if (strcasecmp($column->name, $primaryKey) === 0) {
if (strcasecmp($column->name, $primaryKeyColumn) === 0) {
$column->isPrimaryKey = true; $column->isPrimaryKey = true;
break; break;
} }
} }
} else {
$column->isPrimaryKey = strcasecmp($column->name, $table->primaryKey) === 0;
}
if ($column->isPrimaryKey && $column->autoIncrement) { if ($column->isPrimaryKey && $column->autoIncrement) {
$table->sequenceName = ''; $table->sequenceName = '';
} }

4
framework/yii/helpers/base/Html.php

@ -344,7 +344,7 @@ class Html
/** /**
* Generates a hyperlink tag. * Generates a hyperlink tag.
* @param string $text link body. It will NOT be HTML-encoded. Therefore you can pass in HTML code * @param string $text link body. It will NOT be HTML-encoded. Therefore you can pass in HTML code
* such as an image tag. If this is is coming from end users, you should consider [[encode()]] * such as an image tag. If this is coming from end users, you should consider [[encode()]]
* it to prevent XSS attacks. * it to prevent XSS attacks.
* @param array|string|null $url the URL for the hyperlink tag. This parameter will be processed by [[url()]] * @param array|string|null $url the URL for the hyperlink tag. This parameter will be processed by [[url()]]
* and will be used for the "href" attribute of the tag. If this parameter is null, the "href" attribute * and will be used for the "href" attribute of the tag. If this parameter is null, the "href" attribute
@ -366,7 +366,7 @@ class Html
/** /**
* Generates a mailto hyperlink. * Generates a mailto hyperlink.
* @param string $text link body. It will NOT be HTML-encoded. Therefore you can pass in HTML code * @param string $text link body. It will NOT be HTML-encoded. Therefore you can pass in HTML code
* such as an image tag. If this is is coming from end users, you should consider [[encode()]] * such as an image tag. If this is coming from end users, you should consider [[encode()]]
* it to prevent XSS attacks. * it to prevent XSS attacks.
* @param string $email email address. If this is null, the first parameter (link body) will be treated * @param string $email email address. If this is null, the first parameter (link body) will be treated
* as the email address and used. * as the email address and used.

13
framework/yii/helpers/base/StringHelper.php

@ -18,20 +18,18 @@ class StringHelper
{ {
/** /**
* Returns the number of bytes in the given string. * Returns the number of bytes in the given string.
* This method ensures the string is treated as a byte array. * This method ensures the string is treated as a byte array by using `mb_strlen()`.
* It will use `mb_strlen()` if it is available.
* @param string $string the string being measured for length * @param string $string the string being measured for length
* @return integer the number of bytes in the given string. * @return integer the number of bytes in the given string.
*/ */
public static function strlen($string) public static function strlen($string)
{ {
return function_exists('mb_strlen') ? mb_strlen($string, '8bit') : strlen($string); return mb_strlen($string, '8bit');
} }
/** /**
* Returns the portion of string specified by the start and length parameters. * Returns the portion of string specified by the start and length parameters.
* This method ensures the string is treated as a byte array. * This method ensures the string is treated as a byte array by using `mb_substr()`.
* It will use `mb_substr()` if it is available.
* @param string $string the input string. Must be one character or longer. * @param string $string the input string. Must be one character or longer.
* @param integer $start the starting position * @param integer $start the starting position
* @param integer $length the desired portion length * @param integer $length the desired portion length
@ -40,15 +38,14 @@ class StringHelper
*/ */
public static function substr($string, $start, $length) public static function substr($string, $start, $length)
{ {
return function_exists('mb_substr') ? mb_substr($string, $start, $length, '8bit') : substr($string, $start, $length); return mb_substr($string, $start, $length, '8bit');
} }
/** /**
* Returns the trailing name component of a path. * Returns the trailing name component of a path.
* This method does the same as the php function basename() except that it will * This method does the same as the php function basename() except that it will
* always use \ and / as directory separators, independent of the operating system. * always use \ and / as directory separators, independent of the operating system.
* Note: basename() operates naively on the input string, and is not aware of the * Note: this method is not aware of the actual filesystem, or path components such as "..".
* actual filesystem, or path components such as "..".
* @param string $path A path string. * @param string $path A path string.
* @param string $suffix If the name component ends in suffix this will also be cut off. * @param string $suffix If the name component ends in suffix this will also be cut off.
* @return string the trailing name component of the given path. * @return string the trailing name component of the given path.

232
framework/yii/i18n/Formatter.php

@ -0,0 +1,232 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\i18n;
use Yii;
use IntlDateFormatter;
use NumberFormatter;
use DateTime;
use yii\base\InvalidConfigException;
/**
* Formatter is the localized version of [[\yii\base\Formatter]].
*
* Formatter requires the PHP "intl" extension to be installed. Formatter supports localized
* formatting of date, time and numbers, based on the current [[locale]].
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Formatter extends \yii\base\Formatter
{
/**
* @var string the locale ID that is used to localize the date and number formatting.
* If not set, [[\yii\base\Application::language]] will be used.
*/
public $locale;
/**
* @var string the default format string to be used to format a date using PHP date() function.
*/
public $dateFormat = 'short';
/**
* @var string the default format string to be used to format a time using PHP date() function.
*/
public $timeFormat = 'short';
/**
* @var string the default format string to be used to format a date and time using PHP date() function.
*/
public $datetimeFormat = 'short';
/**
* @var array the options to be set for the NumberFormatter objects. Please refer to
*/
public $numberFormatOptions = array();
/**
* Initializes the component.
* This method will check if the "intl" PHP extension is installed and set the
* default value of [[locale]].
* @throws InvalidConfigException if the "intl" PHP extension is not installed.
*/
public function init()
{
if (!extension_loaded('intl')) {
throw new InvalidConfigException('The "intl" PHP extension is not install. It is required to format data values in localized formats.');
}
if ($this->locale === null) {
$this->locale = Yii::$app->language;
}
parent::init();
}
private $_dateFormats = array(
'short' => IntlDateFormatter::SHORT,
'medium' => IntlDateFormatter::MEDIUM,
'long' => IntlDateFormatter::LONG,
'full' => IntlDateFormatter::FULL,
);
/**
* Formats the value as a date.
* @param integer|string|DateTime $value the value to be formatted. The following
* types of value are supported:
*
* - an integer representing a UNIX timestamp
* - a string that can be parsed into a UNIX timestamp via `strtotime()`
* - a PHP DateTime object
*
* @param string $format the format used to convert the value into a date string.
* If null, [[dateFormat]] will be used. The format string should be the one
* that can be recognized by the PHP `date()` function.
* @return string the formatted result
* @see dateFormat
*/
public function asDate($value, $format = null)
{
$value = $this->normalizeDatetimeValue($value);
if ($format === null) {
$format = $this->dateFormat;
}
if (isset($this->_dateFormats[$format])) {
$formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], IntlDateFormatter::NONE);
} else {
$formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE);
$formatter->setPattern($format);
}
return $formatter->format($value);
}
/**
* Formats the value as a time.
* @param integer|string|DateTime $value the value to be formatted. The following
* types of value are supported:
*
* - an integer representing a UNIX timestamp
* - a string that can be parsed into a UNIX timestamp via `strtotime()`
* - a PHP DateTime object
*
* @param string $format the format used to convert the value into a date string.
* If null, [[dateFormat]] will be used. The format string should be the one
* that can be recognized by the PHP `date()` function.
* @return string the formatted result
* @see timeFormat
*/
public function asTime($value, $format = null)
{
$value = $this->normalizeDatetimeValue($value);
if ($format === null) {
$format = $this->timeFormat;
}
if (isset($this->_dateFormats[$format])) {
$formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, $this->_dateFormats[$format]);
} else {
$formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE);
$formatter->setPattern($format);
}
return $formatter->format($value);
}
/**
* Formats the value as a datetime.
* @param integer|string|DateTime $value the value to be formatted. The following
* types of value are supported:
*
* - an integer representing a UNIX timestamp
* - a string that can be parsed into a UNIX timestamp via `strtotime()`
* - a PHP DateTime object
*
* @param string $format the format used to convert the value into a date string.
* If null, [[dateFormat]] will be used. The format string should be the one
* that can be recognized by the PHP `date()` function.
* @return string the formatted result
* @see datetimeFormat
*/
public function asDatetime($value, $format = null)
{
$value = $this->normalizeDatetimeValue($value);
if ($format === null) {
$format = $this->datetimeFormat;
}
if (isset($this->_dateFormats[$format])) {
$formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], $this->_dateFormats[$format]);
} else {
$formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE);
$formatter->setPattern($format);
}
return $formatter->format($value);
}
/**
* Formats the value as a decimal number.
* @param mixed $value the value to be formatted
* @param string $format the format to be used. Please refer to [ICU manual](http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details)
* for details on how to specify a format.
* @return string the formatted result.
*/
public function asDecimal($value, $format = null)
{
return $this->createNumberFormatter(NumberFormatter::DECIMAL, $format)->format($value);
}
/**
* Formats the value as a currency number.
* @param mixed $value the value to be formatted
* @param string $currency the 3-letter ISO 4217 currency code indicating the currency to use.
* @param string $format the format to be used. Please refer to [ICU manual](http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details)
* for details on how to specify a format.
* @return string the formatted result.
*/
public function asCurrency($value, $currency = 'USD', $format = null)
{
return $this->createNumberFormatter(NumberFormatter::CURRENCY, $format)->formatCurrency($value, $currency);
}
/**
* Formats the value as a percent number.
* @param mixed $value the value to be formatted
* @param string $format the format to be used. Please refer to [ICU manual](http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details)
* for details on how to specify a format.
* @return string the formatted result.
*/
public function asPercent($value, $format = null)
{
return $this->createNumberFormatter(NumberFormatter::PERCENT, $format)->format($value);
}
/**
* Formats the value as a scientific number.
* @param mixed $value the value to be formatted
* @param string $format the format to be used. Please refer to [ICU manual](http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details)
* for details on how to specify a format.
* @return string the formatted result.
*/
public function asScientific($value, $format = null)
{
return $this->createNumberFormatter(NumberFormatter::SCIENTIFIC, $format)->format($value);
}
/**
* Creates a number formatter based on the given type and format.
* @param integer $type the type of the number formatter
* @param string $format the format to be used
* @return NumberFormatter the created formatter instance
*/
protected function createNumberFormatter($type, $format)
{
$formatter = new NumberFormatter($this->locale, $type);
if ($format !== null) {
$formatter->setPattern($format);
}
if (!empty($this->numberFormatOptions)) {
foreach ($this->numberFormatOptions as $name => $attribute) {
$formatter->setAttribute($name, $attribute);
}
}
return $formatter;
}
}

31
framework/yii/jui/Accordion.php

@ -56,15 +56,38 @@ use yii\helpers\Html;
class Accordion extends Widget class Accordion extends Widget
{ {
/** /**
* @var array list of collapsible sections. * @var array the HTML attributes for the widget container tag. The following special options are recognized:
*
* - tag: string, defaults to "div", the tag name of the container tag of this widget
*/
public $options = array();
/**
* @var array list of collapsible items. Each item can be an array of the following structure:
*
* ~~~
* array(
* 'header' => 'Item header',
* 'content' => 'Item content',
* // the HTML attributes of the item header container tag. This will overwrite "headerOptions".
* 'headerOptions' => array(),
* // the HTML attributes of the item container tag. This will overwrite "itemOptions".
* 'options' => array(),
* )
* ~~~
*/ */
public $items = array(); public $items = array();
/** /**
* @var array list of individual collabsible section default options. * @var array list of HTML attributes for the item container tags. This will be overwritten
* by the "options" set in individual [[items]]. The following special options are recognized:
*
* - tag: string, defaults to "div", the tag name of the item container tags.
*/ */
public $itemOptions = array(); public $itemOptions = array();
/** /**
* @var array list of individual collabsible section header default options. * @var array list of HTML attributes for the item header container tags. This will be overwritten
* by the "headerOptions" set in individual [[items]]. The following special options are recognized:
*
* - tag: string, defaults to "h3", the tag name of the item container tags.
*/ */
public $headerOptions = array(); public $headerOptions = array();
@ -83,7 +106,7 @@ class Accordion extends Widget
} }
/** /**
* Renders collapsible sections as specified on [[items]]. * Renders collapsible items as specified on [[items]].
* @return string the rendering result. * @return string the rendering result.
* @throws InvalidConfigException. * @throws InvalidConfigException.
*/ */

52
framework/yii/jui/Draggable.php

@ -0,0 +1,52 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\jui;
use yii\helpers\Html;
/**
* Draggable renders an draggable jQuery UI widget.
*
* For example:
*
* ```php
* Draggable::begin(array(
* 'clientOptions' => array(
* 'grid' => array(50, 20),
* ),
* ));
*
* echo 'Draggable contents here...';
*
* Draggable::end();
* ```
*
* @see http://api.jqueryui.com/draggable/
* @author Alexander Kochetov <creocoder@gmail.com>
* @since 2.0
*/
class Draggable extends Widget
{
/**
* Initializes the widget.
*/
public function init()
{
parent::init();
echo Html::beginTag('div', $this->options) . "\n";
}
/**
* Renders the widget.
*/
public function run()
{
echo Html::endTag('div') . "\n";
$this->registerWidget('draggable', false);
}
}

52
framework/yii/jui/Droppable.php

@ -0,0 +1,52 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\jui;
use yii\helpers\Html;
/**
* Droppable renders an droppable jQuery UI widget.
*
* For example:
*
* ```php
* Droppable::begin(array(
* 'clientOptions' => array(
* 'accept' => '.special',
* ),
* ));
*
* echo 'Droppable body here...';
*
* Droppable::end();
* ```
*
* @see http://api.jqueryui.com/droppable/
* @author Alexander Kochetov <creocoder@gmail.com>
* @since 2.0
*/
class Droppable extends Widget
{
/**
* Initializes the widget.
*/
public function init()
{
parent::init();
echo Html::beginTag('div', $this->options) . "\n";
}
/**
* Renders the widget.
*/
public function run()
{
echo Html::endTag('div') . "\n";
$this->registerWidget('droppable', false);
}
}

52
framework/yii/jui/Resizable.php

@ -0,0 +1,52 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\jui;
use yii\helpers\Html;
/**
* Resizable renders an resizable jQuery UI widget.
*
* For example:
*
* ```php
* Resizable::begin(array(
* 'clientOptions' => array(
* 'grid' => array(20, 10),
* ),
* ));
*
* echo 'Resizable contents here...';
*
* Resizable::end();
* ```
*
* @see http://api.jqueryui.com/resizable/
* @author Alexander Kochetov <creocoder@gmail.com>
* @since 2.0
*/
class Resizable extends Widget
{
/**
* Initializes the widget.
*/
public function init()
{
parent::init();
echo Html::beginTag('div', $this->options) . "\n";
}
/**
* Renders the widget.
*/
public function run()
{
echo Html::endTag('div') . "\n";
$this->registerWidget('resizable');
}
}

116
framework/yii/jui/Selectable.php

@ -0,0 +1,116 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\jui;
use yii\base\InvalidConfigException;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
/**
* Selectable renders a selectable jQuery UI widget.
*
* For example:
*
* ```php
* echo Selectable::widget(array(
* 'items' => array(
* 'Item 1',
* array(
* 'content' => 'Item2',
* ),
* array(
* 'content' => 'Item3',
* 'options' => array(
* 'tag' => 'li',
* ),
* ),
* ),
* 'options' => array(
* 'tag' => 'ul',
* ),
* 'itemOptions' => array(
* 'tag' => 'li',
* ),
* 'clientOptions' => array(
* 'tolerance' => 'fit',
* ),
* ));
* ```
*
* @see http://api.jqueryui.com/selectable/
* @author Alexander Kochetov <creocoder@gmail.com>
* @since 2.0
*/
class Selectable extends Widget
{
/**
* @var array the HTML attributes for the widget container tag. The following special options are recognized:
*
* - tag: string, defaults to "ul", the tag name of the container tag of this widget
*/
public $options = array();
/**
* @var array list of selectable items. Each item can be a string representing the item content
* or an array of the following structure:
*
* ~~~
* array(
* 'content' => 'item content',
* // the HTML attributes of the item container tag. This will overwrite "itemOptions".
* 'options' => array(),
* )
* ~~~
*/
public $items = array();
/**
* @var array list of HTML attributes for the item container tags. This will be overwritten
* by the "options" set in individual [[items]]. The following special options are recognized:
*
* - tag: string, defaults to "li", the tag name of the item container tags.
*/
public $itemOptions = array();
/**
* Renders the widget.
*/
public function run()
{
$options = $this->options;
$tag = ArrayHelper::remove($options, 'tag', 'ul');
echo Html::beginTag($tag, $options) . "\n";
echo $this->renderItems() . "\n";
echo Html::endTag($tag) . "\n";
$this->registerWidget('selectable');
}
/**
* Renders selectable items as specified on [[items]].
* @return string the rendering result.
* @throws InvalidConfigException.
*/
public function renderItems()
{
$items = array();
foreach ($this->items as $item) {
$options = $this->itemOptions;
$tag = ArrayHelper::remove($options, 'tag', 'li');
if (is_array($item)) {
if (!isset($item['content'])) {
throw new InvalidConfigException("The 'content' option is required.");
}
$options = array_merge($options, ArrayHelper::getValue($item, 'options', array()));
$tag = ArrayHelper::remove($options, 'tag', $tag);
$items[] = Html::tag($tag, $item['content'], $options);
} else {
$items[] = Html::tag($tag, $item, $options);
}
}
return implode("\n", $items);
}
}

66
framework/yii/jui/Spinner.php

@ -0,0 +1,66 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\jui;
use Yii;
use yii\helpers\Html;
/**
* Spinner renders an spinner jQuery UI widget.
*
* For example:
*
* ```php
* echo Spinner::widget(array(
* 'model' => $model,
* 'attribute' => 'country',
* 'clientOptions' => array(
* 'step' => 2,
* ),
* ));
* ```
*
* The following example will use the name property instead:
*
* ```php
* echo Spinner::widget(array(
* 'name' => 'country',
* 'clientOptions' => array(
* 'step' => 2,
* ),
* ));
*```
*
* @see http://api.jqueryui.com/spinner/
* @author Alexander Kochetov <creocoder@gmail.com>
* @since 2.0
*/
class Spinner extends InputWidget
{
/**
* Renders the widget.
*/
public function run()
{
echo $this->renderWidget();
$this->registerWidget('spinner');
}
/**
* Renders the Spinner widget.
* @return string the rendering result.
*/
public function renderWidget()
{
if ($this->hasModel()) {
return Html::activeTextInput($this->model, $this->attribute, $this->options);
} else {
return Html::textInput($this->name, $this->value, $this->options);
}
}
}

47
framework/yii/rbac/DbManager.php

@ -160,7 +160,8 @@ class DbManager extends Manager
throw new InvalidCallException("Cannot add '$childName' as a child of '$itemName'. A loop has been detected."); throw new InvalidCallException("Cannot add '$childName' as a child of '$itemName'. A loop has been detected.");
} }
$this->db->createCommand() $this->db->createCommand()
->insert($this->itemChildTable, array('parent' => $itemName, 'child' => $childName)); ->insert($this->itemChildTable, array('parent' => $itemName, 'child' => $childName))
->execute();
return true; return true;
} else { } else {
throw new Exception("Either '$itemName' or '$childName' does not exist."); throw new Exception("Either '$itemName' or '$childName' does not exist.");
@ -177,7 +178,8 @@ class DbManager extends Manager
public function removeItemChild($itemName, $childName) public function removeItemChild($itemName, $childName)
{ {
return $this->db->createCommand() return $this->db->createCommand()
->delete($this->itemChildTable, array('parent' => $itemName, 'child' => $childName)) > 0; ->delete($this->itemChildTable, array('parent' => $itemName, 'child' => $childName))
->execute() > 0;
} }
/** /**
@ -248,7 +250,8 @@ class DbManager extends Manager
'item_name' => $itemName, 'item_name' => $itemName,
'biz_rule' => $bizRule, 'biz_rule' => $bizRule,
'data' => serialize($data), 'data' => serialize($data),
)); ))
->execute();
return new Assignment(array( return new Assignment(array(
'manager' => $this, 'manager' => $this,
'userId' => $userId, 'userId' => $userId,
@ -267,7 +270,8 @@ class DbManager extends Manager
public function revoke($userId, $itemName) public function revoke($userId, $itemName)
{ {
return $this->db->createCommand() return $this->db->createCommand()
->delete($this->assignmentTable, array('user_id' => $userId, 'item_name' => $itemName)) > 0; ->delete($this->assignmentTable, array('user_id' => $userId, 'item_name' => $itemName))
->execute() > 0;
} }
/** /**
@ -276,7 +280,7 @@ class DbManager extends Manager
* @param string $itemName the item name * @param string $itemName the item name
* @return boolean whether the item has been assigned to the user. * @return boolean whether the item has been assigned to the user.
*/ */
public function isAssigned($itemName, $userId) public function isAssigned($userId, $itemName)
{ {
$query = new Query; $query = new Query;
return $query->select(array('item_name')) return $query->select(array('item_name'))
@ -358,7 +362,8 @@ class DbManager extends Manager
), array( ), array(
'user_id' => $assignment->userId, 'user_id' => $assignment->userId,
'item_name' => $assignment->itemName, 'item_name' => $assignment->itemName,
)); ))
->execute();
} }
/** /**
@ -431,7 +436,8 @@ class DbManager extends Manager
'description' => $description, 'description' => $description,
'biz_rule' => $bizRule, 'biz_rule' => $bizRule,
'data' => serialize($data), 'data' => serialize($data),
)); ))
->execute();
return new Item(array( return new Item(array(
'manager' => $this, 'manager' => $this,
'name' => $name, 'name' => $name,
@ -451,12 +457,15 @@ class DbManager extends Manager
{ {
if ($this->usingSqlite()) { if ($this->usingSqlite()) {
$this->db->createCommand() $this->db->createCommand()
->delete($this->itemChildTable, array('or', 'parent=:name', 'child=:name'), array(':name' => $name)); ->delete($this->itemChildTable, array('or', 'parent=:name', 'child=:name'), array(':name' => $name))
->execute();
$this->db->createCommand() $this->db->createCommand()
->delete($this->assignmentTable, array('item_name' => $name)); ->delete($this->assignmentTable, array('item_name' => $name))
->execute();
} }
return $this->db->createCommand() return $this->db->createCommand()
->delete($this->itemTable, array('name' => $name)) > 0; ->delete($this->itemTable, array('name' => $name))
->execute() > 0;
} }
/** /**
@ -497,11 +506,14 @@ class DbManager extends Manager
{ {
if ($this->usingSqlite() && $oldName !== null && $item->getName() !== $oldName) { if ($this->usingSqlite() && $oldName !== null && $item->getName() !== $oldName) {
$this->db->createCommand() $this->db->createCommand()
->update($this->itemChildTable, array('parent' => $item->getName()), array('parent' => $oldName)); ->update($this->itemChildTable, array('parent' => $item->getName()), array('parent' => $oldName))
->execute();
$this->db->createCommand() $this->db->createCommand()
->update($this->itemChildTable, array('child' => $item->getName()), array('child' => $oldName)); ->update($this->itemChildTable, array('child' => $item->getName()), array('child' => $oldName))
->execute();
$this->db->createCommand() $this->db->createCommand()
->update($this->assignmentTable, array('item_name' => $item->getName()), array('item_name' => $oldName)); ->update($this->assignmentTable, array('item_name' => $item->getName()), array('item_name' => $oldName))
->execute();
} }
$this->db->createCommand() $this->db->createCommand()
@ -513,7 +525,8 @@ class DbManager extends Manager
'data' => serialize($item->data), 'data' => serialize($item->data),
), array( ), array(
'name' => $oldName === null ? $item->getName() : $oldName, 'name' => $oldName === null ? $item->getName() : $oldName,
)); ))
->execute();
} }
/** /**
@ -529,8 +542,8 @@ class DbManager extends Manager
public function clearAll() public function clearAll()
{ {
$this->clearAssignments(); $this->clearAssignments();
$this->db->createCommand()->delete($this->itemChildTable); $this->db->createCommand()->delete($this->itemChildTable)->execute();
$this->db->createCommand()->delete($this->itemTable); $this->db->createCommand()->delete($this->itemTable)->execute();
} }
/** /**
@ -538,7 +551,7 @@ class DbManager extends Manager
*/ */
public function clearAssignments() public function clearAssignments()
{ {
$this->db->createCommand()->delete($this->assignmentTable); $this->db->createCommand()->delete($this->assignmentTable)->execute();
} }
/** /**

2
framework/yii/requirements/requirements.php

@ -43,6 +43,6 @@ return array(
'mandatory' => false, 'mandatory' => false,
'condition' => $this->checkPhpExtensionVersion('intl', '1.0.2'), 'condition' => $this->checkPhpExtensionVersion('intl', '1.0.2'),
'by' => '<a href="http://www.php.net/manual/en/book.intl.php">Internationalization</a> support', 'by' => '<a href="http://www.php.net/manual/en/book.intl.php">Internationalization</a> support',
'memo' => 'PHP Intl extension 1.0.2 or higher is required when you want to use <abbr title="Internationalized domain names">IDN</abbr>-feature of EmailValidator or UrlValidator.' 'memo' => 'PHP Intl extension 1.0.2 or higher is required when you want to use <abbr title="Internationalized domain names">IDN</abbr>-feature of EmailValidator or UrlValidator or the <code>yii\i18n\Formatter</code> class.'
), ),
); );

3
framework/yii/validators/UniqueValidator.php

@ -9,6 +9,7 @@ namespace yii\validators;
use Yii; use Yii;
use yii\base\InvalidConfigException; use yii\base\InvalidConfigException;
use yii\db\ActiveRecord;
/** /**
* CUniqueValidator validates that the attribute value is unique in the corresponding database table. * CUniqueValidator validates that the attribute value is unique in the corresponding database table.
@ -71,7 +72,7 @@ class UniqueValidator extends Validator
$query = $className::find(); $query = $className::find();
$query->where(array($column->name => $value)); $query->where(array($column->name => $value));
if ($object->getIsNewRecord()) { if (!$object instanceof ActiveRecord || $object->getIsNewRecord()) {
// if current $object isn't in the database yet then it's OK just to call exists() // if current $object isn't in the database yet then it's OK just to call exists()
$exists = $query->exists(); $exists = $query->exists();
} else { } else {

22
framework/yii/views/errorHandler/callStackItem.php

@ -13,10 +13,11 @@
*/ */
$context = $this->context; $context = $this->context;
?> ?>
<li class="<?php if (!$context->isCoreFile($file) || $index === 1) echo 'application'; ?> call-stack-item"> <li class="<?php if (!$context->isCoreFile($file) || $index === 1) echo 'application'; ?> call-stack-item"
data-line="<?php echo (int)($line - $begin); ?>">
<div class="element-wrap"> <div class="element-wrap">
<div class="element"> <div class="element">
<span class="number"><?php echo (int)$index; ?>.</span> <span class="item-number"><?php echo (int)$index; ?>.</span>
<span class="text"><?php if ($file !== null) echo 'in ' . $context->htmlEncode($file); ?></span> <span class="text"><?php if ($file !== null) echo 'in ' . $context->htmlEncode($file); ?></span>
<?php if ($method !== null): ?> <?php if ($method !== null): ?>
<span class="call"> <span class="call">
@ -25,18 +26,21 @@ $context = $this->context;
</span> </span>
<?php endif; ?> <?php endif; ?>
<span class="at"><?php if ($line !== null) echo 'at line'; ?></span> <span class="at"><?php if ($line !== null) echo 'at line'; ?></span>
<span class="line"><?php if ($line !== null) echo (int)$line; ?></span> <span class="line"><?php if ($line !== null) echo (int)$line + 1; ?></span>
</div> </div>
</div> </div>
<?php if (!empty($lines)): ?> <?php if (!empty($lines)): ?>
<div class="code-wrap"> <div class="code-wrap">
<div class="error-line" style="top: <?php echo 18 * (int)($line - $begin); ?>px;"></div> <div class="error-line"></div>
<?php for ($i = $begin; $i <= $end; ++$i): ?> <?php for ($i = $begin; $i <= $end; ++$i): ?><div class="hover-line"></div><?php endfor; ?>
<div class="hover-line" style="top: <?php echo 18 * (int)($i - $begin); ?>px;"></div>
<?php endfor; ?>
<div class="code"> <div class="code">
<span class="lines"><?php for ($i = $begin; $i <= $end; ++$i) echo (int)$i . '<br/>'; ?></span> <?php for ($i = $begin; $i <= $end; ++$i): ?><span class="lines-item"><?php echo (int)($i + 1); ?></span><?php endfor; ?>
<pre><?php for ($i = $begin; $i <= $end; ++$i) echo $context->htmlEncode($lines[$i]); ?></pre> <pre><?php
// fill empty lines with a whitespace to avoid rendering problems in opera
for ($i = $begin; $i <= $end; ++$i) {
echo (trim($lines[$i]) == '') ? " \n" : $context->htmlEncode($lines[$i]);
}
?></pre>
</div> </div>
</div> </div>
<?php endif; ?> <?php endif; ?>

108
framework/yii/views/errorHandler/main.php

@ -109,8 +109,8 @@ html,body{
filter: progid:DXImageTransform.Microsoft.BasicImage(mirror=1); filter: progid:DXImageTransform.Microsoft.BasicImage(mirror=1);
font-size: 26px; font-size: 26px;
position: absolute; position: absolute;
margin-top: -9px; margin-top: -5px;
margin-left: -22px; margin-left: -25px;
color: #e51717; color: #e51717;
text-shadow: 0 1px 0 #cacaca; text-shadow: 0 1px 0 #cacaca;
} }
@ -169,7 +169,7 @@ html,body{
color: #000; color: #000;
text-shadow: 0 1px 0 #cacaca; text-shadow: 0 1px 0 #cacaca;
} }
.call-stack ul li .number{ .call-stack ul li .item-number{
width: 45px; width: 45px;
display: inline-block; display: inline-block;
} }
@ -200,47 +200,51 @@ html,body{
.call-stack ul li.application .code-wrap{ .call-stack ul li.application .code-wrap{
display: block; display: block;
} }
.call-stack ul li .error-line,.call-stack ul li .hover-line{ .call-stack ul li .error-line,
.call-stack ul li .hover-line{
background-color: #ffebeb; background-color: #ffebeb;
position: absolute; position: absolute;
width: 100%; width: 100%;
height: 20px;
z-index: 100; z-index: 100;
margin-top: 15px; margin-top: -61px;
} }
.call-stack ul li .hover-line{ .call-stack ul li .hover-line{
background: none; background: none;
} }
.call-stack ul li .hover-line.hover,.call-stack ul li .hover-line:hover{ .call-stack ul li .hover-line.hover,
.call-stack ul li .hover-line:hover{
background: #edf9ff !important; background: #edf9ff !important;
} }
.call-stack ul li .code{ .call-stack ul li .code{
min-width: 860px; /* 960px - 50px * 2 */ min-width: 860px; /* 960px - 50px * 2 */
margin: 0 auto; margin: 15px auto;
padding: 15px 50px; padding: 0 50px;
position: relative; position: relative;
} }
.call-stack ul li .code .lines{ .call-stack ul li .code .lines-item{
position: absolute; position: absolute;
z-index: 200; z-index: 200;
left: 50px; display: block;
line-height: 18px; width: 25px;
font-size: 14px; text-align: right;
font-family: Consolas, Courier New, monospace;
color: #aaa; color: #aaa;
line-height: 20px;
font-size: 12px;
margin-top: -63px;
font-family: Consolas, Courier New, monospace;
} }
.call-stack ul li .code pre{ .call-stack ul li .code pre{
position: relative; position: relative;
z-index: 200; z-index: 200;
left: 50px; left: 50px;
line-height: 18px; line-height: 20px;
font-size: 14px; font-size: 12px;
font-family: Consolas, Courier New, monospace; font-family: Consolas, Courier New, monospace;
display: inline; display: inline;
} }
@media screen and (-webkit-min-device-pixel-ratio:0){ @-moz-document url-prefix() {
.call-stack ul li .code pre{ .call-stack ul li .code pre{
line-height: 16px; line-height: 20px;
} }
} }
@ -264,11 +268,6 @@ html,body{
display: inline; display: inline;
word-wrap: break-word; word-wrap: break-word;
} }
@media screen and (-webkit-min-device-pixel-ratio:0){
.request .code pre{
line-height: 16px;
}
}
/* footer */ /* footer */
.footer{ .footer{
@ -413,25 +412,25 @@ var hljs=new function(){function l(o){return o.replace(/&/gm,"&amp;").replace(/<
<script type="text/javascript"> <script type="text/javascript">
window.onload = function() { window.onload = function() {
var i, imax, var codeBlocks = Sizzle('pre'),
codeBlocks = Sizzle('pre'),
callStackItems = Sizzle('.call-stack-item'); callStackItems = Sizzle('.call-stack-item');
// highlight code blocks // highlight code blocks
for (i = 0, imax = codeBlocks.length; i < imax; ++i) { for (var i = 0, imax = codeBlocks.length; i < imax; ++i) {
hljs.highlightBlock(codeBlocks[i], ' '); hljs.highlightBlock(codeBlocks[i], ' ');
} }
// code block hover line // code block hover line
document.onmousemove = function(e) { document.onmousemove = function(e) {
var lines, i, imax, j, jmax, k, kmax, var event = e || window.event,
event = e || window.event, clientY = event.clientY,
y = event.clientY, lineFound = false,
lineFound = false; hoverLines = Sizzle('.hover-line');
for (i = 0, imax = codeBlocks.length; i < imax; ++i) {
lines = codeBlocks[i].getClientRects(); for (var i = 0, imax = codeBlocks.length - 1; i < imax; ++i) {
for (j = 0, jmax = lines.length; j < jmax; ++j) { var lines = codeBlocks[i].getClientRects();
if (y > lines[j].top && y < lines[j].bottom) { for (var j = 0, jmax = lines.length; j < jmax; ++j) {
if (clientY >= lines[j].top && clientY <= lines[j].bottom) {
lineFound = true; lineFound = true;
break; break;
} }
@ -440,23 +439,50 @@ window.onload = function() {
break; break;
} }
} }
var hoverLines = Sizzle('.hover-line');
for (k = 0, kmax = hoverLines.length; k < kmax; ++k) { for (var k = 0, kmax = hoverLines.length; k < kmax; ++k) {
hoverLines[k].className = 'hover-line'; hoverLines[k].className = 'hover-line';
} }
if (lineFound) { if (lineFound) {
var line = Sizzle('.call-stack-item:eq(' + i + ') .hover-line:eq(' + j + ')'); var line = Sizzle('.call-stack-item:eq(' + i + ') .hover-line:eq(' + j + ')')[0];
if (line[0]) { if (line) {
line[0].className = 'hover-line hover'; line.className = 'hover-line hover';
} }
} }
};
var refreshCallStackItemCode = function(callStackItem) {
if (!Sizzle('pre', callStackItem)[0]) {
return;
}
var top = callStackItem.offsetTop - window.pageYOffset,
lines = Sizzle('pre', callStackItem)[0].getClientRects(),
lineNumbers = Sizzle('.lines-item', callStackItem),
errorLine = Sizzle('.error-line', callStackItem)[0],
hoverLines = Sizzle('.hover-line', callStackItem);
for (var i = 0, imax = lines.length; i < imax; ++i) {
if (!lineNumbers[i]) {
continue;
}
lineNumbers[i].style.top = parseInt(lines[i].top - top) + 'px';
hoverLines[i].style.top = parseInt(lines[i].top - top - 3) + 'px';
hoverLines[i].style.height = parseInt(lines[i].bottom - lines[i].top + 6) + 'px';
if (parseInt(callStackItem.getAttribute('data-line')) == i) {
errorLine.style.top = parseInt(lines[i].top - top - 3) + 'px';
errorLine.style.height = parseInt(lines[i].bottom - lines[i].top + 6) + 'px';
} }
}
};
for (var i = 0, imax = callStackItems.length; i < imax; ++i) {
refreshCallStackItemCode(callStackItems[i]);
// toggle code block visibility // toggle code block visibility
for (i = 0, imax = callStackItems.length; i < imax; i++) {
Sizzle('.element-wrap', callStackItems[i])[0].addEventListener('click', function() { Sizzle('.element-wrap', callStackItems[i])[0].addEventListener('click', function() {
var code = Sizzle('.code-wrap', this.parentNode)[0]; var callStackItem = this.parentNode,
code = Sizzle('.code-wrap', callStackItem)[0];
code.style.display = window.getComputedStyle(code).display == 'block' ? 'none' : 'block'; code.style.display = window.getComputedStyle(code).display == 'block' ? 'none' : 'block';
refreshCallStackItemCode(callStackItem);
}); });
} }
}; };

4
framework/yii/web/CacheSession.php

@ -97,10 +97,10 @@ class CacheSession extends Session
/** /**
* Generates a unique key used for storing session data in cache. * Generates a unique key used for storing session data in cache.
* @param string $id session variable name * @param string $id session variable name
* @return string a safe cache key associated with the session variable name * @return mixed a safe cache key associated with the session variable name
*/ */
protected function calculateKey($id) protected function calculateKey($id)
{ {
return $this->cache->buildKey(array(__CLASS__, $id)); return array(__CLASS__, $id);
} }
} }

44
framework/yii/web/Controller.php

@ -8,6 +8,8 @@
namespace yii\web; namespace yii\web;
use Yii; use Yii;
use yii\base\HttpException;
use yii\base\InlineAction;
/** /**
* Controller is the base class of Web controllers. * Controller is the base class of Web controllers.
@ -19,6 +21,48 @@ use Yii;
class Controller extends \yii\base\Controller class Controller extends \yii\base\Controller
{ {
/** /**
* Binds the parameters to the action.
* This method is invoked by [[Action]] when it begins to run with the given parameters.
* This method will check the parameter names that the action requires and return
* the provided parameters according to the requirement. If there is any missing parameter,
* an exception will be thrown.
* @param \yii\base\Action $action the action to be bound with parameters
* @param array $params the parameters to be bound to the action
* @return array the valid parameters that the action can run with.
* @throws HttpException if there are missing parameters.
*/
public function bindActionParams($action, $params)
{
if ($action instanceof InlineAction) {
$method = new \ReflectionMethod($this, $action->actionMethod);
} else {
$method = new \ReflectionMethod($action, 'run');
}
$args = array();
$missing = array();
foreach ($method->getParameters() as $param) {
$name = $param->getName();
if (array_key_exists($name, $params)) {
$args[] = $params[$name];
unset($params[$name]);
} elseif ($param->isDefaultValueAvailable()) {
$args[] = $param->getDefaultValue();
} else {
$missing[] = $name;
}
}
if (!empty($missing)) {
throw new HttpException(400, Yii::t('yii', 'Missing required parameters: {params}', array(
'{params}' => implode(', ', $missing),
)));
}
return $args;
}
/**
* Creates a URL using the given route and parameters. * Creates a URL using the given route and parameters.
* *
* This method enhances [[UrlManager::createUrl()]] by supporting relative routes. * This method enhances [[UrlManager::createUrl()]] by supporting relative routes.

2
framework/yii/web/UrlManager.php

@ -103,7 +103,7 @@ class UrlManager extends Component
$this->cache = Yii::$app->getComponent($this->cache); $this->cache = Yii::$app->getComponent($this->cache);
} }
if ($this->cache instanceof Cache) { if ($this->cache instanceof Cache) {
$key = $this->cache->buildKey(__CLASS__); $key = __CLASS__;
$hash = md5(json_encode($this->rules)); $hash = md5(json_encode($this->rules));
if (($data = $this->cache->get($key)) !== false && isset($data[1]) && $data[1] === $hash) { if (($data = $this->cache->get($key)) !== false && isset($data[1]) && $data[1] === $hash) {
$this->rules = $data[0]; $this->rules = $data[0];

90
framework/yii/web/VerbFilter.php

@ -0,0 +1,90 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\web;
use Yii;
use yii\base\ActionEvent;
use yii\base\Behavior;
use yii\base\HttpException;
/**
* VerbFilter is an action filter that filters by HTTP request methods.
*
* It allows to define allowed HTTP request methods for each action and will throw
* an HTTP 405 error when the method is not allowed.
*
* To use VerbFilter, declare it in the `behaviors()` method of your controller class.
* For example, the following declarations will define a typical set of allowed
* request methods for REST CRUD actions.
*
* ~~~
* public function behaviors()
* {
* return array(
* 'verbs' => array(
* 'class' => \yii\web\VerbFilter::className(),
* 'actions' => array(
* 'index' => array('get'),
* 'view' => array('get'),
* 'create' => array('get', 'post'),
* 'update' => array('get', 'put', 'post'),
* 'delete' => array('post', 'delete'),
* ),
* ),
* );
* }
* ~~~
*
* @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.7
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
class VerbFilter extends Behavior
{
/**
* @var array this property defines the allowed request methods for each action.
* For each action that should only support limited set of request methods
* you add an entry with the action id as array key and an array of
* allowed methods (e.g. GET, HEAD, PUT) as the value.
* If an action is not listed all request methods are considered allowed.
*/
public $actions = array();
/**
* 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(
Controller::EVENT_BEFORE_ACTION => 'beforeAction',
);
}
/**
* @param ActionEvent $event
* @return boolean
* @throws \yii\base\HttpException when the request method is not allowed.
*/
public function beforeAction($event)
{
$action = $event->action->id;
if (isset($this->actions[$action])) {
$verb = Yii::$app->getRequest()->getRequestMethod();
$allowed = array_map('strtoupper', $this->actions[$action]);
if (!in_array($verb, $allowed)) {
$event->isValid = false;
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.7
header('Allow: ' . implode(', ', $allowed));
throw new HttpException(405, 'Method Not Allowed. This url can only handle the following request methods: ' . implode(', ', $allowed));
}
}
return $event->isValid;
}
}

12
framework/yii/widgets/ActiveField.php

@ -557,7 +557,14 @@ class ActiveField extends Component
} }
/** /**
* Renders a field containing a widget. * Renders a field containing an input widget.
*
* Note that the widget must have both `model` and `attribute` properties. They will
* be initialized with [[model]] and [[attribute]] of this field, respectively.
*
* If you want to use a widget that does not have `model` and `attribute` properties,
* please use [[render()]] instead.
*
* @param string $class the widget class name * @param string $class the widget class name
* @param array $config name-value pairs that will be used to initialize the widget * @param array $config name-value pairs that will be used to initialize the widget
* @return string the rendering result * @return string the rendering result
@ -565,6 +572,9 @@ class ActiveField extends Component
public function widget($class, $config = array()) public function widget($class, $config = array())
{ {
/** @var \yii\base\Widget $class */ /** @var \yii\base\Widget $class */
$config['model'] = $this->model;
$config['attribute'] = $this->attribute;
$config['view'] = $this->form->getView();
return $this->render($class::widget($config)); return $this->render($class::widget($config));
} }
} }

4
framework/yii/widgets/ActiveForm.php

@ -134,8 +134,8 @@ class ActiveForm extends Widget
$id = $this->options['id']; $id = $this->options['id'];
$options = Json::encode($this->getClientOptions()); $options = Json::encode($this->getClientOptions());
$attributes = Json::encode($this->attributes); $attributes = Json::encode($this->attributes);
$this->view->registerAssetBundle('yii/form'); $this->getView()->registerAssetBundle('yii/form');
$this->view->registerJs("jQuery('#$id').yiiActiveForm($attributes, $options);"); $this->getView()->registerJs("jQuery('#$id').yiiActiveForm($attributes, $options);");
} }
echo Html::endForm(); echo Html::endForm();
} }

64
framework/yii/widgets/Captcha.php

@ -9,13 +9,12 @@ namespace yii\widgets;
use Yii; use Yii;
use yii\base\InvalidConfigException; use yii\base\InvalidConfigException;
use yii\base\Widget;
use yii\helpers\Html; use yii\helpers\Html;
use yii\helpers\Json; use yii\helpers\Json;
use yii\web\CaptchaAction; use yii\web\CaptchaAction;
/** /**
* Captcha renders a CAPTCHA image element. * Captcha renders a CAPTCHA image and an input field that takes user-entered verification code.
* *
* Captcha is used together with [[CaptchaAction]] provide [CAPTCHA](http://en.wikipedia.org/wiki/Captcha) * Captcha is used together with [[CaptchaAction]] provide [CAPTCHA](http://en.wikipedia.org/wiki/Captcha)
* - a way of preventing Website spamming. * - a way of preventing Website spamming.
@ -32,7 +31,7 @@ use yii\web\CaptchaAction;
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */
class Captcha extends Widget class Captcha extends InputWidget
{ {
/** /**
* @var string the route of the action that generates the CAPTCHA images. * @var string the route of the action that generates the CAPTCHA images.
@ -40,27 +39,66 @@ class Captcha extends Widget
*/ */
public $captchaAction = 'site/captcha'; public $captchaAction = 'site/captcha';
/** /**
* @var array HTML attributes to be applied to the rendered image element. * @var array HTML attributes to be applied to the text input field.
*/ */
public $options = array(); public $options = array();
/**
* @var array HTML attributes to be applied to the CAPTCHA image tag.
*/
public $imageOptions = array();
/**
* @var string the template for arranging the CAPTCHA image tag and the text input tag.
* In this template, the token `{image}` will be replaced with the actual image tag,
* while `{input}` will be replaced with the text input tag.
*/
public $template = '{image} {input}';
/** /**
* Renders the widget. * Initializes the widget.
*/ */
public function run() public function init()
{ {
parent::init();
$this->checkRequirements(); $this->checkRequirements();
if (!isset($this->options['id'])) { if (!isset($this->options['id'])) {
$this->options['id'] = $this->getId(); $this->options['id'] = $this->hasModel() ? Html::getInputId($this->model, $this->attribute) : $this->getId();
}
if (!isset($this->imageOptions['id'])) {
$this->imageOptions['id'] = $this->options['id'] . '-image';
}
}
/**
* Renders the widget.
*/
public function run()
{
$this->registerClientScript();
if ($this->hasModel()) {
$input = Html::activeTextInput($this->model, $this->attribute, $this->options);
} else {
$input = Html::textInput($this->name, $this->value, $this->options);
} }
$id = $this->options['id'];
$options = Json::encode($this->getClientOptions());
$this->view->registerAssetBundle('yii/captcha');
$this->view->registerJs("jQuery('#$id').yiiCaptcha($options);");
$url = Yii::$app->getUrlManager()->createUrl($this->captchaAction, array('v' => uniqid())); $url = Yii::$app->getUrlManager()->createUrl($this->captchaAction, array('v' => uniqid()));
echo Html::img($url, $this->options); $image = Html::img($url, $this->imageOptions);
echo strtr($this->template, array(
'{input}' => $input,
'{image}' => $image,
));
}
/**
* Registers the needed JavaScript.
*/
public function registerClientScript()
{
$options = $this->getClientOptions();
$options = empty($options) ? '' : Json::encode($options);
$id = $this->imageOptions['id'];
$this->getView()->registerAssetBundle('yii/captcha');
$this->getView()->registerJs("jQuery('#$id').yiiCaptcha($options);");
} }
/** /**

4
framework/yii/widgets/FragmentCache.php

@ -159,7 +159,7 @@ class FragmentCache extends Widget
/** /**
* Generates a unique key used for storing the content in cache. * Generates a unique key used for storing the content in cache.
* The key generated depends on both [[id]] and [[variations]]. * The key generated depends on both [[id]] and [[variations]].
* @return string a valid cache key * @return mixed a valid cache key
*/ */
protected function calculateKey() protected function calculateKey()
{ {
@ -169,6 +169,6 @@ class FragmentCache extends Widget
$factors[] = $factor; $factors[] = $factor;
} }
} }
return $this->cache->buildKey($factors); return $factors;
} }
} }

64
framework/yii/widgets/InputWidget.php

@ -0,0 +1,64 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\widgets;
use Yii;
use yii\base\Widget;
use yii\base\Model;
use yii\base\InvalidConfigException;
/**
* InputWidget is the base class for widgets that collect user inputs.
*
* An input widget can be associated with a data model and an attribute,
* or a name and a value. If the former, the name and the value will
* be generated automatically.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class InputWidget extends Widget
{
/**
* @var Model the data model that this widget is associated with.
*/
public $model;
/**
* @var string the model attribute that this widget is associated with.
*/
public $attribute;
/**
* @var string the input name. This must be set if [[model]] and [[attribute]] are not set.
*/
public $name;
/**
* @var string the input value.
*/
public $value;
/**
* Initializes the widget.
* If you override this method, make sure you call the parent implementation first.
*/
public function init()
{
if (!$this->hasModel() && $this->name === null) {
throw new InvalidConfigException("Either 'name' or 'model' and 'attribute' properties must be specified.");
}
parent::init();
}
/**
* @return boolean whether this widget is associated with a data model.
*/
protected function hasModel()
{
return $this->model instanceof Model && $this->attribute !== null;
}
}

136
framework/yii/widgets/MaskedInput.php

@ -0,0 +1,136 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\widgets;
use yii\base\InvalidConfigException;
use yii\helpers\Html;
use yii\helpers\Json;
use yii\web\JsExpression;
/**
* MaskedInput generates a masked text input.
*
* MaskedInput is similar to [[Html::textInput()]] except that
* an input mask will be used to force users to enter properly formatted data,
* such as phone numbers, social security numbers.
*
* To use MaskedInput, you must set the [[mask]] property. The following example
* shows how to use MaskedInput to collect phone numbers:
*
* ~~~
* echo MaskedInput::widget(array(
* 'name' => 'phone',
* 'mask' => '999-999-9999',
* ));
* ~~~
*
* The masked text field is implemented based on the [jQuery masked input plugin](http://digitalbush.com/projects/masked-input-plugin).
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class MaskedInput extends InputWidget
{
/**
* @var string the input mask (e.g. '99/99/9999' for date input). The following characters are predefined:
*
* - `a`: represents an alpha character (A-Z,a-z)
* - `9`: represents a numeric character (0-9)
* - `*`: represents an alphanumeric character (A-Z,a-z,0-9)
* - `?`: anything listed after '?' within the mask is considered optional user input
*
* Additional characters can be defined by specifying the [[charMap]] property.
*/
public $mask;
/**
* @var array the mapping between mask characters and the corresponding patterns.
* For example, `array('~' => '[+-]')` specifies that the '~' character expects '+' or '-' input.
* Defaults to null, meaning using the map as described in [[mask]].
*/
public $charMap;
/**
* @var string the character prompting for user input. Defaults to underscore '_'.
*/
public $placeholder;
/**
* @var string a JavaScript function callback that will be invoked when user finishes the input.
*/
public $completed;
/**
* @var array the HTML attributes for the input tag.
*/
public $options = array();
/**
* Initializes the widget.
* @throws InvalidConfigException if the "mask" property is not set.
*/
public function init()
{
parent::init();
if (empty($this->mask)) {
throw new InvalidConfigException('The "mask" property must be set.');
}
if (!isset($this->options['id'])) {
$this->options['id'] = $this->hasModel() ? Html::getInputId($this->model, $this->attribute) : $this->getId();
}
}
/**
* Runs the widget.
*/
public function run()
{
if ($this->hasModel()) {
echo Html::activeTextInput($this->model, $this->attribute, $this->options);
} else {
echo Html::textInput($this->name, $this->value, $this->options);
}
$this->registerClientScript();
}
/**
* Registers the needed JavaScript.
*/
public function registerClientScript()
{
$options = $this->getClientOptions();
$options = empty($options) ? '' : ',' . Json::encode($options);
$js = '';
if (is_array($this->charMap) && !empty($this->charMap)) {
$js .= 'jQuery.mask.definitions=' . Json::encode($this->charMap) . ";\n";
}
$id = $this->options['id'];
$js .= "jQuery(\"#{$id}\").mask(\"{$this->mask}\"{$options});";
$this->getView()->registerAssetBundle('yii/maskedinput');
$this->getView()->registerJs($js);
}
/**
* @return array the options for the text field
*/
protected function getClientOptions()
{
$options = array();
if ($this->placeholder !== null) {
$options['placeholder'] = $this->placeholder;
}
if ($this->completed !== null) {
if ($this->completed instanceof JsExpression) {
$options['completed'] = $this->completed;
} else {
$options['completed'] = new JsExpression($this->completed);
}
}
return $options;
}
}

179
tests/unit/framework/base/FormatterTest.php

@ -0,0 +1,179 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yiiunit\framework\base;
use yii\base\Formatter;
use yiiunit\TestCase;
/**
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class FormatterTest extends TestCase
{
/**
* @var Formatter
*/
protected $formatter;
protected function setUp()
{
parent::setUp();
$this->mockApplication();
$this->formatter = new Formatter();
}
protected function tearDown()
{
parent::tearDown();
$this->formatter = null;
}
public function testAsRaw()
{
$value = '123';
$this->assertSame($value, $this->formatter->asRaw($value));
$value = 123;
$this->assertSame($value, $this->formatter->asRaw($value));
$value = '<>';
$this->assertSame($value, $this->formatter->asRaw($value));
}
public function testAsText()
{
$value = '123';
$this->assertSame($value, $this->formatter->asText($value));
$value = 123;
$this->assertSame("$value", $this->formatter->asText($value));
$value = '<>';
$this->assertSame('&lt;&gt;', $this->formatter->asText($value));
}
public function testAsNtext()
{
$value = '123';
$this->assertSame($value, $this->formatter->asNtext($value));
$value = 123;
$this->assertSame("$value", $this->formatter->asNtext($value));
$value = '<>';
$this->assertSame('&lt;&gt;', $this->formatter->asNtext($value));
$value = "123\n456";
$this->assertSame("123<br />\n456", $this->formatter->asNtext($value));
}
public function testAsParagraphs()
{
$value = '123';
$this->assertSame("<p>$value</p>", $this->formatter->asParagraphs($value));
$value = 123;
$this->assertSame("<p>$value</p>", $this->formatter->asParagraphs($value));
$value = '<>';
$this->assertSame('<p>&lt;&gt;</p>', $this->formatter->asParagraphs($value));
$value = "123\n456";
$this->assertSame("<p>123\n456</p>", $this->formatter->asParagraphs($value));
$value = "123\n\n456";
$this->assertSame("<p>123</p>\n<p>456</p>", $this->formatter->asParagraphs($value));
$value = "123\n\n\n456";
$this->assertSame("<p>123</p>\n<p>456</p>", $this->formatter->asParagraphs($value));
}
public function testAsHtml()
{
// todo: dependency on HtmlPurifier
}
public function testAsEmail()
{
$value = 'test@sample.com';
$this->assertSame("<a href=\"mailto:$value\">$value</a>", $this->formatter->asEmail($value));
}
public function testAsImage()
{
$value = 'http://sample.com/img.jpg';
$this->assertSame("<img src=\"$value\" alt=\"\" />", $this->formatter->asImage($value));
}
public function testAsBoolean()
{
$value = true;
$this->assertSame('Yes', $this->formatter->asBoolean($value));
$value = false;
$this->assertSame('No', $this->formatter->asBoolean($value));
$value = "111";
$this->assertSame('Yes', $this->formatter->asBoolean($value));
$value = "";
$this->assertSame('No', $this->formatter->asBoolean($value));
}
public function testAsDate()
{
$value = time();
$this->assertSame(date('Y/m/d', $value), $this->formatter->asDate($value));
$this->assertSame(date('Y-m-d', $value), $this->formatter->asDate($value, 'Y-m-d'));
}
public function testAsTime()
{
$value = time();
$this->assertSame(date('h:i:s A', $value), $this->formatter->asTime($value));
$this->assertSame(date('h:i:s', $value), $this->formatter->asTime($value, 'h:i:s'));
}
public function testAsDatetime()
{
$value = time();
$this->assertSame(date('Y/m/d h:i:s A', $value), $this->formatter->asDatetime($value));
$this->assertSame(date('Y-m-d h:i:s', $value), $this->formatter->asDatetime($value, 'Y-m-d h:i:s'));
}
public function testAsInteger()
{
$value = 123;
$this->assertSame("$value", $this->formatter->asInteger($value));
$value = 123.23;
$this->assertSame("123", $this->formatter->asInteger($value));
$value = 'a';
$this->assertSame("0", $this->formatter->asInteger($value));
$value = -123.23;
$this->assertSame("-123", $this->formatter->asInteger($value));
$value = "-123abc";
$this->assertSame("-123", $this->formatter->asInteger($value));
}
public function testAsDouble()
{
$value = 123.12;
$this->assertSame("123.12", $this->formatter->asDouble($value));
$this->assertSame("123.1", $this->formatter->asDouble($value, 1));
$this->assertSame("123", $this->formatter->asDouble($value, 0));
$value = 123;
$this->assertSame("123.00", $this->formatter->asDouble($value));
$this->formatter->decimalSeparator = ',';
$value = 123.12;
$this->assertSame("123,12", $this->formatter->asDouble($value));
$this->assertSame("123,1", $this->formatter->asDouble($value, 1));
$this->assertSame("123", $this->formatter->asDouble($value, 0));
$value = 123123.123;
$this->assertSame("123123,12", $this->formatter->asDouble($value));
}
public function testAsNumber()
{
$value = 123123.123;
$this->assertSame("123,123", $this->formatter->asNumber($value));
$this->assertSame("123,123.12", $this->formatter->asNumber($value, 2));
$this->formatter->decimalSeparator = ',';
$this->formatter->thousandSeparator = ' ';
$this->assertSame("123 123", $this->formatter->asNumber($value));
$this->assertSame("123 123,12", $this->formatter->asNumber($value, 2));
$this->formatter->thousandSeparator = '';
$this->assertSame("123123", $this->formatter->asNumber($value));
$this->assertSame("123123,12", $this->formatter->asNumber($value, 2));
}
}

2
tests/unit/framework/caching/CacheTestCase.php

@ -65,7 +65,7 @@ abstract class CacheTestCase extends TestCase
{ {
$cache = $this->getCacheInstance(); $cache = $this->getCacheInstance();
$this->assertNotNull(\Yii::$app->id); $this->assertNotNull(\Yii::$app->id);
$this->assertEquals(\Yii::$app->id, $cache->keyPrefix); $this->assertNotNull($cache->keyPrefix);
} }
public function testSet() public function testSet()

78
tests/unit/framework/console/controllers/AssetControllerTest.php

@ -169,6 +169,23 @@ class AssetControllerTest extends TestCase
} }
} }
/**
* Invokes the asset controller method even if it is protected.
* @param string $methodName name of the method to be invoked.
* @param array $args method arguments.
* @return mixed method invoke result.
*/
protected function invokeAssetControllerMethod($methodName, array $args = array())
{
$controller = $this->createAssetController();
$controllerClassReflection = new ReflectionClass(get_class($controller));
$methodReflection = $controllerClassReflection->getMethod($methodName);
$methodReflection->setAccessible(true);
$result = $methodReflection->invokeArgs($controller, $args);
$methodReflection->setAccessible(false);
return $result;
}
// Tests : // Tests :
public function testActionTemplate() public function testActionTemplate()
@ -237,4 +254,65 @@ class AssetControllerTest extends TestCase
$this->assertContains($content, $compressedJsFileContent, "Source of '{$name}' is missing in combined file!"); $this->assertContains($content, $compressedJsFileContent, "Source of '{$name}' is missing in combined file!");
} }
} }
/**
* Data provider for [[testAdjustCssUrl()]].
* @return array test data.
*/
public function adjustCssUrlDataProvider()
{
return array(
array(
'.published-same-dir-class {background-image: url(published_same_dir.png);}',
'/test/base/path/assets/input',
'/test/base/path/assets/output',
'.published-same-dir-class {background-image: url(../input/published_same_dir.png);}',
),
array(
'.published-relative-dir-class {background-image: url(../img/published_relative_dir.png);}',
'/test/base/path/assets/input',
'/test/base/path/assets/output',
'.published-relative-dir-class {background-image: url(../img/published_relative_dir.png);}',
),
array(
'.static-same-dir-class {background-image: url(\'static_same_dir.png\');}',
'/test/base/path/css',
'/test/base/path/assets/output',
'.static-same-dir-class {background-image: url(\'../../css/static_same_dir.png\');}',
),
array(
'.static-relative-dir-class {background-image: url("../img/static_relative_dir.png");}',
'/test/base/path/css',
'/test/base/path/assets/output',
'.static-relative-dir-class {background-image: url("../../img/static_relative_dir.png");}',
),
array(
'.absolute-url-class {background-image: url(http://domain.com/img/image.gif);}',
'/test/base/path/assets/input',
'/test/base/path/assets/output',
'.absolute-url-class {background-image: url(http://domain.com/img/image.gif);}',
),
array(
'.absolute-url-secure-class {background-image: url(https://secure.domain.com/img/image.gif);}',
'/test/base/path/assets/input',
'/test/base/path/assets/output',
'.absolute-url-secure-class {background-image: url(https://secure.domain.com/img/image.gif);}',
),
);
}
/**
* @dataProvider adjustCssUrlDataProvider
*
* @param $cssContent
* @param $inputFilePath
* @param $outputFilePath
* @param $expectedCssContent
*/
public function testAdjustCssUrl($cssContent, $inputFilePath, $outputFilePath, $expectedCssContent)
{
$adjustedCssContent = $this->invokeAssetControllerMethod('adjustCssUrl', array($cssContent, $inputFilePath, $outputFilePath));
$this->assertEquals($expectedCssContent, $adjustedCssContent, 'Unable to adjust CSS correctly!');
}
} }

88
tests/unit/framework/i18n/FormatterTest.php

@ -0,0 +1,88 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yiiunit\framework\i18n;
use yii\i18n\Formatter;
use yiiunit\TestCase;
/**
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class FormatterTest extends TestCase
{
/**
* @var Formatter
*/
protected $formatter;
protected function setUp()
{
parent::setUp();
if (!extension_loaded('intl')) {
$this->markTestSkipped('intl extension is required.');
}
$this->mockApplication();
$this->formatter = new Formatter(array(
'locale' => 'en_US',
));
}
protected function tearDown()
{
parent::tearDown();
$this->formatter = null;
}
public function testAsDecimal()
{
$value = '123';
$this->assertSame($value, $this->formatter->asDecimal($value));
$value = '123456';
$this->assertSame("123,456", $this->formatter->asDecimal($value));
$value = '-123456.123';
$this->assertSame("-123,456.123", $this->formatter->asDecimal($value));
}
public function testAsPercent()
{
$value = '123';
$this->assertSame('12,300%', $this->formatter->asPercent($value));
$value = '0.1234';
$this->assertSame("12%", $this->formatter->asPercent($value));
$value = '-0.009343';
$this->assertSame("-1%", $this->formatter->asPercent($value));
}
public function testAsScientific()
{
$value = '123';
$this->assertSame('1.23E2', $this->formatter->asScientific($value));
$value = '123456';
$this->assertSame("1.23456E5", $this->formatter->asScientific($value));
$value = '-123456.123';
$this->assertSame("-1.23456123E5", $this->formatter->asScientific($value));
}
public function testAsCurrency()
{
$value = '123';
$this->assertSame('$123.00', $this->formatter->asCurrency($value));
$value = '123.456';
$this->assertSame("$123.46", $this->formatter->asCurrency($value));
$value = '-123456.123';
$this->assertSame("($123,456.12)", $this->formatter->asCurrency($value));
}
public function testDate()
{
$time = time();
$this->assertSame(date('n/j/y', $time), $this->formatter->asDate($time));
$this->assertSame(date('M j, Y', $time), $this->formatter->asDate($time, 'long'));
}
}
Loading…
Cancel
Save