diff --git a/.travis.yml b/.travis.yml
index e4b8278..01abd50 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -10,5 +10,6 @@ env:
before_script:
- sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'create database IF NOT EXISTS yiitest;'; fi"
-
+ - psql -U postgres -c 'drop database if exists yiitest;';
+ - psql -U postgres -c 'create database yiitest;';
script: phpunit
\ No newline at end of file
diff --git a/apps/advanced/README.md b/apps/advanced/README.md
index c443c90..f2abc1e 100644
--- a/apps/advanced/README.md
+++ b/apps/advanced/README.md
@@ -10,7 +10,7 @@ if you have a project to be deployed for production soon.
Thank you for using Yii 2 Advanced Application Template - an application template
that works out-of-box and can be easily customized to fit for your needs.
-Yii 2 Advanced Application Template is best suitable for large projects requiring frontend and backstage separation,
+Yii 2 Advanced Application Template is best suitable for large projects requiring frontend and backend separation,
deployment in different environments, configuration nesting etc.
@@ -20,18 +20,18 @@ DIRECTORY STRUCTURE
```
common
config/ contains shared configurations
- models/ contains model classes used in both backstage and frontend
+ models/ contains model classes used in both backend and frontend
console
config/ contains console configurations
controllers/ contains console controllers (commands)
migrations/ contains database migrations
models/ contains console-specific model classes
runtime/ contains files generated during runtime
-backstage
+backend
assets/ contains application assets such as JavaScript and CSS
- config/ contains backstage configurations
+ config/ contains backend configurations
controllers/ contains Web controller classes
- models/ contains backstage-specific model classes
+ models/ contains backend-specific model classes
runtime/ contains files generated during runtime
views/ contains view files for the Web application
www/ contains the entry script and Web resources
@@ -79,6 +79,21 @@ php composer.phar create-project --stability=dev yiisoft/yii2-app-advanced yii-a
This is not currently available. We will provide it when Yii 2 is formally released.
+### Install from development repository
+
+If you've cloned the [Yii 2 framework main development repository](https://github.com/yiisoft/yii2) you
+can bootstrap your application with:
+
+~~~
+cd yii2/apps/advanced
+php composer.phar create-project
+~~~
+
+*Note: If the above command fails with `[RuntimeException] Not enough arguments.` run
+`php composer.phar self-update` to obtain an updated version of composer which supports creating projects
+from local packages.*
+
+
GETTING STARTED
---------------
@@ -92,7 +107,7 @@ the installed application. You only need to do these once for all.
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/`
+- the backend using the URL `http://localhost/yii-advanced/backend/www/`
assuming `yii-advanced` is directly under the document root of your Web server.
diff --git a/apps/advanced/backstage/assets/.gitkeep b/apps/advanced/backend/assets/.gitkeep
similarity index 100%
rename from apps/advanced/backstage/assets/.gitkeep
rename to apps/advanced/backend/assets/.gitkeep
diff --git a/apps/advanced/backstage/config/.gitignore b/apps/advanced/backend/config/.gitignore
similarity index 100%
rename from apps/advanced/backstage/config/.gitignore
rename to apps/advanced/backend/config/.gitignore
diff --git a/apps/advanced/backstage/config/assets.php b/apps/advanced/backend/config/assets.php
similarity index 100%
rename from apps/advanced/backstage/config/assets.php
rename to apps/advanced/backend/config/assets.php
diff --git a/apps/advanced/backstage/config/main.php b/apps/advanced/backend/config/main.php
similarity index 94%
rename from apps/advanced/backstage/config/main.php
rename to apps/advanced/backend/config/main.php
index 6e55c47..3140cd2 100644
--- a/apps/advanced/backstage/config/main.php
+++ b/apps/advanced/backend/config/main.php
@@ -13,7 +13,7 @@ return array(
'basePath' => dirname(__DIR__),
'vendorPath' => dirname(dirname(__DIR__)) . '/vendor',
'preload' => array('log'),
- 'controllerNamespace' => 'backstage\controllers',
+ 'controllerNamespace' => 'backend\controllers',
'modules' => array(
),
'components' => array(
diff --git a/apps/advanced/backstage/config/params.php b/apps/advanced/backend/config/params.php
similarity index 100%
rename from apps/advanced/backstage/config/params.php
rename to apps/advanced/backend/config/params.php
diff --git a/apps/advanced/backstage/controllers/SiteController.php b/apps/advanced/backend/controllers/SiteController.php
similarity index 94%
rename from apps/advanced/backstage/controllers/SiteController.php
rename to apps/advanced/backend/controllers/SiteController.php
index d40738a..0306c97 100644
--- a/apps/advanced/backstage/controllers/SiteController.php
+++ b/apps/advanced/backend/controllers/SiteController.php
@@ -1,6 +1,6 @@
rem @link http://www.yiiframework.com/
@@ -15,6 +15,6 @@ set YII_PATH=%~dp0
if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe
-"%PHP_COMMAND%" "%YII_PATH%install" %*
+"%PHP_COMMAND%" "%YII_PATH%init" %*
@endlocal
diff --git a/apps/basic/README.md b/apps/basic/README.md
index 5300448..2f8f1e8 100644
--- a/apps/basic/README.md
+++ b/apps/basic/README.md
@@ -59,3 +59,18 @@ assuming `yii-basic` is directly under the document root of your Web server.
### Install from an Archive File
This is not currently available. We will provide it when Yii 2 is formally released.
+
+
+### Install from development repository
+
+If you've cloned the [Yii 2 framework main development repository](https://github.com/yiisoft/yii2) you
+can bootstrap your application with:
+
+~~~
+cd yii2/apps/basic
+php composer.phar create-project
+~~~
+
+*Note: If the above command fails with `[RuntimeException] Not enough arguments.` run
+`php composer.phar self-update` to obtain an updated version of composer which supports creating projects
+from local packages.*
diff --git a/apps/basic/composer.json b/apps/basic/composer.json
index 29b05d1..b01c56c 100644
--- a/apps/basic/composer.json
+++ b/apps/basic/composer.json
@@ -19,10 +19,7 @@
"yiisoft/yii2-composer": "dev-master"
},
"scripts": {
- "post-install-cmd": [
- "yii\\composer\\InstallHandler::setPermissions"
- ],
- "post-update-cmd": [
+ "post-create-project-cmd": [
"yii\\composer\\InstallHandler::setPermissions"
]
},
diff --git a/apps/basic/composer.lock b/apps/basic/composer.lock
index fe87399..a1cb48d 100644
--- a/apps/basic/composer.lock
+++ b/apps/basic/composer.lock
@@ -3,7 +3,7 @@
"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",
+ "hash": "91ba258de768b93025f86071f3bb4b84",
"packages": [
{
"name": "yiisoft/yii2",
@@ -11,12 +11,12 @@
"source": {
"type": "git",
"url": "https://github.com/yiisoft/yii2-framework.git",
- "reference": "2d93f20ba6044ac3f1957300c4ae85fd98ecf47d"
+ "reference": "3ad6334be076a80df3b2ea0b57f38bd0c6901989"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/2d93f20ba6044ac3f1957300c4ae85fd98ecf47d",
- "reference": "2d93f20ba6044ac3f1957300c4ae85fd98ecf47d",
+ "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/3ad6334be076a80df3b2ea0b57f38bd0c6901989",
+ "reference": "3ad6334be076a80df3b2ea0b57f38bd0c6901989",
"shasum": ""
},
"require": {
@@ -97,7 +97,7 @@
"framework",
"yii"
],
- "time": "2013-05-29 02:55:14"
+ "time": "2013-06-02 19:19:29"
},
{
"name": "yiisoft/yii2-composer",
diff --git a/apps/basic/config/main.php b/apps/basic/config/main.php
index 9adfba6..054259e 100644
--- a/apps/basic/config/main.php
+++ b/apps/basic/config/main.php
@@ -4,7 +4,6 @@ return array(
'id' => 'bootstrap',
'basePath' => dirname(__DIR__),
'preload' => array('log'),
- 'controllerNamespace' => 'app\controllers',
'modules' => array(
// 'debug' => array(
// 'class' => 'yii\debug\Module',
diff --git a/apps/basic/controllers/SiteController.php b/apps/basic/controllers/SiteController.php
index ff3b8b4..9d1922b 100644
--- a/apps/basic/controllers/SiteController.php
+++ b/apps/basic/controllers/SiteController.php
@@ -20,6 +20,7 @@ class SiteController extends Controller
public function actionIndex()
{
+ Yii::$app->end(0, false);
echo $this->render('index');
}
diff --git a/extensions/composer/yii/composer/InstallHandler.php b/extensions/composer/yii/composer/InstallHandler.php
index 9e36a35..be4037b 100644
--- a/extensions/composer/yii/composer/InstallHandler.php
+++ b/extensions/composer/yii/composer/InstallHandler.php
@@ -82,7 +82,7 @@ class InstallHandler
throw new Exception("Config file does not exist: $configFile");
}
- require(__DIR__ . '/../../../yii2/yii/Yii.php');
+ require_once(__DIR__ . '/../../../yii2/yii/Yii.php');
$application = new Application(require($configFile));
$request = $application->getRequest();
diff --git a/extensions/smarty/yii/smarty/ViewRenderer.php b/extensions/smarty/yii/smarty/ViewRenderer.php
index d8c5d30..164ae8c 100644
--- a/extensions/smarty/yii/smarty/ViewRenderer.php
+++ b/extensions/smarty/yii/smarty/ViewRenderer.php
@@ -26,12 +26,12 @@ class ViewRenderer extends BaseViewRenderer
/**
* @var string the directory or path alias pointing to where Smarty cache will be stored.
*/
- public $cachePath = '@app/runtime/Smarty/cache';
+ public $cachePath = '@runtime/Smarty/cache';
/**
* @var string the directory or path alias pointing to where Smarty compiled templates will be stored.
*/
- public $compilePath = '@app/runtime/Smarty/compile';
+ public $compilePath = '@runtime/Smarty/compile';
/**
* @var Smarty
diff --git a/extensions/twig/yii/twig/ViewRenderer.php b/extensions/twig/yii/twig/ViewRenderer.php
index 7498d86..76bee95 100644
--- a/extensions/twig/yii/twig/ViewRenderer.php
+++ b/extensions/twig/yii/twig/ViewRenderer.php
@@ -25,7 +25,7 @@ class ViewRenderer extends BaseViewRenderer
/**
* @var string the directory or path alias pointing to where Twig cache will be stored.
*/
- public $cachePath = '@app/runtime/Twig/cache';
+ public $cachePath = '@runtime/Twig/cache';
/**
* @var array Twig options
diff --git a/framework/yii/YiiBase.php b/framework/yii/YiiBase.php
index 4fe0e77..ee75822 100644
--- a/framework/yii/YiiBase.php
+++ b/framework/yii/YiiBase.php
@@ -606,11 +606,36 @@ class YiiBase
public static function t($category, $message, $params = array(), $language = null)
{
if (self::$app !== null) {
- return self::$app->getI18N()->translate($category, $message, $params, $language);
+ return self::$app->getI18N()->translate($category, $message, $params, $language ?: self::$app->language);
} else {
return is_array($params) ? strtr($message, $params) : $message;
}
}
+
+ /**
+ * Configures an object with the initial property values.
+ * @param object $object the object to be configured
+ * @param array $properties the property initial values given in terms of name-value pairs.
+ */
+ public static function configure($object, $properties)
+ {
+ foreach ($properties as $name => $value) {
+ $object->$name = $value;
+ }
+ }
+
+ /**
+ * Returns the public member variables of an object.
+ * This method is provided such that we can get the public member variables of an object.
+ * It is different from "get_object_vars()" because the latter will return private
+ * and protected variables if it is called within the object itself.
+ * @param object $object the object to be handled
+ * @return array the public member variables of the object
+ */
+ public static function getObjectVars($object)
+ {
+ return get_object_vars($object);
+ }
}
YiiBase::$aliases = array(
diff --git a/framework/yii/base/Application.php b/framework/yii/base/Application.php
index d38f6a9..f5f3d6a 100644
--- a/framework/yii/base/Application.php
+++ b/framework/yii/base/Application.php
@@ -72,25 +72,17 @@ class Application extends Module
public function __construct($config = array())
{
Yii::$app = $this;
-
if (!isset($config['id'])) {
throw new InvalidConfigException('The "id" configuration is required.');
}
-
if (isset($config['basePath'])) {
$this->setBasePath($config['basePath']);
- Yii::setAlias('@app', $this->getBasePath());
unset($config['basePath']);
} else {
throw new InvalidConfigException('The "basePath" configuration is required.');
}
-
- if (isset($config['timeZone'])) {
- $this->setTimeZone($config['timeZone']);
- unset($config['timeZone']);
- } elseif (!ini_get('date.timezone')) {
- $this->setTimeZone('UTC');
- }
+
+ $this->preInit($config);
$this->registerErrorHandlers();
$this->registerCoreComponents();
@@ -99,6 +91,35 @@ class Application extends Module
}
/**
+ * Pre-initializes the application.
+ * This method is called at the beginning of the application constructor.
+ * @param array $config the application configuration
+ */
+ public function preInit(&$config)
+ {
+ if (isset($config['vendorPath'])) {
+ $this->setVendorPath($config['vendorPath']);
+ unset($config['vendorPath']);
+ } else {
+ // set "@vendor"
+ $this->getVendorPath();
+ }
+ if (isset($config['runtimePath'])) {
+ $this->setRuntimePath($config['runtimePath']);
+ unset($config['runtimePath']);
+ } else {
+ // set "@runtime"
+ $this->getRuntimePath();
+ }
+ if (isset($config['timeZone'])) {
+ $this->setTimeZone($config['timeZone']);
+ unset($config['timeZone']);
+ } elseif (!ini_get('date.timezone')) {
+ $this->setTimeZone('UTC');
+ }
+ }
+
+ /**
* Registers error handlers.
*/
public function registerErrorHandlers()
@@ -107,6 +128,11 @@ class Application extends Module
ini_set('display_errors', 0);
set_exception_handler(array($this, 'handleException'));
set_error_handler(array($this, 'handleError'), error_reporting());
+ // Allocating twice more than required to display memory exhausted error
+ // in case of trying to allocate last 1 byte while all memory is taken.
+ $this->_memoryReserve = str_repeat('x', 1024 * 256);
+ register_shutdown_function(array($this, 'end'), 0, false);
+ register_shutdown_function(array($this, 'handleFatalError'));
}
}
@@ -121,11 +147,10 @@ class Application extends Module
{
if (!$this->_ended) {
$this->_ended = true;
+ $this->getResponse()->end();
$this->afterRequest();
}
- $this->handleFatalError();
-
if ($exit) {
exit($status);
}
@@ -139,11 +164,10 @@ class Application extends Module
public function run()
{
$this->beforeRequest();
- // Allocating twice more than required to display memory exhausted error
- // in case of trying to allocate last 1 byte while all memory is taken.
- $this->_memoryReserve = str_repeat('x', 1024 * 256);
- register_shutdown_function(array($this, 'end'), 0, false);
+ $response = $this->getResponse();
+ $response->begin();
$status = $this->processRequest();
+ $response->end();
$this->afterRequest();
return $status;
}
@@ -178,7 +202,8 @@ class Application extends Module
/**
* Returns the directory that stores runtime files.
- * @return string the directory that stores runtime files. Defaults to 'protected/runtime'.
+ * @return string the directory that stores runtime files.
+ * Defaults to the "runtime" subdirectory under [[basePath]].
*/
public function getRuntimePath()
{
@@ -191,16 +216,11 @@ class Application extends Module
/**
* Sets the directory that stores runtime files.
* @param string $path the directory that stores runtime files.
- * @throws InvalidConfigException if the directory does not exist or is not writable
*/
public function setRuntimePath($path)
{
- $path = Yii::getAlias($path);
- if (is_dir($path) && is_writable($path)) {
- $this->_runtimePath = $path;
- } else {
- throw new InvalidConfigException("Runtime path must be a directory writable by the Web server process: $path");
- }
+ $this->_runtimePath = Yii::getAlias($path);
+ Yii::setAlias('@runtime', $this->_runtimePath);
}
private $_vendorPath;
@@ -208,7 +228,7 @@ class Application extends Module
/**
* Returns the directory that stores vendor files.
* @return string the directory that stores vendor files.
- * Defaults to 'vendor' directory under applications [[basePath]].
+ * Defaults to "vendor" directory under [[basePath]].
*/
public function getVendorPath()
{
@@ -225,6 +245,7 @@ class Application extends Module
public function setVendorPath($path)
{
$this->_vendorPath = Yii::getAlias($path);
+ Yii::setAlias('@vendor', $this->_vendorPath);
}
/**
@@ -297,6 +318,15 @@ class Application extends Module
}
/**
+ * Returns the response component.
+ * @return \yii\web\Response|\yii\console\Response the response component
+ */
+ public function getResponse()
+ {
+ return $this->getComponent('response');
+ }
+
+ /**
* Returns the view object.
* @return View the view object that is used to render various view files.
*/
diff --git a/framework/yii/base/ErrorHandler.php b/framework/yii/base/ErrorHandler.php
index 8dc3fce..4e3e92a 100644
--- a/framework/yii/base/ErrorHandler.php
+++ b/framework/yii/base/ErrorHandler.php
@@ -82,11 +82,12 @@ class ErrorHandler extends Component
} elseif (!(Yii::$app instanceof \yii\web\Application)) {
Yii::$app->renderException($exception);
} else {
+ $response = Yii::$app->getResponse();
if (!headers_sent()) {
if ($exception instanceof HttpException) {
- header('HTTP/1.0 ' . $exception->statusCode . ' ' . $exception->getName());
+ $response->setStatusCode($exception->statusCode);
} else {
- header('HTTP/1.0 500 ' . get_class($exception));
+ $response->setStatusCode(500);
}
}
if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') {
@@ -100,13 +101,13 @@ class ErrorHandler extends Component
$view = new View();
$request = '';
- foreach (array('GET', 'POST', 'SERVER', 'FILES', 'COOKIE', 'SESSION', 'ENV') as $name) {
- if (!empty($GLOBALS['_' . $name])) {
- $request .= '$_' . $name . ' = ' . var_export($GLOBALS['_' . $name], true) . ";\n\n";
+ foreach (array('_GET', '_POST', '_SERVER', '_FILES', '_COOKIE', '_SESSION', '_ENV') as $name) {
+ if (!empty($GLOBALS[$name])) {
+ $request .= '$' . $name . ' = ' . var_export($GLOBALS[$name], true) . ";\n\n";
}
}
$request = rtrim($request, "\n\n");
- echo $view->renderFile($this->mainView, array(
+ $response->content = $view->renderFile($this->mainView, array(
'exception' => $exception,
'request' => $request,
), $this);
@@ -255,7 +256,7 @@ class ErrorHandler extends Component
if (isset($_SERVER['SERVER_SOFTWARE'])) {
foreach ($serverUrls as $url => $keywords) {
foreach ($keywords as $keyword) {
- if (stripos($_SERVER['SERVER_SOFTWARE'], $keyword) !== false ) {
+ if (stripos($_SERVER['SERVER_SOFTWARE'], $keyword) !== false) {
return '' . $this->htmlEncode($_SERVER['SERVER_SOFTWARE']) . '';
}
}
diff --git a/framework/yii/base/Formatter.php b/framework/yii/base/Formatter.php
index d15e5f2..545f570 100644
--- a/framework/yii/base/Formatter.php
+++ b/framework/yii/base/Formatter.php
@@ -12,7 +12,6 @@ use DateTime;
use yii\helpers\HtmlPurifier;
use yii\helpers\Html;
-
/**
* Formatter provides a set of commonly used data formatting methods.
*
@@ -39,17 +38,19 @@ class Formatter extends Component
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 array('No', 'Yes')
.
+ * to the text display for false, the second element for true. Defaults to `array('No', 'Yes')`.
*/
public $booleanFormat;
/**
* @var string the character displayed as the decimal point when formatting a number.
+ * If not set, "." will be used.
*/
- public $decimalSeparator = '.';
+ public $decimalSeparator;
/**
* @var string the character displayed as the thousands separator character when formatting a number.
+ * If not set, "," will be used.
*/
- public $thousandSeparator = ',';
+ public $thousandSeparator;
/**
@@ -273,7 +274,11 @@ class Formatter extends Component
*/
public function asDouble($value, $decimals = 2)
{
- return str_replace('.', $this->decimalSeparator, sprintf("%.{$decimals}f", $value));
+ if ($this->decimalSeparator === null) {
+ return sprintf("%.{$decimals}f", $value);
+ } else {
+ return str_replace('.', $this->decimalSeparator, sprintf("%.{$decimals}f", $value));
+ }
}
/**
@@ -287,6 +292,8 @@ class Formatter extends Component
*/
public function asNumber($value, $decimals = 0)
{
- return number_format($value, $decimals, $this->decimalSeparator, $this->thousandSeparator);
+ $ds = isset($this->decimalSeparator) ? $this->decimalSeparator: '.';
+ $ts = isset($this->thousandSeparator) ? $this->thousandSeparator: ',';
+ return number_format($value, $decimals, $ds, $ts);
}
}
diff --git a/framework/yii/base/HttpException.php b/framework/yii/base/HttpException.php
index 4d63764..cce0bb0 100644
--- a/framework/yii/base/HttpException.php
+++ b/framework/yii/base/HttpException.php
@@ -7,6 +7,7 @@
namespace yii\base;
+
/**
* HttpException represents an exception caused by an improper request of the end-user.
*
@@ -42,66 +43,8 @@ class HttpException extends UserException
*/
public function getName()
{
- static $httpCodes = array(
- 100 => 'Continue',
- 101 => 'Switching Protocols',
- 102 => 'Processing',
- 118 => 'Connection timed out',
- 200 => 'OK',
- 201 => 'Created',
- 202 => 'Accepted',
- 203 => 'Non-Authoritative',
- 204 => 'No Content',
- 205 => 'Reset Content',
- 206 => 'Partial Content',
- 207 => 'Multi-Status',
- 210 => 'Content Different',
- 300 => 'Multiple Choices',
- 301 => 'Moved Permanently',
- 302 => 'Found',
- 303 => 'See Other',
- 304 => 'Not Modified',
- 305 => 'Use Proxy',
- 307 => 'Temporary Redirect',
- 310 => 'Too many Redirect',
- 400 => 'Bad Request',
- 401 => 'Unauthorized',
- 402 => 'Payment Required',
- 403 => 'Forbidden',
- 404 => 'Not Found',
- 405 => 'Method Not Allowed',
- 406 => 'Not Acceptable',
- 407 => 'Proxy Authentication Required',
- 408 => 'Request Time-out',
- 409 => 'Conflict',
- 410 => 'Gone',
- 411 => 'Length Required',
- 412 => 'Precondition Failed',
- 413 => 'Request Entity Too Large',
- 414 => 'Request-URI Too Long',
- 415 => 'Unsupported Media Type',
- 416 => 'Requested range unsatisfiable',
- 417 => 'Expectation failed',
- 418 => 'I’m a teapot',
- 422 => 'Unprocessable entity',
- 423 => 'Locked',
- 424 => 'Method failure',
- 425 => 'Unordered Collection',
- 426 => 'Upgrade Required',
- 449 => 'Retry With',
- 450 => 'Blocked by Windows Parental Controls',
- 500 => 'Internal Server Error',
- 501 => 'Not Implemented',
- 502 => 'Bad Gateway ou Proxy Error',
- 503 => 'Service Unavailable',
- 504 => 'Gateway Time-out',
- 505 => 'HTTP Version not supported',
- 507 => 'Insufficient storage',
- 509 => 'Bandwidth Limit Exceeded',
- );
-
- if (isset($httpCodes[$this->statusCode])) {
- return $httpCodes[$this->statusCode];
+ if (isset(\yii\web\Response::$statusTexts[$this->statusCode])) {
+ return \yii\web\Response::$statusTexts[$this->statusCode];
} else {
return 'Error';
}
diff --git a/framework/yii/base/InvalidCallException.php b/framework/yii/base/InvalidCallException.php
index 9a146d4..73cb4b9 100644
--- a/framework/yii/base/InvalidCallException.php
+++ b/framework/yii/base/InvalidCallException.php
@@ -23,4 +23,3 @@ class InvalidCallException extends Exception
return \Yii::t('yii', 'Invalid Call');
}
}
-
diff --git a/framework/yii/base/InvalidConfigException.php b/framework/yii/base/InvalidConfigException.php
index c617381..0a6b4c5 100644
--- a/framework/yii/base/InvalidConfigException.php
+++ b/framework/yii/base/InvalidConfigException.php
@@ -23,4 +23,3 @@ class InvalidConfigException extends Exception
return \Yii::t('yii', 'Invalid Configuration');
}
}
-
diff --git a/framework/yii/base/InvalidParamException.php b/framework/yii/base/InvalidParamException.php
index 0262051..44430a8 100644
--- a/framework/yii/base/InvalidParamException.php
+++ b/framework/yii/base/InvalidParamException.php
@@ -23,4 +23,3 @@ class InvalidParamException extends Exception
return \Yii::t('yii', 'Invalid Parameter');
}
}
-
diff --git a/framework/yii/base/InvalidRouteException.php b/framework/yii/base/InvalidRouteException.php
index a573636..fbcc087 100644
--- a/framework/yii/base/InvalidRouteException.php
+++ b/framework/yii/base/InvalidRouteException.php
@@ -23,4 +23,3 @@ class InvalidRouteException extends UserException
return \Yii::t('yii', 'Invalid Route');
}
}
-
diff --git a/framework/yii/base/Jsonable.php b/framework/yii/base/Jsonable.php
new file mode 100644
index 0000000..e9425a6
--- /dev/null
+++ b/framework/yii/base/Jsonable.php
@@ -0,0 +1,22 @@
+
+ * @since 2.0
+ */
+interface Jsonable
+{
+ /**
+ * @return string the JSON representation of this object
+ */
+ public function toJson();
+}
diff --git a/framework/yii/base/Model.php b/framework/yii/base/Model.php
index c7432f5..b9b7d2d 100644
--- a/framework/yii/base/Model.php
+++ b/framework/yii/base/Model.php
@@ -10,6 +10,7 @@ namespace yii\base;
use ArrayObject;
use ArrayIterator;
use yii\helpers\Inflector;
+use yii\helpers\Json;
use yii\validators\RequiredValidator;
use yii\validators\Validator;
@@ -41,7 +42,7 @@ use yii\validators\Validator;
* @author Qiang Xue
* @since 2.0
*/
-class Model extends Component implements \IteratorAggregate, \ArrayAccess
+class Model extends Component implements \IteratorAggregate, \ArrayAccess, Jsonable
{
/**
* @event ModelEvent an event raised at the beginning of [[validate()]]. You may set
@@ -638,6 +639,16 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess
}
/**
+ * Returns the JSON representation of this object.
+ * The default implementation will return [[attributes]].
+ * @return string the JSON representation of this object.
+ */
+ public function toJson()
+ {
+ return Json::encode($this->getAttributes());
+ }
+
+ /**
* Returns an iterator for traversing the attributes in the model.
* This method is required by the interface IteratorAggregate.
* @return ArrayIterator an iterator for traversing the items in the list.
diff --git a/framework/yii/base/Module.php b/framework/yii/base/Module.php
index ec3001e..cc7c849 100644
--- a/framework/yii/base/Module.php
+++ b/framework/yii/base/Module.php
@@ -1,650 +1,667 @@
- configuration).
- * @property array $components The components (indexed by their IDs) registered within this module.
- * @property array $import List of aliases to be imported. This property is write-only.
- * @property array $aliases List of aliases to be defined. This property is write-only.
- *
- * @author Qiang Xue
- * @since 2.0
- */
-abstract class Module extends Component
-{
- /**
- * @event ActionEvent an event raised before executing a controller action.
- * You may set [[ActionEvent::isValid]] to be false to cancel the action execution.
- */
- const EVENT_BEFORE_ACTION = 'beforeAction';
- /**
- * @event ActionEvent an event raised after executing a controller action.
- */
- const EVENT_AFTER_ACTION = 'afterAction';
- /**
- * @var array custom module parameters (name => value).
- */
- public $params = array();
- /**
- * @var array the IDs of the components that should be preloaded when this module is created.
- */
- public $preload = array();
- /**
- * @var string an ID that uniquely identifies this module among other modules which have the same [[module|parent]].
- */
- public $id;
- /**
- * @var Module the parent module of this module. Null if this module does not have a parent.
- */
- public $module;
- /**
- * @var string|boolean the layout that should be applied for views within this module. This refers to a view name
- * relative to [[layoutPath]]. If this is not set, it means the layout value of the [[module|parent module]]
- * will be taken. If this is false, layout will be disabled within this module.
- */
- public $layout;
- /**
- * @var array mapping from controller ID to controller configurations.
- * Each name-value pair specifies the configuration of a single controller.
- * A controller configuration can be either a string or an array.
- * If the former, the string should be the class name or path alias of the controller.
- * If the latter, the array must contain a 'class' element which specifies
- * the controller's class name or path alias, and the rest of the name-value pairs
- * in the array are used to initialize the corresponding controller properties. For example,
- *
- * ~~~
- * array(
- * 'account' => '@app/controllers/UserController',
- * 'article' => array(
- * 'class' => '@app/controllers/PostController',
- * 'pageTitle' => 'something new',
- * ),
- * )
- * ~~~
- */
- public $controllerMap = array();
- /**
- * @var string the namespace that controller classes are in. Default is to use global namespace.
- */
- public $controllerNamespace;
- /**
- * @return string the default route of this module. Defaults to 'default'.
- * The route may consist of child module ID, controller ID, and/or action ID.
- * For example, `help`, `post/create`, `admin/post/create`.
- * If action ID is not given, it will take the default value as specified in
- * [[Controller::defaultAction]].
- */
- public $defaultRoute = 'default';
- /**
- * @var string the root directory of the module.
- */
- private $_basePath;
- /**
- * @var string the root directory that contains view files for this module
- */
- private $_viewPath;
- /**
- * @var string the root directory that contains layout view files for this module.
- */
- private $_layoutPath;
- /**
- * @var string the directory containing controller classes in the module.
- */
- private $_controllerPath;
- /**
- * @var array child modules of this module
- */
- private $_modules = array();
- /**
- * @var array components registered under this module
- */
- private $_components = array();
-
- /**
- * Constructor.
- * @param string $id the ID of this module
- * @param Module $parent the parent module (if any)
- * @param array $config name-value pairs that will be used to initialize the object properties
- */
- public function __construct($id, $parent = null, $config = array())
- {
- $this->id = $id;
- $this->module = $parent;
- parent::__construct($config);
- }
-
- /**
- * Getter magic method.
- * This method is overridden to support accessing components
- * like reading module properties.
- * @param string $name component or property name
- * @return mixed the named property value
- */
- public function __get($name)
- {
- if ($this->hasComponent($name)) {
- return $this->getComponent($name);
- } else {
- return parent::__get($name);
- }
- }
-
- /**
- * Checks if a property value is null.
- * This method overrides the parent implementation by checking
- * if the named component is loaded.
- * @param string $name the property name or the event name
- * @return boolean whether the property value is null
- */
- public function __isset($name)
- {
- if ($this->hasComponent($name)) {
- return $this->getComponent($name) !== null;
- } else {
- return parent::__isset($name);
- }
- }
-
- /**
- * Initializes the module.
- * This method is called after the module is created and initialized with property values
- * given in configuration. The default implement will create a path alias using the module [[id]]
- * and then call [[preloadComponents()]] to load components that are declared in [[preload]].
- */
- public function init()
- {
- $this->preloadComponents();
- }
-
- /**
- * Returns an ID that uniquely identifies this module among all modules within the current application.
- * Note that if the module is an application, an empty string will be returned.
- * @return string the unique ID of the module.
- */
- public function getUniqueId()
- {
- if ($this instanceof Application) {
- return '';
- } elseif ($this->module) {
- return $this->module->getUniqueId() . '/' . $this->id;
- } else {
- return $this->id;
- }
- }
-
- /**
- * Returns the root directory of the module.
- * It defaults to the directory containing the module class file.
- * @return string the root directory of the module.
- */
- public function getBasePath()
- {
- if ($this->_basePath === null) {
- $class = new \ReflectionClass($this);
- $this->_basePath = dirname($class->getFileName());
- }
- return $this->_basePath;
- }
-
- /**
- * Sets the root directory of the module.
- * This method can only be invoked at the beginning of the constructor.
- * @param string $path the root directory of the module. This can be either a directory name or a path alias.
- * @throws InvalidParamException if the directory does not exist.
- */
- public function setBasePath($path)
- {
- $path = Yii::getAlias($path);
- $p = realpath($path);
- if ($p !== false && is_dir($p)) {
- $this->_basePath = $p;
- } else {
- throw new InvalidParamException("The directory does not exist: $path");
- }
- }
-
- /**
- * Returns the directory that contains the controller classes.
- * Defaults to "[[basePath]]/controllers".
- * @return string the directory that contains the controller classes.
- */
- public function getControllerPath()
- {
- if ($this->_controllerPath !== null) {
- return $this->_controllerPath;
- } else {
- return $this->_controllerPath = $this->getBasePath() . DIRECTORY_SEPARATOR . 'controllers';
- }
- }
-
- /**
- * Sets the directory that contains the controller classes.
- * @param string $path the directory that contains the controller classes.
- * This can be either a directory name or a path alias.
- * @throws Exception if the directory is invalid
- */
- public function setControllerPath($path)
- {
- $this->_controllerPath = Yii::getAlias($path);
- }
-
- /**
- * Returns the directory that contains the view files for this module.
- * @return string the root directory of view files. Defaults to "[[basePath]]/view".
- */
- public function getViewPath()
- {
- if ($this->_viewPath !== null) {
- return $this->_viewPath;
- } else {
- return $this->_viewPath = $this->getBasePath() . DIRECTORY_SEPARATOR . 'views';
- }
- }
-
- /**
- * Sets the directory that contains the view files.
- * @param string $path the root directory of view files.
- * @throws Exception if the directory is invalid
- */
- public function setViewPath($path)
- {
- $this->_viewPath = Yii::getAlias($path);
- }
-
- /**
- * Returns the directory that contains layout view files for this module.
- * @return string the root directory of layout files. Defaults to "[[viewPath]]/layouts".
- */
- public function getLayoutPath()
- {
- if ($this->_layoutPath !== null) {
- return $this->_layoutPath;
- } else {
- return $this->_layoutPath = $this->getViewPath() . DIRECTORY_SEPARATOR . 'layouts';
- }
- }
-
- /**
- * Sets the directory that contains the layout files.
- * @param string $path the root directory of layout files.
- * @throws Exception if the directory is invalid
- */
- public function setLayoutPath($path)
- {
- $this->_layoutPath = Yii::getAlias($path);
- }
-
- /**
- * Defines path aliases.
- * This method calls [[Yii::setAlias()]] to register the path aliases.
- * This method is provided so that you can define path aliases when configuring a module.
- * @param array $aliases list of path aliases to be defined. The array keys are alias names
- * (must start with '@') and the array values are the corresponding paths or aliases.
- * For example,
- *
- * ~~~
- * array(
- * '@models' => '@app/models', // an existing alias
- * '@backend' => __DIR__ . '/../backend', // a directory
- * )
- * ~~~
- */
- public function setAliases($aliases)
- {
- foreach ($aliases as $name => $alias) {
- Yii::setAlias($name, $alias);
- }
- }
-
- /**
- * Checks whether the named module exists.
- * @param string $id module ID
- * @return boolean whether the named module exists. Both loaded and unloaded modules
- * are considered.
- */
- public function hasModule($id)
- {
- return isset($this->_modules[$id]);
- }
-
- /**
- * Retrieves the named module.
- * @param string $id module ID (case-sensitive)
- * @param boolean $load whether to load the module if it is not yet loaded.
- * @return Module|null the module instance, null if the module
- * does not exist.
- * @see hasModule()
- */
- public function getModule($id, $load = true)
- {
- if (isset($this->_modules[$id])) {
- if ($this->_modules[$id] instanceof Module) {
- return $this->_modules[$id];
- } elseif ($load) {
- Yii::trace("Loading module: $id", __METHOD__);
- return $this->_modules[$id] = Yii::createObject($this->_modules[$id], $id, $this);
- }
- }
- return null;
- }
-
- /**
- * Adds a sub-module to this module.
- * @param string $id module ID
- * @param Module|array|null $module the sub-module to be added to this module. This can
- * be one of the followings:
- *
- * - a [[Module]] object
- * - a configuration array: when [[getModule()]] is called initially, the array
- * will be used to instantiate the sub-module
- * - null: the named sub-module will be removed from this module
- */
- public function setModule($id, $module)
- {
- if ($module === null) {
- unset($this->_modules[$id]);
- } else {
- $this->_modules[$id] = $module;
- }
- }
-
- /**
- * Returns the sub-modules in this module.
- * @param boolean $loadedOnly whether to return the loaded sub-modules only. If this is set false,
- * then all sub-modules registered in this module will be returned, whether they are loaded or not.
- * Loaded modules will be returned as objects, while unloaded modules as configuration arrays.
- * @return array the modules (indexed by their IDs)
- */
- public function getModules($loadedOnly = false)
- {
- if ($loadedOnly) {
- $modules = array();
- foreach ($this->_modules as $module) {
- if ($module instanceof Module) {
- $modules[] = $module;
- }
- }
- return $modules;
- } else {
- return $this->_modules;
- }
- }
-
- /**
- * Registers sub-modules in the current module.
- *
- * Each sub-module should be specified as a name-value pair, where
- * name refers to the ID of the module and value the module or a configuration
- * array that can be used to create the module. In the latter case, [[Yii::createObject()]]
- * will be used to create the module.
- *
- * If a new sub-module has the same ID as an existing one, the existing one will be overwritten silently.
- *
- * The following is an example for registering two sub-modules:
- *
- * ~~~
- * array(
- * 'comment' => array(
- * 'class' => 'app\modules\CommentModule',
- * 'db' => 'db',
- * ),
- * 'booking' => array(
- * 'class' => 'app\modules\BookingModule',
- * ),
- * )
- * ~~~
- *
- * @param array $modules modules (id => module configuration or instances)
- */
- public function setModules($modules)
- {
- foreach ($modules as $id => $module) {
- $this->_modules[$id] = $module;
- }
- }
-
- /**
- * Checks whether the named component exists.
- * @param string $id component ID
- * @return boolean whether the named component exists. Both loaded and unloaded components
- * are considered.
- */
- public function hasComponent($id)
- {
- return isset($this->_components[$id]);
- }
-
- /**
- * Retrieves the named component.
- * @param string $id component ID (case-sensitive)
- * @param boolean $load whether to load the component if it is not yet loaded.
- * @return Component|null the component instance, null if the component does not exist.
- * @see hasComponent()
- */
- public function getComponent($id, $load = true)
- {
- if (isset($this->_components[$id])) {
- if ($this->_components[$id] instanceof Object) {
- return $this->_components[$id];
- } elseif ($load) {
- Yii::trace("Loading component: $id", __METHOD__);
- return $this->_components[$id] = Yii::createObject($this->_components[$id]);
- }
- }
- return null;
- }
-
- /**
- * Registers a component with this module.
- * @param string $id component ID
- * @param Component|array|null $component the component to be registered with the module. This can
- * be one of the followings:
- *
- * - a [[Component]] object
- * - a configuration array: when [[getComponent()]] is called initially for this component, the array
- * will be used to instantiate the component via [[Yii::createObject()]].
- * - null: the named component will be removed from the module
- */
- public function setComponent($id, $component)
- {
- if ($component === null) {
- unset($this->_components[$id]);
- } else {
- $this->_components[$id] = $component;
- }
- }
-
- /**
- * Returns the registered components.
- * @param boolean $loadedOnly whether to return the loaded components only. If this is set false,
- * then all components specified in the configuration will be returned, whether they are loaded or not.
- * Loaded components will be returned as objects, while unloaded components as configuration arrays.
- * @return array the components (indexed by their IDs)
- */
- public function getComponents($loadedOnly = false)
- {
- if ($loadedOnly) {
- $components = array();
- foreach ($this->_components as $component) {
- if ($component instanceof Component) {
- $components[] = $component;
- }
- }
- return $components;
- } else {
- return $this->_components;
- }
- }
-
- /**
- * Registers a set of components in this module.
- *
- * Each component should be specified as a name-value pair, where
- * name refers to the ID of the component and value the component or a configuration
- * array that can be used to create the component. In the latter case, [[Yii::createObject()]]
- * will be used to create the component.
- *
- * If a new component has the same ID as an existing one, the existing one will be overwritten silently.
- *
- * The following is an example for setting two components:
- *
- * ~~~
- * array(
- * 'db' => array(
- * 'class' => 'yii\db\Connection',
- * 'dsn' => 'sqlite:path/to/file.db',
- * ),
- * 'cache' => array(
- * 'class' => 'yii\caching\DbCache',
- * 'db' => 'db',
- * ),
- * )
- * ~~~
- *
- * @param array $components components (id => component configuration or instance)
- */
- public function setComponents($components)
- {
- foreach ($components as $id => $component) {
- if (isset($this->_components[$id]['class']) && !isset($component['class'])) {
- $component['class'] = $this->_components[$id]['class'];
- }
- $this->_components[$id] = $component;
- }
- }
-
- /**
- * Loads components that are declared in [[preload]].
- */
- public function preloadComponents()
- {
- foreach ($this->preload as $id) {
- $this->getComponent($id);
- }
- }
-
- /**
- * Runs a controller action specified by a route.
- * This method parses the specified route and creates the corresponding child module(s), controller and action
- * instances. It then calls [[Controller::runAction()]] to run the action with the given parameters.
- * If the route is empty, the method will use [[defaultRoute]].
- * @param string $route the route that specifies the action.
- * @param array $params the parameters to be passed to the action
- * @return integer the status code returned by the action execution. 0 means normal, and other values mean abnormal.
- * @throws InvalidRouteException if the requested route cannot be resolved into an action successfully
- */
- public function runAction($route, $params = array())
- {
- $result = $this->createController($route);
- if (is_array($result)) {
- /** @var $controller Controller */
- list($controller, $actionID) = $result;
- $oldController = Yii::$app->controller;
- Yii::$app->controller = $controller;
- $status = $controller->runAction($actionID, $params);
- Yii::$app->controller = $oldController;
- return $status;
- } else {
- throw new InvalidRouteException('Unable to resolve the request "' . trim($this->getUniqueId() . '/' . $route, '/') . '".');
- }
- }
-
- /**
- * Creates a controller instance based on the controller ID.
- *
- * The controller is created within this module. The method first attempts to
- * create the controller based on the [[controllerMap]] of the module. If not available,
- * it will look for the controller class under the [[controllerPath]] and create an
- * instance of it.
- *
- * @param string $route the route consisting of module, controller and action IDs.
- * @return array|boolean If the controller is created successfully, it will be returned together
- * with the requested action ID. Otherwise false will be returned.
- * @throws InvalidConfigException if the controller class and its file do not match.
- */
- public function createController($route)
- {
- if ($route === '') {
- $route = $this->defaultRoute;
- }
- $route = trim($route, '/');
- if (($pos = strpos($route, '/')) !== false) {
- $id = substr($route, 0, $pos);
- $route = substr($route, $pos + 1);
- } else {
- $id = $route;
- $route = '';
- }
-
- $module = $this->getModule($id);
- if ($module !== null) {
- return $module->createController($route);
- }
-
- if (isset($this->controllerMap[$id])) {
- $controller = Yii::createObject($this->controllerMap[$id], $id, $this);
- } elseif (preg_match('/^[a-z0-9\\-_]+$/', $id)) {
- $className = str_replace(' ', '', ucwords(implode(' ', explode('-', $id)))) . 'Controller';
- $classFile = $this->controllerPath . DIRECTORY_SEPARATOR . $className . '.php';
- if (!is_file($classFile)) {
- return false;
- }
- $className = ltrim($this->controllerNamespace . '\\' . $className, '\\');
- Yii::$classMap[$className] = $classFile;
- if (is_subclass_of($className, 'yii\base\Controller')) {
- $controller = new $className($id, $this);
- } elseif (YII_DEBUG) {
- throw new InvalidConfigException("Controller class must extend from \\yii\\base\\Controller.");
- }
- }
-
- return isset($controller) ? array($controller, $route) : false;
- }
-
- /**
- * This method is invoked right before an action is to be executed (after all possible filters.)
- * You may override this method to do last-minute preparation for the action.
- * @param Action $action the action to be executed.
- * @return boolean whether the action should continue to be executed.
- */
- public function beforeAction($action)
- {
- $event = new ActionEvent($action);
- $this->trigger(self::EVENT_BEFORE_ACTION, $event);
- return $event->isValid;
- }
-
- /**
- * This method is invoked right after an action is executed.
- * You may override this method to do some postprocessing for the action.
- * @param Action $action the action just executed.
- */
- public function afterAction($action)
- {
- $this->trigger(self::EVENT_AFTER_ACTION, new ActionEvent($action));
- }
-}
+ configuration).
+ * @property array $components The components (indexed by their IDs) registered within this module.
+ * @property array $import List of aliases to be imported. This property is write-only.
+ * @property array $aliases List of aliases to be defined. This property is write-only.
+ *
+ * @author Qiang Xue
+ * @since 2.0
+ */
+abstract class Module extends Component
+{
+ /**
+ * @event ActionEvent an event raised before executing a controller action.
+ * You may set [[ActionEvent::isValid]] to be false to cancel the action execution.
+ */
+ const EVENT_BEFORE_ACTION = 'beforeAction';
+ /**
+ * @event ActionEvent an event raised after executing a controller action.
+ */
+ const EVENT_AFTER_ACTION = 'afterAction';
+ /**
+ * @var array custom module parameters (name => value).
+ */
+ public $params = array();
+ /**
+ * @var array the IDs of the components that should be preloaded when this module is created.
+ */
+ public $preload = array();
+ /**
+ * @var string an ID that uniquely identifies this module among other modules which have the same [[module|parent]].
+ */
+ public $id;
+ /**
+ * @var Module the parent module of this module. Null if this module does not have a parent.
+ */
+ public $module;
+ /**
+ * @var string|boolean the layout that should be applied for views within this module. This refers to a view name
+ * relative to [[layoutPath]]. If this is not set, it means the layout value of the [[module|parent module]]
+ * will be taken. If this is false, layout will be disabled within this module.
+ */
+ public $layout;
+ /**
+ * @var array mapping from controller ID to controller configurations.
+ * Each name-value pair specifies the configuration of a single controller.
+ * A controller configuration can be either a string or an array.
+ * If the former, the string should be the class name or path alias of the controller.
+ * If the latter, the array must contain a 'class' element which specifies
+ * the controller's class name or path alias, and the rest of the name-value pairs
+ * in the array are used to initialize the corresponding controller properties. For example,
+ *
+ * ~~~
+ * array(
+ * 'account' => '@app/controllers/UserController',
+ * 'article' => array(
+ * 'class' => '@app/controllers/PostController',
+ * 'pageTitle' => 'something new',
+ * ),
+ * )
+ * ~~~
+ */
+ public $controllerMap = array();
+ /**
+ * @var string the namespace that controller classes are in. If not set,
+ * it will use the "controllers" sub-namespace under the namespace of this module.
+ * For example, if the namespace of this module is "foo\bar", then the default
+ * controller namespace would be "foo\bar\controllers".
+ * If the module is an application, it will default to "app\controllers".
+ */
+ public $controllerNamespace;
+ /**
+ * @return string the default route of this module. Defaults to 'default'.
+ * The route may consist of child module ID, controller ID, and/or action ID.
+ * For example, `help`, `post/create`, `admin/post/create`.
+ * If action ID is not given, it will take the default value as specified in
+ * [[Controller::defaultAction]].
+ */
+ public $defaultRoute = 'default';
+ /**
+ * @var string the root directory of the module.
+ */
+ private $_basePath;
+ /**
+ * @var string the root directory that contains view files for this module
+ */
+ private $_viewPath;
+ /**
+ * @var string the root directory that contains layout view files for this module.
+ */
+ private $_layoutPath;
+ /**
+ * @var string the directory containing controller classes in the module.
+ */
+ private $_controllerPath;
+ /**
+ * @var array child modules of this module
+ */
+ private $_modules = array();
+ /**
+ * @var array components registered under this module
+ */
+ private $_components = array();
+
+ /**
+ * Constructor.
+ * @param string $id the ID of this module
+ * @param Module $parent the parent module (if any)
+ * @param array $config name-value pairs that will be used to initialize the object properties
+ */
+ public function __construct($id, $parent = null, $config = array())
+ {
+ $this->id = $id;
+ $this->module = $parent;
+ parent::__construct($config);
+ }
+
+ /**
+ * Getter magic method.
+ * This method is overridden to support accessing components
+ * like reading module properties.
+ * @param string $name component or property name
+ * @return mixed the named property value
+ */
+ public function __get($name)
+ {
+ if ($this->hasComponent($name)) {
+ return $this->getComponent($name);
+ } else {
+ return parent::__get($name);
+ }
+ }
+
+ /**
+ * Checks if a property value is null.
+ * This method overrides the parent implementation by checking
+ * if the named component is loaded.
+ * @param string $name the property name or the event name
+ * @return boolean whether the property value is null
+ */
+ public function __isset($name)
+ {
+ if ($this->hasComponent($name)) {
+ return $this->getComponent($name) !== null;
+ } else {
+ return parent::__isset($name);
+ }
+ }
+
+ /**
+ * Initializes the module.
+ * This method is called after the module is created and initialized with property values
+ * given in configuration. The default implement will create a path alias using the module [[id]]
+ * and then call [[preloadComponents()]] to load components that are declared in [[preload]].
+ */
+ public function init()
+ {
+ $this->preloadComponents();
+ if ($this->controllerNamespace === null) {
+ if ($this instanceof Application) {
+ $this->controllerNamespace = 'app\\controllers';
+ } else {
+ $class = get_class($this);
+ if (($pos = strrpos($class, '\\')) !== false) {
+ $this->controllerNamespace = substr($class, 0, $pos) . '\\controllers';
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns an ID that uniquely identifies this module among all modules within the current application.
+ * Note that if the module is an application, an empty string will be returned.
+ * @return string the unique ID of the module.
+ */
+ public function getUniqueId()
+ {
+ if ($this instanceof Application) {
+ return '';
+ } elseif ($this->module) {
+ return $this->module->getUniqueId() . '/' . $this->id;
+ } else {
+ return $this->id;
+ }
+ }
+
+ /**
+ * Returns the root directory of the module.
+ * It defaults to the directory containing the module class file.
+ * @return string the root directory of the module.
+ */
+ public function getBasePath()
+ {
+ if ($this->_basePath === null) {
+ $class = new \ReflectionClass($this);
+ $this->_basePath = dirname($class->getFileName());
+ }
+ return $this->_basePath;
+ }
+
+ /**
+ * Sets the root directory of the module.
+ * This method can only be invoked at the beginning of the constructor.
+ * @param string $path the root directory of the module. This can be either a directory name or a path alias.
+ * @throws InvalidParamException if the directory does not exist.
+ */
+ public function setBasePath($path)
+ {
+ $path = Yii::getAlias($path);
+ $p = realpath($path);
+ if ($p !== false && is_dir($p)) {
+ $this->_basePath = $p;
+ if ($this instanceof Application) {
+ Yii::setAlias('@app', $p);
+ }
+ } else {
+ throw new InvalidParamException("The directory does not exist: $path");
+ }
+ }
+
+ /**
+ * Returns the directory that contains the controller classes.
+ * Defaults to "[[basePath]]/controllers".
+ * @return string the directory that contains the controller classes.
+ */
+ public function getControllerPath()
+ {
+ if ($this->_controllerPath !== null) {
+ return $this->_controllerPath;
+ } else {
+ return $this->_controllerPath = $this->getBasePath() . DIRECTORY_SEPARATOR . 'controllers';
+ }
+ }
+
+ /**
+ * Sets the directory that contains the controller classes.
+ * @param string $path the directory that contains the controller classes.
+ * This can be either a directory name or a path alias.
+ * @throws Exception if the directory is invalid
+ */
+ public function setControllerPath($path)
+ {
+ $this->_controllerPath = Yii::getAlias($path);
+ }
+
+ /**
+ * Returns the directory that contains the view files for this module.
+ * @return string the root directory of view files. Defaults to "[[basePath]]/view".
+ */
+ public function getViewPath()
+ {
+ if ($this->_viewPath !== null) {
+ return $this->_viewPath;
+ } else {
+ return $this->_viewPath = $this->getBasePath() . DIRECTORY_SEPARATOR . 'views';
+ }
+ }
+
+ /**
+ * Sets the directory that contains the view files.
+ * @param string $path the root directory of view files.
+ * @throws Exception if the directory is invalid
+ */
+ public function setViewPath($path)
+ {
+ $this->_viewPath = Yii::getAlias($path);
+ }
+
+ /**
+ * Returns the directory that contains layout view files for this module.
+ * @return string the root directory of layout files. Defaults to "[[viewPath]]/layouts".
+ */
+ public function getLayoutPath()
+ {
+ if ($this->_layoutPath !== null) {
+ return $this->_layoutPath;
+ } else {
+ return $this->_layoutPath = $this->getViewPath() . DIRECTORY_SEPARATOR . 'layouts';
+ }
+ }
+
+ /**
+ * Sets the directory that contains the layout files.
+ * @param string $path the root directory of layout files.
+ * @throws Exception if the directory is invalid
+ */
+ public function setLayoutPath($path)
+ {
+ $this->_layoutPath = Yii::getAlias($path);
+ }
+
+ /**
+ * Defines path aliases.
+ * This method calls [[Yii::setAlias()]] to register the path aliases.
+ * This method is provided so that you can define path aliases when configuring a module.
+ * @param array $aliases list of path aliases to be defined. The array keys are alias names
+ * (must start with '@') and the array values are the corresponding paths or aliases.
+ * For example,
+ *
+ * ~~~
+ * array(
+ * '@models' => '@app/models', // an existing alias
+ * '@backend' => __DIR__ . '/../backend', // a directory
+ * )
+ * ~~~
+ */
+ public function setAliases($aliases)
+ {
+ foreach ($aliases as $name => $alias) {
+ Yii::setAlias($name, $alias);
+ }
+ }
+
+ /**
+ * Checks whether the named module exists.
+ * @param string $id module ID
+ * @return boolean whether the named module exists. Both loaded and unloaded modules
+ * are considered.
+ */
+ public function hasModule($id)
+ {
+ return isset($this->_modules[$id]);
+ }
+
+ /**
+ * Retrieves the named module.
+ * @param string $id module ID (case-sensitive)
+ * @param boolean $load whether to load the module if it is not yet loaded.
+ * @return Module|null the module instance, null if the module
+ * does not exist.
+ * @see hasModule()
+ */
+ public function getModule($id, $load = true)
+ {
+ if (isset($this->_modules[$id])) {
+ if ($this->_modules[$id] instanceof Module) {
+ return $this->_modules[$id];
+ } elseif ($load) {
+ Yii::trace("Loading module: $id", __METHOD__);
+ return $this->_modules[$id] = Yii::createObject($this->_modules[$id], $id, $this);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Adds a sub-module to this module.
+ * @param string $id module ID
+ * @param Module|array|null $module the sub-module to be added to this module. This can
+ * be one of the followings:
+ *
+ * - a [[Module]] object
+ * - a configuration array: when [[getModule()]] is called initially, the array
+ * will be used to instantiate the sub-module
+ * - null: the named sub-module will be removed from this module
+ */
+ public function setModule($id, $module)
+ {
+ if ($module === null) {
+ unset($this->_modules[$id]);
+ } else {
+ $this->_modules[$id] = $module;
+ }
+ }
+
+ /**
+ * Returns the sub-modules in this module.
+ * @param boolean $loadedOnly whether to return the loaded sub-modules only. If this is set false,
+ * then all sub-modules registered in this module will be returned, whether they are loaded or not.
+ * Loaded modules will be returned as objects, while unloaded modules as configuration arrays.
+ * @return array the modules (indexed by their IDs)
+ */
+ public function getModules($loadedOnly = false)
+ {
+ if ($loadedOnly) {
+ $modules = array();
+ foreach ($this->_modules as $module) {
+ if ($module instanceof Module) {
+ $modules[] = $module;
+ }
+ }
+ return $modules;
+ } else {
+ return $this->_modules;
+ }
+ }
+
+ /**
+ * Registers sub-modules in the current module.
+ *
+ * Each sub-module should be specified as a name-value pair, where
+ * name refers to the ID of the module and value the module or a configuration
+ * array that can be used to create the module. In the latter case, [[Yii::createObject()]]
+ * will be used to create the module.
+ *
+ * If a new sub-module has the same ID as an existing one, the existing one will be overwritten silently.
+ *
+ * The following is an example for registering two sub-modules:
+ *
+ * ~~~
+ * array(
+ * 'comment' => array(
+ * 'class' => 'app\modules\comment\CommentModule',
+ * 'db' => 'db',
+ * ),
+ * 'booking' => array(
+ * 'class' => 'app\modules\booking\BookingModule',
+ * ),
+ * )
+ * ~~~
+ *
+ * @param array $modules modules (id => module configuration or instances)
+ */
+ public function setModules($modules)
+ {
+ foreach ($modules as $id => $module) {
+ $this->_modules[$id] = $module;
+ }
+ }
+
+ /**
+ * Checks whether the named component exists.
+ * @param string $id component ID
+ * @return boolean whether the named component exists. Both loaded and unloaded components
+ * are considered.
+ */
+ public function hasComponent($id)
+ {
+ return isset($this->_components[$id]);
+ }
+
+ /**
+ * Retrieves the named component.
+ * @param string $id component ID (case-sensitive)
+ * @param boolean $load whether to load the component if it is not yet loaded.
+ * @return Component|null the component instance, null if the component does not exist.
+ * @see hasComponent()
+ */
+ public function getComponent($id, $load = true)
+ {
+ if (isset($this->_components[$id])) {
+ if ($this->_components[$id] instanceof Object) {
+ return $this->_components[$id];
+ } elseif ($load) {
+ Yii::trace("Loading component: $id", __METHOD__);
+ return $this->_components[$id] = Yii::createObject($this->_components[$id]);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Registers a component with this module.
+ * @param string $id component ID
+ * @param Component|array|null $component the component to be registered with the module. This can
+ * be one of the followings:
+ *
+ * - a [[Component]] object
+ * - a configuration array: when [[getComponent()]] is called initially for this component, the array
+ * will be used to instantiate the component via [[Yii::createObject()]].
+ * - null: the named component will be removed from the module
+ */
+ public function setComponent($id, $component)
+ {
+ if ($component === null) {
+ unset($this->_components[$id]);
+ } else {
+ $this->_components[$id] = $component;
+ }
+ }
+
+ /**
+ * Returns the registered components.
+ * @param boolean $loadedOnly whether to return the loaded components only. If this is set false,
+ * then all components specified in the configuration will be returned, whether they are loaded or not.
+ * Loaded components will be returned as objects, while unloaded components as configuration arrays.
+ * @return array the components (indexed by their IDs)
+ */
+ public function getComponents($loadedOnly = false)
+ {
+ if ($loadedOnly) {
+ $components = array();
+ foreach ($this->_components as $component) {
+ if ($component instanceof Component) {
+ $components[] = $component;
+ }
+ }
+ return $components;
+ } else {
+ return $this->_components;
+ }
+ }
+
+ /**
+ * Registers a set of components in this module.
+ *
+ * Each component should be specified as a name-value pair, where
+ * name refers to the ID of the component and value the component or a configuration
+ * array that can be used to create the component. In the latter case, [[Yii::createObject()]]
+ * will be used to create the component.
+ *
+ * If a new component has the same ID as an existing one, the existing one will be overwritten silently.
+ *
+ * The following is an example for setting two components:
+ *
+ * ~~~
+ * array(
+ * 'db' => array(
+ * 'class' => 'yii\db\Connection',
+ * 'dsn' => 'sqlite:path/to/file.db',
+ * ),
+ * 'cache' => array(
+ * 'class' => 'yii\caching\DbCache',
+ * 'db' => 'db',
+ * ),
+ * )
+ * ~~~
+ *
+ * @param array $components components (id => component configuration or instance)
+ */
+ public function setComponents($components)
+ {
+ foreach ($components as $id => $component) {
+ if (isset($this->_components[$id]['class']) && !isset($component['class'])) {
+ $component['class'] = $this->_components[$id]['class'];
+ }
+ $this->_components[$id] = $component;
+ }
+ }
+
+ /**
+ * Loads components that are declared in [[preload]].
+ */
+ public function preloadComponents()
+ {
+ foreach ($this->preload as $id) {
+ $this->getComponent($id);
+ }
+ }
+
+ /**
+ * Runs a controller action specified by a route.
+ * This method parses the specified route and creates the corresponding child module(s), controller and action
+ * instances. It then calls [[Controller::runAction()]] to run the action with the given parameters.
+ * If the route is empty, the method will use [[defaultRoute]].
+ * @param string $route the route that specifies the action.
+ * @param array $params the parameters to be passed to the action
+ * @return integer the status code returned by the action execution. 0 means normal, and other values mean abnormal.
+ * @throws InvalidRouteException if the requested route cannot be resolved into an action successfully
+ */
+ public function runAction($route, $params = array())
+ {
+ $result = $this->createController($route);
+ if (is_array($result)) {
+ /** @var $controller Controller */
+ list($controller, $actionID) = $result;
+ $oldController = Yii::$app->controller;
+ Yii::$app->controller = $controller;
+ $status = $controller->runAction($actionID, $params);
+ Yii::$app->controller = $oldController;
+ return $status;
+ } else {
+ throw new InvalidRouteException('Unable to resolve the request "' . trim($this->getUniqueId() . '/' . $route, '/') . '".');
+ }
+ }
+
+ /**
+ * Creates a controller instance based on the controller ID.
+ *
+ * The controller is created within this module. The method first attempts to
+ * create the controller based on the [[controllerMap]] of the module. If not available,
+ * it will look for the controller class under the [[controllerPath]] and create an
+ * instance of it.
+ *
+ * @param string $route the route consisting of module, controller and action IDs.
+ * @return array|boolean If the controller is created successfully, it will be returned together
+ * with the requested action ID. Otherwise false will be returned.
+ * @throws InvalidConfigException if the controller class and its file do not match.
+ */
+ public function createController($route)
+ {
+ if ($route === '') {
+ $route = $this->defaultRoute;
+ }
+ $route = trim($route, '/');
+ if (($pos = strpos($route, '/')) !== false) {
+ $id = substr($route, 0, $pos);
+ $route = substr($route, $pos + 1);
+ } else {
+ $id = $route;
+ $route = '';
+ }
+
+ $module = $this->getModule($id);
+ if ($module !== null) {
+ return $module->createController($route);
+ }
+
+ if (isset($this->controllerMap[$id])) {
+ $controller = Yii::createObject($this->controllerMap[$id], $id, $this);
+ } elseif (preg_match('/^[a-z0-9\\-_]+$/', $id)) {
+ $className = str_replace(' ', '', ucwords(implode(' ', explode('-', $id)))) . 'Controller';
+ $classFile = $this->controllerPath . DIRECTORY_SEPARATOR . $className . '.php';
+ if (!is_file($classFile)) {
+ return false;
+ }
+ $className = ltrim($this->controllerNamespace . '\\' . $className, '\\');
+ Yii::$classMap[$className] = $classFile;
+ if (is_subclass_of($className, 'yii\base\Controller')) {
+ $controller = new $className($id, $this);
+ } elseif (YII_DEBUG) {
+ throw new InvalidConfigException("Controller class must extend from \\yii\\base\\Controller.");
+ }
+ }
+
+ return isset($controller) ? array($controller, $route) : false;
+ }
+
+ /**
+ * This method is invoked right before an action is to be executed (after all possible filters.)
+ * You may override this method to do last-minute preparation for the action.
+ * @param Action $action the action to be executed.
+ * @return boolean whether the action should continue to be executed.
+ */
+ public function beforeAction($action)
+ {
+ $event = new ActionEvent($action);
+ $this->trigger(self::EVENT_BEFORE_ACTION, $event);
+ return $event->isValid;
+ }
+
+ /**
+ * This method is invoked right after an action is executed.
+ * You may override this method to do some postprocessing for the action.
+ * @param Action $action the action just executed.
+ */
+ public function afterAction($action)
+ {
+ $this->trigger(self::EVENT_AFTER_ACTION, new ActionEvent($action));
+ }
+}
diff --git a/framework/yii/base/NotSupportedException.php b/framework/yii/base/NotSupportedException.php
index 8a93e14..33f936f 100644
--- a/framework/yii/base/NotSupportedException.php
+++ b/framework/yii/base/NotSupportedException.php
@@ -23,4 +23,3 @@ class NotSupportedException extends Exception
return \Yii::t('yii', 'Not Supported');
}
}
-
diff --git a/framework/yii/base/Object.php b/framework/yii/base/Object.php
index a90a231..79c1f7b 100644
--- a/framework/yii/base/Object.php
+++ b/framework/yii/base/Object.php
@@ -7,12 +7,15 @@
namespace yii\base;
+use Yii;
+use yii\helpers\Json;
+
/**
* @include @yii/base/Object.md
* @author Qiang Xue
* @since 2.0
*/
-class Object
+class Object implements Jsonable
{
/**
* @return string the fully qualified name of this class.
@@ -38,8 +41,8 @@ class Object
*/
public function __construct($config = array())
{
- foreach ($config as $name => $value) {
- $this->$name = $value;
+ if (!empty($config)) {
+ Yii::configure($this, $config);
}
$this->init();
}
@@ -216,4 +219,14 @@ class Object
{
return method_exists($this, 'set' . $name) || $checkVar && property_exists($this, $name);
}
+
+ /**
+ * Returns the JSON representation of this object.
+ * The default implementation will return all public member variables.
+ * @return string the JSON representation of this object.
+ */
+ public function toJson()
+ {
+ return Json::encode(Yii::getObjectVars($this));
+ }
}
diff --git a/framework/yii/base/Response.php b/framework/yii/base/Response.php
index 396b073..b89b537 100644
--- a/framework/yii/base/Response.php
+++ b/framework/yii/base/Response.php
@@ -13,6 +13,9 @@ namespace yii\base;
*/
class Response extends Component
{
+ const EVENT_BEGIN_RESPONSE = 'beginResponse';
+ const EVENT_END_RESPONSE = 'endResponse';
+
/**
* Starts output buffering
*/
@@ -56,4 +59,14 @@ class Response extends Component
ob_end_clean();
}
}
+
+ public function begin()
+ {
+ $this->trigger(self::EVENT_BEGIN_RESPONSE);
+ }
+
+ public function end()
+ {
+ $this->trigger(self::EVENT_END_RESPONSE);
+ }
}
diff --git a/framework/yii/base/Theme.php b/framework/yii/base/Theme.php
index ca1efcd..91c32dc 100644
--- a/framework/yii/base/Theme.php
+++ b/framework/yii/base/Theme.php
@@ -73,7 +73,7 @@ class Theme extends Component
*/
public function init()
{
- parent::init();
+ parent::init();
if (empty($this->pathMap)) {
if ($this->basePath !== null) {
$this->basePath = Yii::getAlias($this->basePath);
diff --git a/framework/yii/base/UnknownClassException.php b/framework/yii/base/UnknownClassException.php
index e4a682a..7b893d4 100644
--- a/framework/yii/base/UnknownClassException.php
+++ b/framework/yii/base/UnknownClassException.php
@@ -23,4 +23,3 @@ class UnknownClassException extends Exception
return \Yii::t('yii', 'Unknown Class');
}
}
-
diff --git a/framework/yii/base/UnknownMethodException.php b/framework/yii/base/UnknownMethodException.php
index d8cea34..3b33659 100644
--- a/framework/yii/base/UnknownMethodException.php
+++ b/framework/yii/base/UnknownMethodException.php
@@ -23,4 +23,3 @@ class UnknownMethodException extends Exception
return \Yii::t('yii', 'Unknown Method');
}
}
-
diff --git a/framework/yii/base/UnknownPropertyException.php b/framework/yii/base/UnknownPropertyException.php
index b8e93c5..682fdfa 100644
--- a/framework/yii/base/UnknownPropertyException.php
+++ b/framework/yii/base/UnknownPropertyException.php
@@ -23,4 +23,3 @@ class UnknownPropertyException extends Exception
return \Yii::t('yii', 'Unknown Property');
}
}
-
diff --git a/framework/yii/bootstrap/Button.php b/framework/yii/bootstrap/Button.php
index 104e700..856c420 100644
--- a/framework/yii/bootstrap/Button.php
+++ b/framework/yii/bootstrap/Button.php
@@ -6,9 +6,8 @@
*/
namespace yii\bootstrap;
-use yii\base\InvalidConfigException;
-use yii\helpers\Html;
+use yii\helpers\Html;
/**
* Button renders a bootstrap button.
diff --git a/framework/yii/bootstrap/ButtonDropdown.php b/framework/yii/bootstrap/ButtonDropdown.php
index 4168bd5..fec042e 100644
--- a/framework/yii/bootstrap/ButtonDropdown.php
+++ b/framework/yii/bootstrap/ButtonDropdown.php
@@ -6,8 +6,8 @@
*/
namespace yii\bootstrap;
-use yii\helpers\Html;
+use yii\helpers\Html;
/**
* ButtonDropdown renders a group or split button dropdown bootstrap component.
diff --git a/framework/yii/bootstrap/ButtonGroup.php b/framework/yii/bootstrap/ButtonGroup.php
index f50d6a8..e5bf4e9 100644
--- a/framework/yii/bootstrap/ButtonGroup.php
+++ b/framework/yii/bootstrap/ButtonGroup.php
@@ -10,7 +10,6 @@ namespace yii\bootstrap;
use yii\helpers\base\ArrayHelper;
use yii\helpers\Html;
-
/**
* ButtonGroup renders a button group bootstrap component.
*
diff --git a/framework/yii/bootstrap/Collapse.php b/framework/yii/bootstrap/Collapse.php
index a7929e3..fdcaae1 100644
--- a/framework/yii/bootstrap/Collapse.php
+++ b/framework/yii/bootstrap/Collapse.php
@@ -130,4 +130,4 @@ class Collapse extends Widget
return implode("\n", $group);
}
-}
\ No newline at end of file
+}
diff --git a/framework/yii/bootstrap/Dropdown.php b/framework/yii/bootstrap/Dropdown.php
index 2bee0ff..827e6cc 100644
--- a/framework/yii/bootstrap/Dropdown.php
+++ b/framework/yii/bootstrap/Dropdown.php
@@ -11,7 +11,6 @@ use yii\base\InvalidConfigException;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
-
/**
* Dropdown renders a Bootstrap dropdown menu component.
*
diff --git a/framework/yii/bootstrap/Nav.php b/framework/yii/bootstrap/Nav.php
index 548fe19..8069699 100644
--- a/framework/yii/bootstrap/Nav.php
+++ b/framework/yii/bootstrap/Nav.php
@@ -29,11 +29,17 @@ use yii\helpers\Html;
* 'label' => 'Dropdown',
* 'items' => array(
* array(
- * 'label' => 'DropdownA',
+ * 'label' => 'Level 1 -DropdownA',
* 'url' => '#',
+ * 'items' => array(
+ * array(
+ * 'label' => 'Level 2 -DropdownA',
+ * 'url' => '#',
+ * ),
+ * ),
* ),
* array(
- * 'label' => 'DropdownB',
+ * 'label' => 'Level 1 -DropdownB',
* 'url' => '#',
* ),
* ),
@@ -114,25 +120,27 @@ class Nav extends Widget
}
$label = $this->encodeLabels ? Html::encode($item['label']) : $item['label'];
$options = ArrayHelper::getValue($item, 'options', array());
- $dropdown = ArrayHelper::getValue($item, 'dropdown');
+ $items = ArrayHelper::getValue($item, 'items');
$url = Html::url(ArrayHelper::getValue($item, 'url', '#'));
$linkOptions = ArrayHelper::getValue($item, 'linkOptions', array());
- if(ArrayHelper::getValue($item, 'active')) {
+ if (ArrayHelper::getValue($item, 'active')) {
$this->addCssClass($options, 'active');
}
- if ($dropdown !== null) {
+ if ($items !== 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);
+ if (is_array($items)) {
+ $items = Dropdown::widget(array(
+ 'items' => $items,
+ 'clientOptions' => false,
+ ));
}
}
- return Html::tag('li', Html::a($label, $url, $linkOptions) . $dropdown, $options);
+ return Html::tag('li', Html::a($label, $url, $linkOptions) . $items, $options);
}
}
diff --git a/framework/yii/bootstrap/Progress.php b/framework/yii/bootstrap/Progress.php
index 708c0fe..7c0473e 100644
--- a/framework/yii/bootstrap/Progress.php
+++ b/framework/yii/bootstrap/Progress.php
@@ -11,7 +11,6 @@ use yii\base\InvalidConfigException;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
-
/**
* Progress renders a bootstrap progress bar component.
*
diff --git a/framework/yii/bootstrap/Widget.php b/framework/yii/bootstrap/Widget.php
index a2e6d77..48b0331 100644
--- a/framework/yii/bootstrap/Widget.php
+++ b/framework/yii/bootstrap/Widget.php
@@ -11,7 +11,6 @@ use Yii;
use yii\base\View;
use yii\helpers\Json;
-
/**
* \yii\bootstrap\Widget is the base class for all bootstrap widgets.
*
diff --git a/framework/yii/caching/DbCache.php b/framework/yii/caching/DbCache.php
index 7e5f12d..7571a42 100644
--- a/framework/yii/caching/DbCache.php
+++ b/framework/yii/caching/DbCache.php
@@ -170,7 +170,7 @@ class DbCache extends Cache
} else {
return $this->addValue($key, $value, $expire);
}
- }
+ }
/**
* Stores a value identified by a key into cache if the cache does not contain this key.
diff --git a/framework/yii/caching/FileCache.php b/framework/yii/caching/FileCache.php
index 0c6d119..a15751e 100644
--- a/framework/yii/caching/FileCache.php
+++ b/framework/yii/caching/FileCache.php
@@ -25,8 +25,9 @@ class FileCache extends Cache
{
/**
* @var string the directory to store cache files. You may use path alias here.
+ * If not set, it will use the "cache" subdirectory under the application runtime path.
*/
- public $cachePath = '@app/runtime/cache';
+ public $cachePath = '@runtime/cache';
/**
* @var string cache file suffix. Defaults to '.bin'.
*/
diff --git a/framework/yii/caching/XCache.php b/framework/yii/caching/XCache.php
index 91f483b..1f12f23 100644
--- a/framework/yii/caching/XCache.php
+++ b/framework/yii/caching/XCache.php
@@ -86,4 +86,3 @@ class XCache extends Cache
return true;
}
}
-
diff --git a/framework/yii/console/Exception.php b/framework/yii/console/Exception.php
index 9e9003e..f272bde 100644
--- a/framework/yii/console/Exception.php
+++ b/framework/yii/console/Exception.php
@@ -25,4 +25,3 @@ class Exception extends UserException
return \Yii::t('yii', 'Error');
}
}
-
diff --git a/framework/yii/console/Response.php b/framework/yii/console/Response.php
new file mode 100644
index 0000000..34f105d
--- /dev/null
+++ b/framework/yii/console/Response.php
@@ -0,0 +1,17 @@
+
+ * @since 2.0
+ */
+class Response extends \yii\base\Response
+{
+
+}
diff --git a/framework/yii/console/controllers/AssetController.php b/framework/yii/console/controllers/AssetController.php
index 8e3de29..dcd1667 100644
--- a/framework/yii/console/controllers/AssetController.php
+++ b/framework/yii/console/controllers/AssetController.php
@@ -14,6 +14,20 @@ use yii\console\Controller;
/**
* This command allows you to combine and compress your JavaScript and CSS files.
*
+ * Usage:
+ * 1. Create a configuration file using 'template' action:
+ * yii asset/template /path/to/myapp/config.php
+ * 2. Edit the created config file, adjusting it for your web application needs.
+ * 3. Run the 'compress' action, using created config:
+ * yii asset /path/to/myapp/config.php /path/to/myapp/config/assets_compressed.php
+ * 4. Adjust your web application config to use compressed assets.
+ *
+ * Note: in the console environment some path aliases like '@wwwroot' and '@www' may not exist,
+ * so corresponding paths inside the configuration should be specified directly.
+ *
+ * Note: by default this command relies on an external tools to perform actual files compression,
+ * check [[jsCompressor]] and [[cssCompressor]] for more details.
+ *
* @property array|\yii\web\AssetManager $assetManager asset manager, which will be used for assets processing.
*
* @author Qiang Xue
@@ -43,7 +57,7 @@ class AssetController extends Controller
* ~~~
* 'all' => array(
* 'css' => 'all.css',
- * 'js' => 'js.css',
+ * 'js' => 'all.js',
* 'depends' => array( ... ),
* )
* ~~~
@@ -57,7 +71,7 @@ class AssetController extends Controller
*/
private $_assetManager = array();
/**
- * @var string|callback Java Script file compressor.
+ * @var string|callback JavaScript file compressor.
* If a string, it is treated as shell command template, which should contain
* placeholders {from} - source file name - and {to} - output file name.
* Otherwise, it is treated as PHP callback, which should perform the compression.
@@ -150,7 +164,6 @@ class AssetController extends Controller
protected function loadConfiguration($configFile)
{
echo "Loading configuration from '{$configFile}'...\n";
-
foreach (require($configFile) as $name => $value) {
if (property_exists($this, $name) || $this->canSetProperty($name)) {
$this->$name = $value;
@@ -159,7 +172,7 @@ class AssetController extends Controller
}
}
- $this->getAssetManager(); // check asset manager configuration
+ $this->getAssetManager(); // check if asset manager configuration is correct
}
/**
@@ -207,7 +220,8 @@ class AssetController extends Controller
* @param array $result already loaded bundles list.
* @throws \yii\console\Exception on failure.
*/
- protected function loadBundleDependency($name, $bundle, &$result) {
+ protected function loadBundleDependency($name, $bundle, &$result)
+ {
if (!empty($bundle->depends)) {
$assetManager = $this->getAssetManager();
foreach ($bundle->depends as $dependencyName) {
@@ -308,7 +322,7 @@ class AssetController extends Controller
/**
* Builds output asset bundle.
* @param \yii\web\AssetBundle $target output asset bundle
- * @param string $type either "js" or "css".
+ * @param string $type either 'js' or 'css'.
* @param \yii\web\AssetBundle[] $bundles source asset bundles.
* @param integer $timestamp current timestamp.
* @throws Exception on failure.
@@ -420,24 +434,23 @@ class AssetController extends Controller
}
$array = var_export($array, true);
$version = date('Y-m-d H:i:s', time());
- $bytesWritten = file_put_contents($bundleFile, <<id}" command.
* DO NOT MODIFY THIS FILE DIRECTLY.
- * @version $version
+ * @version {$version}
*/
-return $array;
-EOD
- );
- if ($bytesWritten <= 0) {
+return {$array};
+EOD;
+ if (!file_put_contents($bundleFile, $bundleFileContent)) {
throw new Exception("Unable to write output bundle configuration at '{$bundleFile}'.");
}
echo "Output bundle configuration created at '{$bundleFile}'.\n";
}
/**
- * Compresses given Java Script files and combines them into the single one.
+ * Compresses given JavaScript files and combines them into the single one.
* @param array $inputFiles list of source file names.
* @param string $outputFile output file name.
* @throws \yii\console\Exception on failure
@@ -495,9 +508,10 @@ EOD
}
/**
- * Combines Java Script files into a single one.
+ * Combines JavaScript files into a single one.
* @param array $inputFiles source file names.
* @param string $outputFile output file name.
+ * @throws \yii\console\Exception on failure.
*/
public function combineJsFiles($inputFiles, $outputFile)
{
@@ -507,13 +521,16 @@ EOD
. file_get_contents($file)
. "/*** END FILE: $file ***/\n";
}
- file_put_contents($outputFile, $content);
+ if (!file_put_contents($outputFile, $content)) {
+ throw new Exception("Unable to write output JavaScript file '{$outputFile}'.");
+ }
}
/**
* Combines CSS files into a single one.
* @param array $inputFiles source file names.
* @param string $outputFile output file name.
+ * @throws \yii\console\Exception on failure.
*/
public function combineCssFiles($inputFiles, $outputFile)
{
@@ -523,7 +540,9 @@ EOD
. $this->adjustCssUrl(file_get_contents($file), dirname($file), dirname($outputFile))
. "/*** END FILE: $file ***/\n";
}
- file_put_contents($outputFile, $content);
+ if (!file_put_contents($outputFile, $content)) {
+ throw new Exception("Unable to write output CSS file '{$outputFile}'.");
+ }
}
/**
@@ -554,7 +573,7 @@ EOD
$inputFileRelativePathParts = explode('/', $inputFileRelativePath);
$outputFileRelativePathParts = explode('/', $outputFileRelativePath);
- $callback = function($matches) use ($inputFileRelativePathParts, $outputFileRelativePathParts) {
+ $callback = function ($matches) use ($inputFileRelativePathParts, $outputFileRelativePathParts) {
$fullMatch = $matches[0];
$inputUrl = $matches[1];
@@ -590,18 +609,23 @@ EOD
/**
* Creates template of configuration file for [[actionCompress]].
* @param string $configFile output file name.
+ * @throws \yii\console\Exception on failure.
*/
public function actionTemplate($configFile)
{
$template = << require('path/to/bundles.php'),
- //
+ // The list of extensions to compress:
'extensions' => require('path/to/namespaces.php'),
- //
+ // Asset bundle for compression output:
'targets' => array(
'all' => array(
'basePath' => __DIR__,
@@ -610,7 +634,7 @@ return array(
'css' => 'all-{ts}.css',
),
),
-
+ // Asset manager configuration:
'assetManager' => array(
'basePath' => __DIR__,
'baseUrl' => '/test',
@@ -622,9 +646,8 @@ EOD;
return;
}
}
- $bytesWritten = file_put_contents($configFile, $template);
- if ($bytesWritten<=0) {
- echo "Error: unable to write file '{$configFile}'!\n\n";
+ if (!file_put_contents($configFile, $template)) {
+ throw new Exception("Unable to write template file '{$configFile}'.");
} else {
echo "Configuration file template created at '{$configFile}'.\n\n";
}
diff --git a/framework/yii/console/controllers/MessageController.php b/framework/yii/console/controllers/MessageController.php
index 715fb5c..44cf1b1 100644
--- a/framework/yii/console/controllers/MessageController.php
+++ b/framework/yii/console/controllers/MessageController.php
@@ -179,8 +179,7 @@ class MessageController extends Controller
}
ksort($translated);
foreach ($translated as $message => $translation) {
- if (!isset($merged[$message]) && !isset($todo[$message]) && !$removeOld)
- {
+ if (!isset($merged[$message]) && !isset($todo[$message]) && !$removeOld) {
if (substr($translation, 0, 2) === '@@' && substr($translation, -2) === '@@') {
$todo[$message]=$translation;
} else {
diff --git a/framework/yii/console/controllers/MigrateController.php b/framework/yii/console/controllers/MigrateController.php
index d3eb257..eca7787 100644
--- a/framework/yii/console/controllers/MigrateController.php
+++ b/framework/yii/console/controllers/MigrateController.php
@@ -1,635 +1,635 @@
-
- * @link http://www.yiiframework.com/
- * @copyright Copyright (c) 2008 Yii Software LLC
- * @license http://www.yiiframework.com/license/
- */
-
-namespace yii\console\controllers;
-
-use Yii;
-use yii\console\Exception;
-use yii\console\Controller;
-use yii\db\Connection;
-use yii\db\Query;
-use yii\helpers\ArrayHelper;
-
-/**
- * This command manages application migrations.
- *
- * A migration means a set of persistent changes to the application environment
- * that is shared among different developers. For example, in an application
- * backed by a database, a migration may refer to a set of changes to
- * the database, such as creating a new table, adding a new table column.
- *
- * This command provides support for tracking the migration history, upgrading
- * or downloading with migrations, and creating new migration skeletons.
- *
- * The migration history is stored in a database table named
- * as [[migrationTable]]. The table will be automatically created the first time
- * this command is executed, if it does not exist. You may also manually
- * create it as follows:
- *
- * ~~~
- * CREATE TABLE tbl_migration (
- * version varchar(255) PRIMARY KEY,
- * apply_time integer
- * )
- * ~~~
- *
- * Below are some common usages of this command:
- *
- * ~~~
- * # creates a new migration named 'create_user_table'
- * yii migrate/create create_user_table
- *
- * # applies ALL new migrations
- * yii migrate
- *
- * # reverts the last applied migration
- * yii migrate/down
- * ~~~
- *
- * @author Qiang Xue
- * @since 2.0
- */
-class MigrateController extends Controller
-{
- /**
- * The name of the dummy migration that marks the beginning of the whole migration history.
- */
- const BASE_MIGRATION = 'm000000_000000_base';
-
- /**
- * @var string the default command action.
- */
- public $defaultAction = 'up';
- /**
- * @var string the directory storing the migration classes. This can be either
- * a path alias or a directory.
- */
- public $migrationPath = '@app/migrations';
- /**
- * @var string the name of the table for keeping applied migration information.
- */
- public $migrationTable = 'tbl_migration';
- /**
- * @var string the template file for generating new migrations.
- * This can be either a path alias (e.g. "@app/migrations/template.php")
- * or a file path.
- */
- public $templateFile = '@yii/views/migration.php';
- /**
- * @var boolean whether to execute the migration in an interactive mode.
- */
- public $interactive = true;
- /**
- * @var Connection|string the DB connection object or the application
- * component ID of the DB connection.
- */
- public $db = 'db';
-
- /**
- * Returns the names of the global options for this command.
- * @return array the names of the global options for this command.
- */
- public function globalOptions()
- {
- return array('migrationPath', 'migrationTable', 'db', 'templateFile', 'interactive');
- }
-
- /**
- * This method is invoked right before an action is to be executed (after all possible filters.)
- * It checks the existence of the [[migrationPath]].
- * @param \yii\base\Action $action the action to be executed.
- * @return boolean whether the action should continue to be executed.
- * @throws Exception if the migration directory does not exist.
- */
- public function beforeAction($action)
- {
- if (parent::beforeAction($action)) {
- $path = Yii::getAlias($this->migrationPath);
- if (!is_dir($path)) {
- throw new Exception("The migration directory \"{$this->migrationPath}\" does not exist.");
- }
- $this->migrationPath = $path;
-
- if($action->id!=='create') {
- if (is_string($this->db)) {
- $this->db = Yii::$app->getComponent($this->db);
- }
- if (!$this->db instanceof Connection) {
- throw new Exception("The 'db' option must refer to the application component ID of a DB connection.");
- }
- }
-
- $version = Yii::getVersion();
- echo "Yii Migration Tool (based on Yii v{$version})\n\n";
- return true;
- } else {
- return false;
- }
- }
-
- /**
- * Upgrades the application by applying new migrations.
- * For example,
- *
- * ~~~
- * yii migrate # apply all new migrations
- * yii migrate 3 # apply the first 3 new migrations
- * ~~~
- *
- * @param integer $limit the number of new migrations to be applied. If 0, it means
- * applying all available new migrations.
- */
- public function actionUp($limit = 0)
- {
- $migrations = $this->getNewMigrations();
- if (empty($migrations)) {
- echo "No new migration found. Your system is up-to-date.\n";
- Yii::$app->end();
- }
-
- $total = count($migrations);
- $limit = (int)$limit;
- if ($limit > 0) {
- $migrations = array_slice($migrations, 0, $limit);
- }
-
- $n = count($migrations);
- if ($n === $total) {
- echo "Total $n new " . ($n === 1 ? 'migration' : 'migrations') . " to be applied:\n";
- } else {
- echo "Total $n out of $total new " . ($total === 1 ? 'migration' : 'migrations') . " to be applied:\n";
- }
-
- foreach ($migrations as $migration) {
- echo " $migration\n";
- }
- echo "\n";
-
- if ($this->confirm('Apply the above ' . ($n === 1 ? 'migration' : 'migrations') . "?")) {
- foreach ($migrations as $migration) {
- if (!$this->migrateUp($migration)) {
- echo "\nMigration failed. The rest of the migrations are canceled.\n";
- return;
- }
- }
- echo "\nMigrated up successfully.\n";
- }
- }
-
- /**
- * Downgrades the application by reverting old migrations.
- * For example,
- *
- * ~~~
- * yii migrate/down # revert the last migration
- * yii migrate/down 3 # revert the last 3 migrations
- * ~~~
- *
- * @param integer $limit the number of migrations to be reverted. Defaults to 1,
- * meaning the last applied migration will be reverted.
- * @throws Exception if the number of the steps specified is less than 1.
- */
- public function actionDown($limit = 1)
- {
- $limit = (int)$limit;
- if ($limit < 1) {
- throw new Exception("The step argument must be greater than 0.");
- }
-
- $migrations = $this->getMigrationHistory($limit);
- if (empty($migrations)) {
- echo "No migration has been done before.\n";
- return;
- }
- $migrations = array_keys($migrations);
-
- $n = count($migrations);
- echo "Total $n " . ($n === 1 ? 'migration' : 'migrations') . " to be reverted:\n";
- foreach ($migrations as $migration) {
- echo " $migration\n";
- }
- echo "\n";
-
- if ($this->confirm('Revert the above ' . ($n === 1 ? 'migration' : 'migrations') . "?")) {
- foreach ($migrations as $migration) {
- if (!$this->migrateDown($migration)) {
- echo "\nMigration failed. The rest of the migrations are canceled.\n";
- return;
- }
- }
- echo "\nMigrated down successfully.\n";
- }
- }
-
- /**
- * Redoes the last few migrations.
- *
- * This command will first revert the specified migrations, and then apply
- * them again. For example,
- *
- * ~~~
- * yii migrate/redo # redo the last applied migration
- * yii migrate/redo 3 # redo the last 3 applied migrations
- * ~~~
- *
- * @param integer $limit the number of migrations to be redone. Defaults to 1,
- * meaning the last applied migration will be redone.
- * @throws Exception if the number of the steps specified is less than 1.
- */
- public function actionRedo($limit = 1)
- {
- $limit = (int)$limit;
- if ($limit < 1) {
- throw new Exception("The step argument must be greater than 0.");
- }
-
- $migrations = $this->getMigrationHistory($limit);
- if (empty($migrations)) {
- echo "No migration has been done before.\n";
- return;
- }
- $migrations = array_keys($migrations);
-
- $n = count($migrations);
- echo "Total $n " . ($n === 1 ? 'migration' : 'migrations') . " to be redone:\n";
- foreach ($migrations as $migration) {
- echo " $migration\n";
- }
- echo "\n";
-
- if ($this->confirm('Redo the above ' . ($n === 1 ? 'migration' : 'migrations') . "?")) {
- foreach ($migrations as $migration) {
- if (!$this->migrateDown($migration)) {
- echo "\nMigration failed. The rest of the migrations are canceled.\n";
- return;
- }
- }
- foreach (array_reverse($migrations) as $migration) {
- if (!$this->migrateUp($migration)) {
- echo "\nMigration failed. The rest of the migrations migrations are canceled.\n";
- return;
- }
- }
- echo "\nMigration redone successfully.\n";
- }
- }
-
- /**
- * Upgrades or downgrades till the specified version.
- *
- * This command will first revert the specified migrations, and then apply
- * them again. For example,
- *
- * ~~~
- * yii migrate/to 101129_185401 # using timestamp
- * yii migrate/to m101129_185401_create_user_table # using full name
- * ~~~
- *
- * @param string $version the version name that the application should be migrated to.
- * This can be either the timestamp or the full name of the migration.
- * @throws Exception if the version argument is invalid
- */
- public function actionTo($version)
- {
- $originalVersion = $version;
- if (preg_match('/^m?(\d{6}_\d{6})(_.*?)?$/', $version, $matches)) {
- $version = 'm' . $matches[1];
- } else {
- throw new Exception("The version argument must be either a timestamp (e.g. 101129_185401)\nor the full name of a migration (e.g. m101129_185401_create_user_table).");
- }
-
- // try migrate up
- $migrations = $this->getNewMigrations();
- foreach ($migrations as $i => $migration) {
- if (strpos($migration, $version . '_') === 0) {
- $this->actionUp($i + 1);
- return;
- }
- }
-
- // try migrate down
- $migrations = array_keys($this->getMigrationHistory(-1));
- foreach ($migrations as $i => $migration) {
- if (strpos($migration, $version . '_') === 0) {
- if ($i === 0) {
- echo "Already at '$originalVersion'. Nothing needs to be done.\n";
- } else {
- $this->actionDown($i);
- }
- return;
- }
- }
-
- throw new Exception("Unable to find the version '$originalVersion'.");
- }
-
- /**
- * Modifies the migration history to the specified version.
- *
- * No actual migration will be performed.
- *
- * ~~~
- * yii migrate/mark 101129_185401 # using timestamp
- * yii migrate/mark m101129_185401_create_user_table # using full name
- * ~~~
- *
- * @param string $version the version at which the migration history should be marked.
- * This can be either the timestamp or the full name of the migration.
- * @throws Exception if the version argument is invalid or the version cannot be found.
- */
- public function actionMark($version)
- {
- $originalVersion = $version;
- if (preg_match('/^m?(\d{6}_\d{6})(_.*?)?$/', $version, $matches)) {
- $version = 'm' . $matches[1];
- } else {
- throw new Exception("The version argument must be either a timestamp (e.g. 101129_185401)\nor the full name of a migration (e.g. m101129_185401_create_user_table).");
- }
-
- // try mark up
- $migrations = $this->getNewMigrations();
- foreach ($migrations as $i => $migration) {
- if (strpos($migration, $version . '_') === 0) {
- if ($this->confirm("Set migration history at $originalVersion?")) {
- $command = $this->db->createCommand();
- for ($j = 0; $j <= $i; ++$j) {
- $command->insert($this->migrationTable, array(
- 'version' => $migrations[$j],
- 'apply_time' => time(),
- ))->execute();
- }
- echo "The migration history is set at $originalVersion.\nNo actual migration was performed.\n";
- }
- return;
- }
- }
-
- // try mark down
- $migrations = array_keys($this->getMigrationHistory(-1));
- foreach ($migrations as $i => $migration) {
- if (strpos($migration, $version . '_') === 0) {
- if ($i === 0) {
- echo "Already at '$originalVersion'. Nothing needs to be done.\n";
- } else {
- if ($this->confirm("Set migration history at $originalVersion?")) {
- $command = $this->db->createCommand();
- for ($j = 0; $j < $i; ++$j) {
- $command->delete($this->migrationTable, array(
- 'version' => $migrations[$j],
- ))->execute();
- }
- echo "The migration history is set at $originalVersion.\nNo actual migration was performed.\n";
- }
- }
- return;
- }
- }
-
- throw new Exception("Unable to find the version '$originalVersion'.");
- }
-
- /**
- * Displays the migration history.
- *
- * This command will show the list of migrations that have been applied
- * so far. For example,
- *
- * ~~~
- * yii migrate/history # showing the last 10 migrations
- * yii migrate/history 5 # showing the last 5 migrations
- * yii migrate/history 0 # showing the whole history
- * ~~~
- *
- * @param integer $limit the maximum number of migrations to be displayed.
- * If it is 0, the whole migration history will be displayed.
- */
- public function actionHistory($limit = 10)
- {
- $limit = (int)$limit;
- $migrations = $this->getMigrationHistory($limit);
- if (empty($migrations)) {
- echo "No migration has been done before.\n";
- } else {
- $n = count($migrations);
- if ($limit > 0) {
- echo "Showing the last $n applied " . ($n === 1 ? 'migration' : 'migrations') . ":\n";
- } else {
- echo "Total $n " . ($n === 1 ? 'migration has' : 'migrations have') . " been applied before:\n";
- }
- foreach ($migrations as $version => $time) {
- echo " (" . date('Y-m-d H:i:s', $time) . ') ' . $version . "\n";
- }
- }
- }
-
- /**
- * Displays the un-applied new migrations.
- *
- * This command will show the new migrations that have not been applied.
- * For example,
- *
- * ~~~
- * yii migrate/new # showing the first 10 new migrations
- * yii migrate/new 5 # showing the first 5 new migrations
- * yii migrate/new 0 # showing all new migrations
- * ~~~
- *
- * @param integer $limit the maximum number of new migrations to be displayed.
- * If it is 0, all available new migrations will be displayed.
- */
- public function actionNew($limit = 10)
- {
- $limit = (int)$limit;
- $migrations = $this->getNewMigrations();
- if (empty($migrations)) {
- echo "No new migrations found. Your system is up-to-date.\n";
- } else {
- $n = count($migrations);
- if ($limit > 0 && $n > $limit) {
- $migrations = array_slice($migrations, 0, $limit);
- echo "Showing $limit out of $n new " . ($n === 1 ? 'migration' : 'migrations') . ":\n";
- } else {
- echo "Found $n new " . ($n === 1 ? 'migration' : 'migrations') . ":\n";
- }
-
- foreach ($migrations as $migration) {
- echo " " . $migration . "\n";
- }
- }
- }
-
- /**
- * Creates a new migration.
- *
- * This command creates a new migration using the available migration template.
- * After using this command, developers should modify the created migration
- * skeleton by filling up the actual migration logic.
- *
- * ~~~
- * yii migrate/create create_user_table
- * ~~~
- *
- * @param string $name the name of the new migration. This should only contain
- * letters, digits and/or underscores.
- * @throws Exception if the name argument is invalid.
- */
- public function actionCreate($name)
- {
- if (!preg_match('/^\w+$/', $name)) {
- throw new Exception("The migration name should contain letters, digits and/or underscore characters only.");
- }
-
- $name = 'm' . gmdate('ymd_His') . '_' . $name;
- $file = $this->migrationPath . DIRECTORY_SEPARATOR . $name . '.php';
-
- if ($this->confirm("Create new migration '$file'?")) {
- $content = $this->renderFile(Yii::getAlias($this->templateFile), array(
- 'className' => $name,
- ));
- file_put_contents($file, $content);
- echo "New migration created successfully.\n";
- }
- }
-
- /**
- * Upgrades with the specified migration class.
- * @param string $class the migration class name
- * @return boolean whether the migration is successful
- */
- protected function migrateUp($class)
- {
- if ($class === self::BASE_MIGRATION) {
- return true;
- }
-
- echo "*** applying $class\n";
- $start = microtime(true);
- $migration = $this->createMigration($class);
- if ($migration->up() !== false) {
- $this->db->createCommand()->insert($this->migrationTable, array(
- 'version' => $class,
- 'apply_time' => time(),
- ))->execute();
- $time = microtime(true) - $start;
- echo "*** applied $class (time: " . sprintf("%.3f", $time) . "s)\n\n";
- return true;
- } else {
- $time = microtime(true) - $start;
- echo "*** failed to apply $class (time: " . sprintf("%.3f", $time) . "s)\n\n";
- return false;
- }
- }
-
- /**
- * Downgrades with the specified migration class.
- * @param string $class the migration class name
- * @return boolean whether the migration is successful
- */
- protected function migrateDown($class)
- {
- if ($class === self::BASE_MIGRATION) {
- return true;
- }
-
- echo "*** reverting $class\n";
- $start = microtime(true);
- $migration = $this->createMigration($class);
- if ($migration->down() !== false) {
- $this->db->createCommand()->delete($this->migrationTable, array(
- 'version' => $class,
- ))->execute();
- $time = microtime(true) - $start;
- echo "*** reverted $class (time: " . sprintf("%.3f", $time) . "s)\n\n";
- return true;
- } else {
- $time = microtime(true) - $start;
- echo "*** failed to revert $class (time: " . sprintf("%.3f", $time) . "s)\n\n";
- return false;
- }
- }
-
- /**
- * Creates a new migration instance.
- * @param string $class the migration class name
- * @return \yii\db\Migration the migration instance
- */
- protected function createMigration($class)
- {
- $file = $this->migrationPath . DIRECTORY_SEPARATOR . $class . '.php';
- require_once($file);
- return new $class(array(
- 'db' => $this->db,
- ));
- }
-
- /**
- * Returns the migration history.
- * @param integer $limit the maximum number of records in the history to be returned
- * @return array the migration history
- */
- protected function getMigrationHistory($limit)
- {
- if ($this->db->schema->getTableSchema($this->migrationTable, true) === null) {
- $this->createMigrationHistoryTable();
- }
- $query = new Query;
- $rows = $query->select(array('version', 'apply_time'))
- ->from($this->migrationTable)
- ->orderBy('version DESC')
- ->limit($limit)
- ->createCommand()
- ->queryAll();
- $history = ArrayHelper::map($rows, 'version', 'apply_time');
- unset($history[self::BASE_MIGRATION]);
- return $history;
- }
-
- /**
- * Creates the migration history table.
- */
- protected function createMigrationHistoryTable()
- {
- echo 'Creating migration history table "' . $this->migrationTable . '"...';
- $this->db->createCommand()->createTable($this->migrationTable, array(
- 'version' => 'varchar(255) NOT NULL PRIMARY KEY',
- 'apply_time' => 'integer',
- ))->execute();
- $this->db->createCommand()->insert($this->migrationTable, array(
- 'version' => self::BASE_MIGRATION,
- 'apply_time' => time(),
- ))->execute();
- echo "done.\n";
- }
-
- /**
- * Returns the migrations that are not applied.
- * @return array list of new migrations
- */
- protected function getNewMigrations()
- {
- $applied = array();
- foreach ($this->getMigrationHistory(-1) as $version => $time) {
- $applied[substr($version, 1, 13)] = true;
- }
-
- $migrations = array();
- $handle = opendir($this->migrationPath);
- while (($file = readdir($handle)) !== false) {
- if ($file === '.' || $file === '..') {
- continue;
- }
- $path = $this->migrationPath . DIRECTORY_SEPARATOR . $file;
- if (preg_match('/^(m(\d{6}_\d{6})_.*?)\.php$/', $file, $matches) && is_file($path) && !isset($applied[$matches[2]])) {
- $migrations[] = $matches[1];
- }
- }
- closedir($handle);
- sort($migrations);
- return $migrations;
- }
-}
+
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\console\controllers;
+
+use Yii;
+use yii\console\Exception;
+use yii\console\Controller;
+use yii\db\Connection;
+use yii\db\Query;
+use yii\helpers\ArrayHelper;
+
+/**
+ * This command manages application migrations.
+ *
+ * A migration means a set of persistent changes to the application environment
+ * that is shared among different developers. For example, in an application
+ * backed by a database, a migration may refer to a set of changes to
+ * the database, such as creating a new table, adding a new table column.
+ *
+ * This command provides support for tracking the migration history, upgrading
+ * or downloading with migrations, and creating new migration skeletons.
+ *
+ * The migration history is stored in a database table named
+ * as [[migrationTable]]. The table will be automatically created the first time
+ * this command is executed, if it does not exist. You may also manually
+ * create it as follows:
+ *
+ * ~~~
+ * CREATE TABLE tbl_migration (
+ * version varchar(255) PRIMARY KEY,
+ * apply_time integer
+ * )
+ * ~~~
+ *
+ * Below are some common usages of this command:
+ *
+ * ~~~
+ * # creates a new migration named 'create_user_table'
+ * yii migrate/create create_user_table
+ *
+ * # applies ALL new migrations
+ * yii migrate
+ *
+ * # reverts the last applied migration
+ * yii migrate/down
+ * ~~~
+ *
+ * @author Qiang Xue
+ * @since 2.0
+ */
+class MigrateController extends Controller
+{
+ /**
+ * The name of the dummy migration that marks the beginning of the whole migration history.
+ */
+ const BASE_MIGRATION = 'm000000_000000_base';
+
+ /**
+ * @var string the default command action.
+ */
+ public $defaultAction = 'up';
+ /**
+ * @var string the directory storing the migration classes. This can be either
+ * a path alias or a directory.
+ */
+ public $migrationPath = '@app/migrations';
+ /**
+ * @var string the name of the table for keeping applied migration information.
+ */
+ public $migrationTable = 'tbl_migration';
+ /**
+ * @var string the template file for generating new migrations.
+ * This can be either a path alias (e.g. "@app/migrations/template.php")
+ * or a file path.
+ */
+ public $templateFile = '@yii/views/migration.php';
+ /**
+ * @var boolean whether to execute the migration in an interactive mode.
+ */
+ public $interactive = true;
+ /**
+ * @var Connection|string the DB connection object or the application
+ * component ID of the DB connection.
+ */
+ public $db = 'db';
+
+ /**
+ * Returns the names of the global options for this command.
+ * @return array the names of the global options for this command.
+ */
+ public function globalOptions()
+ {
+ return array('migrationPath', 'migrationTable', 'db', 'templateFile', 'interactive');
+ }
+
+ /**
+ * This method is invoked right before an action is to be executed (after all possible filters.)
+ * It checks the existence of the [[migrationPath]].
+ * @param \yii\base\Action $action the action to be executed.
+ * @return boolean whether the action should continue to be executed.
+ * @throws Exception if the migration directory does not exist.
+ */
+ public function beforeAction($action)
+ {
+ if (parent::beforeAction($action)) {
+ $path = Yii::getAlias($this->migrationPath);
+ if (!is_dir($path)) {
+ throw new Exception("The migration directory \"{$this->migrationPath}\" does not exist.");
+ }
+ $this->migrationPath = $path;
+
+ if ($action->id !== 'create') {
+ if (is_string($this->db)) {
+ $this->db = Yii::$app->getComponent($this->db);
+ }
+ if (!$this->db instanceof Connection) {
+ throw new Exception("The 'db' option must refer to the application component ID of a DB connection.");
+ }
+ }
+
+ $version = Yii::getVersion();
+ echo "Yii Migration Tool (based on Yii v{$version})\n\n";
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Upgrades the application by applying new migrations.
+ * For example,
+ *
+ * ~~~
+ * yii migrate # apply all new migrations
+ * yii migrate 3 # apply the first 3 new migrations
+ * ~~~
+ *
+ * @param integer $limit the number of new migrations to be applied. If 0, it means
+ * applying all available new migrations.
+ */
+ public function actionUp($limit = 0)
+ {
+ $migrations = $this->getNewMigrations();
+ if (empty($migrations)) {
+ echo "No new migration found. Your system is up-to-date.\n";
+ Yii::$app->end();
+ }
+
+ $total = count($migrations);
+ $limit = (int)$limit;
+ if ($limit > 0) {
+ $migrations = array_slice($migrations, 0, $limit);
+ }
+
+ $n = count($migrations);
+ if ($n === $total) {
+ echo "Total $n new " . ($n === 1 ? 'migration' : 'migrations') . " to be applied:\n";
+ } else {
+ echo "Total $n out of $total new " . ($total === 1 ? 'migration' : 'migrations') . " to be applied:\n";
+ }
+
+ foreach ($migrations as $migration) {
+ echo " $migration\n";
+ }
+ echo "\n";
+
+ if ($this->confirm('Apply the above ' . ($n === 1 ? 'migration' : 'migrations') . "?")) {
+ foreach ($migrations as $migration) {
+ if (!$this->migrateUp($migration)) {
+ echo "\nMigration failed. The rest of the migrations are canceled.\n";
+ return;
+ }
+ }
+ echo "\nMigrated up successfully.\n";
+ }
+ }
+
+ /**
+ * Downgrades the application by reverting old migrations.
+ * For example,
+ *
+ * ~~~
+ * yii migrate/down # revert the last migration
+ * yii migrate/down 3 # revert the last 3 migrations
+ * ~~~
+ *
+ * @param integer $limit the number of migrations to be reverted. Defaults to 1,
+ * meaning the last applied migration will be reverted.
+ * @throws Exception if the number of the steps specified is less than 1.
+ */
+ public function actionDown($limit = 1)
+ {
+ $limit = (int)$limit;
+ if ($limit < 1) {
+ throw new Exception("The step argument must be greater than 0.");
+ }
+
+ $migrations = $this->getMigrationHistory($limit);
+ if (empty($migrations)) {
+ echo "No migration has been done before.\n";
+ return;
+ }
+ $migrations = array_keys($migrations);
+
+ $n = count($migrations);
+ echo "Total $n " . ($n === 1 ? 'migration' : 'migrations') . " to be reverted:\n";
+ foreach ($migrations as $migration) {
+ echo " $migration\n";
+ }
+ echo "\n";
+
+ if ($this->confirm('Revert the above ' . ($n === 1 ? 'migration' : 'migrations') . "?")) {
+ foreach ($migrations as $migration) {
+ if (!$this->migrateDown($migration)) {
+ echo "\nMigration failed. The rest of the migrations are canceled.\n";
+ return;
+ }
+ }
+ echo "\nMigrated down successfully.\n";
+ }
+ }
+
+ /**
+ * Redoes the last few migrations.
+ *
+ * This command will first revert the specified migrations, and then apply
+ * them again. For example,
+ *
+ * ~~~
+ * yii migrate/redo # redo the last applied migration
+ * yii migrate/redo 3 # redo the last 3 applied migrations
+ * ~~~
+ *
+ * @param integer $limit the number of migrations to be redone. Defaults to 1,
+ * meaning the last applied migration will be redone.
+ * @throws Exception if the number of the steps specified is less than 1.
+ */
+ public function actionRedo($limit = 1)
+ {
+ $limit = (int)$limit;
+ if ($limit < 1) {
+ throw new Exception("The step argument must be greater than 0.");
+ }
+
+ $migrations = $this->getMigrationHistory($limit);
+ if (empty($migrations)) {
+ echo "No migration has been done before.\n";
+ return;
+ }
+ $migrations = array_keys($migrations);
+
+ $n = count($migrations);
+ echo "Total $n " . ($n === 1 ? 'migration' : 'migrations') . " to be redone:\n";
+ foreach ($migrations as $migration) {
+ echo " $migration\n";
+ }
+ echo "\n";
+
+ if ($this->confirm('Redo the above ' . ($n === 1 ? 'migration' : 'migrations') . "?")) {
+ foreach ($migrations as $migration) {
+ if (!$this->migrateDown($migration)) {
+ echo "\nMigration failed. The rest of the migrations are canceled.\n";
+ return;
+ }
+ }
+ foreach (array_reverse($migrations) as $migration) {
+ if (!$this->migrateUp($migration)) {
+ echo "\nMigration failed. The rest of the migrations migrations are canceled.\n";
+ return;
+ }
+ }
+ echo "\nMigration redone successfully.\n";
+ }
+ }
+
+ /**
+ * Upgrades or downgrades till the specified version.
+ *
+ * This command will first revert the specified migrations, and then apply
+ * them again. For example,
+ *
+ * ~~~
+ * yii migrate/to 101129_185401 # using timestamp
+ * yii migrate/to m101129_185401_create_user_table # using full name
+ * ~~~
+ *
+ * @param string $version the version name that the application should be migrated to.
+ * This can be either the timestamp or the full name of the migration.
+ * @throws Exception if the version argument is invalid
+ */
+ public function actionTo($version)
+ {
+ $originalVersion = $version;
+ if (preg_match('/^m?(\d{6}_\d{6})(_.*?)?$/', $version, $matches)) {
+ $version = 'm' . $matches[1];
+ } else {
+ throw new Exception("The version argument must be either a timestamp (e.g. 101129_185401)\nor the full name of a migration (e.g. m101129_185401_create_user_table).");
+ }
+
+ // try migrate up
+ $migrations = $this->getNewMigrations();
+ foreach ($migrations as $i => $migration) {
+ if (strpos($migration, $version . '_') === 0) {
+ $this->actionUp($i + 1);
+ return;
+ }
+ }
+
+ // try migrate down
+ $migrations = array_keys($this->getMigrationHistory(-1));
+ foreach ($migrations as $i => $migration) {
+ if (strpos($migration, $version . '_') === 0) {
+ if ($i === 0) {
+ echo "Already at '$originalVersion'. Nothing needs to be done.\n";
+ } else {
+ $this->actionDown($i);
+ }
+ return;
+ }
+ }
+
+ throw new Exception("Unable to find the version '$originalVersion'.");
+ }
+
+ /**
+ * Modifies the migration history to the specified version.
+ *
+ * No actual migration will be performed.
+ *
+ * ~~~
+ * yii migrate/mark 101129_185401 # using timestamp
+ * yii migrate/mark m101129_185401_create_user_table # using full name
+ * ~~~
+ *
+ * @param string $version the version at which the migration history should be marked.
+ * This can be either the timestamp or the full name of the migration.
+ * @throws Exception if the version argument is invalid or the version cannot be found.
+ */
+ public function actionMark($version)
+ {
+ $originalVersion = $version;
+ if (preg_match('/^m?(\d{6}_\d{6})(_.*?)?$/', $version, $matches)) {
+ $version = 'm' . $matches[1];
+ } else {
+ throw new Exception("The version argument must be either a timestamp (e.g. 101129_185401)\nor the full name of a migration (e.g. m101129_185401_create_user_table).");
+ }
+
+ // try mark up
+ $migrations = $this->getNewMigrations();
+ foreach ($migrations as $i => $migration) {
+ if (strpos($migration, $version . '_') === 0) {
+ if ($this->confirm("Set migration history at $originalVersion?")) {
+ $command = $this->db->createCommand();
+ for ($j = 0; $j <= $i; ++$j) {
+ $command->insert($this->migrationTable, array(
+ 'version' => $migrations[$j],
+ 'apply_time' => time(),
+ ))->execute();
+ }
+ echo "The migration history is set at $originalVersion.\nNo actual migration was performed.\n";
+ }
+ return;
+ }
+ }
+
+ // try mark down
+ $migrations = array_keys($this->getMigrationHistory(-1));
+ foreach ($migrations as $i => $migration) {
+ if (strpos($migration, $version . '_') === 0) {
+ if ($i === 0) {
+ echo "Already at '$originalVersion'. Nothing needs to be done.\n";
+ } else {
+ if ($this->confirm("Set migration history at $originalVersion?")) {
+ $command = $this->db->createCommand();
+ for ($j = 0; $j < $i; ++$j) {
+ $command->delete($this->migrationTable, array(
+ 'version' => $migrations[$j],
+ ))->execute();
+ }
+ echo "The migration history is set at $originalVersion.\nNo actual migration was performed.\n";
+ }
+ }
+ return;
+ }
+ }
+
+ throw new Exception("Unable to find the version '$originalVersion'.");
+ }
+
+ /**
+ * Displays the migration history.
+ *
+ * This command will show the list of migrations that have been applied
+ * so far. For example,
+ *
+ * ~~~
+ * yii migrate/history # showing the last 10 migrations
+ * yii migrate/history 5 # showing the last 5 migrations
+ * yii migrate/history 0 # showing the whole history
+ * ~~~
+ *
+ * @param integer $limit the maximum number of migrations to be displayed.
+ * If it is 0, the whole migration history will be displayed.
+ */
+ public function actionHistory($limit = 10)
+ {
+ $limit = (int)$limit;
+ $migrations = $this->getMigrationHistory($limit);
+ if (empty($migrations)) {
+ echo "No migration has been done before.\n";
+ } else {
+ $n = count($migrations);
+ if ($limit > 0) {
+ echo "Showing the last $n applied " . ($n === 1 ? 'migration' : 'migrations') . ":\n";
+ } else {
+ echo "Total $n " . ($n === 1 ? 'migration has' : 'migrations have') . " been applied before:\n";
+ }
+ foreach ($migrations as $version => $time) {
+ echo " (" . date('Y-m-d H:i:s', $time) . ') ' . $version . "\n";
+ }
+ }
+ }
+
+ /**
+ * Displays the un-applied new migrations.
+ *
+ * This command will show the new migrations that have not been applied.
+ * For example,
+ *
+ * ~~~
+ * yii migrate/new # showing the first 10 new migrations
+ * yii migrate/new 5 # showing the first 5 new migrations
+ * yii migrate/new 0 # showing all new migrations
+ * ~~~
+ *
+ * @param integer $limit the maximum number of new migrations to be displayed.
+ * If it is 0, all available new migrations will be displayed.
+ */
+ public function actionNew($limit = 10)
+ {
+ $limit = (int)$limit;
+ $migrations = $this->getNewMigrations();
+ if (empty($migrations)) {
+ echo "No new migrations found. Your system is up-to-date.\n";
+ } else {
+ $n = count($migrations);
+ if ($limit > 0 && $n > $limit) {
+ $migrations = array_slice($migrations, 0, $limit);
+ echo "Showing $limit out of $n new " . ($n === 1 ? 'migration' : 'migrations') . ":\n";
+ } else {
+ echo "Found $n new " . ($n === 1 ? 'migration' : 'migrations') . ":\n";
+ }
+
+ foreach ($migrations as $migration) {
+ echo " " . $migration . "\n";
+ }
+ }
+ }
+
+ /**
+ * Creates a new migration.
+ *
+ * This command creates a new migration using the available migration template.
+ * After using this command, developers should modify the created migration
+ * skeleton by filling up the actual migration logic.
+ *
+ * ~~~
+ * yii migrate/create create_user_table
+ * ~~~
+ *
+ * @param string $name the name of the new migration. This should only contain
+ * letters, digits and/or underscores.
+ * @throws Exception if the name argument is invalid.
+ */
+ public function actionCreate($name)
+ {
+ if (!preg_match('/^\w+$/', $name)) {
+ throw new Exception("The migration name should contain letters, digits and/or underscore characters only.");
+ }
+
+ $name = 'm' . gmdate('ymd_His') . '_' . $name;
+ $file = $this->migrationPath . DIRECTORY_SEPARATOR . $name . '.php';
+
+ if ($this->confirm("Create new migration '$file'?")) {
+ $content = $this->renderFile(Yii::getAlias($this->templateFile), array(
+ 'className' => $name,
+ ));
+ file_put_contents($file, $content);
+ echo "New migration created successfully.\n";
+ }
+ }
+
+ /**
+ * Upgrades with the specified migration class.
+ * @param string $class the migration class name
+ * @return boolean whether the migration is successful
+ */
+ protected function migrateUp($class)
+ {
+ if ($class === self::BASE_MIGRATION) {
+ return true;
+ }
+
+ echo "*** applying $class\n";
+ $start = microtime(true);
+ $migration = $this->createMigration($class);
+ if ($migration->up() !== false) {
+ $this->db->createCommand()->insert($this->migrationTable, array(
+ 'version' => $class,
+ 'apply_time' => time(),
+ ))->execute();
+ $time = microtime(true) - $start;
+ echo "*** applied $class (time: " . sprintf("%.3f", $time) . "s)\n\n";
+ return true;
+ } else {
+ $time = microtime(true) - $start;
+ echo "*** failed to apply $class (time: " . sprintf("%.3f", $time) . "s)\n\n";
+ return false;
+ }
+ }
+
+ /**
+ * Downgrades with the specified migration class.
+ * @param string $class the migration class name
+ * @return boolean whether the migration is successful
+ */
+ protected function migrateDown($class)
+ {
+ if ($class === self::BASE_MIGRATION) {
+ return true;
+ }
+
+ echo "*** reverting $class\n";
+ $start = microtime(true);
+ $migration = $this->createMigration($class);
+ if ($migration->down() !== false) {
+ $this->db->createCommand()->delete($this->migrationTable, array(
+ 'version' => $class,
+ ))->execute();
+ $time = microtime(true) - $start;
+ echo "*** reverted $class (time: " . sprintf("%.3f", $time) . "s)\n\n";
+ return true;
+ } else {
+ $time = microtime(true) - $start;
+ echo "*** failed to revert $class (time: " . sprintf("%.3f", $time) . "s)\n\n";
+ return false;
+ }
+ }
+
+ /**
+ * Creates a new migration instance.
+ * @param string $class the migration class name
+ * @return \yii\db\Migration the migration instance
+ */
+ protected function createMigration($class)
+ {
+ $file = $this->migrationPath . DIRECTORY_SEPARATOR . $class . '.php';
+ require_once($file);
+ return new $class(array(
+ 'db' => $this->db,
+ ));
+ }
+
+ /**
+ * Returns the migration history.
+ * @param integer $limit the maximum number of records in the history to be returned
+ * @return array the migration history
+ */
+ protected function getMigrationHistory($limit)
+ {
+ if ($this->db->schema->getTableSchema($this->migrationTable, true) === null) {
+ $this->createMigrationHistoryTable();
+ }
+ $query = new Query;
+ $rows = $query->select(array('version', 'apply_time'))
+ ->from($this->migrationTable)
+ ->orderBy('version DESC')
+ ->limit($limit)
+ ->createCommand()
+ ->queryAll();
+ $history = ArrayHelper::map($rows, 'version', 'apply_time');
+ unset($history[self::BASE_MIGRATION]);
+ return $history;
+ }
+
+ /**
+ * Creates the migration history table.
+ */
+ protected function createMigrationHistoryTable()
+ {
+ echo 'Creating migration history table "' . $this->migrationTable . '"...';
+ $this->db->createCommand()->createTable($this->migrationTable, array(
+ 'version' => 'varchar(255) NOT NULL PRIMARY KEY',
+ 'apply_time' => 'integer',
+ ))->execute();
+ $this->db->createCommand()->insert($this->migrationTable, array(
+ 'version' => self::BASE_MIGRATION,
+ 'apply_time' => time(),
+ ))->execute();
+ echo "done.\n";
+ }
+
+ /**
+ * Returns the migrations that are not applied.
+ * @return array list of new migrations
+ */
+ protected function getNewMigrations()
+ {
+ $applied = array();
+ foreach ($this->getMigrationHistory(-1) as $version => $time) {
+ $applied[substr($version, 1, 13)] = true;
+ }
+
+ $migrations = array();
+ $handle = opendir($this->migrationPath);
+ while (($file = readdir($handle)) !== false) {
+ if ($file === '.' || $file === '..') {
+ continue;
+ }
+ $path = $this->migrationPath . DIRECTORY_SEPARATOR . $file;
+ if (preg_match('/^(m(\d{6}_\d{6})_.*?)\.php$/', $file, $matches) && is_file($path) && !isset($applied[$matches[2]])) {
+ $migrations[] = $matches[1];
+ }
+ }
+ closedir($handle);
+ sort($migrations);
+ return $migrations;
+ }
+}
diff --git a/framework/yii/web/Pagination.php b/framework/yii/data/Pagination.php
similarity index 98%
rename from framework/yii/web/Pagination.php
rename to framework/yii/data/Pagination.php
index c4a8106..4e25809 100644
--- a/framework/yii/web/Pagination.php
+++ b/framework/yii/data/Pagination.php
@@ -5,9 +5,10 @@
* @license http://www.yiiframework.com/license/
*/
-namespace yii\web;
+namespace yii\data;
use Yii;
+use yii\base\Object;
/**
* Pagination represents information relevant to pagination of data items.
@@ -62,7 +63,7 @@ use Yii;
* @author Qiang Xue
* @since 2.0
*/
-class Pagination extends \yii\base\Object
+class Pagination extends Object
{
/**
* @var string name of the parameter storing the current page index. Defaults to 'page'.
diff --git a/framework/yii/web/Sort.php b/framework/yii/data/Sort.php
similarity index 99%
rename from framework/yii/web/Sort.php
rename to framework/yii/data/Sort.php
index 6014a3e..a46d0ce 100644
--- a/framework/yii/web/Sort.php
+++ b/framework/yii/data/Sort.php
@@ -5,9 +5,10 @@
* @license http://www.yiiframework.com/license/
*/
-namespace yii\web;
+namespace yii\data;
use Yii;
+use yii\base\Object;
use yii\helpers\Html;
/**
@@ -68,7 +69,7 @@ use yii\helpers\Html;
* @author Qiang Xue
* @since 2.0
*/
-class Sort extends \yii\base\Object
+class Sort extends Object
{
/**
* Sort ascending
diff --git a/framework/yii/db/ActiveRecord.php b/framework/yii/db/ActiveRecord.php
index 58411f0..6faebbf 100644
--- a/framework/yii/db/ActiveRecord.php
+++ b/framework/yii/db/ActiveRecord.php
@@ -275,10 +275,16 @@ class ActiveRecord extends Model
/**
* Returns the schema information of the DB table associated with this AR class.
* @return TableSchema the schema information of the DB table associated with this AR class.
+ * @throws InvalidConfigException if the table for the AR class does not exist.
*/
public static function getTableSchema()
{
- return static::getDb()->getTableSchema(static::tableName());
+ $schema = static::getDb()->getTableSchema(static::tableName());
+ if ($schema !== null) {
+ return $schema;
+ } else {
+ throw new InvalidConfigException("The table does not exist: " . static::tableName());
+ }
}
/**
diff --git a/framework/yii/db/Connection.php b/framework/yii/db/Connection.php
index 3a4d0ad..0dd47d8 100644
--- a/framework/yii/db/Connection.php
+++ b/framework/yii/db/Connection.php
@@ -305,8 +305,7 @@ class Connection extends Component
$this->pdo = $this->createPdoInstance();
$this->initConnection();
Yii::endProfile($token, __METHOD__);
- }
- catch (\PDOException $e) {
+ } catch (\PDOException $e) {
Yii::endProfile($token, __METHOD__);
Yii::error("Failed to open DB connection ({$this->dsn}): " . $e->getMessage(), __METHOD__);
$message = YII_DEBUG ? 'Failed to open DB connection: ' . $e->getMessage() : 'Failed to open DB connection.';
@@ -508,7 +507,7 @@ class Connection extends Component
{
$db = $this;
return preg_replace_callback('/(\\{\\{([%\w\-\. ]+)\\}\\}|\\[\\[([\w\-\. ]+)\\]\\])/',
- function($matches) use($db) {
+ function ($matches) use ($db) {
if (isset($matches[3])) {
return $db->quoteColumnName($matches[3]);
} else {
diff --git a/framework/yii/db/QueryBuilder.php b/framework/yii/db/QueryBuilder.php
index c0b4223..04f1969 100644
--- a/framework/yii/db/QueryBuilder.php
+++ b/framework/yii/db/QueryBuilder.php
@@ -464,6 +464,12 @@ class QueryBuilder extends \yii\base\Object
* the first part will be converted, and the rest of the parts will be appended to the converted result.
* For example, 'string NOT NULL' is converted to 'varchar(255) NOT NULL'.
*
+ * For some of the abstract types you can also specify a length or precision constraint
+ * by prepending it in round brackets directly to the type.
+ * For example `string(32)` will be converted into "varchar(32)" on a MySQL database.
+ * If the underlying DBMS does not support these kind of constraints for a type it will
+ * be ignored.
+ *
* If a type cannot be found in [[typeMap]], it will be returned without any change.
* @param string $type abstract column type
* @return string physical column type.
@@ -472,6 +478,10 @@ class QueryBuilder extends \yii\base\Object
{
if (isset($this->typeMap[$type])) {
return $this->typeMap[$type];
+ } elseif (preg_match('/^(\w+)\((.+?)\)(.*)$/', $type, $matches)) {
+ if (isset($this->typeMap[$matches[1]])) {
+ return preg_replace('/\(.+\)/', '(' . $matches[2] . ')', $this->typeMap[$matches[1]]) . $matches[3];
+ }
} elseif (preg_match('/^(\w+)\s+/', $type, $matches)) {
if (isset($this->typeMap[$matches[1]])) {
return preg_replace('/^\w+/', $this->typeMap[$matches[1]], $type);
diff --git a/framework/yii/db/mssql/QueryBuilder.php b/framework/yii/db/mssql/QueryBuilder.php
index 45a7507..e7f8f80 100644
--- a/framework/yii/db/mssql/QueryBuilder.php
+++ b/framework/yii/db/mssql/QueryBuilder.php
@@ -28,7 +28,7 @@ class QueryBuilder extends \yii\db\QueryBuilder
Schema::TYPE_INTEGER => 'int(11)',
Schema::TYPE_BIGINT => 'bigint(20)',
Schema::TYPE_FLOAT => 'float',
- Schema::TYPE_DECIMAL => 'decimal',
+ Schema::TYPE_DECIMAL => 'decimal(10,0)',
Schema::TYPE_DATETIME => 'datetime',
Schema::TYPE_TIMESTAMP => 'timestamp',
Schema::TYPE_TIME => 'time',
diff --git a/framework/yii/db/mysql/QueryBuilder.php b/framework/yii/db/mysql/QueryBuilder.php
index 70c6d64..4b35e24 100644
--- a/framework/yii/db/mysql/QueryBuilder.php
+++ b/framework/yii/db/mysql/QueryBuilder.php
@@ -29,7 +29,7 @@ class QueryBuilder extends \yii\db\QueryBuilder
Schema::TYPE_INTEGER => 'int(11)',
Schema::TYPE_BIGINT => 'bigint(20)',
Schema::TYPE_FLOAT => 'float',
- Schema::TYPE_DECIMAL => 'decimal',
+ Schema::TYPE_DECIMAL => 'decimal(10,0)',
Schema::TYPE_DATETIME => 'datetime',
Schema::TYPE_TIMESTAMP => 'timestamp',
Schema::TYPE_TIME => 'time',
diff --git a/framework/yii/db/mysql/Schema.php b/framework/yii/db/mysql/Schema.php
index 501149a..b42ef15 100644
--- a/framework/yii/db/mysql/Schema.php
+++ b/framework/yii/db/mysql/Schema.php
@@ -178,6 +178,7 @@ class Schema extends \yii\db\Schema
* Collects the metadata of table columns.
* @param TableSchema $table the table metadata
* @return boolean whether the table exists in the database
+ * @throws \Exception if DB query fails
*/
protected function findColumns($table)
{
@@ -185,7 +186,12 @@ class Schema extends \yii\db\Schema
try {
$columns = $this->db->createCommand($sql)->queryAll();
} catch (\Exception $e) {
- return false;
+ $previous = $e->getPrevious();
+ if ($previous instanceof \PDOException && $previous->getCode() == '42S02') {
+ // table does not exist
+ return false;
+ }
+ throw $e;
}
foreach ($columns as $info) {
$column = $this->loadColumnSchema($info);
diff --git a/framework/yii/db/pgsql/QueryBuilder.php b/framework/yii/db/pgsql/QueryBuilder.php
new file mode 100644
index 0000000..3417ad9
--- /dev/null
+++ b/framework/yii/db/pgsql/QueryBuilder.php
@@ -0,0 +1,41 @@
+
+ * @since 2.0
+ */
+class QueryBuilder extends \yii\db\QueryBuilder
+{
+
+ /**
+ * @var array mapping from abstract column types (keys) to physical column types (values).
+ */
+ public $typeMap = array(
+ Schema::TYPE_PK => 'serial not null primary key',
+ Schema::TYPE_STRING => 'varchar',
+ Schema::TYPE_TEXT => 'text',
+ Schema::TYPE_SMALLINT => 'smallint',
+ Schema::TYPE_INTEGER => 'integer',
+ Schema::TYPE_BIGINT => 'bigint',
+ Schema::TYPE_FLOAT => 'double precision',
+ Schema::TYPE_DECIMAL => 'numeric',
+ Schema::TYPE_DATETIME => 'timestamp',
+ Schema::TYPE_TIMESTAMP => 'timestamp',
+ Schema::TYPE_TIME => 'time',
+ Schema::TYPE_DATE => 'date',
+ Schema::TYPE_BINARY => 'bytea',
+ Schema::TYPE_BOOLEAN => 'boolean',
+ Schema::TYPE_MONEY => 'numeric(19,4)',
+ );
+
+}
diff --git a/framework/yii/db/pgsql/Schema.php b/framework/yii/db/pgsql/Schema.php
new file mode 100644
index 0000000..94f845f
--- /dev/null
+++ b/framework/yii/db/pgsql/Schema.php
@@ -0,0 +1,288 @@
+
+ * @since 2.0
+ */
+class Schema extends \yii\db\Schema
+{
+
+ /**
+ * The default schema used for the current session.
+ * @var string
+ */
+ public $defaultSchema = 'public';
+
+ /**
+ * @var array mapping from physical column types (keys) to abstract
+ * column types (values)
+ */
+ public $typeMap = array(
+ 'abstime' => self::TYPE_TIMESTAMP,
+ 'bit' => self::TYPE_STRING,
+ 'boolean' => self::TYPE_BOOLEAN,
+ 'box' => self::TYPE_STRING,
+ 'character' => self::TYPE_STRING,
+ 'bytea' => self::TYPE_BINARY,
+ 'char' => self::TYPE_STRING,
+ 'cidr' => self::TYPE_STRING,
+ 'circle' => self::TYPE_STRING,
+ 'date' => self::TYPE_DATE,
+ 'real' => self::TYPE_FLOAT,
+ 'double precision' => self::TYPE_DECIMAL,
+ 'inet' => self::TYPE_STRING,
+ 'smallint' => self::TYPE_SMALLINT,
+ 'integer' => self::TYPE_INTEGER,
+ 'bigint' => self::TYPE_BIGINT,
+ 'interval' => self::TYPE_STRING,
+ 'json' => self::TYPE_STRING,
+ 'line' => self::TYPE_STRING,
+ 'macaddr' => self::TYPE_STRING,
+ 'money' => self::TYPE_MONEY,
+ 'name' => self::TYPE_STRING,
+ 'numeric' => self::TYPE_STRING,
+ 'numrange' => self::TYPE_DECIMAL,
+ 'oid' => self::TYPE_BIGINT, // should not be used. it's pg internal!
+ 'path' => self::TYPE_STRING,
+ 'point' => self::TYPE_STRING,
+ 'polygon' => self::TYPE_STRING,
+ 'text' => self::TYPE_TEXT,
+ 'time without time zone' => self::TYPE_TIME,
+ 'timestamp without time zone' => self::TYPE_TIMESTAMP,
+ 'timestamp with time zone' => self::TYPE_TIMESTAMP,
+ 'time with time zone' => self::TYPE_TIMESTAMP,
+ 'unknown' => self::TYPE_STRING,
+ 'uuid' => self::TYPE_STRING,
+ 'bit varying' => self::TYPE_STRING,
+ 'character varying' => self::TYPE_STRING,
+ 'xml' => self::TYPE_STRING
+ );
+
+ /**
+ * Creates a query builder for the PostgreSQL database.
+ * @return QueryBuilder query builder instance
+ */
+ public function createQueryBuilder()
+ {
+ return new QueryBuilder($this->db);
+ }
+
+ /**
+ * Resolves the table name and schema name (if any).
+ * @param TableSchema $table the table metadata object
+ * @param string $name the table name
+ */
+ protected function resolveTableNames($table, $name)
+ {
+ $parts = explode('.', str_replace('"', '', $name));
+ if (isset($parts[1])) {
+ $table->schemaName = $parts[0];
+ $table->name = $parts[1];
+ } else {
+ $table->name = $parts[0];
+ }
+ if ($table->schemaName === null) {
+ $table->schemaName = $this->defaultSchema;
+ }
+ }
+
+ /**
+ * Quotes a table name for use in a query.
+ * A simple table name has no schema prefix.
+ * @param string $name table name
+ * @return string the properly quoted table name
+ */
+ public function quoteSimpleTableName($name)
+ {
+ return strpos($name, '"') !== false ? $name : '"' . $name . '"';
+ }
+
+ /**
+ * Loads the metadata for the specified table.
+ * @param string $name table name
+ * @return TableSchema|null driver dependent table metadata. Null if the table does not exist.
+ */
+ public function loadTableSchema($name)
+ {
+ $table = new TableSchema();
+ $this->resolveTableNames($table, $name);
+ if ($this->findColumns($table)) {
+ $this->findConstraints($table);
+ return $table;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Collects the foreign key column details for the given table.
+ * @param TableSchema $table the table metadata
+ */
+ protected function findConstraints($table)
+ {
+
+ $tableName = $this->quoteValue($table->name);
+ $tableSchema = $this->quoteValue($table->schemaName);
+
+ //We need to extract the constraints de hard way since:
+ //http://www.postgresql.org/message-id/26677.1086673982@sss.pgh.pa.us
+
+ $sql = <<db->createCommand($sql)->queryAll();
+ foreach ($constraints as $constraint) {
+ $columns = explode(',', $constraint['columns']);
+ $fcolumns = explode(',', $constraint['foreign_columns']);
+ if ($constraint['foreign_table_schema'] !== $this->defaultSchema) {
+ $foreignTable = $constraint['foreign_table_schema'] . '.' . $constraint['foreign_table_name'];
+ } else {
+ $foreignTable = $constraint['foreign_table_name'];
+ }
+ $citem = array($foreignTable);
+ foreach ($columns as $idx => $column) {
+ $citem[] = array($fcolumns[$idx] => $column);
+ }
+ $table->foreignKeys[] = $citem;
+ }
+ }
+
+ /**
+ * Collects the metadata of table columns.
+ * @param TableSchema $table the table metadata
+ * @return boolean whether the table exists in the database
+ */
+ protected function findColumns($table)
+ {
+ $tableName = $this->db->quoteValue($table->name);
+ $schemaName = $this->db->quoteValue($table->schemaName);
+ $sql = <<> 16) & 65535
+ END
+ WHEN 700 /*float4*/ THEN 24 /*FLT_MANT_DIG*/
+ WHEN 701 /*float8*/ THEN 53 /*DBL_MANT_DIG*/
+ ELSE null
+ END AS numeric_precision,
+ CASE
+ WHEN atttypid IN (21, 23, 20) THEN 0
+ WHEN atttypid IN (1700) THEN
+ CASE
+ WHEN atttypmod = -1 THEN null
+ ELSE (atttypmod - 4) & 65535
+ END
+ ELSE null
+ END AS numeric_scale,
+ CAST(
+ information_schema._pg_char_max_length(information_schema._pg_truetypid(a, t), information_schema._pg_truetypmod(a, t))
+ AS numeric
+ ) AS size,
+ a.attnum = any (ct.conkey) as is_pkey
+FROM
+ pg_class c
+ LEFT JOIN pg_attribute a ON a.attrelid = c.oid
+ LEFT JOIN pg_attrdef ad ON a.attrelid = ad.adrelid AND a.attnum = ad.adnum
+ LEFT JOIN pg_type t ON a.atttypid = t.oid
+ LEFT JOIN pg_namespace d ON d.oid = c.relnamespace
+ LEFT join pg_constraint ct on ct.conrelid=c.oid and ct.contype='p'
+WHERE
+ a.attnum > 0
+ and c.relname = {$tableName}
+ and d.nspname = {$schemaName}
+ORDER BY
+ a.attnum;
+SQL;
+
+ $columns = $this->db->createCommand($sql)->queryAll();
+ foreach ($columns as $column) {
+ $column = $this->loadColumnSchema($column);
+ $table->columns[$column->name] = $column;
+ if ($column->isPrimaryKey === true) {
+ $table->primaryKey[] = $column->name;
+ if ($table->sequenceName === null && preg_match("/nextval\('\w+'(::regclass)?\)/", $column->defaultValue) === 1) {
+ $table->sequenceName = preg_replace(array('/nextval/', '/::/', '/regclass/', '/\'\)/', '/\(\'/'), '', $column->defaultValue);
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Loads the column information into a [[ColumnSchema]] object.
+ * @param array $info column information
+ * @return ColumnSchema the column schema object
+ */
+ protected function loadColumnSchema($info)
+ {
+ $column = new ColumnSchema();
+ $column->allowNull = $info['is_nullable'];
+ $column->autoIncrement = $info['is_autoinc'];
+ $column->comment = $info['column_comment'];
+ $column->dbType = $info['data_type'];
+ $column->defaultValue = $info['column_default'];
+ $column->enumValues = explode(',', str_replace(array("''"), array("'"), $info['enum_values']));
+ $column->unsigned = false; // has no meanining in PG
+ $column->isPrimaryKey = $info['is_pkey'];
+ $column->name = $info['column_name'];
+ $column->precision = $info['numeric_precision'];
+ $column->scale = $info['numeric_scale'];
+ $column->size = $info['size'];
+
+ if (isset($this->typeMap[$column->dbType])) {
+ $column->type = $this->typeMap[$column->dbType];
+ } else {
+ $column->type = self::TYPE_STRING;
+ }
+ $column->phpType = $this->getColumnPhpType($column);
+ return $column;
+ }
+}
diff --git a/framework/yii/db/sqlite/QueryBuilder.php b/framework/yii/db/sqlite/QueryBuilder.php
index 72d48f4..52c101b 100644
--- a/framework/yii/db/sqlite/QueryBuilder.php
+++ b/framework/yii/db/sqlite/QueryBuilder.php
@@ -30,13 +30,13 @@ class QueryBuilder extends \yii\db\QueryBuilder
Schema::TYPE_INTEGER => 'integer',
Schema::TYPE_BIGINT => 'bigint',
Schema::TYPE_FLOAT => 'float',
- Schema::TYPE_DECIMAL => 'decimal',
+ Schema::TYPE_DECIMAL => 'decimal(10,0)',
Schema::TYPE_DATETIME => 'datetime',
Schema::TYPE_TIMESTAMP => 'timestamp',
Schema::TYPE_TIME => 'time',
Schema::TYPE_DATE => 'date',
Schema::TYPE_BINARY => 'blob',
- Schema::TYPE_BOOLEAN => 'tinyint(1)',
+ Schema::TYPE_BOOLEAN => 'boolean',
Schema::TYPE_MONEY => 'decimal(19,4)',
);
diff --git a/framework/yii/debug/Module.php b/framework/yii/debug/Module.php
index 3421d95..a680f53 100644
--- a/framework/yii/debug/Module.php
+++ b/framework/yii/debug/Module.php
@@ -14,4 +14,4 @@ namespace yii\debug;
class Module extends \yii\base\Module
{
public $controllerNamespace = 'yii\debug\controllers';
-}
\ No newline at end of file
+}
diff --git a/framework/yii/debug/controllers/DefaultController.php b/framework/yii/debug/controllers/DefaultController.php
index 4d686ee..f1160b1 100644
--- a/framework/yii/debug/controllers/DefaultController.php
+++ b/framework/yii/debug/controllers/DefaultController.php
@@ -31,4 +31,4 @@ class DefaultController extends Controller
echo "Unable to find debug data tagged with '$tag'.";
}
}
-}
\ No newline at end of file
+}
diff --git a/framework/yii/helpers/Json.php b/framework/yii/helpers/Json.php
index 5e77c3f..117db1f 100644
--- a/framework/yii/helpers/Json.php
+++ b/framework/yii/helpers/Json.php
@@ -14,5 +14,4 @@ namespace yii\helpers;
*/
class Json extends base\Json
{
-
}
diff --git a/framework/yii/helpers/base/Console.php b/framework/yii/helpers/base/Console.php
index 6ad0b7b..e3acbd9 100644
--- a/framework/yii/helpers/base/Console.php
+++ b/framework/yii/helpers/base/Console.php
@@ -286,7 +286,7 @@ class Console
* You can pass any of the FG_*, BG_* and TEXT_* constants and also [[xtermFgColor]] and [[xtermBgColor]].
* @return string
*/
- public static function ansiFormat($string, $format=array())
+ public static function ansiFormat($string, $format = array())
{
$code = implode(';', $format);
return "\033[0m" . ($code !== '' ? "\033[" . $code . "m" : '') . $string . "\033[0m";
@@ -589,11 +589,10 @@ class Console
if (static::isRunningOnWindows()) {
$output = array();
exec('mode con', $output);
- if(isset($output) && strpos($output[1], 'CON')!==false) {
+ if (isset($output) && strpos($output[1], 'CON') !== false) {
return $size = array((int)preg_replace('~[^0-9]~', '', $output[3]), (int)preg_replace('~[^0-9]~', '', $output[4]));
}
} else {
-
// try stty if available
$stty = array();
if (exec('stty -a 2>&1', $stty) && preg_match('/rows\s+(\d+);\s*columns\s+(\d+);/mi', implode(' ', $stty), $matches)) {
diff --git a/framework/yii/helpers/base/Html.php b/framework/yii/helpers/base/Html.php
index 9a0001c..47385e2 100644
--- a/framework/yii/helpers/base/Html.php
+++ b/framework/yii/helpers/base/Html.php
@@ -1479,5 +1479,4 @@ class Html
$name = strtolower(static::getInputName($model, $attribute));
return str_replace(array('[]', '][', '[', ']', ' '), array('', '-', '-', '', '-'), $name);
}
-
}
diff --git a/framework/yii/helpers/base/Inflector.php b/framework/yii/helpers/base/Inflector.php
index 7454e4e..113d350 100644
--- a/framework/yii/helpers/base/Inflector.php
+++ b/framework/yii/helpers/base/Inflector.php
@@ -498,7 +498,7 @@ class Inflector
if (in_array(($number % 100), range(11, 13))) {
return $number . 'th';
}
- switch (($number % 10)) {
+ switch ($number % 10) {
case 1: return $number . 'st';
case 2: return $number . 'nd';
case 3: return $number . 'rd';
diff --git a/framework/yii/helpers/base/Json.php b/framework/yii/helpers/base/Json.php
index 8de55f9..41d5bf0 100644
--- a/framework/yii/helpers/base/Json.php
+++ b/framework/yii/helpers/base/Json.php
@@ -8,6 +8,7 @@
namespace yii\helpers\base;
use yii\base\InvalidParamException;
+use yii\base\Jsonable;
use yii\web\JsExpression;
/**
@@ -90,16 +91,19 @@ class Json
$token = '!{[' . count($expressions) . ']}!';
$expressions['"' . $token . '"'] = $data->expression;
return $token;
- }
- $result = array();
- foreach ($data as $key => $value) {
- if (is_array($value) || is_object($value)) {
- $result[$key] = static::processData($value, $expressions);
- } else {
- $result[$key] = $value;
+ } elseif ($data instanceof Jsonable) {
+ return $data->toJson();
+ } else {
+ $result = array();
+ foreach ($data as $key => $value) {
+ if (is_array($value) || is_object($value)) {
+ $result[$key] = static::processData($value, $expressions);
+ } else {
+ $result[$key] = $value;
+ }
}
+ return $result;
}
- return $result;
} else {
return $data;
}
diff --git a/framework/yii/helpers/base/SecurityHelper.php b/framework/yii/helpers/base/SecurityHelper.php
index 3f69fee..f646a24 100644
--- a/framework/yii/helpers/base/SecurityHelper.php
+++ b/framework/yii/helpers/base/SecurityHelper.php
@@ -131,15 +131,30 @@ class SecurityHelper
$keys = is_file($keyFile) ? require($keyFile) : array();
}
if (!isset($keys[$name])) {
- // generate a 32-char random key
- $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
- $keys[$name] = substr(str_shuffle(str_repeat($chars, 5)), 0, $length);
+ $keys[$name] = static::generateRandomKey($length);
file_put_contents($keyFile, "
+ * @since 2.0
+ */
+class DbMessageSource extends MessageSource
+{
+ /**
+ * Prefix which would be used when generating cache key.
+ */
+ const CACHE_KEY_PREFIX = 'DbMessageSource';
+
+ /**
+ * @var Connection|string the DB connection object or the application component ID of the DB connection.
+ * After the DbMessageSource object is created, if you want to change this property, you should only assign
+ * it with a DB connection object.
+ */
+ public $db = 'db';
+ /**
+ * @var Cache|string the cache object or the application component ID of the cache object.
+ * The messages data will be cached using this cache object. Note, this property has meaning only
+ * in case [[cachingDuration]] set to non-zero value.
+ * After the DbMessageSource object is created, if you want to change this property, you should only assign
+ * it with a cache object.
+ */
+ public $cache = 'cache';
+ /**
+ * @var string the name of the source message table.
+ */
+ public $sourceMessageTable = 'tbl_source_message';
+ /**
+ * @var string the name of the translated message table.
+ */
+ public $messageTable = 'tbl_message';
+ /**
+ * @var integer the time in seconds that the messages can remain valid in cache.
+ * Use 0 to indicate that the cached data will never expire.
+ * @see enableCaching
+ */
+ public $cachingDuration = 0;
+ /**
+ * @var boolean whether to enable caching translated messages
+ */
+ public $enableCaching = false;
+
+ /**
+ * Initializes the DbMessageSource component.
+ * This method will initialize the [[db]] property to make sure it refers to a valid DB connection.
+ * Configured [[cache]] component would also be initialized.
+ * @throws InvalidConfigException if [[db]] is invalid or [[cache]] is invalid.
+ */
+ public function init()
+ {
+ parent::init();
+ if (is_string($this->db)) {
+ $this->db = Yii::$app->getComponent($this->db);
+ }
+ if (!$this->db instanceof Connection) {
+ throw new InvalidConfigException("DbMessageSource::db must be either a DB connection instance or the application component ID of a DB connection.");
+ }
+ if ($this->enableCaching) {
+ if (is_string($this->cache)) {
+ $this->cache = Yii::$app->getComponent($this->cache);
+ }
+ if (!$this->cache instanceof Cache) {
+ throw new InvalidConfigException("DbMessageSource::cache must be either a cache object or the application component ID of the cache object.");
+ }
+ }
+ }
+
+ /**
+ * Loads the message translation for the specified language and category.
+ * Child classes should override this method to return the message translations of
+ * the specified language and category.
+ * @param string $category the message category
+ * @param string $language the target language
+ * @return array the loaded messages. The keys are original messages, and the values
+ * are translated messages.
+ */
+ protected function loadMessages($category, $language)
+ {
+ if ($this->enableCaching) {
+ $key = array(
+ __CLASS__,
+ $category,
+ $language,
+ );
+ $messages = $this->cache->get($key);
+ if ($messages === false) {
+ $messages = $this->loadMessagesFromDb($category, $language);
+ $this->cache->set($key, $messages, $this->cachingDuration);
+ }
+ return $messages;
+ } else {
+ return $this->loadMessagesFromDb($category, $language);
+ }
+ }
+
+ /**
+ * Loads the messages from database.
+ * You may override this method to customize the message storage in the database.
+ * @param string $category the message category.
+ * @param string $language the target language.
+ * @return array the messages loaded from database.
+ */
+ protected function loadMessagesFromDb($category, $language)
+ {
+ $query = new Query();
+ $messages = $query->select(array('t1.message message', 't2.translation translation'))
+ ->from(array($this->sourceMessageTable . ' t1', $this->messageTable . ' t2'))
+ ->where('t1.id = t2.id AND t1.category = :category AND t2.language = :language')
+ ->params(array(':category' => $category, ':language' => $language))
+ ->createCommand($this->db)
+ ->queryAll();
+ return ArrayHelper::map($messages, 'message', 'translation');
+ }
+}
diff --git a/framework/yii/i18n/Formatter.php b/framework/yii/i18n/Formatter.php
index d688a15..948e277 100644
--- a/framework/yii/i18n/Formatter.php
+++ b/framework/yii/i18n/Formatter.php
@@ -30,21 +30,40 @@ class Formatter extends \yii\base\Formatter
*/
public $locale;
/**
- * @var string the default format string to be used to format a date using PHP date() function.
+ * @var string the default format string to be used to format a date.
+ * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
+ * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
*/
public $dateFormat = 'short';
/**
- * @var string the default format string to be used to format a time using PHP date() function.
+ * @var string the default format string to be used to format a time.
+ * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
+ * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
*/
public $timeFormat = 'short';
/**
- * @var string the default format string to be used to format a date and time using PHP date() function.
+ * @var string the default format string to be used to format a date and time.
+ * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
+ * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
*/
public $datetimeFormat = 'short';
/**
* @var array the options to be set for the NumberFormatter objects. Please refer to
+ * [PHP manual](http://php.net/manual/en/class.numberformatter.php#intl.numberformatter-constants.unumberformatattribute)
+ * for the possible options. This property is used by [[createNumberFormatter]] when
+ * creating a new number formatter to format decimals, currencies, etc.
*/
public $numberFormatOptions = array();
+ /**
+ * @var string the character displayed as the decimal point when formatting a number.
+ * If not set, the decimal separator corresponding to [[locale]] will be used.
+ */
+ public $decimalSeparator;
+ /**
+ * @var string the character displayed as the thousands separator character when formatting a number.
+ * If not set, the thousand separator corresponding to [[locale]] will be used.
+ */
+ public $thousandSeparator;
/**
@@ -61,6 +80,16 @@ class Formatter extends \yii\base\Formatter
if ($this->locale === null) {
$this->locale = Yii::$app->language;
}
+ if ($this->decimalSeparator === null || $this->thousandSeparator === null) {
+ $formatter = new NumberFormatter($this->locale, NumberFormatter::DECIMAL);
+ if ($this->decimalSeparator === null) {
+ $this->decimalSeparator = $formatter->getSymbol(NumberFormatter::DECIMAL_SEPARATOR_SYMBOL);
+ }
+ if ($this->thousandSeparator === null) {
+ $this->thousandSeparator = $formatter->getSymbol(NumberFormatter::GROUPING_SEPARATOR_SYMBOL);
+ }
+ }
+
parent::init();
}
@@ -81,8 +110,11 @@ class Formatter extends \yii\base\Formatter
* - 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.
+ * If null, [[dateFormat]] will be used.
+ *
+ * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
+ * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
+ *
* @return string the formatted result
* @see dateFormat
*/
@@ -111,8 +143,11 @@ class Formatter extends \yii\base\Formatter
* - 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.
+ * If null, [[dateFormat]] will be used.
+ *
+ * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
+ * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
+ *
* @return string the formatted result
* @see timeFormat
*/
@@ -141,8 +176,11 @@ class Formatter extends \yii\base\Formatter
* - 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.
+ * If null, [[dateFormat]] will be used.
+ *
+ * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
+ * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
+ *
* @return string the formatted result
* @see datetimeFormat
*/
@@ -213,7 +251,7 @@ class Formatter extends \yii\base\Formatter
/**
* 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
+ * @param string $format the format to be used. Please refer to [ICU manual](http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details)
* @return NumberFormatter the created formatter instance
*/
protected function createNumberFormatter($type, $format)
diff --git a/framework/yii/i18n/I18N.php b/framework/yii/i18n/I18N.php
index a358683..b929f49 100644
--- a/framework/yii/i18n/I18N.php
+++ b/framework/yii/i18n/I18N.php
@@ -78,16 +78,11 @@ class I18N extends Component
* @param string $category the message category.
* @param string $message the message to be translated.
* @param array $params the parameters that will be used to replace the corresponding placeholders in the message.
- * @param string $language the language code (e.g. `en_US`, `en`). If this is null, the current
- * [[\yii\base\Application::language|application language]] will be used.
+ * @param string $language the language code (e.g. `en_US`, `en`).
* @return string the translated message.
*/
- public function translate($category, $message, $params = array(), $language = null)
+ public function translate($category, $message, $params, $language)
{
- if ($language === null) {
- $language = Yii::$app->language;
- }
-
$message = $this->getMessageSource($category)->translate($category, $message, $language);
if (!is_array($params)) {
diff --git a/framework/yii/i18n/MessageSource.php b/framework/yii/i18n/MessageSource.php
index cf23338..90adbfb 100644
--- a/framework/yii/i18n/MessageSource.php
+++ b/framework/yii/i18n/MessageSource.php
@@ -118,4 +118,3 @@ class MessageSource extends Component
}
}
}
-
diff --git a/framework/yii/jui/Accordion.php b/framework/yii/jui/Accordion.php
index f36c981..898649e 100644
--- a/framework/yii/jui/Accordion.php
+++ b/framework/yii/jui/Accordion.php
@@ -125,7 +125,7 @@ class Accordion extends Widget
$items[] = Html::tag($headerTag, $item['header'], $headerOptions);
$options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', array()));
$tag = ArrayHelper::remove($options, 'tag', 'div');
- $items[] = Html::tag($tag, $item['content'], $options);;
+ $items[] = Html::tag($tag, $item['content'], $options);
}
return implode("\n", $items);
diff --git a/framework/yii/jui/Menu.php b/framework/yii/jui/Menu.php
index d4e390c..83523e7 100644
--- a/framework/yii/jui/Menu.php
+++ b/framework/yii/jui/Menu.php
@@ -10,7 +10,6 @@ namespace yii\jui;
use Yii;
use yii\helpers\Json;
-
/**
* Menu renders a menu jQuery UI widget.
*
diff --git a/framework/yii/jui/Widget.php b/framework/yii/jui/Widget.php
index d34a8bd..5724919 100644
--- a/framework/yii/jui/Widget.php
+++ b/framework/yii/jui/Widget.php
@@ -10,7 +10,6 @@ namespace yii\jui;
use Yii;
use yii\helpers\Json;
-
/**
* \yii\jui\Widget is the base class for all jQuery UI widgets.
*
diff --git a/framework/yii/logging/ProfileTarget.php b/framework/yii/logging/ProfileTarget.php
index f4522ac..be1d73d 100644
--- a/framework/yii/logging/ProfileTarget.php
+++ b/framework/yii/logging/ProfileTarget.php
@@ -70,7 +70,7 @@ class CProfileLogRoute extends CWebLogRoute
public function processLogs($logs)
{
$app = \Yii::$app;
- if (!($app instanceof CWebApplication) || $app->getRequest()->getIsAjaxRequest())
+ if (!($app instanceof \yii\web\Application) || $app->getRequest()->getIsAjax())
return;
if ($this->getReport() === 'summary')
diff --git a/framework/yii/logging/Target.php b/framework/yii/logging/Target.php
index fac8b53..7be7001 100644
--- a/framework/yii/logging/Target.php
+++ b/framework/yii/logging/Target.php
@@ -204,11 +204,9 @@ abstract class Target extends Component
if ($matched) {
foreach ($this->except as $category) {
$prefix = rtrim($category, '*');
- foreach ($messages as $i => $message) {
- if (strpos($message[2], $prefix) === 0 && ($message[2] === $category || $prefix !== $category)) {
- $matched = false;
- break;
- }
+ if (strpos($message[2], $prefix) === 0 && ($message[2] === $category || $prefix !== $category)) {
+ $matched = false;
+ break;
}
}
}
diff --git a/framework/yii/rbac/DbManager.php b/framework/yii/rbac/DbManager.php
index b7a5d4e..8d3bea2 100644
--- a/framework/yii/rbac/DbManager.php
+++ b/framework/yii/rbac/DbManager.php
@@ -493,8 +493,9 @@ class DbManager extends Manager
'bizRule' => $row['biz_rule'],
'data' => $data,
));
- } else
+ } else {
return null;
+ }
}
/**
diff --git a/framework/yii/rbac/PhpManager.php b/framework/yii/rbac/PhpManager.php
index 7a476e0..8ecc75c 100644
--- a/framework/yii/rbac/PhpManager.php
+++ b/framework/yii/rbac/PhpManager.php
@@ -468,7 +468,7 @@ class PhpManager extends Manager
'bizRule' => $assignment['bizRule'],
'data' => $assignment['data'],
));
- }
+ }
}
}
}
diff --git a/framework/yii/requirements/requirements.php b/framework/yii/requirements/requirements.php
index 63aa70d..670544d 100644
--- a/framework/yii/requirements/requirements.php
+++ b/framework/yii/requirements/requirements.php
@@ -45,4 +45,4 @@ return array(
'by' => 'Internationalization support',
'memo' => 'PHP Intl extension 1.0.2 or higher is required when you want to use IDN-feature of EmailValidator or UrlValidator or the yii\i18n\Formatter
class.'
),
-);
\ No newline at end of file
+);
diff --git a/framework/yii/validators/CaptchaValidator.php b/framework/yii/validators/CaptchaValidator.php
index dbc263e..01870d3 100644
--- a/framework/yii/validators/CaptchaValidator.php
+++ b/framework/yii/validators/CaptchaValidator.php
@@ -117,4 +117,3 @@ class CaptchaValidator extends Validator
return 'yii.validation.captcha(value, messages, ' . json_encode($options) . ');';
}
}
-
diff --git a/framework/yii/validators/CompareValidator.php b/framework/yii/validators/CompareValidator.php
index b8e8a50..c94577c 100644
--- a/framework/yii/validators/CompareValidator.php
+++ b/framework/yii/validators/CompareValidator.php
@@ -58,7 +58,7 @@ class CompareValidator extends Validator
* - `<`: validates to see if the value being validated is less than the value being compared with.
* - `<=`: validates to see if the value being validated is less than or equal to the value being compared with.
*/
- public $operator = '=';
+ public $operator = '==';
/**
* @var string the user-defined error message. It may contain the following placeholders which
* will be replaced accordingly by the validator:
diff --git a/framework/yii/validators/DateValidator.php b/framework/yii/validators/DateValidator.php
index 7370b78..2f9e18b 100644
--- a/framework/yii/validators/DateValidator.php
+++ b/framework/yii/validators/DateValidator.php
@@ -58,7 +58,7 @@ class DateValidator extends Validator
$date = DateTime::createFromFormat($this->format, $value);
if ($date === false) {
$this->addError($object, $attribute, $this->message);
- } elseif ($this->timestampAttribute !== false) {
+ } elseif ($this->timestampAttribute !== null) {
$object->{$this->timestampAttribute} = $date->getTimestamp();
}
}
@@ -73,4 +73,3 @@ class DateValidator extends Validator
return DateTime::createFromFormat($this->format, $value) !== false;
}
}
-
diff --git a/framework/yii/validators/DefaultValueValidator.php b/framework/yii/validators/DefaultValueValidator.php
index 185dbd4..20df5fd 100644
--- a/framework/yii/validators/DefaultValueValidator.php
+++ b/framework/yii/validators/DefaultValueValidator.php
@@ -40,4 +40,3 @@ class DefaultValueValidator extends Validator
}
}
}
-
diff --git a/framework/yii/validators/ExistValidator.php b/framework/yii/validators/ExistValidator.php
index 7c45491..9c74890 100644
--- a/framework/yii/validators/ExistValidator.php
+++ b/framework/yii/validators/ExistValidator.php
@@ -99,4 +99,3 @@ class ExistValidator extends Validator
return $query->exists();
}
}
-
diff --git a/framework/yii/validators/FilterValidator.php b/framework/yii/validators/FilterValidator.php
index 72a9a9d..560feb1 100644
--- a/framework/yii/validators/FilterValidator.php
+++ b/framework/yii/validators/FilterValidator.php
@@ -6,6 +6,7 @@
*/
namespace yii\validators;
+
use yii\base\InvalidConfigException;
/**
diff --git a/framework/yii/validators/RangeValidator.php b/framework/yii/validators/RangeValidator.php
index a915275..90256df 100644
--- a/framework/yii/validators/RangeValidator.php
+++ b/framework/yii/validators/RangeValidator.php
@@ -35,7 +35,7 @@ class RangeValidator extends Validator
* @var boolean whether to invert the validation logic. Defaults to false. If set to true,
* the attribute value should NOT be among the list of values defined via [[range]].
**/
- public $not = false;
+ public $not = false;
/**
* Initializes the validator.
diff --git a/framework/yii/validators/RegularExpressionValidator.php b/framework/yii/validators/RegularExpressionValidator.php
index 417f2bc..72a5a74 100644
--- a/framework/yii/validators/RegularExpressionValidator.php
+++ b/framework/yii/validators/RegularExpressionValidator.php
@@ -32,7 +32,7 @@ class RegularExpressionValidator extends Validator
* the regular expression defined via [[pattern]] should NOT match the attribute value.
* @throws InvalidConfigException if the "pattern" is not a valid regular expression
**/
- public $not = false;
+ public $not = false;
/**
* Initializes the validator.
diff --git a/framework/yii/validators/StringValidator.php b/framework/yii/validators/StringValidator.php
index abe4634..e06354b 100644
--- a/framework/yii/validators/StringValidator.php
+++ b/framework/yii/validators/StringValidator.php
@@ -174,4 +174,3 @@ class StringValidator extends Validator
return 'yii.validation.string(value, messages, ' . json_encode($options) . ');';
}
}
-
diff --git a/framework/yii/validators/UrlValidator.php b/framework/yii/validators/UrlValidator.php
index 6cf12c1..18f2f45 100644
--- a/framework/yii/validators/UrlValidator.php
+++ b/framework/yii/validators/UrlValidator.php
@@ -99,7 +99,7 @@ class UrlValidator extends Validator
}
if ($this->enableIDN) {
- $value = preg_replace_callback('/:\/\/([^\/]+)/', function($matches) {
+ $value = preg_replace_callback('/:\/\/([^\/]+)/', function ($matches) {
return '://' . idn_to_ascii($matches[1]);
}, $value);
}
diff --git a/framework/yii/validators/Validator.php b/framework/yii/validators/Validator.php
index 6b103bf..2629002 100644
--- a/framework/yii/validators/Validator.php
+++ b/framework/yii/validators/Validator.php
@@ -179,7 +179,7 @@ abstract class Validator extends Component
}
foreach ($attributes as $attribute) {
$skip = $this->skipOnError && $object->hasErrors($attribute)
- || $this->skipOnEmpty && $this->isEmpty($object->$attribute);
+ || $this->skipOnEmpty && $this->isEmpty($object->$attribute);
if (!$skip) {
$this->validateAttribute($object, $attribute);
}
diff --git a/framework/yii/web/AccessRule.php b/framework/yii/web/AccessRule.php
index 3897769..9bd52ce 100644
--- a/framework/yii/web/AccessRule.php
+++ b/framework/yii/web/AccessRule.php
@@ -99,7 +99,7 @@ class AccessRule extends Component
if ($this->matchAction($action)
&& $this->matchRole($user)
&& $this->matchIP($request->getUserIP())
- && $this->matchVerb($request->getRequestMethod())
+ && $this->matchVerb($request->getMethod())
&& $this->matchController($action->controller)
&& $this->matchCustom($action)
) {
diff --git a/framework/yii/web/CaptchaAction.php b/framework/yii/web/CaptchaAction.php
index cff2314..1ed1fb0 100644
--- a/framework/yii/web/CaptchaAction.php
+++ b/framework/yii/web/CaptchaAction.php
@@ -277,11 +277,8 @@ class CaptchaAction extends Action
imagecolordeallocate($image, $foreColor);
- header('Pragma: public');
- header('Expires: 0');
- header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
- header('Content-Transfer-Encoding: binary');
- header("Content-type: image/png");
+ $this->sendHttpHeaders();
+
imagepng($image);
imagedestroy($image);
}
@@ -319,12 +316,21 @@ class CaptchaAction extends Action
$x += (int)($fontMetrics['textWidth']) + $this->offset;
}
- header('Pragma: public');
- header('Expires: 0');
- header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
- header('Content-Transfer-Encoding: binary');
- header("Content-type: image/png");
$image->setImageFormat('png');
- echo $image;
+ Yii::$app->getResponse()->content = (string)$image;
+ $this->sendHttpHeaders();
+ }
+
+ /**
+ * Sends the HTTP headers needed by image response.
+ */
+ protected function sendHttpHeaders()
+ {
+ Yii::$app->getResponse()->getHeaders()
+ ->set('Pragma', 'public')
+ ->set('Expires', '0')
+ ->set('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
+ ->set('Content-Transfer-Encoding', 'binary')
+ ->set('Content-type', 'image/png');
}
}
diff --git a/framework/yii/web/Cookie.php b/framework/yii/web/Cookie.php
index 610e5aa..8cbb412 100644
--- a/framework/yii/web/Cookie.php
+++ b/framework/yii/web/Cookie.php
@@ -45,7 +45,7 @@ class Cookie extends \yii\base\Object
* By setting this property to true, the cookie will not be accessible by scripting languages,
* such as JavaScript, which can effectively help to reduce identity theft through XSS attacks.
*/
- public $httponly = false;
+ public $httpOnly = false;
/**
* Magic method to turn a cookie object into a string without having to explicitly access [[value]].
diff --git a/framework/yii/web/CookieCollection.php b/framework/yii/web/CookieCollection.php
index 97726c7..3e22092 100644
--- a/framework/yii/web/CookieCollection.php
+++ b/framework/yii/web/CookieCollection.php
@@ -9,7 +9,8 @@ namespace yii\web;
use Yii;
use ArrayIterator;
-use yii\helpers\SecurityHelper;
+use yii\base\InvalidCallException;
+use yii\base\Object;
/**
* CookieCollection maintains the cookies available in the current request.
@@ -19,17 +20,12 @@ use yii\helpers\SecurityHelper;
* @author Qiang Xue
* @since 2.0
*/
-class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \ArrayAccess, \Countable
+class CookieCollection extends Object implements \IteratorAggregate, \ArrayAccess, \Countable
{
/**
- * @var boolean whether to enable cookie validation. By setting this property to true,
- * if a cookie is tampered on the client side, it will be ignored when received on the server side.
+ * @var boolean whether this collection is read only.
*/
- public $enableValidation = true;
- /**
- * @var string the secret key used for cookie validation. If not set, a random key will be generated and used.
- */
- public $validationKey;
+ public $readOnly = false;
/**
* @var Cookie[] the cookies in this collection (indexed by the cookie names)
@@ -38,12 +34,14 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \
/**
* Constructor.
+ * @param array $cookies the cookies that this collection initially contains. This should be
+ * an array of name-value pairs.s
* @param array $config name-value pairs that will be used to initialize the object properties
*/
- public function __construct($config = array())
+ public function __construct($cookies = array(), $config = array())
{
+ $this->_cookies = $cookies;
parent::__construct($config);
- $this->_cookies = $this->loadCookies();
}
/**
@@ -101,53 +99,66 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \
}
/**
+ * Returns whether there is a cookie with the specified name.
+ * @param string $name the cookie name
+ * @return boolean whether the named cookie exists
+ */
+ public function has($name)
+ {
+ return isset($this->_cookies[$name]);
+ }
+
+ /**
* Adds a cookie to the collection.
* If there is already a cookie with the same name in the collection, it will be removed first.
* @param Cookie $cookie the cookie to be added
+ * @throws InvalidCallException if the cookie collection is read only
*/
public function add($cookie)
{
- if (isset($this->_cookies[$cookie->name])) {
- $c = $this->_cookies[$cookie->name];
- setcookie($c->name, '', 0, $c->path, $c->domain, $c->secure, $c->httponly);
- }
-
- $value = $cookie->value;
- if ($this->enableValidation) {
- if ($this->validationKey === null) {
- $key = SecurityHelper::getSecretKey(__CLASS__ . '/' . Yii::$app->id);
- } else {
- $key = $this->validationKey;
- }
- $value = SecurityHelper::hashData(serialize($value), $key);
+ if ($this->readOnly) {
+ throw new InvalidCallException('The cookie collection is read only.');
}
-
- setcookie($cookie->name, $value, $cookie->expire, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httponly);
$this->_cookies[$cookie->name] = $cookie;
}
/**
- * Removes a cookie from the collection.
+ * Removes a cookie.
+ * If `$removeFromBrowser` is true, the cookie will be removed from the browser.
+ * In this case, a cookie with outdated expiry will be added to the collection.
* @param Cookie|string $cookie the cookie object or the name of the cookie to be removed.
+ * @param boolean $removeFromBrowser whether to remove the cookie from browser
+ * @throws InvalidCallException if the cookie collection is read only
*/
- public function remove($cookie)
+ public function remove($cookie, $removeFromBrowser = true)
{
- if (is_string($cookie) && isset($this->_cookies[$cookie])) {
- $cookie = $this->_cookies[$cookie];
+ if ($this->readOnly) {
+ throw new InvalidCallException('The cookie collection is read only.');
}
if ($cookie instanceof Cookie) {
- setcookie($cookie->name, '', 0, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httponly);
+ $cookie->expire = 1;
+ $cookie->value = '';
+ } else {
+ $cookie = new Cookie(array(
+ 'name' => $cookie,
+ 'expire' => 1,
+ ));
+ }
+ if ($removeFromBrowser) {
+ $this->_cookies[$cookie->name] = $cookie;
+ } else {
unset($this->_cookies[$cookie->name]);
}
}
/**
* Removes all cookies.
+ * @throws InvalidCallException if the cookie collection is read only
*/
public function removeAll()
{
- foreach ($this->_cookies as $cookie) {
- setcookie($cookie->name, '', 0, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httponly);
+ if ($this->readOnly) {
+ throw new InvalidCallException('The cookie collection is read only.');
}
$this->_cookies = array();
}
@@ -172,7 +183,7 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \
*/
public function offsetExists($name)
{
- return isset($this->_cookies[$name]);
+ return $this->has($name);
}
/**
@@ -212,36 +223,4 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \
{
$this->remove($name);
}
-
- /**
- * Returns the current cookies in terms of [[Cookie]] objects.
- * @return Cookie[] list of current cookies
- */
- protected function loadCookies()
- {
- $cookies = array();
- if ($this->enableValidation) {
- if ($this->validationKey === null) {
- $key = SecurityHelper::getSecretKey(__CLASS__ . '/' . Yii::$app->id);
- } else {
- $key = $this->validationKey;
- }
- foreach ($_COOKIE as $name => $value) {
- if (is_string($value) && ($value = SecurityHelper::validateData($value, $key)) !== false) {
- $cookies[$name] = new Cookie(array(
- 'name' => $name,
- 'value' => @unserialize($value),
- ));
- }
- }
- } else {
- foreach ($_COOKIE as $name => $value) {
- $cookies[$name] = new Cookie(array(
- 'name' => $name,
- 'value' => $value,
- ));
- }
- }
- return $cookies;
- }
}
diff --git a/framework/yii/web/HeaderCollection.php b/framework/yii/web/HeaderCollection.php
new file mode 100644
index 0000000..ed9ec6f
--- /dev/null
+++ b/framework/yii/web/HeaderCollection.php
@@ -0,0 +1,201 @@
+
+ * @since 2.0
+ */
+class HeaderCollection extends Object implements \IteratorAggregate, \ArrayAccess, \Countable
+{
+ /**
+ * @var array the headers in this collection (indexed by the header names)
+ */
+ private $_headers = array();
+
+ /**
+ * Returns an iterator for traversing the headers in the collection.
+ * This method is required by the SPL interface `IteratorAggregate`.
+ * It will be implicitly called when you use `foreach` to traverse the collection.
+ * @return ArrayIterator an iterator for traversing the headers in the collection.
+ */
+ public function getIterator()
+ {
+ return new ArrayIterator($this->_headers);
+ }
+
+ /**
+ * Returns the number of headers in the collection.
+ * This method is required by the SPL `Countable` interface.
+ * It will be implicitly called when you use `count($collection)`.
+ * @return integer the number of headers in the collection.
+ */
+ public function count()
+ {
+ return $this->getCount();
+ }
+
+ /**
+ * Returns the number of headers in the collection.
+ * @return integer the number of headers in the collection.
+ */
+ public function getCount()
+ {
+ return count($this->_headers);
+ }
+
+ /**
+ * Returns the named header(s).
+ * @param string $name the name of the header to return
+ * @param mixed $default the value to return in case the named header does not exist
+ * @param boolean $first whether to only return the first header of the specified name.
+ * If false, all headers of the specified name will be returned.
+ * @return string|array the named header(s). If `$first` is true, a string will be returned;
+ * If `$first` is false, an array will be returned.
+ */
+ public function get($name, $default = null, $first = true)
+ {
+ $name = strtolower($name);
+ if (isset($this->_headers[$name])) {
+ return $first ? reset($this->_headers[$name]) : $this->_headers[$name];
+ } else {
+ return $default;
+ }
+ }
+
+ /**
+ * Adds a new header.
+ * If there is already a header with the same name, it will be replaced.
+ * @param string $name the name of the header
+ * @param string $value the value of the header
+ * @return HeaderCollection the collection object itself
+ */
+ public function set($name, $value = '')
+ {
+ $name = strtolower($name);
+ $this->_headers[$name] = (array)$value;
+ return $this;
+ }
+
+ /**
+ * Adds a new header.
+ * If there is already a header with the same name, the new one will
+ * be appended to it instead of replacing it.
+ * @param string $name the name of the header
+ * @param string $value the value of the header
+ * @return HeaderCollection the collection object itself
+ */
+ public function add($name, $value)
+ {
+ $name = strtolower($name);
+ $this->_headers[$name][] = $value;
+ return $this;
+ }
+
+ /**
+ * Returns a value indicating whether the named header exists.
+ * @param string $name the name of the header
+ * @return boolean whether the named header exists
+ */
+ public function has($name)
+ {
+ $name = strtolower($name);
+ return isset($this->_headers[$name]);
+ }
+
+ /**
+ * Removes a header.
+ * @param string $name the name of the header to be removed.
+ * @return string the value of the removed header. Null is returned if the header does not exist.
+ */
+ public function remove($name)
+ {
+ $name = strtolower($name);
+ if (isset($this->_headers[$name])) {
+ $value = $this->_headers[$name];
+ unset($this->_headers[$name]);
+ return $value;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Removes all headers.
+ */
+ public function removeAll()
+ {
+ $this->_headers = array();
+ }
+
+ /**
+ * Returns the collection as a PHP array.
+ * @return array the array representation of the collection.
+ * The array keys are header names, and the array values are the corresponding header values.
+ */
+ public function toArray()
+ {
+ return $this->_headers;
+ }
+
+ /**
+ * Returns whether there is a header with the specified name.
+ * This method is required by the SPL interface `ArrayAccess`.
+ * It is implicitly called when you use something like `isset($collection[$name])`.
+ * @param string $name the header name
+ * @return boolean whether the named header exists
+ */
+ public function offsetExists($name)
+ {
+ return $this->has($name);
+ }
+
+ /**
+ * Returns the header with the specified name.
+ * This method is required by the SPL interface `ArrayAccess`.
+ * It is implicitly called when you use something like `$header = $collection[$name];`.
+ * This is equivalent to [[get()]].
+ * @param string $name the header name
+ * @return string the header value with the specified name, null if the named header does not exist.
+ */
+ public function offsetGet($name)
+ {
+ return $this->get($name);
+ }
+
+ /**
+ * Adds the header to the collection.
+ * This method is required by the SPL interface `ArrayAccess`.
+ * It is implicitly called when you use something like `$collection[$name] = $header;`.
+ * This is equivalent to [[add()]].
+ * @param string $name the header name
+ * @param string $value the header value to be added
+ */
+ public function offsetSet($name, $value)
+ {
+ $this->set($name, $value);
+ }
+
+ /**
+ * Removes the named header.
+ * This method is required by the SPL interface `ArrayAccess`.
+ * It is implicitly called when you use something like `unset($collection[$name])`.
+ * This is equivalent to [[remove()]].
+ * @param string $name the header name
+ */
+ public function offsetUnset($name)
+ {
+ $this->remove($name);
+ }
+}
diff --git a/framework/yii/web/HttpCache.php b/framework/yii/web/HttpCache.php
index 0a3bb86..cc9e6ed 100644
--- a/framework/yii/web/HttpCache.php
+++ b/framework/yii/web/HttpCache.php
@@ -50,7 +50,7 @@ class HttpCache extends ActionFilter
/**
* @var string HTTP cache control header. If null, the header will not be sent.
*/
- public $cacheControlHeader = 'Cache-Control: max-age=3600, public';
+ public $cacheControlHeader = 'max-age=3600, public';
/**
* This method is invoked right before an action is to be executed (after all possible filters.)
@@ -60,7 +60,7 @@ class HttpCache extends ActionFilter
*/
public function beforeAction($action)
{
- $verb = Yii::$app->request->getRequestMethod();
+ $verb = Yii::$app->getRequest()->getMethod();
if ($verb !== 'GET' && $verb !== 'HEAD' || $this->lastModified === null && $this->etagSeed === null) {
return true;
}
@@ -75,17 +75,18 @@ class HttpCache extends ActionFilter
}
$this->sendCacheControlHeader();
+ $response = Yii::$app->getResponse();
if ($etag !== null) {
- header("ETag: $etag");
+ $response->getHeaders()->set('Etag', $etag);
}
if ($this->validateCache($lastModified, $etag)) {
- header('HTTP/1.1 304 Not Modified');
+ $response->setStatusCode(304);
return false;
}
if ($lastModified !== null) {
- header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $lastModified) . ' GMT');
+ $response->getHeaders()->set('Last-Modified', gmdate('D, d M Y H:i:s', $lastModified) . ' GMT');
}
return true;
}
@@ -113,9 +114,10 @@ class HttpCache extends ActionFilter
protected function sendCacheControlHeader()
{
session_cache_limiter('public');
- header('Pragma:', true);
+ $headers = Yii::$app->getResponse()->getHeaders();
+ $headers->set('Pragma');
if ($this->cacheControlHeader !== null) {
- header($this->cacheControlHeader, true);
+ $headers->set('Cache-Control', $this->cacheControlHeader);
}
}
diff --git a/framework/yii/web/PageCache.php b/framework/yii/web/PageCache.php
index 2fe36b3..8b28e62 100644
--- a/framework/yii/web/PageCache.php
+++ b/framework/yii/web/PageCache.php
@@ -1,104 +1,104 @@
-
- * @since 2.0
- */
-class PageCache extends ActionFilter
-{
- /**
- * @var boolean whether the content being cached should be differentiated according to the route.
- * A route consists of the requested controller ID and action ID. Defaults to true.
- */
- public $varyByRoute = true;
- /**
- * @var string the application component ID of the [[\yii\caching\Cache|cache]] object.
- */
- public $cache = 'cache';
- /**
- * @var integer number of seconds that the data can remain valid in cache.
- * Use 0 to indicate that the cached data will never expire.
- */
- public $duration = 60;
- /**
- * @var array|Dependency the dependency that the cached content depends on.
- * This can be either a [[Dependency]] object or a configuration array for creating the dependency object.
- * For example,
- *
- * ~~~
- * array(
- * 'class' => 'yii\caching\DbDependency',
- * 'sql' => 'SELECT MAX(lastModified) FROM Post',
- * )
- * ~~~
- *
- * would make the output cache depends on the last modified time of all posts.
- * If any post has its modification time changed, the cached content would be invalidated.
- */
- public $dependency;
- /**
- * @var array list of factors that would cause the variation of the content being cached.
- * Each factor is a string representing a variation (e.g. the language, a GET parameter).
- * The following variation setting will cause the content to be cached in different versions
- * according to the current application language:
- *
- * ~~~
- * array(
- * Yii::$app->language,
- * )
- */
- public $variations;
- /**
- * @var boolean whether to enable the fragment cache. You may use this property to turn on and off
- * the fragment cache according to specific setting (e.g. enable fragment cache only for GET requests).
- */
- public $enabled = true;
-
-
- public function init()
- {
- parent::init();
- if ($this->view === null) {
- $this->view = Yii::$app->getView();
- }
- }
-
- /**
- * This method is invoked right before an action is to be executed (after all possible filters.)
- * You may override this method to do last-minute preparation for the action.
- * @param Action $action the action to be executed.
- * @return boolean whether the action should continue to be executed.
- */
- public function beforeAction($action)
- {
- $properties = array();
- foreach (array('cache', 'duration', 'dependency', 'variations', 'enabled') as $name) {
- $properties[$name] = $this->$name;
- }
- $id = $this->varyByRoute ? $action->getUniqueId() : __CLASS__;
- return $this->view->beginCache($id, $properties);
- }
-
- /**
- * This method is invoked right after an action is executed.
- * You may override this method to do some postprocessing for the action.
- * @param Action $action the action just executed.
- */
- public function afterAction($action)
- {
- $this->view->endCache();
- }
+
+ * @since 2.0
+ */
+class PageCache extends ActionFilter
+{
+ /**
+ * @var boolean whether the content being cached should be differentiated according to the route.
+ * A route consists of the requested controller ID and action ID. Defaults to true.
+ */
+ public $varyByRoute = true;
+ /**
+ * @var string the application component ID of the [[\yii\caching\Cache|cache]] object.
+ */
+ public $cache = 'cache';
+ /**
+ * @var integer number of seconds that the data can remain valid in cache.
+ * Use 0 to indicate that the cached data will never expire.
+ */
+ public $duration = 60;
+ /**
+ * @var array|Dependency the dependency that the cached content depends on.
+ * This can be either a [[Dependency]] object or a configuration array for creating the dependency object.
+ * For example,
+ *
+ * ~~~
+ * array(
+ * 'class' => 'yii\caching\DbDependency',
+ * 'sql' => 'SELECT MAX(lastModified) FROM Post',
+ * )
+ * ~~~
+ *
+ * would make the output cache depends on the last modified time of all posts.
+ * If any post has its modification time changed, the cached content would be invalidated.
+ */
+ public $dependency;
+ /**
+ * @var array list of factors that would cause the variation of the content being cached.
+ * Each factor is a string representing a variation (e.g. the language, a GET parameter).
+ * The following variation setting will cause the content to be cached in different versions
+ * according to the current application language:
+ *
+ * ~~~
+ * array(
+ * Yii::$app->language,
+ * )
+ */
+ public $variations;
+ /**
+ * @var boolean whether to enable the fragment cache. You may use this property to turn on and off
+ * the fragment cache according to specific setting (e.g. enable fragment cache only for GET requests).
+ */
+ public $enabled = true;
+
+
+ public function init()
+ {
+ parent::init();
+ if ($this->view === null) {
+ $this->view = Yii::$app->getView();
+ }
+ }
+
+ /**
+ * This method is invoked right before an action is to be executed (after all possible filters.)
+ * You may override this method to do last-minute preparation for the action.
+ * @param Action $action the action to be executed.
+ * @return boolean whether the action should continue to be executed.
+ */
+ public function beforeAction($action)
+ {
+ $properties = array();
+ foreach (array('cache', 'duration', 'dependency', 'variations', 'enabled') as $name) {
+ $properties[$name] = $this->$name;
+ }
+ $id = $this->varyByRoute ? $action->getUniqueId() : __CLASS__;
+ return $this->view->beginCache($id, $properties);
+ }
+
+ /**
+ * This method is invoked right after an action is executed.
+ * You may override this method to do some postprocessing for the action.
+ * @param Action $action the action just executed.
+ */
+ public function afterAction($action)
+ {
+ $this->view->endCache();
+ }
}
diff --git a/framework/yii/web/Request.php b/framework/yii/web/Request.php
index a857926..6f5cdb5 100644
--- a/framework/yii/web/Request.php
+++ b/framework/yii/web/Request.php
@@ -10,6 +10,7 @@ namespace yii\web;
use Yii;
use yii\base\HttpException;
use yii\base\InvalidConfigException;
+use yii\helpers\SecurityHelper;
/**
* @author Qiang Xue
@@ -37,19 +38,15 @@ class Request extends \yii\base\Request
* @var array the configuration of the CSRF cookie. This property is used only when [[enableCsrfValidation]] is true.
* @see Cookie
*/
- public $csrfCookie = array('httponly' => true);
+ public $csrfCookie = array('httpOnly' => true);
/**
* @var boolean whether cookies should be validated to ensure they are not tampered. Defaults to true.
*/
public $enableCookieValidation = true;
/**
- * @var string the secret key used for cookie validation. If not set, a random key will be generated and used.
- */
- public $cookieValidationKey;
- /**
* @var string|boolean the name of the POST parameter that is used to indicate if a request is a PUT or DELETE
* request tunneled through POST. Default to '_method'.
- * @see getRequestMethod
+ * @see getMethod
* @see getRestParams
*/
public $restVar = '_method';
@@ -81,7 +78,7 @@ class Request extends \yii\base\Request
* @return string request method, such as GET, POST, HEAD, PUT, DELETE.
* The value returned is turned into upper case.
*/
- public function getRequestMethod()
+ public function getMethod()
{
if (isset($_POST[$this->restVar])) {
return strtoupper($_POST[$this->restVar]);
@@ -94,34 +91,34 @@ class Request extends \yii\base\Request
* Returns whether this is a POST request.
* @return boolean whether this is a POST request.
*/
- public function getIsPostRequest()
+ public function getIsPost()
{
- return $this->getRequestMethod() === 'POST';
+ return $this->getMethod() === 'POST';
}
/**
* Returns whether this is a DELETE request.
* @return boolean whether this is a DELETE request.
*/
- public function getIsDeleteRequest()
+ public function getIsDelete()
{
- return $this->getRequestMethod() === 'DELETE';
+ return $this->getMethod() === 'DELETE';
}
/**
* Returns whether this is a PUT request.
* @return boolean whether this is a PUT request.
*/
- public function getIsPutRequest()
+ public function getIsPut()
{
- return $this->getRequestMethod() === 'PUT';
+ return $this->getMethod() === 'PUT';
}
/**
* Returns whether this is an AJAX (XMLHttpRequest) request.
* @return boolean whether this is an AJAX (XMLHttpRequest) request.
*/
- public function getIsAjaxRequest()
+ public function getIsAjax()
{
return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest';
}
@@ -130,7 +127,7 @@ class Request extends \yii\base\Request
* Returns whether this is an Adobe Flash or Flex request.
* @return boolean whether this is an Adobe Flash or Adobe Flex request.
*/
- public function getIsFlashRequest()
+ public function getIsFlash()
{
return isset($_SERVER['HTTP_USER_AGENT']) &&
(stripos($_SERVER['HTTP_USER_AGENT'], 'Shockwave') !== false || stripos($_SERVER['HTTP_USER_AGENT'], 'Flash') !== false);
@@ -141,7 +138,7 @@ class Request extends \yii\base\Request
/**
* Returns the request parameters for the RESTful request.
* @return array the RESTful request parameters
- * @see getRequestMethod
+ * @see getMethod
*/
public function getRestParams()
{
@@ -203,7 +200,7 @@ class Request extends \yii\base\Request
* @return mixed the GET parameter value
* @see getPost
*/
- public function getParam($name, $defaultValue = null)
+ public function get($name, $defaultValue = null)
{
return isset($_GET[$name]) ? $_GET[$name] : $defaultValue;
}
@@ -229,7 +226,7 @@ class Request extends \yii\base\Request
*/
public function getDelete($name, $defaultValue = null)
{
- return $this->getIsDeleteRequest() ? $this->getRestParam($name, $defaultValue) : null;
+ return $this->getIsDelete() ? $this->getRestParam($name, $defaultValue) : null;
}
/**
@@ -240,7 +237,7 @@ class Request extends \yii\base\Request
*/
public function getPut($name, $defaultValue = null)
{
- return $this->getIsPutRequest() ? $this->getRestParam($name, $defaultValue) : null;
+ return $this->getIsPut() ? $this->getRestParam($name, $defaultValue) : null;
}
private $_hostInfo;
@@ -717,14 +714,64 @@ class Request extends \yii\base\Request
public function getCookies()
{
if ($this->_cookies === null) {
- $this->_cookies = new CookieCollection(array(
- 'enableValidation' => $this->enableCookieValidation,
- 'validationKey' => $this->cookieValidationKey,
+ $this->_cookies = new CookieCollection($this->loadCookies(), array(
+ 'readOnly' => true,
));
}
return $this->_cookies;
}
+ /**
+ * Converts `$_COOKIE` into an array of [[Cookie]].
+ * @return array the cookies obtained from request
+ */
+ protected function loadCookies()
+ {
+ $cookies = array();
+ if ($this->enableCookieValidation) {
+ $key = $this->getCookieValidationKey();
+ foreach ($_COOKIE as $name => $value) {
+ if (is_string($value) && ($value = SecurityHelper::validateData($value, $key)) !== false) {
+ $cookies[$name] = new Cookie(array(
+ 'name' => $name,
+ 'value' => @unserialize($value),
+ ));
+ }
+ }
+ } else {
+ foreach ($_COOKIE as $name => $value) {
+ $cookies[$name] = new Cookie(array(
+ 'name' => $name,
+ 'value' => $value,
+ ));
+ }
+ }
+ return $cookies;
+ }
+
+ private $_cookieValidationKey;
+
+ /**
+ * @return string the secret key used for cookie validation. If it was set previously,
+ * a random key will be generated and used.
+ */
+ public function getCookieValidationKey()
+ {
+ if ($this->_cookieValidationKey === null) {
+ $this->_cookieValidationKey = SecurityHelper::getSecretKey(__CLASS__ . '/' . Yii::$app->id);
+ }
+ return $this->_cookieValidationKey;
+ }
+
+ /**
+ * Sets the secret key used for cookie validation.
+ * @param string $value the secret key used for cookie validation.
+ */
+ public function setCookieValidationKey($value)
+ {
+ $this->_cookieValidationKey = $value;
+ }
+
private $_csrfToken;
/**
@@ -772,7 +819,7 @@ class Request extends \yii\base\Request
if (!$this->enableCsrfValidation) {
return;
}
- $method = $this->getRequestMethod();
+ $method = $this->getMethod();
if ($method === 'POST' || $method === 'PUT' || $method === 'DELETE') {
$cookies = $this->getCookies();
switch ($method) {
@@ -792,4 +839,3 @@ class Request extends \yii\base\Request
}
}
}
-
diff --git a/framework/yii/web/Response.php b/framework/yii/web/Response.php
index d37c66a..f337fbb 100644
--- a/framework/yii/web/Response.php
+++ b/framework/yii/web/Response.php
@@ -9,8 +9,11 @@ namespace yii\web;
use Yii;
use yii\base\HttpException;
+use yii\base\InvalidParamException;
use yii\helpers\FileHelper;
use yii\helpers\Html;
+use yii\helpers\Json;
+use yii\helpers\SecurityHelper;
use yii\helpers\StringHelper;
/**
@@ -27,6 +30,237 @@ class Response extends \yii\base\Response
* @see redirect
*/
public $ajaxRedirectCode = 278;
+ /**
+ * @var string
+ */
+ public $content;
+ /**
+ * @var string
+ */
+ public $statusText;
+ /**
+ * @var string the charset to use. If not set, [[\yii\base\Application::charset]] will be used.
+ */
+ public $charset;
+ /**
+ * @var string the version of the HTTP protocol to use
+ */
+ public $version = '1.0';
+
+ /**
+ * @var array list of HTTP status codes and the corresponding texts
+ */
+ public static $statusTexts = array(
+ 100 => 'Continue',
+ 101 => 'Switching Protocols',
+ 102 => 'Processing',
+ 118 => 'Connection timed out',
+ 200 => 'OK',
+ 201 => 'Created',
+ 202 => 'Accepted',
+ 203 => 'Non-Authoritative',
+ 204 => 'No Content',
+ 205 => 'Reset Content',
+ 206 => 'Partial Content',
+ 207 => 'Multi-Status',
+ 208 => 'Already Reported',
+ 210 => 'Content Different',
+ 226 => 'IM Used',
+ 300 => 'Multiple Choices',
+ 301 => 'Moved Permanently',
+ 302 => 'Found',
+ 303 => 'See Other',
+ 304 => 'Not Modified',
+ 305 => 'Use Proxy',
+ 306 => 'Reserved',
+ 307 => 'Temporary Redirect',
+ 308 => 'Permanent Redirect',
+ 310 => 'Too many Redirect',
+ 400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 402 => 'Payment Required',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Time-out',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Request Entity Too Large',
+ 414 => 'Request-URI Too Long',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Requested range unsatisfiable',
+ 417 => 'Expectation failed',
+ 418 => 'I’m a teapot',
+ 422 => 'Unprocessable entity',
+ 423 => 'Locked',
+ 424 => 'Method failure',
+ 425 => 'Unordered Collection',
+ 426 => 'Upgrade Required',
+ 428 => 'Precondition Required',
+ 429 => 'Too Many Requests',
+ 431 => 'Request Header Fields Too Large',
+ 449 => 'Retry With',
+ 450 => 'Blocked by Windows Parental Controls',
+ 500 => 'Internal Server Error',
+ 501 => 'Not Implemented',
+ 502 => 'Bad Gateway ou Proxy Error',
+ 503 => 'Service Unavailable',
+ 504 => 'Gateway Time-out',
+ 505 => 'HTTP Version not supported',
+ 507 => 'Insufficient storage',
+ 508 => 'Loop Detected',
+ 509 => 'Bandwidth Limit Exceeded',
+ 510 => 'Not Extended',
+ 511 => 'Network Authentication Required',
+ );
+
+ private $_statusCode;
+ /**
+ * @var HeaderCollection
+ */
+ private $_headers;
+
+
+ public function init()
+ {
+ if ($this->charset === null) {
+ $this->charset = Yii::$app->charset;
+ }
+ }
+
+ public function begin()
+ {
+ parent::begin();
+ $this->beginOutput();
+ }
+
+ public function end()
+ {
+ $this->content .= $this->endOutput();
+ $this->send();
+ parent::end();
+ }
+
+ public function getStatusCode()
+ {
+ return $this->_statusCode;
+ }
+
+ public function setStatusCode($value, $text = null)
+ {
+ $this->_statusCode = (int)$value;
+ if ($this->isInvalid()) {
+ throw new InvalidParamException("The HTTP status code is invalid: $value");
+ }
+ if ($text === null) {
+ $this->statusText = isset(self::$statusTexts[$this->_statusCode]) ? self::$statusTexts[$this->_statusCode] : '';
+ } else {
+ $this->statusText = $text;
+ }
+ }
+
+ /**
+ * Returns the header collection.
+ * The header collection contains the currently registered HTTP headers.
+ * @return HeaderCollection the header collection
+ */
+ public function getHeaders()
+ {
+ if ($this->_headers === null) {
+ $this->_headers = new HeaderCollection;
+ }
+ return $this->_headers;
+ }
+
+ public function renderJson($data)
+ {
+ $this->getHeaders()->set('content-type', 'application/json');
+ $this->content = Json::encode($data);
+ }
+
+ public function renderJsonp($data, $callbackName)
+ {
+ $this->getHeaders()->set('content-type', 'text/javascript');
+ $data = Json::encode($data);
+ $this->content = "$callbackName($data);";
+ }
+
+ /**
+ * Sends the response to the client.
+ * @return boolean true if the response was sent
+ */
+ public function send()
+ {
+ $this->sendHeaders();
+ $this->sendContent();
+ }
+
+ public function reset()
+ {
+ $this->_headers = null;
+ $this->_statusCode = null;
+ $this->statusText = null;
+ $this->content = null;
+ }
+
+ /**
+ * Sends the response headers to the client
+ */
+ protected function sendHeaders()
+ {
+ if (headers_sent()) {
+ return;
+ }
+ $statusCode = $this->getStatusCode();
+ if ($statusCode !== null) {
+ header("HTTP/{$this->version} $statusCode {$this->statusText}");
+ }
+ if ($this->_headers) {
+ $headers = $this->getHeaders();
+ foreach ($headers as $name => $values) {
+ foreach ($values as $value) {
+ header("$name: $value", false);
+ }
+ }
+ $headers->removeAll();
+ }
+ $this->sendCookies();
+ }
+
+ /**
+ * Sends the cookies to the client.
+ */
+ protected function sendCookies()
+ {
+ if ($this->_cookies === null) {
+ return;
+ }
+ $request = Yii::$app->getRequest();
+ if ($request->enableCookieValidation) {
+ $validationKey = $request->getCookieValidationKey();
+ }
+ foreach ($this->getCookies() as $cookie) {
+ $value = $cookie->value;
+ if ($cookie->expire != 1 && isset($validationKey)) {
+ $value = SecurityHelper::hashData(serialize($value), $validationKey);
+ }
+ setcookie($cookie->name, $value, $cookie->expire, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httpOnly);
+ }
+ $this->getCookies()->removeAll();
+ }
+
+ /**
+ * Sends the response content to the client
+ */
+ protected function sendContent()
+ {
+ echo $this->content;
+ $this->content = null;
+ }
/**
* Sends a file to user.
@@ -46,13 +280,15 @@ class Response extends \yii\base\Response
$contentStart = 0;
$contentEnd = $fileSize - 1;
+ $headers = $this->getHeaders();
+
// tell the client that we accept range requests
- header('Accept-Ranges: bytes');
+ $headers->set('Accept-Ranges', 'bytes');
if (isset($_SERVER['HTTP_RANGE'])) {
// client sent us a multibyte range, can not hold this one for now
- if (strpos($_SERVER['HTTP_RANGE'],',') !== false) {
- header("Content-Range: bytes $contentStart-$contentEnd/$fileSize");
+ if (strpos($_SERVER['HTTP_RANGE'], ',') !== false) {
+ $headers->set('Content-Range', "bytes $contentStart-$contentEnd/$fileSize");
throw new HttpException(416, 'Requested Range Not Satisfiable');
}
@@ -75,31 +311,32 @@ class Response extends \yii\base\Response
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
*/
// End bytes can not be larger than $end.
- $contentEnd = ($contentEnd > $fileSize) ? $fileSize -1 : $contentEnd;
+ $contentEnd = ($contentEnd > $fileSize) ? $fileSize - 1 : $contentEnd;
// Validate the requested range and return an error if it's not correct.
$wrongContentStart = ($contentStart > $contentEnd || $contentStart > $fileSize - 1 || $contentStart < 0);
- if ($wrongContentStart) {
- header("Content-Range: bytes $contentStart-$contentEnd/$fileSize");
+ if ($wrongContentStart) {
+ $headers->set('Content-Range', "bytes $contentStart-$contentEnd/$fileSize");
throw new HttpException(416, 'Requested Range Not Satisfiable');
}
- header('HTTP/1.1 206 Partial Content');
- header("Content-Range: bytes $contentStart-$contentEnd/$fileSize");
+ $this->setStatusCode(206);
+ $headers->set('Content-Range', "bytes $contentStart-$contentEnd/$fileSize");
} else {
- header('HTTP/1.1 200 OK');
+ $this->setStatusCode(200);
}
$length = $contentEnd - $contentStart + 1; // Calculate new content length
- header('Pragma: public');
- header('Expires: 0');
- header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
- header('Content-Type: ' . $mimeType);
- header('Content-Length: ' . $length);
- header('Content-Disposition: attachment; filename="' . $fileName . '"');
- header('Content-Transfer-Encoding: binary');
+ $headers->set('Pragma', 'public')
+ ->set('Expires', '0')
+ ->set('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
+ ->set('Content-Type', $mimeType)
+ ->set('Content-Length', $length)
+ ->set('Content-Disposition', "attachment; filename=\"$fileName\"")
+ ->set('Content-Transfer-Encoding', 'binary');
+
$content = StringHelper::substr($content, $contentStart, $length);
if ($terminate) {
@@ -108,10 +345,10 @@ class Response extends \yii\base\Response
ob_start();
Yii::$app->end(0, false);
ob_end_clean();
- echo $content;
+ $this->content = $content;
exit(0);
} else {
- echo $content;
+ $this->content = $content;
}
}
@@ -186,7 +423,7 @@ class Response extends \yii\base\Response
}
if (!isset($options['mimeType'])) {
- if (($options['mimeType'] = CFileHelper::getMimeTypeByExtension($filePath)) === null) {
+ if (($options['mimeType'] = FileHelper::getMimeTypeByExtension($filePath)) === null) {
$options['mimeType'] = 'text/plain';
}
}
@@ -195,16 +432,18 @@ class Response extends \yii\base\Response
$options['xHeader'] = 'X-Sendfile';
}
+ $headers = $this->getHeaders();
+
if ($options['mimeType'] !== null) {
- header('Content-type: ' . $options['mimeType']);
+ $headers->set('Content-Type', $options['mimeType']);
}
- header('Content-Disposition: ' . $disposition . '; filename="' . $options['saveName'] . '"');
+ $headers->set('Content-Disposition', "$disposition; filename=\"{$options['saveName']}\"");
if (isset($options['addHeaders'])) {
foreach ($options['addHeaders'] as $header => $value) {
- header($header . ': ' . $value);
+ $headers->set($header, $value);
}
}
- header(trim($options['xHeader']) . ': ' . $filePath);
+ $headers->set(trim($options['xHeader']), $filePath);
if (!isset($options['terminate']) || $options['terminate']) {
Yii::$app->end();
@@ -243,10 +482,11 @@ class Response extends \yii\base\Response
if (strpos($url, '/') === 0 && strpos($url, '//') !== 0) {
$url = Yii::$app->getRequest()->getHostInfo() . $url;
}
- if (Yii::$app->getRequest()->getIsAjaxRequest()) {
+ if (Yii::$app->getRequest()->getIsAjax()) {
$statusCode = $this->ajaxRedirectCode;
}
- header('Location: ' . $url, true, $statusCode);
+ $this->getHeaders()->set('Location', $url);
+ $this->setStatusCode($statusCode);
if ($terminate) {
Yii::$app->end();
}
@@ -265,6 +505,8 @@ class Response extends \yii\base\Response
$this->redirect(Yii::$app->getRequest()->getUrl() . $anchor, $terminate);
}
+ private $_cookies;
+
/**
* Returns the cookie collection.
* Through the returned cookie collection, you add or remove cookies as follows,
@@ -286,6 +528,89 @@ class Response extends \yii\base\Response
*/
public function getCookies()
{
- return Yii::$app->getRequest()->getCookies();
+ if ($this->_cookies === null) {
+ $this->_cookies = new CookieCollection;
+ }
+ return $this->_cookies;
+ }
+
+ /**
+ * @return boolean whether this response has a valid [[statusCode]].
+ */
+ public function isInvalid()
+ {
+ return $this->getStatusCode() < 100 || $this->getStatusCode() >= 600;
+ }
+
+ /**
+ * @return boolean whether this response is informational
+ */
+ public function isInformational()
+ {
+ return $this->getStatusCode() >= 100 && $this->getStatusCode() < 200;
+ }
+
+ /**
+ * @return boolean whether this response is successfully
+ */
+ public function isSuccessful()
+ {
+ return $this->getStatusCode() >= 200 && $this->getStatusCode() < 300;
+ }
+
+ /**
+ * @return boolean whether this response is a redirection
+ */
+ public function isRedirection()
+ {
+ return $this->getStatusCode() >= 300 && $this->getStatusCode() < 400;
+ }
+
+ /**
+ * @return boolean whether this response indicates a client error
+ */
+ public function isClientError()
+ {
+ return $this->getStatusCode() >= 400 && $this->getStatusCode() < 500;
+ }
+
+ /**
+ * @return boolean whether this response indicates a server error
+ */
+ public function isServerError()
+ {
+ return $this->getStatusCode() >= 500 && $this->getStatusCode() < 600;
+ }
+
+ /**
+ * @return boolean whether this response is OK
+ */
+ public function isOk()
+ {
+ return 200 === $this->getStatusCode();
+ }
+
+ /**
+ * @return boolean whether this response indicates the current request is forbidden
+ */
+ public function isForbidden()
+ {
+ return 403 === $this->getStatusCode();
+ }
+
+ /**
+ * @return boolean whether this response indicates the currently requested resource is not found
+ */
+ public function isNotFound()
+ {
+ return 404 === $this->getStatusCode();
+ }
+
+ /**
+ * @return boolean whether this response is empty
+ */
+ public function isEmpty()
+ {
+ return in_array($this->getStatusCode(), array(201, 204, 304));
}
}
diff --git a/framework/yii/web/Session.php b/framework/yii/web/Session.php
index 1b48433..cf1fa21 100644
--- a/framework/yii/web/Session.php
+++ b/framework/yii/web/Session.php
@@ -63,7 +63,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
* @var array parameter-value pairs to override default session cookie parameters
*/
public $cookieParams = array(
- 'httponly' => true
+ 'httpOnly' => true
);
/**
@@ -241,26 +241,31 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
*/
public function getCookieParams()
{
- return session_get_cookie_params();
+ $params = session_get_cookie_params();
+ if (isset($params['httponly'])) {
+ $params['httpOnly'] = $params['httponly'];
+ unset($params['httponly']);
+ }
+ return $params;
}
/**
* Sets the session cookie parameters.
* The effect of this method only lasts for the duration of the script.
* Call this method before the session starts.
- * @param array $value cookie parameters, valid keys include: lifetime, path, domain, secure and httponly.
+ * @param array $value cookie parameters, valid keys include: `lifetime`, `path`, `domain`, `secure` and `httpOnly`.
* @throws InvalidParamException if the parameters are incomplete.
* @see http://us2.php.net/manual/en/function.session-set-cookie-params.php
*/
public function setCookieParams($value)
{
- $data = session_get_cookie_params();
+ $data = $this->getCookieParams();
extract($data);
extract($value);
- if (isset($lifetime, $path, $domain, $secure, $httponly)) {
- session_set_cookie_params($lifetime, $path, $domain, $secure, $httponly);
+ if (isset($lifetime, $path, $domain, $secure, $httpOnly)) {
+ session_set_cookie_params($lifetime, $path, $domain, $secure, $httpOnly);
} else {
- throw new InvalidParamException('Please make sure these parameters are provided: lifetime, path, domain, secure and httponly.');
+ throw new InvalidParamException('Please make sure these parameters are provided: lifetime, path, domain, secure and httpOnly.');
}
}
diff --git a/framework/yii/web/UploadedFile.php b/framework/yii/web/UploadedFile.php
index 6e685a3..a1cd735 100644
--- a/framework/yii/web/UploadedFile.php
+++ b/framework/yii/web/UploadedFile.php
@@ -7,7 +7,7 @@
namespace yii\web;
-use yii\widgets\Html;
+use yii\helpers\Html;
/**
* @author Qiang Xue
diff --git a/framework/yii/web/UrlManager.php b/framework/yii/web/UrlManager.php
index 47f5c5d..4478a6c 100644
--- a/framework/yii/web/UrlManager.php
+++ b/framework/yii/web/UrlManager.php
@@ -43,6 +43,31 @@ class UrlManager extends Component
* array, one can use the key to represent the pattern and the value the corresponding route.
* For example, `'post/' => 'post/view'`.
*
+ * For RESTful routing the mentioned shortcut format also allows you to specify the
+ * [[UrlRule::verb|HTTP verb]] that the rule should apply for.
+ * You can do that by prepending it to the pattern, separated by space.
+ * For example, `'PUT post/' => 'post/update'`.
+ * You may specify multiple verbs by separating them with comma
+ * like this: `'POST,PUT post/index' => 'post/create'`.
+ * The supported verbs in the shortcut format are: GET, HEAD, POST, PUT and DELETE.
+ * Note that [[UrlRule::mode|mode]] will be set to PARSING_ONLY when specifying verb in this way
+ * so you normally would not specify a verb for normal GET request.
+ *
+ * Here is an example configuration for RESTful CRUD controller:
+ *
+ * ~~~php
+ * array(
+ * 'dashboard' => 'site/index',
+ *
+ * 'POST s' => '/create',
+ * 's' => '/index',
+ *
+ * 'PUT /' => '/update',
+ * 'DELETE /' => '/delete',
+ * '/' => '/view',
+ * );
+ * ~~~
+ *
* Note that if you modify this property after the UrlManager object is created, make sure
* you populate the array with rule objects instead of rule configurations.
*/
@@ -115,9 +140,14 @@ class UrlManager extends Component
foreach ($this->rules as $key => $rule) {
if (!is_array($rule)) {
$rule = array(
- 'pattern' => $key,
'route' => $rule,
);
+ if (preg_match('/^((?:(GET|HEAD|POST|PUT|DELETE),)*(GET|HEAD|POST|PUT|DELETE))\s+(.*)$/', $key, $matches)) {
+ $rule['verb'] = explode(',', $matches[1]);
+ $rule['mode'] = UrlRule::PARSING_ONLY;
+ $key = $matches[4];
+ }
+ $rule['pattern'] = $key;
}
$rules[] = Yii::createObject(array_merge($this->ruleConfig, $rule));
}
@@ -166,7 +196,7 @@ class UrlManager extends Component
return array($pathInfo, array());
} else {
- $route = $request->getParam($this->routeVar);
+ $route = $request->get($this->routeVar);
if (is_array($route)) {
$route = '';
}
diff --git a/framework/yii/web/UrlRule.php b/framework/yii/web/UrlRule.php
index b1e74da..fc19ea3 100644
--- a/framework/yii/web/UrlRule.php
+++ b/framework/yii/web/UrlRule.php
@@ -171,7 +171,7 @@ class UrlRule extends Object
return false;
}
- if ($this->verb !== null && !in_array($request->getRequestMethod(), $this->verb, true)) {
+ if ($this->verb !== null && !in_array($request->getMethod(), $this->verb, true)) {
return false;
}
diff --git a/framework/yii/web/User.php b/framework/yii/web/User.php
index 79665ae..7ea561c 100644
--- a/framework/yii/web/User.php
+++ b/framework/yii/web/User.php
@@ -56,7 +56,7 @@ class User extends Component
* @var array the configuration of the identity cookie. This property is used only when [[enableAutoLogin]] is true.
* @see Cookie
*/
- public $identityCookie = array('name' => '_identity', 'httponly' => true);
+ public $identityCookie = array('name' => '_identity', 'httpOnly' => true);
/**
* @var integer the number of seconds in which the user will be logged out automatically if he
* remains inactive. If this property is not set, the user will be logged out after
@@ -221,7 +221,7 @@ class User extends Component
if ($destroySession) {
Yii::$app->getSession()->destroy();
}
- $this->afterLogout($identity);
+ $this->afterLogout($identity);
}
}
@@ -280,7 +280,7 @@ class User extends Component
public function loginRequired()
{
$request = Yii::$app->getRequest();
- if (!$request->getIsAjaxRequest()) {
+ if (!$request->getIsAjax()) {
$this->setReturnUrl($request->getUrl());
}
if ($this->loginUrl !== null) {
diff --git a/framework/yii/web/VerbFilter.php b/framework/yii/web/VerbFilter.php
index 9b475e3..2b7567f 100644
--- a/framework/yii/web/VerbFilter.php
+++ b/framework/yii/web/VerbFilter.php
@@ -76,12 +76,12 @@ class VerbFilter extends Behavior
{
$action = $event->action->id;
if (isset($this->actions[$action])) {
- $verb = Yii::$app->getRequest()->getRequestMethod();
+ $verb = Yii::$app->getRequest()->getMethod();
$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));
+ Yii::$app->getResponse()->getHeaders()->set('Allow', implode(', ', $allowed));
throw new HttpException(405, 'Method Not Allowed. This url can only handle the following request methods: ' . implode(', ', $allowed));
}
}
diff --git a/framework/yii/widgets/FragmentCache.php b/framework/yii/widgets/FragmentCache.php
index 8445955..0fd8646 100644
--- a/framework/yii/widgets/FragmentCache.php
+++ b/framework/yii/widgets/FragmentCache.php
@@ -1,174 +1,174 @@
-
- * @since 2.0
- */
-class FragmentCache extends Widget
-{
- /**
- * @var Cache|string the cache object or the application component ID of the cache object.
- * After the FragmentCache object is created, if you want to change this property,
- * you should only assign it with a cache object.
- */
- public $cache = 'cache';
- /**
- * @var integer number of seconds that the data can remain valid in cache.
- * Use 0 to indicate that the cached data will never expire.
- */
- public $duration = 60;
- /**
- * @var array|Dependency the dependency that the cached content depends on.
- * This can be either a [[Dependency]] object or a configuration array for creating the dependency object.
- * For example,
- *
- * ~~~
- * array(
- * 'class' => 'yii\caching\DbDependency',
- * 'sql' => 'SELECT MAX(lastModified) FROM Post',
- * )
- * ~~~
- *
- * would make the output cache depends on the last modified time of all posts.
- * If any post has its modification time changed, the cached content would be invalidated.
- */
- public $dependency;
- /**
- * @var array list of factors that would cause the variation of the content being cached.
- * Each factor is a string representing a variation (e.g. the language, a GET parameter).
- * The following variation setting will cause the content to be cached in different versions
- * according to the current application language:
- *
- * ~~~
- * array(
- * Yii::$app->language,
- * )
- */
- public $variations;
- /**
- * @var boolean whether to enable the fragment cache. You may use this property to turn on and off
- * the fragment cache according to specific setting (e.g. enable fragment cache only for GET requests).
- */
- public $enabled = true;
- /**
- * @var array a list of placeholders for embedding dynamic contents. This property
- * is used internally to implement the content caching feature. Do not modify it.
- */
- public $dynamicPlaceholders;
-
- /**
- * Initializes the FragmentCache object.
- */
- public function init()
- {
- parent::init();
-
- if (!$this->enabled) {
- $this->cache = null;
- } elseif (is_string($this->cache)) {
- $this->cache = Yii::$app->getComponent($this->cache);
- }
-
- if ($this->getCachedContent() === false) {
- $this->view->cacheStack[] = $this;
- ob_start();
- ob_implicit_flush(false);
- }
- }
-
- /**
- * Marks the end of content to be cached.
- * Content displayed before this method call and after {@link init()}
- * will be captured and saved in cache.
- * This method does nothing if valid content is already found in cache.
- */
- public function run()
- {
- if (($content = $this->getCachedContent()) !== false) {
- echo $content;
- } elseif ($this->cache instanceof Cache) {
- $content = ob_get_clean();
- array_pop($this->view->cacheStack);
- if (is_array($this->dependency)) {
- $this->dependency = Yii::createObject($this->dependency);
- }
- $data = array($content, $this->dynamicPlaceholders);
- $this->cache->set($this->calculateKey(), $data, $this->duration, $this->dependency);
-
- if (empty($this->view->cacheStack) && !empty($this->dynamicPlaceholders)) {
- $content = $this->updateDynamicContent($content, $this->dynamicPlaceholders);
- }
- echo $content;
- }
- }
-
- /**
- * @var string|boolean the cached content. False if the content is not cached.
- */
- private $_content;
-
- /**
- * Returns the cached content if available.
- * @return string|boolean the cached content. False is returned if valid content is not found in the cache.
- */
- public function getCachedContent()
- {
- if ($this->_content === null) {
- $this->_content = false;
- if ($this->cache instanceof Cache) {
- $key = $this->calculateKey();
- $data = $this->cache->get($key);
- if (is_array($data) && count($data) === 2) {
- list ($content, $placeholders) = $data;
- if (is_array($placeholders) && count($placeholders) > 0) {
- if (empty($this->view->cacheStack)) {
- // outermost cache: replace placeholder with dynamic content
- $content = $this->updateDynamicContent($content, $placeholders);
- }
- foreach ($placeholders as $name => $statements) {
- $this->view->addDynamicPlaceholder($name, $statements);
- }
- }
- $this->_content = $content;
- }
- }
- }
- return $this->_content;
- }
-
- protected function updateDynamicContent($content, $placeholders)
- {
- foreach ($placeholders as $name => $statements) {
- $placeholders[$name] = $this->view->evaluateDynamicContent($statements);
- }
- return strtr($content, $placeholders);
- }
-
- /**
- * Generates a unique key used for storing the content in cache.
- * The key generated depends on both [[id]] and [[variations]].
- * @return mixed a valid cache key
- */
- protected function calculateKey()
- {
- $factors = array(__CLASS__, $this->getId());
- if (is_array($this->variations)) {
- foreach ($this->variations as $factor) {
- $factors[] = $factor;
- }
- }
- return $factors;
- }
-}
+
+ * @since 2.0
+ */
+class FragmentCache extends Widget
+{
+ /**
+ * @var Cache|string the cache object or the application component ID of the cache object.
+ * After the FragmentCache object is created, if you want to change this property,
+ * you should only assign it with a cache object.
+ */
+ public $cache = 'cache';
+ /**
+ * @var integer number of seconds that the data can remain valid in cache.
+ * Use 0 to indicate that the cached data will never expire.
+ */
+ public $duration = 60;
+ /**
+ * @var array|Dependency the dependency that the cached content depends on.
+ * This can be either a [[Dependency]] object or a configuration array for creating the dependency object.
+ * For example,
+ *
+ * ~~~
+ * array(
+ * 'class' => 'yii\caching\DbDependency',
+ * 'sql' => 'SELECT MAX(lastModified) FROM Post',
+ * )
+ * ~~~
+ *
+ * would make the output cache depends on the last modified time of all posts.
+ * If any post has its modification time changed, the cached content would be invalidated.
+ */
+ public $dependency;
+ /**
+ * @var array list of factors that would cause the variation of the content being cached.
+ * Each factor is a string representing a variation (e.g. the language, a GET parameter).
+ * The following variation setting will cause the content to be cached in different versions
+ * according to the current application language:
+ *
+ * ~~~
+ * array(
+ * Yii::$app->language,
+ * )
+ */
+ public $variations;
+ /**
+ * @var boolean whether to enable the fragment cache. You may use this property to turn on and off
+ * the fragment cache according to specific setting (e.g. enable fragment cache only for GET requests).
+ */
+ public $enabled = true;
+ /**
+ * @var array a list of placeholders for embedding dynamic contents. This property
+ * is used internally to implement the content caching feature. Do not modify it.
+ */
+ public $dynamicPlaceholders;
+
+ /**
+ * Initializes the FragmentCache object.
+ */
+ public function init()
+ {
+ parent::init();
+
+ if (!$this->enabled) {
+ $this->cache = null;
+ } elseif (is_string($this->cache)) {
+ $this->cache = Yii::$app->getComponent($this->cache);
+ }
+
+ if ($this->getCachedContent() === false) {
+ $this->view->cacheStack[] = $this;
+ ob_start();
+ ob_implicit_flush(false);
+ }
+ }
+
+ /**
+ * Marks the end of content to be cached.
+ * Content displayed before this method call and after {@link init()}
+ * will be captured and saved in cache.
+ * This method does nothing if valid content is already found in cache.
+ */
+ public function run()
+ {
+ if (($content = $this->getCachedContent()) !== false) {
+ echo $content;
+ } elseif ($this->cache instanceof Cache) {
+ $content = ob_get_clean();
+ array_pop($this->view->cacheStack);
+ if (is_array($this->dependency)) {
+ $this->dependency = Yii::createObject($this->dependency);
+ }
+ $data = array($content, $this->dynamicPlaceholders);
+ $this->cache->set($this->calculateKey(), $data, $this->duration, $this->dependency);
+
+ if (empty($this->view->cacheStack) && !empty($this->dynamicPlaceholders)) {
+ $content = $this->updateDynamicContent($content, $this->dynamicPlaceholders);
+ }
+ echo $content;
+ }
+ }
+
+ /**
+ * @var string|boolean the cached content. False if the content is not cached.
+ */
+ private $_content;
+
+ /**
+ * Returns the cached content if available.
+ * @return string|boolean the cached content. False is returned if valid content is not found in the cache.
+ */
+ public function getCachedContent()
+ {
+ if ($this->_content === null) {
+ $this->_content = false;
+ if ($this->cache instanceof Cache) {
+ $key = $this->calculateKey();
+ $data = $this->cache->get($key);
+ if (is_array($data) && count($data) === 2) {
+ list ($content, $placeholders) = $data;
+ if (is_array($placeholders) && count($placeholders) > 0) {
+ if (empty($this->view->cacheStack)) {
+ // outermost cache: replace placeholder with dynamic content
+ $content = $this->updateDynamicContent($content, $placeholders);
+ }
+ foreach ($placeholders as $name => $statements) {
+ $this->view->addDynamicPlaceholder($name, $statements);
+ }
+ }
+ $this->_content = $content;
+ }
+ }
+ }
+ return $this->_content;
+ }
+
+ protected function updateDynamicContent($content, $placeholders)
+ {
+ foreach ($placeholders as $name => $statements) {
+ $placeholders[$name] = $this->view->evaluateDynamicContent($statements);
+ }
+ return strtr($content, $placeholders);
+ }
+
+ /**
+ * Generates a unique key used for storing the content in cache.
+ * The key generated depends on both [[id]] and [[variations]].
+ * @return mixed a valid cache key
+ */
+ protected function calculateKey()
+ {
+ $factors = array(__CLASS__, $this->getId());
+ if (is_array($this->variations)) {
+ foreach ($this->variations as $factor) {
+ $factors[] = $factor;
+ }
+ }
+ return $factors;
+ }
+}
diff --git a/framework/yii/widgets/LinkPager.php b/framework/yii/widgets/LinkPager.php
index 2510579..62d99f6 100644
--- a/framework/yii/widgets/LinkPager.php
+++ b/framework/yii/widgets/LinkPager.php
@@ -11,7 +11,7 @@ use Yii;
use yii\base\InvalidConfigException;
use yii\helpers\Html;
use yii\base\Widget;
-use yii\web\Pagination;
+use yii\data\Pagination;
/**
* LinkPager displays a list of hyperlinks that lead to different pages of target.
@@ -198,4 +198,4 @@ class LinkPager extends Widget
}
return array($beginPage, $endPage);
}
-}
\ No newline at end of file
+}
diff --git a/framework/yii/widgets/ListPager.php b/framework/yii/widgets/ListPager.php
index 7b16f7d..30371d3 100644
--- a/framework/yii/widgets/ListPager.php
+++ b/framework/yii/widgets/ListPager.php
@@ -10,7 +10,7 @@ namespace yii\widgets;
use yii\base\InvalidConfigException;
use yii\helpers\Html;
use yii\base\Widget;
-use yii\web\Pagination;
+use yii\data\Pagination;
/**
* ListPager displays a drop-down list that contains options leading to different pages.
@@ -91,5 +91,4 @@ class ListPager extends Widget
'{page}' => $page + 1,
));
}
-
-}
\ No newline at end of file
+}
diff --git a/tests/unit/TestCase.php b/tests/unit/TestCase.php
index 479f85d..efcedf0 100644
--- a/tests/unit/TestCase.php
+++ b/tests/unit/TestCase.php
@@ -38,14 +38,13 @@ abstract class TestCase extends \yii\test\TestCase
* The application will be destroyed on tearDown() automatically.
* @param array $config The application configuration, if needed
*/
- protected function mockApplication($config=array())
+ protected function mockApplication($config = array(), $appClass = '\yii\console\Application')
{
static $defaultConfig = array(
'id' => 'testapp',
'basePath' => __DIR__,
);
- $appClass = $this->getParam( 'appClass', '\yii\web\Application' );
new $appClass(array_merge($defaultConfig,$config));
}
diff --git a/tests/unit/data/config.php b/tests/unit/data/config.php
index 88c8127..036624b 100644
--- a/tests/unit/data/config.php
+++ b/tests/unit/data/config.php
@@ -1,8 +1,6 @@
'\yii\web\Application',
- 'appClass' => '\yii\console\Application',
'databases' => array(
'mysql' => array(
'dsn' => 'mysql:host=127.0.0.1;dbname=yiitest',
@@ -20,5 +18,11 @@ return array(
'password' => '',
'fixture' => __DIR__ . '/mssql.sql',
),
+ 'pgsql' => array(
+ 'dsn' => 'pgsql:host=localhost;dbname=yiitest;port=5432;',
+ 'username' => 'postgres',
+ 'password' => 'postgres',
+ 'fixture' => __DIR__ . '/postgres.sql',
+ )
)
);
diff --git a/tests/unit/data/postgres.sql b/tests/unit/data/postgres.sql
index e46b284..52fad0f 100644
--- a/tests/unit/data/postgres.sql
+++ b/tests/unit/data/postgres.sql
@@ -1,165 +1,87 @@
/**
* This is the database schema for testing PostgreSQL support of yii Active Record.
- * To test this feature, you need to create a database named 'yii' on 'localhost'
- * and create an account 'test/test' which owns this test database.
+ * To test this feature, you need to create a database named 'yiitest' on 'localhost'
+ * and create an account 'postgres/postgres' which owns this test database.
*/
-CREATE SCHEMA test;
-CREATE TABLE test.users
-(
- id SERIAL NOT NULL PRIMARY KEY,
- username VARCHAR(128) NOT NULL,
- password VARCHAR(128) NOT NULL,
- email VARCHAR(128) NOT NULL
+DROP TABLE IF EXISTS tbl_order_item CASCADE;
+DROP TABLE IF EXISTS tbl_item CASCADE;
+DROP TABLE IF EXISTS tbl_order CASCADE;
+DROP TABLE IF EXISTS tbl_category CASCADE;
+DROP TABLE IF EXISTS tbl_customer CASCADE;
+DROP TABLE IF EXISTS tbl_type CASCADE;
+
+CREATE TABLE tbl_customer (
+ id serial not null primary key,
+ email varchar(128) NOT NULL,
+ name varchar(128) NOT NULL,
+ address text,
+ status integer DEFAULT 0
);
-INSERT INTO test.users (username, password, email) VALUES ('user1','pass1','email1');
-INSERT INTO test.users (username, password, email) VALUES ('user2','pass2','email2');
-INSERT INTO test.users (username, password, email) VALUES ('user3','pass3','email3');
+comment on column public.tbl_customer.email is 'someone@example.com';
-CREATE TABLE test.user_friends
-(
- id INTEGER NOT NULL,
- friend INTEGER NOT NULL,
- PRIMARY KEY (id, friend),
- CONSTRAINT FK_user_id FOREIGN KEY (id)
- REFERENCES test.users (id) ON DELETE CASCADE ON UPDATE RESTRICT,
- CONSTRAINT FK_friend_id FOREIGN KEY (friend)
- REFERENCES test.users (id) ON DELETE CASCADE ON UPDATE RESTRICT
+CREATE TABLE tbl_category (
+ id serial not null primary key,
+ name varchar(128) NOT NULL
);
-INSERT INTO test.user_friends VALUES (1,2);
-INSERT INTO test.user_friends VALUES (1,3);
-INSERT INTO test.user_friends VALUES (2,3);
-
-CREATE TABLE test.profiles
-(
- id SERIAL NOT NULL PRIMARY KEY,
- first_name VARCHAR(128) NOT NULL,
- last_name VARCHAR(128) NOT NULL,
- user_id INTEGER NOT NULL,
- CONSTRAINT FK_profile_user FOREIGN KEY (user_id)
- REFERENCES test.users (id) ON DELETE CASCADE ON UPDATE RESTRICT
-);
-
-INSERT INTO test.profiles (first_name, last_name, user_id) VALUES ('first 1','last 1',1);
-INSERT INTO test.profiles (first_name, last_name, user_id) VALUES ('first 2','last 2',2);
-
-CREATE TABLE test.posts
-(
- id SERIAL NOT NULL PRIMARY KEY,
- title VARCHAR(128) NOT NULL,
- create_time TIMESTAMP NOT NULL,
- author_id INTEGER NOT NULL,
- content TEXT,
- CONSTRAINT FK_post_author FOREIGN KEY (author_id)
- REFERENCES test.users (id) ON DELETE CASCADE ON UPDATE RESTRICT
-);
-
-INSERT INTO test.posts (title, create_time, author_id, content) VALUES ('post 1',TIMESTAMP '2004-10-19 10:23:54',1,'content 1');
-INSERT INTO test.posts (title, create_time, author_id, content) VALUES ('post 2',TIMESTAMP '2004-10-19 10:23:54',2,'content 2');
-INSERT INTO test.posts (title, create_time, author_id, content) VALUES ('post 3',TIMESTAMP '2004-10-19 10:23:54',2,'content 3');
-INSERT INTO test.posts (title, create_time, author_id, content) VALUES ('post 4',TIMESTAMP '2004-10-19 10:23:54',2,'content 4');
-INSERT INTO test.posts (title, create_time, author_id, content) VALUES ('post 5',TIMESTAMP '2004-10-19 10:23:54',3,'content 5');
-
-CREATE TABLE test.comments
-(
- id SERIAL NOT NULL PRIMARY KEY,
- content TEXT NOT NULL,
- post_id INTEGER NOT NULL,
- author_id INTEGER NOT NULL,
- CONSTRAINT FK_post_comment FOREIGN KEY (post_id)
- REFERENCES test.posts (id) ON DELETE CASCADE ON UPDATE RESTRICT,
- CONSTRAINT FK_user_comment FOREIGN KEY (author_id)
- REFERENCES test.users (id) ON DELETE CASCADE ON UPDATE RESTRICT
+CREATE TABLE tbl_item (
+ id serial not null primary key,
+ name varchar(128) NOT NULL,
+ category_id integer NOT NULL references tbl_category(id) on UPDATE CASCADE on DELETE CASCADE
);
-INSERT INTO test.comments (content, post_id, author_id) VALUES ('comment 1',1, 2);
-INSERT INTO test.comments (content, post_id, author_id) VALUES ('comment 2',1, 2);
-INSERT INTO test.comments (content, post_id, author_id) VALUES ('comment 3',1, 2);
-INSERT INTO test.comments (content, post_id, author_id) VALUES ('comment 4',2, 2);
-INSERT INTO test.comments (content, post_id, author_id) VALUES ('comment 5',2, 2);
-INSERT INTO test.comments (content, post_id, author_id) VALUES ('comment 6',3, 2);
-INSERT INTO test.comments (content, post_id, author_id) VALUES ('comment 7',3, 2);
-INSERT INTO test.comments (content, post_id, author_id) VALUES ('comment 8',3, 2);
-INSERT INTO test.comments (content, post_id, author_id) VALUES ('comment 9',3, 2);
-INSERT INTO test.comments (content, post_id, author_id) VALUES ('comment 10',5, 3);
-
-CREATE TABLE test.categories
-(
- id SERIAL NOT NULL PRIMARY KEY,
- name VARCHAR(128) NOT NULL,
- parent_id INTEGER,
- CONSTRAINT FK_category_category FOREIGN KEY (parent_id)
- REFERENCES test.categories (id) ON DELETE CASCADE ON UPDATE RESTRICT
+CREATE TABLE tbl_order (
+ id serial not null primary key,
+ customer_id integer NOT NULL references tbl_customer(id) on UPDATE CASCADE on DELETE CASCADE,
+ create_time integer NOT NULL,
+ total decimal(10,0) NOT NULL
);
-INSERT INTO test.categories (name, parent_id) VALUES ('cat 1',NULL);
-INSERT INTO test.categories (name, parent_id) VALUES ('cat 2',NULL);
-INSERT INTO test.categories (name, parent_id) VALUES ('cat 3',NULL);
-INSERT INTO test.categories (name, parent_id) VALUES ('cat 4',1);
-INSERT INTO test.categories (name, parent_id) VALUES ('cat 5',1);
-INSERT INTO test.categories (name, parent_id) VALUES ('cat 6',5);
-INSERT INTO test.categories (name, parent_id) VALUES ('cat 7',5);
-
-CREATE TABLE test.post_category
-(
- category_id INTEGER NOT NULL,
- post_id INTEGER NOT NULL,
- PRIMARY KEY (category_id, post_id),
- CONSTRAINT FK_post_category_post FOREIGN KEY (post_id)
- REFERENCES test.posts (id) ON DELETE CASCADE ON UPDATE RESTRICT,
- CONSTRAINT FK_post_category_category FOREIGN KEY (category_id)
- REFERENCES test.categories (id) ON DELETE CASCADE ON UPDATE RESTRICT
+CREATE TABLE tbl_order_item (
+ order_id integer NOT NULL references tbl_order(id) on UPDATE CASCADE on DELETE CASCADE,
+ item_id integer NOT NULL references tbl_item(id) on UPDATE CASCADE on DELETE CASCADE,
+ quantity integer NOT NULL,
+ subtotal decimal(10,0) NOT NULL,
+ PRIMARY KEY (order_id,item_id)
);
-INSERT INTO test.post_category (category_id, post_id) VALUES (1,1);
-INSERT INTO test.post_category (category_id, post_id) VALUES (2,1);
-INSERT INTO test.post_category (category_id, post_id) VALUES (3,1);
-INSERT INTO test.post_category (category_id, post_id) VALUES (4,2);
-INSERT INTO test.post_category (category_id, post_id) VALUES (1,2);
-INSERT INTO test.post_category (category_id, post_id) VALUES (1,3);
-
-CREATE TABLE test.orders
-(
- key1 INTEGER NOT NULL,
- key2 INTEGER NOT NULL,
- name VARCHAR(128),
- PRIMARY KEY (key1, key2)
+CREATE TABLE tbl_type (
+ int_col integer NOT NULL,
+ int_col2 integer DEFAULT '1',
+ char_col char(100) NOT NULL,
+ char_col2 varchar(100) DEFAULT 'something',
+ char_col3 text,
+ float_col double precision NOT NULL,
+ float_col2 double precision DEFAULT '1.23',
+ blob_col bytea,
+ numeric_col decimal(5,2) DEFAULT '33.22',
+ time timestamp NOT NULL DEFAULT '2002-01-01 00:00:00',
+ bool_col smallint NOT NULL,
+ bool_col2 smallint DEFAULT '1'
);
-INSERT INTO test.orders (key1,key2,name) VALUES (1,2,'order 12');
-INSERT INTO test.orders (key1,key2,name) VALUES (1,3,'order 13');
-INSERT INTO test.orders (key1,key2,name) VALUES (2,1,'order 21');
-INSERT INTO test.orders (key1,key2,name) VALUES (2,2,'order 22');
-
-CREATE TABLE test.items
-(
- id SERIAL NOT NULL PRIMARY KEY,
- name VARCHAR(128),
- col1 INTEGER NOT NULL,
- col2 INTEGER NOT NULL,
- CONSTRAINT FK_order_item FOREIGN KEY (col1,col2)
- REFERENCES test.orders (key1,key2) ON DELETE CASCADE ON UPDATE RESTRICT
-);
-
-INSERT INTO test.items (name,col1,col2) VALUES ('item 1',1,2);
-INSERT INTO test.items (name,col1,col2) VALUES ('item 2',1,2);
-INSERT INTO test.items (name,col1,col2) VALUES ('item 3',1,3);
-INSERT INTO test.items (name,col1,col2) VALUES ('item 4',2,2);
-INSERT INTO test.items (name,col1,col2) VALUES ('item 5',2,2);
-
-CREATE TABLE public.yii_types
-(
- int_col INT NOT NULL,
- int_col2 INTEGER DEFAULT 1,
- char_col CHAR(100) NOT NULL,
- char_col2 VARCHAR(100) DEFAULT 'something',
- char_col3 TEXT,
- numeric_col NUMERIC(4,3) NOT NULL,
- real_col REAL DEFAULT 1.23,
- blob_col BYTEA,
- time TIMESTAMP,
- bool_col BOOL NOT NULL,
- bool_col2 BOOLEAN DEFAULT TRUE
-);
\ No newline at end of file
+INSERT INTO tbl_customer (email, name, address, status) VALUES ('user1@example.com', 'user1', 'address1', 1);
+INSERT INTO tbl_customer (email, name, address, status) VALUES ('user2@example.com', 'user2', 'address2', 1);
+INSERT INTO tbl_customer (email, name, address, status) VALUES ('user3@example.com', 'user3', 'address3', 2);
+
+INSERT INTO tbl_category (name) VALUES ('Books');
+INSERT INTO tbl_category (name) VALUES ('Movies');
+
+INSERT INTO tbl_item (name, category_id) VALUES ('Agile Web Application Development with Yii1.1 and PHP5', 1);
+INSERT INTO tbl_item (name, category_id) VALUES ('Yii 1.1 Application Development Cookbook', 1);
+INSERT INTO tbl_item (name, category_id) VALUES ('Ice Age', 2);
+INSERT INTO tbl_item (name, category_id) VALUES ('Toy Story', 2);
+INSERT INTO tbl_item (name, category_id) VALUES ('Cars', 2);
+
+INSERT INTO tbl_order (customer_id, create_time, total) VALUES (1, 1325282384, 110.0);
+INSERT INTO tbl_order (customer_id, create_time, total) VALUES (2, 1325334482, 33.0);
+INSERT INTO tbl_order (customer_id, create_time, total) VALUES (2, 1325502201, 40.0);
+
+INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (1, 1, 1, 30.0);
+INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (1, 2, 2, 40.0);
+INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (2, 4, 1, 10.0);
+INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (2, 5, 1, 15.0);
+INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (2, 3, 1, 8.0);
+INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (3, 2, 1, 40.0);
diff --git a/tests/unit/framework/console/controllers/AssetControllerTest.php b/tests/unit/framework/console/controllers/AssetControllerTest.php
index db6d2a7..9d7dd28 100644
--- a/tests/unit/framework/console/controllers/AssetControllerTest.php
+++ b/tests/unit/framework/console/controllers/AssetControllerTest.php
@@ -239,6 +239,7 @@ class AssetControllerTest extends TestCase
// Then :
$this->assertTrue(file_exists($bundleFile), 'Unable to create output bundle file!');
+ $this->assertTrue(is_array(require($bundleFile)), 'Output bundle file has incorrect format!');
$compressedCssFileName = $this->testAssetsBasePath . DIRECTORY_SEPARATOR . 'all.css';
$this->assertTrue(file_exists($compressedCssFileName), 'Unable to compress CSS files!');
diff --git a/tests/unit/framework/db/DatabaseTestCase.php b/tests/unit/framework/db/DatabaseTestCase.php
index 429d961..7ce9bec 100644
--- a/tests/unit/framework/db/DatabaseTestCase.php
+++ b/tests/unit/framework/db/DatabaseTestCase.php
@@ -37,6 +37,9 @@ abstract class DatabaseTestCase extends TestCase
$db->username = $this->database['username'];
$db->password = $this->database['password'];
}
+ if (isset($this->database['attributes'])) {
+ $db->attributes = $this->database['attributes'];
+ }
if ($open) {
$db->open();
$lines = explode(';', file_get_contents($this->database['fixture']));
diff --git a/tests/unit/framework/db/QueryBuilderTest.php b/tests/unit/framework/db/QueryBuilderTest.php
new file mode 100644
index 0000000..7dc4731
--- /dev/null
+++ b/tests/unit/framework/db/QueryBuilderTest.php
@@ -0,0 +1,109 @@
+driverName)
+ {
+ case 'mysql':
+ return new MysqlQueryBuilder($this->getConnection());
+ case 'sqlite':
+ return new SqliteQueryBuilder($this->getConnection());
+ case 'mssql':
+ return new MssqlQueryBuilder($this->getConnection());
+ }
+ throw new \Exception('Test is not implemented for ' . $this->driverName);
+ }
+
+ /**
+ * this is not used as a dataprovider for testGetColumnType to speed up the test
+ * when used as dataprovider every single line will cause a reconnect with the database which is not needed here
+ */
+ public function columnTypes()
+ {
+ return array(
+ array(Schema::TYPE_PK, 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY'),
+ array(Schema::TYPE_PK . '(8)', 'int(8) NOT NULL AUTO_INCREMENT PRIMARY KEY'),
+ array(Schema::TYPE_PK . ' CHECK (value > 5)', 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY CHECK (value > 5)'),
+ array(Schema::TYPE_PK . '(8) CHECK (value > 5)', 'int(8) NOT NULL AUTO_INCREMENT PRIMARY KEY CHECK (value > 5)'),
+ array(Schema::TYPE_STRING, 'varchar(255)'),
+ array(Schema::TYPE_STRING . '(32)', 'varchar(32)'),
+ array(Schema::TYPE_STRING . ' CHECK (value LIKE "test%")', 'varchar(255) CHECK (value LIKE "test%")'),
+ array(Schema::TYPE_STRING . '(32) CHECK (value LIKE "test%")', 'varchar(32) CHECK (value LIKE "test%")'),
+ array(Schema::TYPE_STRING . ' NOT NULL', 'varchar(255) NOT NULL'),
+ array(Schema::TYPE_TEXT, 'text'),
+ array(Schema::TYPE_TEXT . '(255)', 'text'),
+ array(Schema::TYPE_TEXT . ' CHECK (value LIKE "test%")', 'text CHECK (value LIKE "test%")'),
+ array(Schema::TYPE_TEXT . '(255) CHECK (value LIKE "test%")', 'text CHECK (value LIKE "test%")'),
+ array(Schema::TYPE_TEXT . ' NOT NULL', 'text NOT NULL'),
+ array(Schema::TYPE_TEXT . '(255) NOT NULL', 'text NOT NULL'),
+ array(Schema::TYPE_SMALLINT, 'smallint(6)'),
+ array(Schema::TYPE_SMALLINT . '(8)', 'smallint(8)'),
+ array(Schema::TYPE_INTEGER, 'int(11)'),
+ array(Schema::TYPE_INTEGER . '(8)', 'int(8)'),
+ array(Schema::TYPE_INTEGER . ' CHECK (value > 5)', 'int(11) CHECK (value > 5)'),
+ array(Schema::TYPE_INTEGER . '(8) CHECK (value > 5)', 'int(8) CHECK (value > 5)'),
+ array(Schema::TYPE_INTEGER . ' NOT NULL', 'int(11) NOT NULL'),
+ array(Schema::TYPE_BIGINT, 'bigint(20)'),
+ array(Schema::TYPE_BIGINT . '(8)', 'bigint(8)'),
+ array(Schema::TYPE_BIGINT . ' CHECK (value > 5)', 'bigint(20) CHECK (value > 5)'),
+ array(Schema::TYPE_BIGINT . '(8) CHECK (value > 5)', 'bigint(8) CHECK (value > 5)'),
+ array(Schema::TYPE_BIGINT . ' NOT NULL', 'bigint(20) NOT NULL'),
+ array(Schema::TYPE_FLOAT, 'float'),
+ array(Schema::TYPE_FLOAT . '(16,5)', 'float'),
+ array(Schema::TYPE_FLOAT . ' CHECK (value > 5.6)', 'float CHECK (value > 5.6)'),
+ array(Schema::TYPE_FLOAT . '(16,5) CHECK (value > 5.6)', 'float CHECK (value > 5.6)'),
+ array(Schema::TYPE_FLOAT . ' NOT NULL', 'float NOT NULL'),
+ array(Schema::TYPE_DECIMAL, 'decimal(10,0)'),
+ array(Schema::TYPE_DECIMAL . '(12,4)', 'decimal(12,4)'),
+ array(Schema::TYPE_DECIMAL . ' CHECK (value > 5.6)', 'decimal(10,0) CHECK (value > 5.6)'),
+ array(Schema::TYPE_DECIMAL . '(12,4) CHECK (value > 5.6)', 'decimal(12,4) CHECK (value > 5.6)'),
+ array(Schema::TYPE_DECIMAL . ' NOT NULL', 'decimal(10,0) NOT NULL'),
+ array(Schema::TYPE_DATETIME, 'datetime'),
+ array(Schema::TYPE_DATETIME . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "datetime CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"),
+ array(Schema::TYPE_DATETIME . ' NOT NULL', 'datetime NOT NULL'),
+ array(Schema::TYPE_TIMESTAMP, 'timestamp'),
+ array(Schema::TYPE_TIMESTAMP . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "timestamp CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"),
+ array(Schema::TYPE_TIMESTAMP . ' NOT NULL', 'timestamp NOT NULL'),
+ array(Schema::TYPE_TIME, 'time'),
+ array(Schema::TYPE_TIME . " CHECK(value BETWEEN '12:00:00' AND '13:01:01')", "time CHECK(value BETWEEN '12:00:00' AND '13:01:01')"),
+ array(Schema::TYPE_TIME . ' NOT NULL', 'time NOT NULL'),
+ array(Schema::TYPE_DATE, 'date'),
+ array(Schema::TYPE_DATE . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "date CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"),
+ array(Schema::TYPE_DATE . ' NOT NULL', 'date NOT NULL'),
+ array(Schema::TYPE_BINARY, 'blob'),
+ array(Schema::TYPE_BOOLEAN, 'tinyint(1)'),
+ array(Schema::TYPE_BOOLEAN . ' NOT NULL DEFAULT 1', 'tinyint(1) NOT NULL DEFAULT 1'),
+ array(Schema::TYPE_MONEY, 'decimal(19,4)'),
+ array(Schema::TYPE_MONEY . '(16,2)', 'decimal(16,2)'),
+ array(Schema::TYPE_MONEY . ' CHECK (value > 0.0)', 'decimal(19,4) CHECK (value > 0.0)'),
+ array(Schema::TYPE_MONEY . '(16,2) CHECK (value > 0.0)', 'decimal(16,2) CHECK (value > 0.0)'),
+ array(Schema::TYPE_MONEY . ' NOT NULL', 'decimal(19,4) NOT NULL'),
+ );
+ }
+
+ /**
+ *
+ */
+ public function testGetColumnType()
+ {
+ $qb = $this->getQueryBuilder();
+ foreach($this->columnTypes() as $item) {
+ list ($column, $expected) = $item;
+ $this->assertEquals($expected, $qb->getColumnType($column));
+ }
+ }
+}
diff --git a/tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php b/tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php
new file mode 100644
index 0000000..18bfbd8
--- /dev/null
+++ b/tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php
@@ -0,0 +1,14 @@
+driverName = 'pgsql';
+ parent::setUp();
+ }
+}
diff --git a/tests/unit/framework/db/pgsql/PostgreSQLConnectionTest.php b/tests/unit/framework/db/pgsql/PostgreSQLConnectionTest.php
new file mode 100644
index 0000000..678b197
--- /dev/null
+++ b/tests/unit/framework/db/pgsql/PostgreSQLConnectionTest.php
@@ -0,0 +1,51 @@
+driverName = 'pgsql';
+ parent::setUp();
+ }
+
+ public function testConnection() {
+ $connection = $this->getConnection(true);
+ }
+
+ function testQuoteValue() {
+ $connection = $this->getConnection(false);
+ $this->assertEquals(123, $connection->quoteValue(123));
+ $this->assertEquals("'string'", $connection->quoteValue('string'));
+ $this->assertEquals("'It''s interesting'", $connection->quoteValue("It's interesting"));
+ }
+
+ function testQuoteTableName()
+ {
+ $connection = $this->getConnection(false);
+ $this->assertEquals('"table"', $connection->quoteTableName('table'));
+ $this->assertEquals('"table"', $connection->quoteTableName('"table"'));
+ $this->assertEquals('"schema"."table"', $connection->quoteTableName('schema.table'));
+ $this->assertEquals('"schema"."table"', $connection->quoteTableName('schema."table"'));
+ $this->assertEquals('"schema"."table"', $connection->quoteTableName('"schema"."table"'));
+ $this->assertEquals('{{table}}', $connection->quoteTableName('{{table}}'));
+ $this->assertEquals('(table)', $connection->quoteTableName('(table)'));
+ }
+
+ function testQuoteColumnName()
+ {
+ $connection = $this->getConnection(false);
+ $this->assertEquals('"column"', $connection->quoteColumnName('column'));
+ $this->assertEquals('"column"', $connection->quoteColumnName('"column"'));
+ $this->assertEquals('"table"."column"', $connection->quoteColumnName('table.column'));
+ $this->assertEquals('"table"."column"', $connection->quoteColumnName('table."column"'));
+ $this->assertEquals('"table"."column"', $connection->quoteColumnName('"table"."column"'));
+ $this->assertEquals('[[column]]', $connection->quoteColumnName('[[column]]'));
+ $this->assertEquals('{{column}}', $connection->quoteColumnName('{{column}}'));
+ $this->assertEquals('(column)', $connection->quoteColumnName('(column)'));
+ }
+
+
+}
diff --git a/tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php b/tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php
new file mode 100644
index 0000000..c36628f
--- /dev/null
+++ b/tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php
@@ -0,0 +1,74 @@
+ 5)', 'integer PRIMARY KEY AUTOINCREMENT NOT NULL CHECK (value > 5)'),
+ array(Schema::TYPE_PK . '(8) CHECK (value > 5)', 'integer PRIMARY KEY AUTOINCREMENT NOT NULL CHECK (value > 5)'),
+ array(Schema::TYPE_STRING, 'varchar(255)'),
+ array(Schema::TYPE_STRING . '(32)', 'varchar(32)'),
+ array(Schema::TYPE_STRING . ' CHECK (value LIKE "test%")', 'varchar(255) CHECK (value LIKE "test%")'),
+ array(Schema::TYPE_STRING . '(32) CHECK (value LIKE "test%")', 'varchar(32) CHECK (value LIKE "test%")'),
+ array(Schema::TYPE_STRING . ' NOT NULL', 'varchar(255) NOT NULL'),
+ array(Schema::TYPE_TEXT, 'text'),
+ array(Schema::TYPE_TEXT . '(255)', 'text'),
+ array(Schema::TYPE_TEXT . ' CHECK (value LIKE "test%")', 'text CHECK (value LIKE "test%")'),
+ array(Schema::TYPE_TEXT . '(255) CHECK (value LIKE "test%")', 'text CHECK (value LIKE "test%")'),
+ array(Schema::TYPE_TEXT . ' NOT NULL', 'text NOT NULL'),
+ array(Schema::TYPE_TEXT . '(255) NOT NULL', 'text NOT NULL'),
+ array(Schema::TYPE_SMALLINT, 'smallint'),
+ array(Schema::TYPE_SMALLINT . '(8)', 'smallint'),
+ array(Schema::TYPE_INTEGER, 'integer'),
+ array(Schema::TYPE_INTEGER . '(8)', 'integer'),
+ array(Schema::TYPE_INTEGER . ' CHECK (value > 5)', 'integer CHECK (value > 5)'),
+ array(Schema::TYPE_INTEGER . '(8) CHECK (value > 5)', 'integer CHECK (value > 5)'),
+ array(Schema::TYPE_INTEGER . ' NOT NULL', 'integer NOT NULL'),
+ array(Schema::TYPE_BIGINT, 'bigint'),
+ array(Schema::TYPE_BIGINT . '(8)', 'bigint'),
+ array(Schema::TYPE_BIGINT . ' CHECK (value > 5)', 'bigint CHECK (value > 5)'),
+ array(Schema::TYPE_BIGINT . '(8) CHECK (value > 5)', 'bigint CHECK (value > 5)'),
+ array(Schema::TYPE_BIGINT . ' NOT NULL', 'bigint NOT NULL'),
+ array(Schema::TYPE_FLOAT, 'float'),
+ array(Schema::TYPE_FLOAT . '(16,5)', 'float'),
+ array(Schema::TYPE_FLOAT . ' CHECK (value > 5.6)', 'float CHECK (value > 5.6)'),
+ array(Schema::TYPE_FLOAT . '(16,5) CHECK (value > 5.6)', 'float CHECK (value > 5.6)'),
+ array(Schema::TYPE_FLOAT . ' NOT NULL', 'float NOT NULL'),
+ array(Schema::TYPE_DECIMAL, 'decimal(10,0)'),
+ array(Schema::TYPE_DECIMAL . '(12,4)', 'decimal(12,4)'),
+ array(Schema::TYPE_DECIMAL . ' CHECK (value > 5.6)', 'decimal(10,0) CHECK (value > 5.6)'),
+ array(Schema::TYPE_DECIMAL . '(12,4) CHECK (value > 5.6)', 'decimal(12,4) CHECK (value > 5.6)'),
+ array(Schema::TYPE_DECIMAL . ' NOT NULL', 'decimal(10,0) NOT NULL'),
+ array(Schema::TYPE_DATETIME, 'datetime'),
+ array(Schema::TYPE_DATETIME . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "datetime CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"),
+ array(Schema::TYPE_DATETIME . ' NOT NULL', 'datetime NOT NULL'),
+ array(Schema::TYPE_TIMESTAMP, 'timestamp'),
+ array(Schema::TYPE_TIMESTAMP . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "timestamp CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"),
+ array(Schema::TYPE_TIMESTAMP . ' NOT NULL', 'timestamp NOT NULL'),
+ array(Schema::TYPE_TIME, 'time'),
+ array(Schema::TYPE_TIME . " CHECK(value BETWEEN '12:00:00' AND '13:01:01')", "time CHECK(value BETWEEN '12:00:00' AND '13:01:01')"),
+ array(Schema::TYPE_TIME . ' NOT NULL', 'time NOT NULL'),
+ array(Schema::TYPE_DATE, 'date'),
+ array(Schema::TYPE_DATE . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "date CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"),
+ array(Schema::TYPE_DATE . ' NOT NULL', 'date NOT NULL'),
+ array(Schema::TYPE_BINARY, 'blob'),
+ array(Schema::TYPE_BOOLEAN, 'boolean'),
+ array(Schema::TYPE_BOOLEAN . ' NOT NULL DEFAULT 1', 'boolean NOT NULL DEFAULT 1'),
+ array(Schema::TYPE_MONEY, 'decimal(19,4)'),
+ array(Schema::TYPE_MONEY . '(16,2)', 'decimal(16,2)'),
+ array(Schema::TYPE_MONEY . ' CHECK (value > 0.0)', 'decimal(19,4) CHECK (value > 0.0)'),
+ array(Schema::TYPE_MONEY . '(16,2) CHECK (value > 0.0)', 'decimal(16,2) CHECK (value > 0.0)'),
+ array(Schema::TYPE_MONEY . ' NOT NULL', 'decimal(19,4) NOT NULL'),
+ );
+ }
+}
\ No newline at end of file
diff --git a/tests/unit/framework/helpers/ArrayHelperTest.php b/tests/unit/framework/helpers/ArrayHelperTest.php
index 84277d6..cfda9ae 100644
--- a/tests/unit/framework/helpers/ArrayHelperTest.php
+++ b/tests/unit/framework/helpers/ArrayHelperTest.php
@@ -4,7 +4,7 @@ namespace yiiunit\framework\helpers;
use yii\helpers\ArrayHelper;
use yii\test\TestCase;
-use yii\web\Sort;
+use yii\data\Sort;
class ArrayHelperTest extends TestCase
{
diff --git a/tests/unit/framework/web/ResponseTest.php b/tests/unit/framework/web/ResponseTest.php
index 2fde63d..74d90cf 100644
--- a/tests/unit/framework/web/ResponseTest.php
+++ b/tests/unit/framework/web/ResponseTest.php
@@ -1,44 +1,20 @@
reset();
- }
-
- protected function reset()
- {
- static::$headers = array();
- static::$httpResponseCode = 200;
+ $this->mockApplication();
+ $this->response = new Response;
}
public function rightRanges()
@@ -59,14 +35,15 @@ class ResponseTest extends \yiiunit\TestCase
{
$content = $this->generateTestFileContent();
$_SERVER['HTTP_RANGE'] = 'bytes=' . $rangeHeader;
- $sent = $this->runSendFile('testFile.txt', $content, null);
-
- $this->assertEquals($expectedFile, $sent);
- $this->assertTrue(in_array('HTTP/1.1 206 Partial Content', static::$headers));
- $this->assertTrue(in_array('Accept-Ranges: bytes', static::$headers));
- $this->assertArrayHasKey('Content-Range: bytes ' . $expectedHeader . '/' . StringHelper::strlen($content), array_flip(static::$headers));
- $this->assertTrue(in_array('Content-Type: text/plain', static::$headers));
- $this->assertTrue(in_array('Content-Length: ' . $length, static::$headers));
+ $this->response->sendFile('testFile.txt', $content, null, false);
+
+ $this->assertEquals($expectedFile, $this->response->content);
+ $this->assertEquals(206, $this->response->statusCode);
+ $headers = $this->response->headers;
+ $this->assertEquals("bytes", $headers->get('Accept-Ranges'));
+ $this->assertEquals("bytes " . $expectedHeader . '/' . StringHelper::strlen($content), $headers->get('Content-Range'));
+ $this->assertEquals('text/plain', $headers->get('Content-Type'));
+ $this->assertEquals("$length", $headers->get('Content-Length'));
}
public function wrongRanges()
@@ -90,21 +67,11 @@ class ResponseTest extends \yiiunit\TestCase
$content = $this->generateTestFileContent();
$_SERVER['HTTP_RANGE'] = 'bytes=' . $rangeHeader;
- $this->runSendFile('testFile.txt', $content, null);
+ $this->response->sendFile('testFile.txt', $content, null, false);
}
protected function generateTestFileContent()
{
return '12ёжик3456798áèabcdefghijklmnopqrstuvwxyz!"§$%&/(ёжик)=?';
}
-
- protected function runSendFile($fileName, $content, $mimeType)
- {
- ob_start();
- ob_implicit_flush(false);
- $response = new Response();
- $response->sendFile($fileName, $content, $mimeType, false);
- $file = ob_get_clean();
- return $file;
- }
}
diff --git a/tests/unit/framework/web/UrlManagerTest.php b/tests/unit/framework/web/UrlManagerTest.php
index 0f08790..7da8f34 100644
--- a/tests/unit/framework/web/UrlManagerTest.php
+++ b/tests/unit/framework/web/UrlManagerTest.php
@@ -248,4 +248,58 @@ class UrlManagerTest extends TestCase
$result = $manager->parseRequest($request);
$this->assertFalse($result);
}
+
+ public function testParseRESTRequest()
+ {
+ $manager = new UrlManager(array(
+ 'cache' => null,
+ ));
+ $request = new Request;
+
+ // pretty URL rules
+ $manager = new UrlManager(array(
+ 'enablePrettyUrl' => true,
+ 'cache' => null,
+ 'rules' => array(
+ 'PUT,POST post//' => 'post/create',
+ 'DELETE post/' => 'post/delete',
+ 'post//' => 'post/view',
+ 'POST/GET' => 'post/get',
+ ),
+ ));
+ // matching pathinfo GET request
+ $_SERVER['REQUEST_METHOD'] = 'GET';
+ $request->pathInfo = 'post/123/this+is+sample';
+ $result = $manager->parseRequest($request);
+ $this->assertEquals(array('post/view', array('id' => '123', 'title' => 'this+is+sample')), $result);
+ // matching pathinfo PUT/POST request
+ $_SERVER['REQUEST_METHOD'] = 'PUT';
+ $request->pathInfo = 'post/123/this+is+sample';
+ $result = $manager->parseRequest($request);
+ $this->assertEquals(array('post/create', array('id' => '123', 'title' => 'this+is+sample')), $result);
+ $_SERVER['REQUEST_METHOD'] = 'POST';
+ $request->pathInfo = 'post/123/this+is+sample';
+ $result = $manager->parseRequest($request);
+ $this->assertEquals(array('post/create', array('id' => '123', 'title' => 'this+is+sample')), $result);
+
+ // no wrong matching
+ $_SERVER['REQUEST_METHOD'] = 'POST';
+ $request->pathInfo = 'POST/GET';
+ $result = $manager->parseRequest($request);
+ $this->assertEquals(array('post/get', array()), $result);
+
+ // createUrl should ignore REST rules
+ $this->mockApplication(array(
+ 'components' => array(
+ 'request' => array(
+ 'hostInfo' => 'http://localhost/',
+ 'baseUrl' => '/app'
+ )
+ )
+ ), \yii\web\Application::className());
+ $this->assertEquals('/app/post/delete?id=123', $manager->createUrl('post/delete', array('id' => 123)));
+ $this->destroyApplication();
+
+ unset($_SERVER['REQUEST_METHOD']);
+ }
}