From bcc833200a9861eabf43d7ebe1d5cacf71ca1de4 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 11 Apr 2013 17:40:23 -0400 Subject: [PATCH 01/53] simplified Yii::import(). --- framework/YiiBase.php | 81 +++++++++----------------------- framework/base/Module.php | 13 ----- framework/validators/UniqueValidator.php | 2 +- 3 files changed, 23 insertions(+), 73 deletions(-) diff --git a/framework/YiiBase.php b/framework/YiiBase.php index 6df998b..7b3acee 100644 --- a/framework/YiiBase.php +++ b/framework/YiiBase.php @@ -107,73 +107,36 @@ class YiiBase } /** - * Imports a class or a directory. + * Imports a class by its alias. * - * Importing a class is like including the corresponding class file. - * The main difference is that importing a class is much lighter because it only - * includes the class file when the class is referenced in the code the first time. + * This method is provided to support autoloading of non-namespaced classes. + * Such a class can be specified in terms of an alias. For example, the alias `@old/code/Sample` + * may represent the `Sample` class under the directory `@old/code` (a path alias). * - * Importing a directory will add the directory to the front of the [[classPath]] array. - * When [[autoload()]] is loading an unknown class, it will search in the directories - * specified in [[classPath]] to find the corresponding class file to include. - * For this reason, if multiple directories are imported, the directories imported later - * will take precedence in class file searching. + * By importing a class, the class is put in an internal storage such that when + * the class is used for the first time, the class autoloader will be able to + * find the corresponding class file and include it. For this reason, this method + * is much lighter than `include()`. * - * The same class or directory can be imported multiple times. Only the first importing - * will count. Importing a directory does not import any of its subdirectories. + * You may import the same class multiple times. Only the first importing will count. * - * To import a class or a directory, one can use either path alias or class name (can be namespaced): - * - * - `@app/components/GoogleMap`: importing the `GoogleMap` class with a path alias; - * - `@app/components/*`: importing the whole `components` directory with a path alias; - * - `GoogleMap`: importing the `GoogleMap` class with a class name. [[autoload()]] will be used - * when this class is used for the first time. - * - * @param string $alias path alias or a simple class name to be imported - * @param boolean $forceInclude whether to include the class file immediately. If false, the class file - * will be included only when the class is being used. This parameter is used only when - * the path alias refers to a class. - * @return string the class name or the directory that this alias refers to - * @throws Exception if the path alias is invalid + * @param string $alias the class to be imported. This may be either a class alias or a fully-qualified class name. + * If the latter, it will be returned back without change. + * @return string the actual class name that `$alias` refers to + * @throws Exception if the alias is invalid */ - public static function import($alias, $forceInclude = false) + public static function import($alias) { - if (isset(self::$_imported[$alias])) { - return self::$_imported[$alias]; - } - - if ($alias[0] !== '@') { - // a simple class name - if (class_exists($alias, false) || interface_exists($alias, false)) { - return self::$_imported[$alias] = $alias; - } - if ($forceInclude && static::autoload($alias)) { - self::$_imported[$alias] = $alias; - } + if (strncmp($alias, '@', 1)) { return $alias; - } - - $className = basename($alias); - $isClass = $className !== '*'; - - if ($isClass && (class_exists($className, false) || interface_exists($className, false))) { - return self::$_imported[$alias] = $className; - } - - $path = static::getAlias(dirname($alias)); - - if ($isClass) { - if ($forceInclude) { - require($path . "/$className.php"); + } else { + $alias = static::getAlias($alias); + if (!isset(self::$_imported[$alias])) { + $className = basename($alias); self::$_imported[$alias] = $className; - } else { - self::$classMap[$className] = $path . DIRECTORY_SEPARATOR . "$className.php"; + self::$classMap[$className] = $alias . '.php'; } - return $className; - } else { - // a directory - array_unshift(self::$classPath, $path); - return self::$_imported[$alias] = $path; + return self::$_imported[$alias]; } } @@ -357,7 +320,7 @@ class YiiBase } if (!class_exists($class, false)) { - $class = static::import($class, true); + $class = static::import($class); } $class = ltrim($class, '\\'); diff --git a/framework/base/Module.php b/framework/base/Module.php index 2ccf61d..74c848b 100644 --- a/framework/base/Module.php +++ b/framework/base/Module.php @@ -286,19 +286,6 @@ abstract class Module extends Component } /** - * Imports the specified path aliases. - * This method is provided so that you can import a set of path aliases when configuring a module. - * The path aliases will be imported by calling [[Yii::import()]]. - * @param array $aliases list of path aliases to be imported - */ - public function setImport($aliases) - { - foreach ($aliases as $alias) { - Yii::import($alias); - } - } - - /** * 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. diff --git a/framework/validators/UniqueValidator.php b/framework/validators/UniqueValidator.php index fa55df7..2240e0a 100644 --- a/framework/validators/UniqueValidator.php +++ b/framework/validators/UniqueValidator.php @@ -60,7 +60,7 @@ class UniqueValidator extends Validator } /** @var $className \yii\db\ActiveRecord */ - $className = $this->className === null ? get_class($object) : \Yii::import($this->className); + $className = $this->className === null ? get_class($object) : Yii::import($this->className); $attributeName = $this->attributeName === null ? $attribute : $this->attributeName; $table = $className::getTableSchema(); From f22dd82fb6426982c85d1a852cfc385494419f39 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 11 Apr 2013 20:30:55 -0400 Subject: [PATCH 02/53] refactored autoloader. --- framework/YiiBase.php | 79 +++++++++++++++++++------------ framework/base/Module.php | 13 ++--- framework/base/UnknownClassException.php | 26 ++++++++++ framework/base/UnknownMethodException.php | 2 +- framework/web/User.php | 4 +- 5 files changed, 85 insertions(+), 39 deletions(-) create mode 100644 framework/base/UnknownClassException.php diff --git a/framework/YiiBase.php b/framework/YiiBase.php index 7b3acee..ceaeda1 100644 --- a/framework/YiiBase.php +++ b/framework/YiiBase.php @@ -7,6 +7,7 @@ use yii\base\Exception; use yii\base\InvalidConfigException; use yii\base\InvalidParamException; +use yii\base\UnknownClassException; use yii\logging\Logger; /** @@ -54,13 +55,10 @@ class YiiBase */ public static $classMap = array(); /** - * @var array list of directories where Yii will search for new classes to be included. - * The first directory in the array will be searched first, and so on. - * This property mainly affects how [[autoload]] works. - * @see import - * @see autoload + * @var boolean whether to search PHP include_path when autoloading unknown classes. + * You may want to turn this off if you are also using autoloaders from other libraries. */ - public static $classPath = array(); + public static $enableIncludePath = true; /** * @var yii\console\Application|yii\web\Application the application instance */ @@ -214,8 +212,8 @@ class YiiBase /** * Class autoload loader. - * This method is invoked automatically when the execution encounters an unknown class. - * The method will attempt to include the class file as follows: + * This method is invoked automatically when PHP sees an unknown class. + * The method will attempt to include the class file according to the following procedure: * * 1. Search in [[classMap]]; * 2. If the class is namespaced (e.g. `yii\base\Component`), it will attempt @@ -224,43 +222,64 @@ class YiiBase * 3. If the class is named in PEAR style (e.g. `PHPUnit_Framework_TestCase`), * it will attempt to include the file associated with the corresponding path alias * (e.g. `@PHPUnit/Framework/TestCase.php`); - * 4. Search in [[classPath]]; + * 4. Search PHP include_path for the actual class file if [[enableIncludePath]] is true; * 5. Return false so that other autoloaders have chance to include the class file. * * @param string $className class name * @return boolean whether the class has been loaded successfully - * @throws Exception if the class file does not exist + * @throws InvalidConfigException if the class file does not exist + * @throws UnknownClassException if the class does not exist in the class file */ public static function autoload($className) { $className = ltrim($className, '\\'); if (isset(self::$classMap[$className])) { - $classFile = self::$classMap[$className]; + $classFile = static::getAlias(self::$classMap[$className]); + if (!is_file($classFile)) { + throw new InvalidConfigException("Class file does not exist: $classFile"); + } } else { + // follow PSR-0 to determine the class file if (($pos = strrpos($className, '\\')) !== false) { // namespaced class, e.g. yii\base\Component - $classFile = str_replace('\\', '/', substr($className, 0, $pos + 1)) + $path = str_replace('\\', '/', substr($className, 0, $pos + 1)) . str_replace('_', '/', substr($className, $pos + 1)) . '.php'; } else { - $classFile = str_replace('_', '/', $className) . '.php'; + $path = str_replace('_', '/', $className) . '.php'; } - if (strpos($classFile, '/') !== false) { - // make it into a path alias - $classFile = '@' . $classFile; + + // try via path alias first + if (strpos($path, '/') !== false) { + $fullPath = static::getAlias('@' . $path, false); + if ($fullPath !== false && is_file($fullPath)) { + $classFile = $fullPath; + } } - } - $classFile = static::getAlias($classFile); - if ($classFile !== false && is_file($classFile)) { - include($classFile); - if (class_exists($className, false) || interface_exists($className, false)) { - return true; - } else { - throw new Exception("Unable to find '$className' in file: $classFile"); + // search include_path + if (!isset($classFile) && self::$enableIncludePath) { + foreach (array_unique(explode(PATH_SEPARATOR, get_include_path())) as $basePath) { + $fullPath = $basePath . '/' . $path; + if (is_file($fullPath)) { + $classFile = $fullPath; + break; + } + } + } + + if (!isset($classFile)) { + // return false to let other autoloaders to try loading the class + return false; } + } + + include($classFile); + + if (class_exists($className, false) || interface_exists($className, false)) { + return true; } else { - return false; + throw new UnknownClassException("Unable to find '$className' in file: $classFile"); } } @@ -268,16 +287,16 @@ class YiiBase * Creates a new object using the given configuration. * * The configuration can be either a string or an array. - * If a string, it is treated as the *object type*; if an array, - * it must contain a `class` element specifying the *object type*, and + * If a string, it is treated as the *object class*; if an array, + * it must contain a `class` element specifying the *object class*, and * the rest of the name-value pairs in the array will be used to initialize * the corresponding object properties. * - * The object type can be either a class name or the [[getAlias|alias]] of + * The object type can be either a class name or the [[getAlias()|alias]] of * the class. For example, * - * - `\app\components\GoogleMap`: fully-qualified namespaced class. - * - `@app/components/GoogleMap`: an alias + * - `app\components\GoogleMap`: fully-qualified namespaced class. + * - `@app/components/GoogleMap`: an alias, used for non-namespaced class. * * Below are some usage examples: * diff --git a/framework/base/Module.php b/framework/base/Module.php index 74c848b..0b2bd16 100644 --- a/framework/base/Module.php +++ b/framework/base/Module.php @@ -593,14 +593,15 @@ abstract class Module extends Component } elseif (preg_match('/^[a-z0-9\\-_]+$/', $id)) { $className = StringHelper::id2camel($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 (class_exists($className)) { - if (is_subclass_of($className, 'yii\base\Controller')) { - $controller = new $className($id, $this); - } elseif (YII_DEBUG && !is_subclass_of($className, 'yii\base\Controller')) { - throw new InvalidConfigException("Controller class must extend from \\yii\\base\\Controller."); - } + 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."); } } diff --git a/framework/base/UnknownClassException.php b/framework/base/UnknownClassException.php new file mode 100644 index 0000000..ac44746 --- /dev/null +++ b/framework/base/UnknownClassException.php @@ -0,0 +1,26 @@ + + * @since 2.0 + */ +class UnknownClassException extends Exception +{ + /** + * @return string the user-friendly name of this exception + */ + public function getName() + { + return \Yii::t('yii|Unknown Class'); + } +} + diff --git a/framework/base/UnknownMethodException.php b/framework/base/UnknownMethodException.php index 29bedca..440e76e 100644 --- a/framework/base/UnknownMethodException.php +++ b/framework/base/UnknownMethodException.php @@ -8,7 +8,7 @@ namespace yii\base; /** - * UnknownMethodException represents an exception caused by accessing unknown object methods. + * UnknownMethodException represents an exception caused by accessing an unknown object method. * * @author Qiang Xue * @since 2.0 diff --git a/framework/web/User.php b/framework/web/User.php index 4dc2607..435b606 100644 --- a/framework/web/User.php +++ b/framework/web/User.php @@ -32,7 +32,7 @@ class User extends Component const EVENT_AFTER_LOGOUT = 'afterLogout'; /** - * @var string the class name of the [[identity]] object. + * @var string the class name or alias of the [[identity]] object. */ public $identityClass; /** @@ -131,7 +131,7 @@ class User extends Component $this->_identity = null; } else { /** @var $class Identity */ - $class = $this->identityClass; + $class = Yii::import($this->identityClass); $this->_identity = $class::findIdentity($id); } } From 7599d7860c030788d0e2db086ae41e45cf67c5b6 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 11 Apr 2013 22:55:31 -0400 Subject: [PATCH 03/53] refactored getAlias and setAlias. --- framework/YiiBase.php | 118 ++++++++++++++++++++++++----------- framework/caching/FileCache.php | 4 +- tests/unit/framework/YiiBaseTest.php | 36 ++++++++++- 3 files changed, 116 insertions(+), 42 deletions(-) diff --git a/framework/YiiBase.php b/framework/YiiBase.php index ceaeda1..fb2967a 100644 --- a/framework/YiiBase.php +++ b/framework/YiiBase.php @@ -141,15 +141,26 @@ class YiiBase /** * Translates a path alias into an actual path. * - * The path alias can be either a root alias registered via [[setAlias]] or an - * alias starting with a root alias (e.g. `@yii/base/Component.php`). - * In the latter case, the root alias will be replaced by the corresponding registered path - * and the remaining part will be appended to it. + * The translation is done according to the following procedure: * - * In case the given parameter is not an alias (i.e., not starting with '@'), - * it will be returned back without change. + * 1. If the given alias does not start with '@', it is returned back without change; + * 2. Otherwise, look for the longest registered alias that matches the beginning part + * of the given alias. If it exists, replace the matching part of the given alias with + * the corresponding registered path. + * 3. Throw an exception or return false, depending on the `$throwException` parameter. + * + * For example, by default '@yii' is registered as the alias to the Yii framework directory, + * say '/path/to/yii'. The alias '@yii/web' would then be translated into '/path/to/yii/web'. + * + * If you have registered two aliases '@foo' and '@foo/bar'. Then translating '@foo/bar/config' + * would replace the part '@foo/bar' (instead of '@foo') with the corresponding registered path. + * This is because the longest alias takes precedence. + * + * However, if the alias to be translated is '@foo/barbar/config', then '@foo' will be replaced + * instead of '@foo/bar', because '/' serves as the boundary character. + * + * Note, this method does not check if the returned path exists or not. * - * Note, this method does not ensure the existence of the resulting path. * @param string $alias the alias to be translated. * @param boolean $throwException whether to throw an exception if the given alias is invalid. * If this is false and an invalid alias is given, false will be returned by this method. @@ -159,18 +170,26 @@ class YiiBase */ public static function getAlias($alias, $throwException = true) { - if (is_string($alias)) { - if (isset(self::$aliases[$alias])) { - return self::$aliases[$alias]; - } elseif ($alias === '' || $alias[0] !== '@') { // not an alias - return $alias; - } elseif (($pos = strpos($alias, '/')) !== false || ($pos = strpos($alias, '\\')) !== false) { - $rootAlias = substr($alias, 0, $pos); - if (isset(self::$aliases[$rootAlias])) { - return self::$aliases[$alias] = self::$aliases[$rootAlias] . substr($alias, $pos); + if (strncmp($alias, '@', 1)) { + // not an alias + return $alias; + } + + $pos = strpos($alias, '/'); + $root = $pos === false ? $alias : substr($alias, 0, $pos); + + if (isset(self::$aliases[$root])) { + if (is_string(self::$aliases[$root])) { + return $pos === false ? self::$aliases[$root] : self::$aliases[$root] . substr($alias, $pos); + } else { + foreach (self::$aliases[$root] as $name => $path) { + if (strpos($alias . '/', $name . '/') === 0) { + return $path . substr($alias, strlen($name)); + } } } } + if ($throwException) { throw new InvalidParamException("Invalid path alias: $alias"); } else { @@ -181,32 +200,61 @@ class YiiBase /** * Registers a path alias. * - * A path alias is a short name representing a path (a file path, a URL, etc.) - * A path alias must start with '@' (e.g. '@yii'). + * A path alias is a short name representing a long path (a file path, a URL, etc.) + * For example, we use '@yii' as the alias of the path to the Yii framework directory. * - * Note that this method neither checks the existence of the path nor normalizes the path. - * Any trailing '/' and '\' characters in the path will be trimmed. + * A path alias must start with the character '@' so that it can be easily differentiated + * from non-alias paths. * - * @param string $alias the alias name (e.g. "@yii"). It should start with a '@' character - * and should NOT contain the forward slash "/" or the backward slash "\". - * @param string $path the path corresponding to the alias. This can be + * Note that this method does not check if the given path exists or not. All it does is + * to associate the alias with the path. + * + * Any trailing '/' and '\' characters in the given path will be trimmed. + * + * @param string $alias the alias name (e.g. "@yii"). It must start with a '@' character. + * It may contain the forward slash '/' which serves as boundary character when performing + * alias translation by [[getAlias()]]. + * @param string $path the path corresponding to the alias. Trailing '/' and '\' characters + * will be trimmed. This can be * * - a directory or a file path (e.g. `/tmp`, `/tmp/main.txt`) * - a URL (e.g. `http://www.yiiframework.com`) * - a path alias (e.g. `@yii/base`). In this case, the path alias will be converted into the * actual path first by calling [[getAlias()]]. * - * @throws Exception if $path is an invalid alias + * @throws InvalidParamException the alias does not start with '@', or if $path is an invalid alias. * @see getAlias */ public static function setAlias($alias, $path) { - if ($path === null) { - unset(self::$aliases[$alias]); - } elseif (strncmp($path, '@', 1)) { - self::$aliases[$alias] = rtrim($path, '\\/'); - } else { - self::$aliases[$alias] = static::getAlias($path); + if (strncmp($alias, '@', 1)) { + throw new InvalidParamException('The alias must start with the "@" character.'); + } + $pos = strpos($alias, '/'); + $root = $pos === false ? $alias : substr($alias, 0, $pos); + if ($path !== null) { + $path = strncmp($path, '@', 1) ? rtrim($path, '\\/') : static::getAlias($path); + if (!isset(self::$aliases[$root])) { + self::$aliases[$root] = $path; + } elseif (is_string(self::$aliases[$root])) { + if ($pos === false) { + self::$aliases[$root] = $path; + } else { + self::$aliases[$root] = array( + $alias => $path, + $root => self::$aliases[$root], + ); + } + } else { + self::$aliases[$root][$alias] = $path; + krsort(self::$aliases[$root]); + } + } elseif (isset(self::$aliases[$root])) { + if (is_array(self::$aliases[$root])) { + unset(self::$aliases[$root][$alias]); + } elseif ($pos === false) { + unset(self::$aliases[$root]); + } } } @@ -258,14 +306,8 @@ class YiiBase } // search include_path - if (!isset($classFile) && self::$enableIncludePath) { - foreach (array_unique(explode(PATH_SEPARATOR, get_include_path())) as $basePath) { - $fullPath = $basePath . '/' . $path; - if (is_file($fullPath)) { - $classFile = $fullPath; - break; - } - } + if (!isset($classFile) && self::$enableIncludePath && ($fullPath = stream_resolve_include_path($path)) !== false) { + $classFile = $fullPath; } if (!isset($classFile)) { diff --git a/framework/caching/FileCache.php b/framework/caching/FileCache.php index e565cad..cc1a871 100644 --- a/framework/caching/FileCache.php +++ b/framework/caching/FileCache.php @@ -7,7 +7,7 @@ namespace yii\caching; -use yii\base\InvalidConfigException; +use Yii; /** * FileCache implements a cache component using files. @@ -51,7 +51,7 @@ class FileCache extends Cache public function init() { parent::init(); - $this->cachePath = \Yii::getAlias($this->cachePath); + $this->cachePath = Yii::getAlias($this->cachePath); if (!is_dir($this->cachePath)) { mkdir($this->cachePath, 0777, true); } diff --git a/tests/unit/framework/YiiBaseTest.php b/tests/unit/framework/YiiBaseTest.php index df12bf9..47474f2 100644 --- a/tests/unit/framework/YiiBaseTest.php +++ b/tests/unit/framework/YiiBaseTest.php @@ -1,6 +1,7 @@ aliases = Yii::$aliases; + } + + public function tearDown() + { + Yii::$aliases = $this->aliases; + } + public function testAlias() { + $this->assertEquals(YII_PATH, Yii::getAlias('@yii')); + + Yii::$aliases = array(); + $this->assertFalse(Yii::getAlias('@yii', false)); + + Yii::setAlias('@yii', '/yii/framework'); + $this->assertEquals('/yii/framework', Yii::getAlias('@yii')); + $this->assertEquals('/yii/framework/test/file', Yii::getAlias('@yii/test/file')); + Yii::setAlias('@yii/gii', '/yii/gii'); + $this->assertEquals('/yii/framework', Yii::getAlias('@yii')); + $this->assertEquals('/yii/framework/test/file', Yii::getAlias('@yii/test/file')); + $this->assertEquals('/yii/gii', Yii::getAlias('@yii/gii')); + $this->assertEquals('/yii/gii/file', Yii::getAlias('@yii/gii/file')); + + Yii::setAlias('@tii', '@yii/test'); + $this->assertEquals('/yii/framework/test', Yii::getAlias('@tii')); + Yii::setAlias('@yii', null); + $this->assertFalse(Yii::getAlias('@yii', false)); + $this->assertEquals('/yii/gii/file', Yii::getAlias('@yii/gii/file')); } public function testGetVersion() { - echo \Yii::getVersion(); + echo Yii::getVersion(); $this->assertTrue((boolean)preg_match('~\d+\.\d+(?:\.\d+)?(?:-\w+)?~', \Yii::getVersion())); } public function testPowered() { - $this->assertTrue(is_string(\Yii::powered())); + $this->assertTrue(is_string(Yii::powered())); } } From 3616278ada48fb316d7fbd1b57073518fad9e863 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Fri, 12 Apr 2013 07:08:01 -0400 Subject: [PATCH 04/53] typo. --- framework/base/Application.php | 2 +- framework/base/ErrorException.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/base/Application.php b/framework/base/Application.php index e1c1d60..1dad257 100644 --- a/framework/base/Application.php +++ b/framework/base/Application.php @@ -165,7 +165,7 @@ class Application extends Module if (YII_ENABLE_ERROR_HANDLER) { $error = error_get_last(); - if (ErrorException::isFatalErorr($error)) { + if (ErrorException::isFatalError($error)) { unset($this->_memoryReserve); $exception = new ErrorException($error['message'], $error['type'], $error['type'], $error['file'], $error['line']); $this->logException($exception); diff --git a/framework/base/ErrorException.php b/framework/base/ErrorException.php index 93390e7..b41e9ed 100644 --- a/framework/base/ErrorException.php +++ b/framework/base/ErrorException.php @@ -79,7 +79,7 @@ class ErrorException extends Exception * @param array $error error got from error_get_last() * @return bool if error is one of fatal type */ - public static function isFatalErorr($error) + public static function isFatalError($error) { return isset($error['type']) && in_array($error['type'], array(E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING)); } From c1428a174add65820038463f80d2c8e0a9a8bb6a Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Fri, 12 Apr 2013 07:25:37 -0400 Subject: [PATCH 05/53] minor cleanup. --- framework/views/error.php | 2 +- framework/views/exception.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/views/error.php b/framework/views/error.php index 893640a..548d04b 100644 --- a/framework/views/error.php +++ b/framework/views/error.php @@ -4,7 +4,7 @@ * @var \yii\base\ErrorHandler $context */ $context = $this->context; -$title = $context->htmlEncode($exception instanceof \yii\base\Exception || $exception instanceof \yii\base\ErrorException ? $exception->getName() : get_class($exception)); +$title = $context->htmlEncode($exception instanceof \yii\base\Exception ? $exception->getName() : get_class($exception)); ?> diff --git a/framework/views/exception.php b/framework/views/exception.php index db29302..f2aced0 100644 --- a/framework/views/exception.php +++ b/framework/views/exception.php @@ -4,7 +4,7 @@ * @var \yii\base\ErrorHandler $context */ $context = $this->context; -$title = $context->htmlEncode($exception instanceof \yii\base\Exception || $exception instanceof \yii\base\ErrorException ? $exception->getName().' ('.get_class($exception).')' : get_class($exception)); +$title = $context->htmlEncode($exception instanceof \yii\base\Exception ? $exception->getName().' ('.get_class($exception).')' : get_class($exception)); ?> From 428d912811654ef57992af226c0be9167a81527c Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sat, 13 Apr 2013 13:05:54 -0400 Subject: [PATCH 06/53] script WIP --- framework/base/Application.php | 27 ++++++++++++++++++++++++++- framework/base/ViewContent.php | 17 +++++++++++++++-- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/framework/base/Application.php b/framework/base/Application.php index 1dad257..1053210 100644 --- a/framework/base/Application.php +++ b/framework/base/Application.php @@ -87,7 +87,6 @@ class Application extends Module */ public $layout = 'main'; - private $_runtimePath; private $_ended = false; /** @@ -224,6 +223,8 @@ class Application extends Module return 0; } + private $_runtimePath; + /** * Returns the directory that stores runtime files. * @return string the directory that stores runtime files. Defaults to 'protected/runtime'. @@ -251,6 +252,30 @@ class Application extends Module } } + private $_vendorPath; + + /** + * Returns the directory that stores vendor files. + * @return string the directory that stores vendor files. Defaults to 'protected/vendor'. + */ + public function getVendorPath() + { + if ($this->_vendorPath !== null) { + $this->setVendorPath($this->getBasePath() . DIRECTORY_SEPARATOR . 'vendor'); + } + return $this->_vendorPath; + } + + /** + * Sets the directory that stores vendor files. + * @param string $path the directory that stores vendor files. + * @throws InvalidConfigException if the directory does not exist + */ + public function setVendorPath($path) + { + $this->_vendorPath = FileHelper::ensureDirectory($path); + } + /** * Returns the time zone used by this application. * This is a simple wrapper of PHP function date_default_timezone_get(). diff --git a/framework/base/ViewContent.php b/framework/base/ViewContent.php index 6a3b489..cf7684b 100644 --- a/framework/base/ViewContent.php +++ b/framework/base/ViewContent.php @@ -7,6 +7,8 @@ namespace yii\base; +use Yii; + /** * @author Qiang Xue * @since 2.0 @@ -43,7 +45,6 @@ class ViewContent extends Component * ~~~ */ public $bundles; - public $title; public $metaTags; public $linkTags; @@ -68,9 +69,21 @@ class ViewContent extends Component $this->jsFiles = null; } - public function registerBundle($name) + public function renderScripts($pos) { + } + public function registerBundle($name) + { + if (!isset($this->bundles[$name])) { + $am = Yii::$app->assets; + $bundle = $am->getBundle($name); + if ($bundle !== null) { + $this->bundles[$name] = $bundle; + } else { + throw new InvalidConfigException("Asset bundle does not exist: $name"); + } + } } public function getMetaTag($key) From 5cd7961a38d96708468d77a2ea6f4a894eef5241 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sat, 13 Apr 2013 14:44:32 -0400 Subject: [PATCH 07/53] renamed clip to block. --- framework/base/View.php | 26 ++++++------- framework/base/ViewContent.php | 86 +++++------------------------------------- framework/widgets/Block.php | 49 ++++++++++++++++++++++++ framework/widgets/Clip.php | 51 ------------------------- 4 files changed, 72 insertions(+), 140 deletions(-) create mode 100644 framework/widgets/Block.php delete mode 100644 framework/widgets/Clip.php diff --git a/framework/base/View.php b/framework/base/View.php index 8b1f4ef..d1a3c5f 100644 --- a/framework/base/View.php +++ b/framework/base/View.php @@ -53,11 +53,12 @@ class View extends Component */ public $theme; /** - * @var array a list of named output clips. You can call [[beginClip()]] and [[endClip()]] + * @var array a list of named output blocks. The keys are the block names and the values + * are the corresponding block content. You can call [[beginBlock()]] and [[endBlock()]] * to capture small fragments of a view. They can be later accessed at somewhere else * through this property. */ - public $clips; + public $blocks; /** * @var Widget[] the widgets that are currently being rendered (not ended). This property * is maintained by [[beginWidget()]] and [[endWidget()]] methods. Do not modify it. @@ -350,26 +351,25 @@ class View extends Component } /** - * Begins recording a clip. - * This method is a shortcut to beginning [[yii\widgets\Clip]] - * @param string $id the clip ID. - * @param boolean $renderInPlace whether to render the clip content in place. - * Defaults to false, meaning the captured clip will not be displayed. - * @return \yii\widgets\Clip the Clip widget instance - * @see \yii\widgets\Clip + * Begins recording a block. + * This method is a shortcut to beginning [[yii\widgets\Block]] + * @param string $id the block ID. + * @param boolean $renderInPlace whether to render the block content in place. + * Defaults to false, meaning the captured block will not be displayed. + * @return \yii\widgets\Block the Block widget instance */ - public function beginClip($id, $renderInPlace = false) + public function beginBlock($id, $renderInPlace = false) { - return $this->beginWidget('yii\widgets\Clip', array( + return $this->beginWidget('yii\widgets\Block', array( 'id' => $id, 'renderInPlace' => $renderInPlace, )); } /** - * Ends recording a clip. + * Ends recording a block. */ - public function endClip() + public function endBlock() { $this->endWidget(); } diff --git a/framework/base/ViewContent.php b/framework/base/ViewContent.php index cf7684b..cea3c7c 100644 --- a/framework/base/ViewContent.php +++ b/framework/base/ViewContent.php @@ -49,9 +49,13 @@ class ViewContent extends Component public $metaTags; public $linkTags; public $css; - public $js; public $cssFiles; + public $js; public $jsFiles; + public $jsInHead; + public $jsFilesInHead; + public $jsInBody; + public $jsFilesInBody; public function populate($content) { @@ -64,86 +68,16 @@ class ViewContent extends Component $this->metaTags = null; $this->linkTags = null; $this->css = null; - $this->js = null; $this->cssFiles = null; + $this->js = null; $this->jsFiles = null; + $this->jsInHead = null; + $this->jsFilesInHead = null; + $this->jsInBody = null; + $this->jsFilesInBody = null; } public function renderScripts($pos) { } - - public function registerBundle($name) - { - if (!isset($this->bundles[$name])) { - $am = Yii::$app->assets; - $bundle = $am->getBundle($name); - if ($bundle !== null) { - $this->bundles[$name] = $bundle; - } else { - throw new InvalidConfigException("Asset bundle does not exist: $name"); - } - } - } - - public function getMetaTag($key) - { - return isset($this->metaTags[$key]) ? $this->metaTags[$key] : null; - } - - public function setMetaTag($key, $tag) - { - $this->metaTags[$key] = $tag; - } - - public function getLinkTag($key) - { - return isset($this->linkTags[$key]) ? $this->linkTags[$key] : null; - } - - public function setLinkTag($key, $tag) - { - $this->linkTags[$key] = $tag; - } - - public function getCss($key) - { - return isset($this->css[$key]) ? $this->css[$key]: null; - } - - public function setCss($key, $css) - { - $this->css[$key] = $css; - } - - public function getCssFile($key) - { - return isset($this->cssFiles[$key]) ? $this->cssFiles[$key]: null; - } - - public function setCssFile($key, $file) - { - $this->cssFiles[$key] = $file; - } - - public function getJs($key, $position = self::POS_END) - { - return isset($this->js[$position][$key]) ? $this->js[$position][$key] : null; - } - - public function setJs($key, $js, $position = self::POS_END) - { - $this->js[$position][$key] = $js; - } - - public function getJsFile($key, $position = self::POS_END) - { - return isset($this->jsFiles[$position][$key]) ? $this->jsFiles[$position][$key] : null; - } - - public function setJsFile($key, $file, $position = self::POS_END) - { - $this->jsFiles[$position][$key] = $file; - } - } \ No newline at end of file diff --git a/framework/widgets/Block.php b/framework/widgets/Block.php new file mode 100644 index 0000000..d6f7317 --- /dev/null +++ b/framework/widgets/Block.php @@ -0,0 +1,49 @@ + + * @since 2.0 + */ +class Block extends Widget +{ + /** + * @var string the ID of this block. + */ + public $id; + /** + * @var boolean whether to render the block content in place. Defaults to false, + * meaning the captured block content will not be displayed. + */ + public $renderInPlace = false; + + /** + * Starts recording a block. + */ + public function init() + { + ob_start(); + ob_implicit_flush(false); + } + + /** + * Ends recording a block. + * This method stops output buffering and saves the rendering result as a named block in the controller. + */ + public function run() + { + $block = ob_get_clean(); + if ($this->renderInPlace) { + echo $block; + } + $this->view->blocks[$this->id] = $block; + } +} \ No newline at end of file diff --git a/framework/widgets/Clip.php b/framework/widgets/Clip.php deleted file mode 100644 index f321209..0000000 --- a/framework/widgets/Clip.php +++ /dev/null @@ -1,51 +0,0 @@ - - * @since 2.0 - */ -class Clip extends Widget -{ - /** - * @var string the ID of this clip. - */ - public $id; - /** - * @var boolean whether to render the clip content in place. Defaults to false, - * meaning the captured clip will not be displayed. - */ - public $renderInPlace = false; - - /** - * Starts recording a clip. - */ - public function init() - { - ob_start(); - ob_implicit_flush(false); - } - - /** - * Ends recording a clip. - * This method stops output buffering and saves the rendering result as a named clip in the controller. - */ - public function run() - { - $clip = ob_get_clean(); - if ($this->renderClip) { - echo $clip; - } - $this->view->clips[$this->id] = $clip; - } -} \ No newline at end of file From 9183e837532805f933a2a3addf83b0265ad16636 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sat, 13 Apr 2013 14:51:50 -0400 Subject: [PATCH 08/53] restructured helper classes --- framework/helpers/ArrayHelper.php | 323 +--------- framework/helpers/ConsoleColor.php | 449 +------------- framework/helpers/FileHelper.php | 255 +------- framework/helpers/Html.php | 965 +---------------------------- framework/helpers/SecurityHelper.php | 245 +------- framework/helpers/StringHelper.php | 108 +--- framework/helpers/VarDumper.php | 108 +--- framework/helpers/base/ArrayHelper.php | 340 +++++++++++ framework/helpers/base/ConsoleColor.php | 470 ++++++++++++++ framework/helpers/base/FileHelper.php | 274 +++++++++ framework/helpers/base/Html.php | 981 ++++++++++++++++++++++++++++++ framework/helpers/base/SecurityHelper.php | 272 +++++++++ framework/helpers/base/StringHelper.php | 125 ++++ framework/helpers/base/VarDumper.php | 134 ++++ framework/helpers/base/mimeTypes.php | 187 ++++++ framework/helpers/mimeTypes.php | 187 ------ 16 files changed, 2790 insertions(+), 2633 deletions(-) create mode 100644 framework/helpers/base/ArrayHelper.php create mode 100644 framework/helpers/base/ConsoleColor.php create mode 100644 framework/helpers/base/FileHelper.php create mode 100644 framework/helpers/base/Html.php create mode 100644 framework/helpers/base/SecurityHelper.php create mode 100644 framework/helpers/base/StringHelper.php create mode 100644 framework/helpers/base/VarDumper.php create mode 100644 framework/helpers/base/mimeTypes.php delete mode 100644 framework/helpers/mimeTypes.php diff --git a/framework/helpers/ArrayHelper.php b/framework/helpers/ArrayHelper.php index 65fa962..3061717 100644 --- a/framework/helpers/ArrayHelper.php +++ b/framework/helpers/ArrayHelper.php @@ -7,9 +7,6 @@ namespace yii\helpers; -use Yii; -use yii\base\InvalidParamException; - /** * ArrayHelper provides additional array functionality you can use in your * application. @@ -17,324 +14,6 @@ use yii\base\InvalidParamException; * @author Qiang Xue * @since 2.0 */ -class ArrayHelper +class ArrayHelper extends base\ArrayHelper { - /** - * Merges two or more arrays into one recursively. - * If each array has an element with the same string key value, the latter - * will overwrite the former (different from array_merge_recursive). - * Recursive merging will be conducted if both arrays have an element of array - * type and are having the same key. - * For integer-keyed elements, the elements from the latter array will - * be appended to the former array. - * @param array $a array to be merged to - * @param array $b array to be merged from. You can specify additional - * arrays via third argument, fourth argument etc. - * @return array the merged array (the original arrays are not changed.) - */ - public static function merge($a, $b) - { - $args = func_get_args(); - $res = array_shift($args); - while ($args !== array()) { - $next = array_shift($args); - foreach ($next as $k => $v) { - if (is_integer($k)) { - isset($res[$k]) ? $res[] = $v : $res[$k] = $v; - } elseif (is_array($v) && isset($res[$k]) && is_array($res[$k])) { - $res[$k] = self::merge($res[$k], $v); - } else { - $res[$k] = $v; - } - } - } - return $res; - } - - /** - * Retrieves the value of an array element or object property with the given key or property name. - * If the key does not exist in the array, the default value will be returned instead. - * - * Below are some usage examples, - * - * ~~~ - * // working with array - * $username = \yii\helpers\ArrayHelper::getValue($_POST, 'username'); - * // working with object - * $username = \yii\helpers\ArrayHelper::getValue($user, 'username'); - * // working with anonymous function - * $fullName = \yii\helpers\ArrayHelper::getValue($user, function($user, $defaultValue) { - * return $user->firstName . ' ' . $user->lastName; - * }); - * ~~~ - * - * @param array|object $array array or object to extract value from - * @param string|\Closure $key key name of the array element, or property name of the object, - * or an anonymous function returning the value. The anonymous function signature should be: - * `function($array, $defaultValue)`. - * @param mixed $default the default value to be returned if the specified key does not exist - * @return mixed the value of the - */ - public static function getValue($array, $key, $default = null) - { - if ($key instanceof \Closure) { - return $key($array, $default); - } elseif (is_array($array)) { - return isset($array[$key]) || array_key_exists($key, $array) ? $array[$key] : $default; - } else { - return $array->$key; - } - } - - /** - * Indexes an array according to a specified key. - * The input array should be multidimensional or an array of objects. - * - * The key can be a key name of the sub-array, a property name of object, or an anonymous - * function which returns the key value given an array element. - * - * If a key value is null, the corresponding array element will be discarded and not put in the result. - * - * For example, - * - * ~~~ - * $array = array( - * array('id' => '123', 'data' => 'abc'), - * array('id' => '345', 'data' => 'def'), - * ); - * $result = ArrayHelper::index($array, 'id'); - * // the result is: - * // array( - * // '123' => array('id' => '123', 'data' => 'abc'), - * // '345' => array('id' => '345', 'data' => 'def'), - * // ) - * - * // using anonymous function - * $result = ArrayHelper::index($array, function(element) { - * return $element['id']; - * }); - * ~~~ - * - * @param array $array the array that needs to be indexed - * @param string|\Closure $key the column name or anonymous function whose result will be used to index the array - * @return array the indexed array - */ - public static function index($array, $key) - { - $result = array(); - foreach ($array as $element) { - $value = static::getValue($element, $key); - $result[$value] = $element; - } - return $result; - } - - /** - * Returns the values of a specified column in an array. - * The input array should be multidimensional or an array of objects. - * - * For example, - * - * ~~~ - * $array = array( - * array('id' => '123', 'data' => 'abc'), - * array('id' => '345', 'data' => 'def'), - * ); - * $result = ArrayHelper::getColumn($array, 'id'); - * // the result is: array( '123', '345') - * - * // using anonymous function - * $result = ArrayHelper::getColumn($array, function(element) { - * return $element['id']; - * }); - * ~~~ - * - * @param array $array - * @param string|\Closure $name - * @param boolean $keepKeys whether to maintain the array keys. If false, the resulting array - * will be re-indexed with integers. - * @return array the list of column values - */ - public static function getColumn($array, $name, $keepKeys = true) - { - $result = array(); - if ($keepKeys) { - foreach ($array as $k => $element) { - $result[$k] = static::getValue($element, $name); - } - } else { - foreach ($array as $element) { - $result[] = static::getValue($element, $name); - } - } - - return $result; - } - - /** - * Builds a map (key-value pairs) from a multidimensional array or an array of objects. - * The `$from` and `$to` parameters specify the key names or property names to set up the map. - * Optionally, one can further group the map according to a grouping field `$group`. - * - * For example, - * - * ~~~ - * $array = array( - * array('id' => '123', 'name' => 'aaa', 'class' => 'x'), - * array('id' => '124', 'name' => 'bbb', 'class' => 'x'), - * array('id' => '345', 'name' => 'ccc', 'class' => 'y'), - * ); - * - * $result = ArrayHelper::map($array, 'id', 'name'); - * // the result is: - * // array( - * // '123' => 'aaa', - * // '124' => 'bbb', - * // '345' => 'ccc', - * // ) - * - * $result = ArrayHelper::map($array, 'id', 'name', 'class'); - * // the result is: - * // array( - * // 'x' => array( - * // '123' => 'aaa', - * // '124' => 'bbb', - * // ), - * // 'y' => array( - * // '345' => 'ccc', - * // ), - * // ) - * ~~~ - * - * @param array $array - * @param string|\Closure $from - * @param string|\Closure $to - * @param string|\Closure $group - * @return array - */ - public static function map($array, $from, $to, $group = null) - { - $result = array(); - foreach ($array as $element) { - $key = static::getValue($element, $from); - $value = static::getValue($element, $to); - if ($group !== null) { - $result[static::getValue($element, $group)][$key] = $value; - } else { - $result[$key] = $value; - } - } - return $result; - } - - /** - * Sorts an array of objects or arrays (with the same structure) by one or several keys. - * @param array $array the array to be sorted. The array will be modified after calling this method. - * @param string|\Closure|array $key the key(s) to be sorted by. This refers to a key name of the sub-array - * elements, a property name of the objects, or an anonymous function returning the values for comparison - * purpose. The anonymous function signature should be: `function($item)`. - * To sort by multiple keys, provide an array of keys here. - * @param boolean|array $ascending whether to sort in ascending or descending order. When - * sorting by multiple keys with different ascending orders, use an array of ascending flags. - * @param integer|array $sortFlag the PHP sort flag. Valid values include: - * `SORT_REGULAR`, `SORT_NUMERIC`, `SORT_STRING`, and `SORT_STRING | SORT_FLAG_CASE`. The last - * value is for sorting strings in case-insensitive manner. Please refer to - * See [PHP manual](http://php.net/manual/en/function.sort.php) for more details. - * When sorting by multiple keys with different sort flags, use an array of sort flags. - * @throws InvalidParamException if the $ascending or $sortFlag parameters do not have - * correct number of elements as that of $key. - */ - public static function multisort(&$array, $key, $ascending = true, $sortFlag = SORT_REGULAR) - { - $keys = is_array($key) ? $key : array($key); - if (empty($keys) || empty($array)) { - return; - } - $n = count($keys); - if (is_scalar($ascending)) { - $ascending = array_fill(0, $n, $ascending); - } elseif (count($ascending) !== $n) { - throw new InvalidParamException('The length of $ascending parameter must be the same as that of $keys.'); - } - if (is_scalar($sortFlag)) { - $sortFlag = array_fill(0, $n, $sortFlag); - } elseif (count($sortFlag) !== $n) { - throw new InvalidParamException('The length of $ascending parameter must be the same as that of $keys.'); - } - $args = array(); - foreach ($keys as $i => $key) { - $flag = $sortFlag[$i]; - if ($flag == (SORT_STRING | SORT_FLAG_CASE)) { - $flag = SORT_STRING; - $column = array(); - foreach (static::getColumn($array, $key) as $k => $value) { - $column[$k] = strtolower($value); - } - $args[] = $column; - } else { - $args[] = static::getColumn($array, $key); - } - $args[] = $ascending[$i] ? SORT_ASC : SORT_DESC; - $args[] = $flag; - } - $args[] = &$array; - call_user_func_array('array_multisort', $args); - } - - /** - * Encodes special characters in an array of strings into HTML entities. - * Both the array keys and values will be encoded. - * If a value is an array, this method will also encode it recursively. - * @param array $data data to be encoded - * @param boolean $valuesOnly whether to encode array values only. If false, - * both the array keys and array values will be encoded. - * @param string $charset the charset that the data is using. If not set, - * [[\yii\base\Application::charset]] will be used. - * @return array the encoded data - * @see http://www.php.net/manual/en/function.htmlspecialchars.php - */ - public static function htmlEncode($data, $valuesOnly = true, $charset = null) - { - if ($charset === null) { - $charset = Yii::$app->charset; - } - $d = array(); - foreach ($data as $key => $value) { - if (!$valuesOnly && is_string($key)) { - $key = htmlspecialchars($key, ENT_QUOTES, $charset); - } - if (is_string($value)) { - $d[$key] = htmlspecialchars($value, ENT_QUOTES, $charset); - } elseif (is_array($value)) { - $d[$key] = static::htmlEncode($value, $charset); - } - } - return $d; - } - - /** - * Decodes HTML entities into the corresponding characters in an array of strings. - * Both the array keys and values will be decoded. - * If a value is an array, this method will also decode it recursively. - * @param array $data data to be decoded - * @param boolean $valuesOnly whether to decode array values only. If false, - * both the array keys and array values will be decoded. - * @return array the decoded data - * @see http://www.php.net/manual/en/function.htmlspecialchars-decode.php - */ - public static function htmlDecode($data, $valuesOnly = true) - { - $d = array(); - foreach ($data as $key => $value) { - if (!$valuesOnly && is_string($key)) { - $key = htmlspecialchars_decode($key, ENT_QUOTES); - } - if (is_string($value)) { - $d[$key] = htmlspecialchars_decode($value, ENT_QUOTES); - } elseif (is_array($value)) { - $d[$key] = static::htmlDecode($value); - } - } - return $d; - } } \ No newline at end of file diff --git a/framework/helpers/ConsoleColor.php b/framework/helpers/ConsoleColor.php index 429aeb1..794b9c8 100644 --- a/framework/helpers/ConsoleColor.php +++ b/framework/helpers/ConsoleColor.php @@ -18,453 +18,6 @@ namespace yii\helpers; * @author Carsten Brandt * @since 2.0 */ -class ConsoleColor +class ConsoleColor extends base\ConsoleColor { - const FG_BLACK = 30; - const FG_RED = 31; - const FG_GREEN = 32; - const FG_YELLOW = 33; - const FG_BLUE = 34; - const FG_PURPLE = 35; - const FG_CYAN = 36; - const FG_GREY = 37; - - const BG_BLACK = 40; - const BG_RED = 41; - const BG_GREEN = 42; - const BG_YELLOW = 43; - const BG_BLUE = 44; - const BG_PURPLE = 45; - const BG_CYAN = 46; - const BG_GREY = 47; - - const BOLD = 1; - const ITALIC = 3; - const UNDERLINE = 4; - const BLINK = 5; - const NEGATIVE = 7; - const CONCEALED = 8; - const CROSSED_OUT = 9; - const FRAMED = 51; - const ENCIRCLED = 52; - const OVERLINED = 53; - - /** - * Moves the terminal cursor up by sending ANSI control code CUU to the terminal. - * If the cursor is already at the edge of the screen, this has no effect. - * @param integer $rows number of rows the cursor should be moved up - */ - public static function moveCursorUp($rows=1) - { - echo "\033[" . (int) $rows . 'A'; - } - - /** - * Moves the terminal cursor down by sending ANSI control code CUD to the terminal. - * If the cursor is already at the edge of the screen, this has no effect. - * @param integer $rows number of rows the cursor should be moved down - */ - public static function moveCursorDown($rows=1) - { - echo "\033[" . (int) $rows . 'B'; - } - - /** - * Moves the terminal cursor forward by sending ANSI control code CUF to the terminal. - * If the cursor is already at the edge of the screen, this has no effect. - * @param integer $steps number of steps the cursor should be moved forward - */ - public static function moveCursorForward($steps=1) - { - echo "\033[" . (int) $steps . 'C'; - } - - /** - * Moves the terminal cursor backward by sending ANSI control code CUB to the terminal. - * If the cursor is already at the edge of the screen, this has no effect. - * @param integer $steps number of steps the cursor should be moved backward - */ - public static function moveCursorBackward($steps=1) - { - echo "\033[" . (int) $steps . 'D'; - } - - /** - * Moves the terminal cursor to the beginning of the next line by sending ANSI control code CNL to the terminal. - * @param integer $lines number of lines the cursor should be moved down - */ - public static function moveCursorNextLine($lines=1) - { - echo "\033[" . (int) $lines . 'E'; - } - - /** - * Moves the terminal cursor to the beginning of the previous line by sending ANSI control code CPL to the terminal. - * @param integer $lines number of lines the cursor should be moved up - */ - public static function moveCursorPrevLine($lines=1) - { - echo "\033[" . (int) $lines . 'F'; - } - - /** - * Moves the cursor to an absolute position given as column and row by sending ANSI control code CUP or CHA to the terminal. - * @param integer $column 1-based column number, 1 is the left edge of the screen. - * @param integer|null $row 1-based row number, 1 is the top edge of the screen. if not set, will move cursor only in current line. - */ - public static function moveCursorTo($column, $row=null) - { - if ($row === null) { - echo "\033[" . (int) $column . 'G'; - } else { - echo "\033[" . (int) $row . ';' . (int) $column . 'H'; - } - } - - /** - * Scrolls whole page up by sending ANSI control code SU to the terminal. - * New lines are added at the bottom. This is not supported by ANSI.SYS used in windows. - * @param int $lines number of lines to scroll up - */ - public static function scrollUp($lines=1) - { - echo "\033[".(int)$lines."S"; - } - - /** - * Scrolls whole page down by sending ANSI control code SD to the terminal. - * New lines are added at the top. This is not supported by ANSI.SYS used in windows. - * @param int $lines number of lines to scroll down - */ - public static function scrollDown($lines=1) - { - echo "\033[".(int)$lines."T"; - } - - /** - * Saves the current cursor position by sending ANSI control code SCP to the terminal. - * Position can then be restored with {@link restoreCursorPosition}. - */ - public static function saveCursorPosition() - { - echo "\033[s"; - } - - /** - * Restores the cursor position saved with {@link saveCursorPosition} by sending ANSI control code RCP to the terminal. - */ - public static function restoreCursorPosition() - { - echo "\033[u"; - } - - /** - * Hides the cursor by sending ANSI DECTCEM code ?25l to the terminal. - * Use {@link showCursor} to bring it back. - * Do not forget to show cursor when your application exits. Cursor might stay hidden in terminal after exit. - */ - public static function hideCursor() - { - echo "\033[?25l"; - } - - /** - * Will show a cursor again when it has been hidden by {@link hideCursor} by sending ANSI DECTCEM code ?25h to the terminal. - */ - public static function showCursor() - { - echo "\033[?25h"; - } - - /** - * Clears entire screen content by sending ANSI control code ED with argument 2 to the terminal. - * Cursor position will not be changed. - * **Note:** ANSI.SYS implementation used in windows will reset cursor position to upper left corner of the screen. - */ - public static function clearScreen() - { - echo "\033[2J"; - } - - /** - * Clears text from cursor to the beginning of the screen by sending ANSI control code ED with argument 1 to the terminal. - * Cursor position will not be changed. - */ - public static function clearScreenBeforeCursor() - { - echo "\033[1J"; - } - - /** - * Clears text from cursor to the end of the screen by sending ANSI control code ED with argument 0 to the terminal. - * Cursor position will not be changed. - */ - public static function clearScreenAfterCursor() - { - echo "\033[0J"; - } - - /** - * Clears the line, the cursor is currently on by sending ANSI control code EL with argument 2 to the terminal. - * Cursor position will not be changed. - */ - public static function clearLine() - { - echo "\033[2K"; - } - - /** - * Clears text from cursor position to the beginning of the line by sending ANSI control code EL with argument 1 to the terminal. - * Cursor position will not be changed. - */ - public static function clearLineBeforeCursor() - { - echo "\033[1K"; - } - - /** - * Clears text from cursor position to the end of the line by sending ANSI control code EL with argument 0 to the terminal. - * Cursor position will not be changed. - */ - public static function clearLineAfterCursor() - { - echo "\033[0K"; - } - - /** - * Will send ANSI format for following output - * - * You can pass any of the FG_*, BG_* and TEXT_* constants and also xterm256ColorBg - * TODO: documentation - */ - public static function ansiStyle() - { - echo "\033[" . implode(';', func_get_args()) . 'm'; - } - - /** - * Will return a string formatted with the given ANSI style - * - * See {@link ansiStyle} for possible arguments. - * @param string $string the string to be formatted - * @return string - */ - public static function ansiStyleString($string) - { - $args = func_get_args(); - array_shift($args); - $code = implode(';', $args); - return "\033[0m" . ($code !== '' ? "\033[" . $code . "m" : '') . $string."\033[0m"; - } - - //const COLOR_XTERM256 = 38;// http://en.wikipedia.org/wiki/Talk:ANSI_escape_code#xterm-256colors - public static function xterm256ColorFg($i) // TODO naming! - { - return '38;5;'.$i; - } - - public static function xterm256ColorBg($i) // TODO naming! - { - return '48;5;'.$i; - } - - /** - * Usage: list($w, $h) = ConsoleHelper::getScreenSize(); - * - * @return array - */ - public static function getScreenSize() - { - // TODO implement - return array(150,50); - } - - /** - * resets any ansi style set by previous method {@link ansiStyle} - * Any output after this is will have default text style. - */ - public static function reset() - { - echo "\033[0m"; - } - - /** - * Strips ANSI control codes from a string - * - * @param string $string String to strip - * @return string - */ - public static function strip($string) - { - return preg_replace('/\033\[[\d;]+m/', '', $string); // TODO currently only strips color - } - - // TODO refactor and review - public static function ansiToHtml($string) - { - $tags = 0; - return preg_replace_callback('/\033\[[\d;]+m/', function($ansi) use (&$tags) { - $styleA = array(); - foreach(explode(';', $ansi) as $controlCode) - { - switch($controlCode) - { - case static::FG_BLACK: $style = array('color' => '#000000'); break; - case static::FG_BLUE: $style = array('color' => '#000078'); break; - case static::FG_CYAN: $style = array('color' => '#007878'); break; - case static::FG_GREEN: $style = array('color' => '#007800'); break; - case static::FG_GREY: $style = array('color' => '#787878'); break; - case static::FG_PURPLE: $style = array('color' => '#780078'); break; - case static::FG_RED: $style = array('color' => '#780000'); break; - case static::FG_YELLOW: $style = array('color' => '#787800'); break; - case static::BG_BLACK: $style = array('background-color' => '#000000'); break; - case static::BG_BLUE: $style = array('background-color' => '#000078'); break; - case static::BG_CYAN: $style = array('background-color' => '#007878'); break; - case static::BG_GREEN: $style = array('background-color' => '#007800'); break; - case static::BG_GREY: $style = array('background-color' => '#787878'); break; - case static::BG_PURPLE: $style = array('background-color' => '#780078'); break; - case static::BG_RED: $style = array('background-color' => '#780000'); break; - case static::BG_YELLOW: $style = array('background-color' => '#787800'); break; - case static::BOLD: $style = array('font-weight' => 'bold'); break; - case static::ITALIC: $style = array('font-style' => 'italic'); break; - case static::UNDERLINE: $style = array('text-decoration' => array('underline')); break; - case static::OVERLINED: $style = array('text-decoration' => array('overline')); break; - case static::CROSSED_OUT:$style = array('text-decoration' => array('line-through')); break; - case static::BLINK: $style = array('text-decoration' => array('blink')); break; - case static::NEGATIVE: // ??? - case static::CONCEALED: - case static::ENCIRCLED: - case static::FRAMED: - // TODO allow resetting codes - break; - case 0: // ansi reset - $return = ''; - for($n=$tags; $tags>0; $tags--) { - $return .= ''; - } - return $return; - } - - $styleA = ArrayHelper::merge($styleA, $style); - } - $styleString[] = array(); - foreach($styleA as $name => $content) { - if ($name === 'text-decoration') { - $content = implode(' ', $content); - } - $styleString[] = $name.':'.$content; - } - $tags++; - return ' $ds, '\\' => $ds)), $ds); - } - - /** - * Returns the localized version of a specified file. - * - * The searching is based on the specified language code. In particular, - * a file with the same name will be looked for under the subdirectory - * whose name is same as the language code. For example, given the file "path/to/view.php" - * and language code "zh_cn", the localized file will be looked for as - * "path/to/zh_cn/view.php". If the file is not found, the original file - * will be returned. - * - * If the target and the source language codes are the same, - * the original file will be returned. - * - * For consistency, it is recommended that the language code is given - * in lower case and in the format of LanguageID_RegionID (e.g. "en_us"). - * - * @param string $file the original file - * @param string $language the target language that the file should be localized to. - * If not set, the value of [[\yii\base\Application::language]] will be used. - * @param string $sourceLanguage the language that the original file is in. - * If not set, the value of [[\yii\base\Application::sourceLanguage]] will be used. - * @return string the matching localized file, or the original file if the localized version is not found. - * If the target and the source language codes are the same, the original file will be returned. - */ - public static function localize($file, $language = null, $sourceLanguage = null) - { - if ($language === null) { - $language = \Yii::$app->language; - } - if ($sourceLanguage === null) { - $sourceLanguage = \Yii::$app->sourceLanguage; - } - if ($language === $sourceLanguage) { - return $file; - } - $desiredFile = dirname($file) . DIRECTORY_SEPARATOR . $sourceLanguage . DIRECTORY_SEPARATOR . basename($file); - return is_file($desiredFile) ? $desiredFile : $file; - } - - /** - * Determines the MIME type of the specified file. - * This method will first try to determine the MIME type based on - * [finfo_open](http://php.net/manual/en/function.finfo-open.php). If this doesn't work, it will - * fall back to [[getMimeTypeByExtension()]]. - * @param string $file the file name. - * @param string $magicFile name of the optional magic database file, usually something like `/path/to/magic.mime`. - * This will be passed as the second parameter to [finfo_open](http://php.net/manual/en/function.finfo-open.php). - * @param boolean $checkExtension whether to use the file extension to determine the MIME type in case - * `finfo_open()` cannot determine it. - * @return string the MIME type (e.g. `text/plain`). Null is returned if the MIME type cannot be determined. - */ - public static function getMimeType($file, $magicFile = null, $checkExtension = true) - { - if (function_exists('finfo_open')) { - $info = finfo_open(FILEINFO_MIME_TYPE, $magicFile); - if ($info && ($result = finfo_file($info, $file)) !== false) { - return $result; - } - } - - return $checkExtension ? self::getMimeTypeByExtension($file) : null; - } - - /** - * Determines the MIME type based on the extension name of the specified file. - * This method will use a local map between extension names and MIME types. - * @param string $file the file name. - * @param string $magicFile the path of the file that contains all available MIME type information. - * If this is not set, the default file aliased by `@yii/util/mimeTypes.php` will be used. - * @return string the MIME type. Null is returned if the MIME type cannot be determined. - */ - public static function getMimeTypeByExtension($file, $magicFile = null) - { - if ($magicFile === null) { - $magicFile = \Yii::getAlias('@yii/util/mimeTypes.php'); - } - $mimeTypes = require($magicFile); - if (($ext = pathinfo($file, PATHINFO_EXTENSION)) !== '') { - $ext = strtolower($ext); - if (isset($mimeTypes[$ext])) { - return $mimeTypes[$ext]; - } - } - return null; - } - - /** - * Copies a list of files from one place to another. - * @param array $fileList the list of files to be copied (name=>spec). - * The array keys are names displayed during the copy process, and array values are specifications - * for files to be copied. Each array value must be an array of the following structure: - *
    - *
  • source: required, the full path of the file/directory to be copied from
  • - *
  • target: required, the full path of the file/directory to be copied to
  • - *
  • callback: optional, the callback to be invoked when copying a file. The callback function - * should be declared as follows: - *
    -	 *   function foo($source,$params)
    -	 *   
    - * where $source parameter is the source file path, and the content returned - * by the function will be saved into the target file.
  • - *
  • params: optional, the parameters to be passed to the callback
  • - *
- * @see buildFileList - */ - public static function copyFiles($fileList) - { - $overwriteAll = false; - foreach($fileList as $name=>$file) { - $source = strtr($file['source'], '/\\', DIRECTORY_SEPARATOR); - $target = strtr($file['target'], '/\\', DIRECTORY_SEPARATOR); - $callback = isset($file['callback']) ? $file['callback'] : null; - $params = isset($file['params']) ? $file['params'] : null; - - if(is_dir($source)) { - try { - self::ensureDirectory($target); - } - catch (Exception $e) { - mkdir($target, true, 0777); - } - continue; - } - - if($callback !== null) { - $content = call_user_func($callback, $source, $params); - } - else { - $content = file_get_contents($source); - } - if(is_file($target)) { - if($content === file_get_contents($target)) { - echo " unchanged $name\n"; - continue; - } - if($overwriteAll) { - echo " overwrite $name\n"; - } - else { - echo " exist $name\n"; - echo " ...overwrite? [Yes|No|All|Quit] "; - $answer = trim(fgets(STDIN)); - if(!strncasecmp($answer, 'q', 1)) { - return; - } - elseif(!strncasecmp($answer, 'y', 1)) { - echo " overwrite $name\n"; - } - elseif(!strncasecmp($answer, 'a', 1)) { - echo " overwrite $name\n"; - $overwriteAll = true; - } - else { - echo " skip $name\n"; - continue; - } - } - } - else { - try { - self::ensureDirectory(dirname($target)); - } - catch (Exception $e) { - mkdir(dirname($target), true, 0777); - } - echo " generate $name\n"; - } - file_put_contents($target, $content); - } - } - - /** - * Builds the file list of a directory. - * This method traverses through the specified directory and builds - * a list of files and subdirectories that the directory contains. - * The result of this function can be passed to {@link copyFiles}. - * @param string $sourceDir the source directory - * @param string $targetDir the target directory - * @param string $baseDir base directory - * @param array $ignoreFiles list of the names of files that should - * be ignored in list building process. Argument available since 1.1.11. - * @param array $renameMap hash array of file names that should be - * renamed. Example value: array('1.old.txt'=>'2.new.txt'). - * @return array the file list (see {@link copyFiles}) - */ - public static function buildFileList($sourceDir, $targetDir, $baseDir='', $ignoreFiles=array(), $renameMap=array()) - { - $list = array(); - $handle = opendir($sourceDir); - while(($file = readdir($handle)) !== false) { - if(in_array($file, array('.', '..', '.svn', '.gitignore')) || in_array($file, $ignoreFiles)) { - continue; - } - $sourcePath = $sourceDir.DIRECTORY_SEPARATOR.$file; - $targetPath = $targetDir.DIRECTORY_SEPARATOR.strtr($file, $renameMap); - $name = $baseDir === '' ? $file : $baseDir.'/'.$file; - $list[$name] = array( - 'source' => $sourcePath, - 'target' => $targetPath, - ); - if(is_dir($sourcePath)) { - $list = array_merge($list, self::buildFileList($sourcePath, $targetPath, $name, $ignoreFiles, $renameMap)); - } - } - closedir($handle); - return $list; - } } \ No newline at end of file diff --git a/framework/helpers/Html.php b/framework/helpers/Html.php index b2ca576..b3a0743 100644 --- a/framework/helpers/Html.php +++ b/framework/helpers/Html.php @@ -7,975 +7,12 @@ namespace yii\helpers; -use Yii; -use yii\base\InvalidParamException; - /** * Html provides a set of static methods for generating commonly used HTML tags. * * @author Qiang Xue * @since 2.0 */ -class Html +class Html extends base\Html { - /** - * @var boolean whether to close void (empty) elements. Defaults to true. - * @see voidElements - */ - public static $closeVoidElements = true; - /** - * @var array list of void elements (element name => 1) - * @see closeVoidElements - * @see http://www.w3.org/TR/html-markup/syntax.html#void-element - */ - public static $voidElements = array( - 'area' => 1, - 'base' => 1, - 'br' => 1, - 'col' => 1, - 'command' => 1, - 'embed' => 1, - 'hr' => 1, - 'img' => 1, - 'input' => 1, - 'keygen' => 1, - 'link' => 1, - 'meta' => 1, - 'param' => 1, - 'source' => 1, - 'track' => 1, - 'wbr' => 1, - ); - /** - * @var boolean whether to show the values of boolean attributes in element tags. - * If false, only the attribute names will be generated. - * @see booleanAttributes - */ - public static $showBooleanAttributeValues = true; - /** - * @var array list of boolean attributes. The presence of a boolean attribute on - * an element represents the true value, and the absence of the attribute represents the false value. - * @see showBooleanAttributeValues - * @see http://www.w3.org/TR/html5/infrastructure.html#boolean-attributes - */ - public static $booleanAttributes = array( - 'async' => 1, - 'autofocus' => 1, - 'autoplay' => 1, - 'checked' => 1, - 'controls' => 1, - 'declare' => 1, - 'default' => 1, - 'defer' => 1, - 'disabled' => 1, - 'formnovalidate' => 1, - 'hidden' => 1, - 'ismap' => 1, - 'loop' => 1, - 'multiple' => 1, - 'muted' => 1, - 'nohref' => 1, - 'noresize' => 1, - 'novalidate' => 1, - 'open' => 1, - 'readonly' => 1, - 'required' => 1, - 'reversed' => 1, - 'scoped' => 1, - 'seamless' => 1, - 'selected' => 1, - 'typemustmatch' => 1, - ); - /** - * @var array the preferred order of attributes in a tag. This mainly affects the order of the attributes - * that are rendered by [[renderAttributes()]]. - */ - public static $attributeOrder = array( - 'type', - 'id', - 'class', - 'name', - 'value', - - 'href', - 'src', - 'action', - 'method', - - 'selected', - 'checked', - 'readonly', - 'disabled', - 'multiple', - - 'size', - 'maxlength', - 'width', - 'height', - 'rows', - 'cols', - - 'alt', - 'title', - 'rel', - 'media', - ); - - /** - * Encodes special characters into HTML entities. - * The [[yii\base\Application::charset|application charset]] will be used for encoding. - * @param string $content the content to be encoded - * @return string the encoded content - * @see decode - * @see http://www.php.net/manual/en/function.htmlspecialchars.php - */ - public static function encode($content) - { - return htmlspecialchars($content, ENT_QUOTES, Yii::$app->charset); - } - - /** - * Decodes special HTML entities back to the corresponding characters. - * This is the opposite of [[encode()]]. - * @param string $content the content to be decoded - * @return string the decoded content - * @see encode - * @see http://www.php.net/manual/en/function.htmlspecialchars-decode.php - */ - public static function decode($content) - { - return htmlspecialchars_decode($content, ENT_QUOTES); - } - - /** - * Generates a complete HTML tag. - * @param string $name the tag name - * @param string $content the content to be enclosed between the start and end tags. It will not be HTML-encoded. - * If this is coming from end users, you should consider [[encode()]] it to prevent XSS attacks. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated HTML tag - * @see beginTag - * @see endTag - */ - public static function tag($name, $content = '', $options = array()) - { - $html = '<' . $name . static::renderTagAttributes($options); - if (isset(static::$voidElements[strtolower($name)])) { - return $html . (static::$closeVoidElements ? ' />' : '>'); - } else { - return $html . ">$content"; - } - } - - /** - * Generates a start tag. - * @param string $name the tag name - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated start tag - * @see endTag - * @see tag - */ - public static function beginTag($name, $options = array()) - { - return '<' . $name . static::renderTagAttributes($options) . '>'; - } - - /** - * Generates an end tag. - * @param string $name the tag name - * @return string the generated end tag - * @see beginTag - * @see tag - */ - public static function endTag($name) - { - return ""; - } - - /** - * Encloses the given content within a CDATA tag. - * @param string $content the content to be enclosed within the CDATA tag - * @return string the CDATA tag with the enclosed content. - */ - public static function cdata($content) - { - return ''; - } - - /** - * Generates a style tag. - * @param string $content the style content - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * If the options does not contain "type", a "type" attribute with value "text/css" will be used. - * @return string the generated style tag - */ - public static function style($content, $options = array()) - { - if (!isset($options['type'])) { - $options['type'] = 'text/css'; - } - return static::tag('style', "/**/", $options); - } - - /** - * Generates a script tag. - * @param string $content the script content - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * If the options does not contain "type", a "type" attribute with value "text/javascript" will be rendered. - * @return string the generated script tag - */ - public static function script($content, $options = array()) - { - if (!isset($options['type'])) { - $options['type'] = 'text/javascript'; - } - return static::tag('script', "/**/", $options); - } - - /** - * Generates a link tag that refers to an external CSS file. - * @param array|string $url the URL of the external CSS file. This parameter will be processed by [[url()]]. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated link tag - * @see url - */ - public static function cssFile($url, $options = array()) - { - $options['rel'] = 'stylesheet'; - $options['type'] = 'text/css'; - $options['href'] = static::url($url); - return static::tag('link', '', $options); - } - - /** - * Generates a script tag that refers to an external JavaScript file. - * @param string $url the URL of the external JavaScript file. This parameter will be processed by [[url()]]. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated script tag - * @see url - */ - public static function jsFile($url, $options = array()) - { - $options['type'] = 'text/javascript'; - $options['src'] = static::url($url); - return static::tag('script', '', $options); - } - - /** - * Generates a form start tag. - * @param array|string $action the form action URL. This parameter will be processed by [[url()]]. - * @param string $method the form submission method, either "post" or "get" (case-insensitive) - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated form start tag. - * @see endForm - */ - public static function beginForm($action = '', $method = 'post', $options = array()) - { - $action = static::url($action); - - // query parameters in the action are ignored for GET method - // we use hidden fields to add them back - $hiddens = array(); - if (!strcasecmp($method, 'get') && ($pos = strpos($action, '?')) !== false) { - foreach (explode('&', substr($action, $pos + 1)) as $pair) { - if (($pos1 = strpos($pair, '=')) !== false) { - $hiddens[] = static::hiddenInput(urldecode(substr($pair, 0, $pos1)), urldecode(substr($pair, $pos1 + 1))); - } else { - $hiddens[] = static::hiddenInput(urldecode($pair), ''); - } - } - $action = substr($action, 0, $pos); - } - - $options['action'] = $action; - $options['method'] = $method; - $form = static::beginTag('form', $options); - if ($hiddens !== array()) { - $form .= "\n" . implode("\n", $hiddens); - } - - return $form; - } - - /** - * Generates a form end tag. - * @return string the generated tag - * @see beginForm - */ - public static function endForm() - { - return ''; - } - - /** - * Generates a hyperlink tag. - * @param string $text link body. It will NOT be HTML-encoded. Therefore you can pass in HTML code - * such as an image tag. If this is is coming from end users, you should consider [[encode()]] - * it to prevent XSS attacks. - * @param array|string|null $url the URL for the hyperlink tag. This parameter will be processed by [[url()]] - * and will be used for the "href" attribute of the tag. If this parameter is null, the "href" attribute - * will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated hyperlink - * @see url - */ - public static function a($text, $url = null, $options = array()) - { - if ($url !== null) { - $options['href'] = static::url($url); - } - return static::tag('a', $text, $options); - } - - /** - * Generates a mailto hyperlink. - * @param string $text link body. It will NOT be HTML-encoded. Therefore you can pass in HTML code - * such as an image tag. If this is is coming from end users, you should consider [[encode()]] - * it to prevent XSS attacks. - * @param string $email email address. If this is null, the first parameter (link body) will be treated - * as the email address and used. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated mailto link - */ - public static function mailto($text, $email = null, $options = array()) - { - return static::a($text, 'mailto:' . ($email === null ? $text : $email), $options); - } - - /** - * Generates an image tag. - * @param string $src the image URL. This parameter will be processed by [[url()]]. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated image tag - */ - public static function img($src, $options = array()) - { - $options['src'] = static::url($src); - if (!isset($options['alt'])) { - $options['alt'] = ''; - } - return static::tag('img', null, $options); - } - - /** - * Generates a label tag. - * @param string $content label text. It will NOT be HTML-encoded. Therefore you can pass in HTML code - * such as an image tag. If this is is coming from end users, you should consider [[encode()]] - * it to prevent XSS attacks. - * @param string $for the ID of the HTML element that this label is associated with. - * If this is null, the "for" attribute will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated label tag - */ - public static function label($content, $for = null, $options = array()) - { - $options['for'] = $for; - return static::tag('label', $content, $options); - } - - /** - * Generates a button tag. - * @param string $name the name attribute. If it is null, the name attribute will not be generated. - * @param string $value the value attribute. If it is null, the value attribute will not be generated. - * @param string $content the content enclosed within the button tag. It will NOT be HTML-encoded. - * Therefore you can pass in HTML code such as an image tag. If this is is coming from end users, - * you should consider [[encode()]] it to prevent XSS attacks. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * If the options does not contain "type", a "type" attribute with value "button" will be rendered. - * @return string the generated button tag - */ - public static function button($name = null, $value = null, $content = 'Button', $options = array()) - { - $options['name'] = $name; - $options['value'] = $value; - if (!isset($options['type'])) { - $options['type'] = 'button'; - } - return static::tag('button', $content, $options); - } - - /** - * Generates a submit button tag. - * @param string $name the name attribute. If it is null, the name attribute will not be generated. - * @param string $value the value attribute. If it is null, the value attribute will not be generated. - * @param string $content the content enclosed within the button tag. It will NOT be HTML-encoded. - * Therefore you can pass in HTML code such as an image tag. If this is is coming from end users, - * you should consider [[encode()]] it to prevent XSS attacks. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated submit button tag - */ - public static function submitButton($name = null, $value = null, $content = 'Submit', $options = array()) - { - $options['type'] = 'submit'; - return static::button($name, $value, $content, $options); - } - - /** - * Generates a reset button tag. - * @param string $name the name attribute. If it is null, the name attribute will not be generated. - * @param string $value the value attribute. If it is null, the value attribute will not be generated. - * @param string $content the content enclosed within the button tag. It will NOT be HTML-encoded. - * Therefore you can pass in HTML code such as an image tag. If this is is coming from end users, - * you should consider [[encode()]] it to prevent XSS attacks. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated reset button tag - */ - public static function resetButton($name = null, $value = null, $content = 'Reset', $options = array()) - { - $options['type'] = 'reset'; - return static::button($name, $value, $content, $options); - } - - /** - * Generates an input type of the given type. - * @param string $type the type attribute. - * @param string $name the name attribute. If it is null, the name attribute will not be generated. - * @param string $value the value attribute. If it is null, the value attribute will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated input tag - */ - public static function input($type, $name = null, $value = null, $options = array()) - { - $options['type'] = $type; - $options['name'] = $name; - $options['value'] = $value; - return static::tag('input', null, $options); - } - - /** - * Generates an input button. - * @param string $name the name attribute. - * @param string $value the value attribute. If it is null, the value attribute will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated button tag - */ - public static function buttonInput($name, $value = 'Button', $options = array()) - { - return static::input('button', $name, $value, $options); - } - - /** - * Generates a submit input button. - * @param string $name the name attribute. If it is null, the name attribute will not be generated. - * @param string $value the value attribute. If it is null, the value attribute will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated button tag - */ - public static function submitInput($name = null, $value = 'Submit', $options = array()) - { - return static::input('submit', $name, $value, $options); - } - - /** - * Generates a reset input button. - * @param string $name the name attribute. If it is null, the name attribute will not be generated. - * @param string $value the value attribute. If it is null, the value attribute will not be generated. - * @param array $options the attributes of the button tag. The values will be HTML-encoded using [[encode()]]. - * Attributes whose value is null will be ignored and not put in the tag returned. - * @return string the generated button tag - */ - public static function resetInput($name = null, $value = 'Reset', $options = array()) - { - return static::input('reset', $name, $value, $options); - } - - /** - * Generates a text input field. - * @param string $name the name attribute. - * @param string $value the value attribute. If it is null, the value attribute will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated button tag - */ - public static function textInput($name, $value = null, $options = array()) - { - return static::input('text', $name, $value, $options); - } - - /** - * Generates a hidden input field. - * @param string $name the name attribute. - * @param string $value the value attribute. If it is null, the value attribute will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated button tag - */ - public static function hiddenInput($name, $value = null, $options = array()) - { - return static::input('hidden', $name, $value, $options); - } - - /** - * Generates a password input field. - * @param string $name the name attribute. - * @param string $value the value attribute. If it is null, the value attribute will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated button tag - */ - public static function passwordInput($name, $value = null, $options = array()) - { - return static::input('password', $name, $value, $options); - } - - /** - * Generates a file input field. - * To use a file input field, you should set the enclosing form's "enctype" attribute to - * be "multipart/form-data". After the form is submitted, the uploaded file information - * can be obtained via $_FILES[$name] (see PHP documentation). - * @param string $name the name attribute. - * @param string $value the value attribute. If it is null, the value attribute will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated button tag - */ - public static function fileInput($name, $value = null, $options = array()) - { - return static::input('file', $name, $value, $options); - } - - /** - * Generates a text area input. - * @param string $name the input name - * @param string $value the input value. Note that it will be encoded using [[encode()]]. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated text area tag - */ - public static function textarea($name, $value = '', $options = array()) - { - $options['name'] = $name; - return static::tag('textarea', static::encode($value), $options); - } - - /** - * Generates a radio button input. - * @param string $name the name attribute. - * @param boolean $checked whether the radio button should be checked. - * @param string $value the value attribute. If it is null, the value attribute will not be rendered. - * @param array $options the tag options in terms of name-value pairs. The following options are supported: - * - * - uncheck: string, the value associated with the uncheck state of the radio button. When this attribute - * is present, a hidden input will be generated so that if the radio button is not checked and is submitted, - * the value of this attribute will still be submitted to the server via the hidden input. - * - * The rest of the options will be rendered as the attributes of the resulting tag. The values will - * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * @return string the generated radio button tag - */ - public static function radio($name, $checked = false, $value = '1', $options = array()) - { - $options['checked'] = $checked; - $options['value'] = $value; - if (isset($options['uncheck'])) { - // add a hidden field so that if the radio button is not selected, it still submits a value - $hidden = static::hiddenInput($name, $options['uncheck']); - unset($options['uncheck']); - } else { - $hidden = ''; - } - return $hidden . static::input('radio', $name, $value, $options); - } - - /** - * Generates a checkbox input. - * @param string $name the name attribute. - * @param boolean $checked whether the checkbox should be checked. - * @param string $value the value attribute. If it is null, the value attribute will not be rendered. - * @param array $options the tag options in terms of name-value pairs. The following options are supported: - * - * - uncheck: string, the value associated with the uncheck state of the checkbox. When this attribute - * is present, a hidden input will be generated so that if the checkbox is not checked and is submitted, - * the value of this attribute will still be submitted to the server via the hidden input. - * - * The rest of the options will be rendered as the attributes of the resulting tag. The values will - * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * @return string the generated checkbox tag - */ - public static function checkbox($name, $checked = false, $value = '1', $options = array()) - { - $options['checked'] = $checked; - $options['value'] = $value; - if (isset($options['uncheck'])) { - // add a hidden field so that if the checkbox is not selected, it still submits a value - $hidden = static::hiddenInput($name, $options['uncheck']); - unset($options['uncheck']); - } else { - $hidden = ''; - } - return $hidden . static::input('checkbox', $name, $value, $options); - } - - /** - * Generates a drop-down list. - * @param string $name the input name - * @param string $selection the selected value - * @param array $items the option data items. The array keys are option values, and the array values - * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). - * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. - * If you have a list of data models, you may convert them into the format described above using - * [[\yii\helpers\ArrayHelper::map()]]. - * - * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in - * the labels will also be HTML-encoded. - * @param array $options the tag options in terms of name-value pairs. The following options are supported: - * - * - prompt: string, a prompt text to be displayed as the first option; - * - options: array, the attributes for the select option tags. The array keys must be valid option values, - * and the array values are the extra attributes for the corresponding option tags. For example, - * - * ~~~ - * array( - * 'value1' => array('disabled' => true), - * 'value2' => array('label' => 'value 2'), - * ); - * ~~~ - * - * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', - * except that the array keys represent the optgroup labels specified in $items. - * - * The rest of the options will be rendered as the attributes of the resulting tag. The values will - * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * @return string the generated drop-down list tag - */ - public static function dropDownList($name, $selection = null, $items = array(), $options = array()) - { - $options['name'] = $name; - $selectOptions = static::renderSelectOptions($selection, $items, $options); - return static::tag('select', "\n" . $selectOptions . "\n", $options); - } - - /** - * Generates a list box. - * @param string $name the input name - * @param string|array $selection the selected value(s) - * @param array $items the option data items. The array keys are option values, and the array values - * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). - * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. - * If you have a list of data models, you may convert them into the format described above using - * [[\yii\helpers\ArrayHelper::map()]]. - * - * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in - * the labels will also be HTML-encoded. - * @param array $options the tag options in terms of name-value pairs. The following options are supported: - * - * - prompt: string, a prompt text to be displayed as the first option; - * - options: array, the attributes for the select option tags. The array keys must be valid option values, - * and the array values are the extra attributes for the corresponding option tags. For example, - * - * ~~~ - * array( - * 'value1' => array('disabled' => true), - * 'value2' => array('label' => 'value 2'), - * ); - * ~~~ - * - * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', - * except that the array keys represent the optgroup labels specified in $items. - * - unselect: string, the value that will be submitted when no option is selected. - * When this attribute is set, a hidden field will be generated so that if no option is selected in multiple - * mode, we can still obtain the posted unselect value. - * - * The rest of the options will be rendered as the attributes of the resulting tag. The values will - * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * @return string the generated list box tag - */ - public static function listBox($name, $selection = null, $items = array(), $options = array()) - { - if (!isset($options['size'])) { - $options['size'] = 4; - } - if (isset($options['multiple']) && $options['multiple'] && substr($name, -2) !== '[]') { - $name .= '[]'; - } - $options['name'] = $name; - if (isset($options['unselect'])) { - // add a hidden field so that if the list box has no option being selected, it still submits a value - if (substr($name, -2) === '[]') { - $name = substr($name, 0, -2); - } - $hidden = static::hiddenInput($name, $options['unselect']); - unset($options['unselect']); - } else { - $hidden = ''; - } - $selectOptions = static::renderSelectOptions($selection, $items, $options); - return $hidden . static::tag('select', "\n" . $selectOptions . "\n", $options); - } - - /** - * Generates a list of checkboxes. - * A checkbox list allows multiple selection, like [[listBox()]]. - * As a result, the corresponding submitted value is an array. - * @param string $name the name attribute of each checkbox. - * @param string|array $selection the selected value(s). - * @param array $items the data item used to generate the checkboxes. - * The array keys are the labels, while the array values are the corresponding checkbox values. - * Note that the labels will NOT be HTML-encoded, while the values will. - * @param array $options options (name => config) for the checkbox list. The following options are supported: - * - * - unselect: string, the value that should be submitted when none of the checkboxes is selected. - * By setting this option, a hidden input will be generated. - * - separator: string, the HTML code that separates items. - * - item: callable, a callback that can be used to customize the generation of the HTML code - * corresponding to a single item in $items. The signature of this callback must be: - * - * ~~~ - * function ($index, $label, $name, $checked, $value) - * ~~~ - * - * where $index is the zero-based index of the checkbox in the whole list; $label - * is the label for the checkbox; and $name, $value and $checked represent the name, - * value and the checked status of the checkbox input. - * @return string the generated checkbox list - */ - public static function checkboxList($name, $selection = null, $items = array(), $options = array()) - { - if (substr($name, -2) !== '[]') { - $name .= '[]'; - } - - $formatter = isset($options['item']) ? $options['item'] : null; - $lines = array(); - $index = 0; - foreach ($items as $value => $label) { - $checked = $selection !== null && - (!is_array($selection) && !strcmp($value, $selection) - || is_array($selection) && in_array($value, $selection)); - if ($formatter !== null) { - $lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value); - } else { - $lines[] = static::label(static::checkbox($name, $checked, $value) . ' ' . $label); - } - $index++; - } - - if (isset($options['unselect'])) { - // add a hidden field so that if the list box has no option being selected, it still submits a value - $name2 = substr($name, -2) === '[]' ? substr($name, 0, -2) : $name; - $hidden = static::hiddenInput($name2, $options['unselect']); - } else { - $hidden = ''; - } - $separator = isset($options['separator']) ? $options['separator'] : "\n"; - - return $hidden . implode($separator, $lines); - } - - /** - * Generates a list of radio buttons. - * A radio button list is like a checkbox list, except that it only allows single selection. - * @param string $name the name attribute of each radio button. - * @param string|array $selection the selected value(s). - * @param array $items the data item used to generate the radio buttons. - * The array keys are the labels, while the array values are the corresponding radio button values. - * Note that the labels will NOT be HTML-encoded, while the values will. - * @param array $options options (name => config) for the radio button list. The following options are supported: - * - * - unselect: string, the value that should be submitted when none of the radio buttons is selected. - * By setting this option, a hidden input will be generated. - * - separator: string, the HTML code that separates items. - * - item: callable, a callback that can be used to customize the generation of the HTML code - * corresponding to a single item in $items. The signature of this callback must be: - * - * ~~~ - * function ($index, $label, $name, $checked, $value) - * ~~~ - * - * where $index is the zero-based index of the radio button in the whole list; $label - * is the label for the radio button; and $name, $value and $checked represent the name, - * value and the checked status of the radio button input. - * @return string the generated radio button list - */ - public static function radioList($name, $selection = null, $items = array(), $options = array()) - { - $formatter = isset($options['item']) ? $options['item'] : null; - $lines = array(); - $index = 0; - foreach ($items as $value => $label) { - $checked = $selection !== null && - (!is_array($selection) && !strcmp($value, $selection) - || is_array($selection) && in_array($value, $selection)); - if ($formatter !== null) { - $lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value); - } else { - $lines[] = static::label(static::radio($name, $checked, $value) . ' ' . $label); - } - $index++; - } - - $separator = isset($options['separator']) ? $options['separator'] : "\n"; - if (isset($options['unselect'])) { - // add a hidden field so that if the list box has no option being selected, it still submits a value - $hidden = static::hiddenInput($name, $options['unselect']); - } else { - $hidden = ''; - } - - return $hidden . implode($separator, $lines); - } - - /** - * Renders the option tags that can be used by [[dropDownList()]] and [[listBox()]]. - * @param string|array $selection the selected value(s). This can be either a string for single selection - * or an array for multiple selections. - * @param array $items the option data items. The array keys are option values, and the array values - * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). - * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. - * If you have a list of data models, you may convert them into the format described above using - * [[\yii\helpers\ArrayHelper::map()]]. - * - * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in - * the labels will also be HTML-encoded. - * @param array $tagOptions the $options parameter that is passed to the [[dropDownList()]] or [[listBox()]] call. - * This method will take out these elements, if any: "prompt", "options" and "groups". See more details - * in [[dropDownList()]] for the explanation of these elements. - * - * @return string the generated list options - */ - public static function renderSelectOptions($selection, $items, &$tagOptions = array()) - { - $lines = array(); - if (isset($tagOptions['prompt'])) { - $prompt = str_replace(' ', ' ', static::encode($tagOptions['prompt'])); - $lines[] = static::tag('option', $prompt, array('value' => '')); - } - - $options = isset($tagOptions['options']) ? $tagOptions['options'] : array(); - $groups = isset($tagOptions['groups']) ? $tagOptions['groups'] : array(); - unset($tagOptions['prompt'], $tagOptions['options'], $tagOptions['groups']); - - foreach ($items as $key => $value) { - if (is_array($value)) { - $groupAttrs = isset($groups[$key]) ? $groups[$key] : array(); - $groupAttrs['label'] = $key; - $attrs = array('options' => $options, 'groups' => $groups); - $content = static::renderSelectOptions($selection, $value, $attrs); - $lines[] = static::tag('optgroup', "\n" . $content . "\n", $groupAttrs); - } else { - $attrs = isset($options[$key]) ? $options[$key] : array(); - $attrs['value'] = $key; - $attrs['selected'] = $selection !== null && - (!is_array($selection) && !strcmp($key, $selection) - || is_array($selection) && in_array($key, $selection)); - $lines[] = static::tag('option', str_replace(' ', ' ', static::encode($value)), $attrs); - } - } - - return implode("\n", $lines); - } - - /** - * Renders the HTML tag attributes. - * Boolean attributes such as s 'checked', 'disabled', 'readonly', will be handled specially - * according to [[booleanAttributes]] and [[showBooleanAttributeValues]]. - * @param array $attributes attributes to be rendered. The attribute values will be HTML-encoded using [[encode()]]. - * Attributes whose value is null will be ignored and not put in the rendering result. - * @return string the rendering result. If the attributes are not empty, they will be rendered - * into a string with a leading white space (such that it can be directly appended to the tag name - * in a tag. If there is no attribute, an empty string will be returned. - */ - public static function renderTagAttributes($attributes) - { - if (count($attributes) > 1) { - $sorted = array(); - foreach (static::$attributeOrder as $name) { - if (isset($attributes[$name])) { - $sorted[$name] = $attributes[$name]; - } - } - $attributes = array_merge($sorted, $attributes); - } - - $html = ''; - foreach ($attributes as $name => $value) { - if (isset(static::$booleanAttributes[strtolower($name)])) { - if ($value || strcasecmp($name, $value) === 0) { - $html .= static::$showBooleanAttributeValues ? " $name=\"$name\"" : " $name"; - } - } elseif ($value !== null) { - $html .= " $name=\"" . static::encode($value) . '"'; - } - } - return $html; - } - - /** - * Normalizes the input parameter to be a valid URL. - * - * If the input parameter - * - * - is an empty string: the currently requested URL will be returned; - * - is a non-empty string: it will be processed by [[Yii::getAlias()]] and returned; - * - is an array: the first array element is considered a route, while the rest of the name-value - * pairs are treated as the parameters to be used for URL creation using [[\yii\web\Controller::createUrl()]]. - * For example: `array('post/index', 'page' => 2)`, `array('index')`. - * - * @param array|string $url the parameter to be used to generate a valid URL - * @return string the normalized URL - * @throws InvalidParamException if the parameter is invalid. - */ - public static function url($url) - { - if (is_array($url)) { - if (isset($url[0])) { - $route = $url[0]; - $params = array_splice($url, 1); - if (Yii::$app->controller !== null) { - return Yii::$app->controller->createUrl($route, $params); - } else { - return Yii::$app->getUrlManager()->createUrl($route, $params); - } - } else { - throw new InvalidParamException('The array specifying a URL must contain at least one element.'); - } - } elseif ($url === '') { - return Yii::$app->getRequest()->getUrl(); - } else { - return Yii::getAlias($url); - } - } } diff --git a/framework/helpers/SecurityHelper.php b/framework/helpers/SecurityHelper.php index 5029dd6..d3cb2ad 100644 --- a/framework/helpers/SecurityHelper.php +++ b/framework/helpers/SecurityHelper.php @@ -7,11 +7,6 @@ namespace yii\helpers; -use Yii; -use yii\base\Exception; -use yii\base\InvalidConfigException; -use yii\base\InvalidParamException; - /** * SecurityHelper provides a set of methods to handle common security-related tasks. * @@ -29,244 +24,6 @@ use yii\base\InvalidParamException; * @author Tom Worster * @since 2.0 */ -class SecurityHelper +class SecurityHelper extends base\SecurityHelper { - /** - * Encrypts data. - * @param string $data data to be encrypted. - * @param string $key the encryption secret key - * @return string the encrypted data - * @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized - * @see decrypt() - */ - public static function encrypt($data, $key) - { - $module = static::openCryptModule(); - $key = StringHelper::substr($key, 0, mcrypt_enc_get_key_size($module)); - srand(); - $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($module), MCRYPT_RAND); - mcrypt_generic_init($module, $key, $iv); - $encrypted = $iv . mcrypt_generic($module, $data); - mcrypt_generic_deinit($module); - mcrypt_module_close($module); - return $encrypted; - } - - /** - * Decrypts data - * @param string $data data to be decrypted. - * @param string $key the decryption secret key - * @return string the decrypted data - * @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized - * @see encrypt() - */ - public static function decrypt($data, $key) - { - $module = static::openCryptModule(); - $key = StringHelper::substr($key, 0, mcrypt_enc_get_key_size($module)); - $ivSize = mcrypt_enc_get_iv_size($module); - $iv = StringHelper::substr($data, 0, $ivSize); - mcrypt_generic_init($module, $key, $iv); - $decrypted = mdecrypt_generic($module, StringHelper::substr($data, $ivSize, StringHelper::strlen($data))); - mcrypt_generic_deinit($module); - mcrypt_module_close($module); - return rtrim($decrypted, "\0"); - } - - /** - * Prefixes data with a keyed hash value so that it can later be detected if it is tampered. - * @param string $data the data to be protected - * @param string $key the secret key to be used for generating hash - * @param string $algorithm the hashing algorithm (e.g. "md5", "sha1", "sha256", etc.). Call PHP "hash_algos()" - * function to see the supported hashing algorithms on your system. - * @return string the data prefixed with the keyed hash - * @see validateData() - * @see getSecretKey() - */ - public static function hashData($data, $key, $algorithm = 'sha256') - { - return hash_hmac($algorithm, $data, $key) . $data; - } - - /** - * Validates if the given data is tampered. - * @param string $data the data to be validated. The data must be previously - * generated by [[hashData()]]. - * @param string $key the secret key that was previously used to generate the hash for the data in [[hashData()]]. - * @param string $algorithm the hashing algorithm (e.g. "md5", "sha1", "sha256", etc.). Call PHP "hash_algos()" - * function to see the supported hashing algorithms on your system. This must be the same - * as the value passed to [[hashData()]] when generating the hash for the data. - * @return string the real data with the hash stripped off. False if the data is tampered. - * @see hashData() - */ - public static function validateData($data, $key, $algorithm = 'sha256') - { - $hashSize = StringHelper::strlen(hash_hmac($algorithm, 'test', $key)); - $n = StringHelper::strlen($data); - if ($n >= $hashSize) { - $hash = StringHelper::substr($data, 0, $hashSize); - $data2 = StringHelper::substr($data, $hashSize, $n - $hashSize); - return $hash === hash_hmac($algorithm, $data2, $key) ? $data2 : false; - } else { - return false; - } - } - - /** - * Returns a secret key associated with the specified name. - * If the secret key does not exist, a random key will be generated - * and saved in the file "keys.php" under the application's runtime directory - * so that the same secret key can be returned in future requests. - * @param string $name the name that is associated with the secret key - * @param integer $length the length of the key that should be generated if not exists - * @return string the secret key associated with the specified name - */ - public static function getSecretKey($name, $length = 32) - { - static $keys; - $keyFile = Yii::$app->getRuntimePath() . '/keys.php'; - if ($keys === null) { - $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); - file_put_contents($keyFile, " 30) { - throw new InvalidParamException('Hash is invalid.'); - } - - $test = crypt($password, $hash); - $n = strlen($test); - if (strlen($test) < 32 || $n !== strlen($hash)) { - return false; - } - - // Use a for-loop to compare two strings to prevent timing attacks. See: - // http://codereview.stackexchange.com/questions/13512 - $check = 0; - for ($i = 0; $i < $n; ++$i) { - $check |= (ord($test[$i]) ^ ord($hash[$i])); - } - - return $check === 0; - } - - /** - * Generates a salt that can be used to generate a password hash. - * - * The PHP [crypt()](http://php.net/manual/en/function.crypt.php) built-in function - * requires, for the Blowfish hash algorithm, a salt string in a specific format: - * "$2a$", "$2x$" or "$2y$", a two digit cost parameter, "$", and 22 characters - * from the alphabet "./0-9A-Za-z". - * - * @param integer $cost the cost parameter - * @return string the random salt value. - * @throws InvalidParamException if the cost parameter is not between 4 and 30 - */ - protected static function generateSalt($cost = 13) - { - $cost = (int)$cost; - if ($cost < 4 || $cost > 30) { - throw new InvalidParamException('Cost must be between 4 and 31.'); - } - - // Get 20 * 8bits of pseudo-random entropy from mt_rand(). - $rand = ''; - for ($i = 0; $i < 20; ++$i) { - $rand .= chr(mt_rand(0, 255)); - } - - // Add the microtime for a little more entropy. - $rand .= microtime(); - // Mix the bits cryptographically into a 20-byte binary string. - $rand = sha1($rand, true); - // Form the prefix that specifies Blowfish algorithm and cost parameter. - $salt = sprintf("$2y$%02d$", $cost); - // Append the random salt data in the required base64 format. - $salt .= str_replace('+', '.', substr(base64_encode($rand), 0, 22)); - return $salt; - } } \ No newline at end of file diff --git a/framework/helpers/StringHelper.php b/framework/helpers/StringHelper.php index ace34db..22b881a 100644 --- a/framework/helpers/StringHelper.php +++ b/framework/helpers/StringHelper.php @@ -14,112 +14,6 @@ namespace yii\helpers; * @author Alex Makarov * @since 2.0 */ -class StringHelper +class StringHelper extends base\StringHelper { - /** - * Returns the number of bytes in the given string. - * This method ensures the string is treated as a byte array. - * It will use `mb_strlen()` if it is available. - * @param string $string the string being measured for length - * @return integer the number of bytes in the given string. - */ - public static function strlen($string) - { - return function_exists('mb_strlen') ? mb_strlen($string, '8bit') : strlen($string); - } - - /** - * Returns the portion of string specified by the start and length parameters. - * This method ensures the string is treated as a byte array. - * It will use `mb_substr()` if it is available. - * @param string $string the input string. Must be one character or longer. - * @param integer $start the starting position - * @param integer $length the desired portion length - * @return string the extracted part of string, or FALSE on failure or an empty string. - * @see http://www.php.net/manual/en/function.substr.php - */ - public static function substr($string, $start, $length) - { - return function_exists('mb_substr') ? mb_substr($string, $start, $length, '8bit') : substr($string, $start, $length); - } - - /** - * Converts a word to its plural form. - * Note that this is for English only! - * For example, 'apple' will become 'apples', and 'child' will become 'children'. - * @param string $name the word to be pluralized - * @return string the pluralized word - */ - public static function pluralize($name) - { - static $rules = array( - '/(m)ove$/i' => '\1oves', - '/(f)oot$/i' => '\1eet', - '/(c)hild$/i' => '\1hildren', - '/(h)uman$/i' => '\1umans', - '/(m)an$/i' => '\1en', - '/(s)taff$/i' => '\1taff', - '/(t)ooth$/i' => '\1eeth', - '/(p)erson$/i' => '\1eople', - '/([m|l])ouse$/i' => '\1ice', - '/(x|ch|ss|sh|us|as|is|os)$/i' => '\1es', - '/([^aeiouy]|qu)y$/i' => '\1ies', - '/(?:([^f])fe|([lr])f)$/i' => '\1\2ves', - '/(shea|lea|loa|thie)f$/i' => '\1ves', - '/([ti])um$/i' => '\1a', - '/(tomat|potat|ech|her|vet)o$/i' => '\1oes', - '/(bu)s$/i' => '\1ses', - '/(ax|test)is$/i' => '\1es', - '/s$/' => 's', - ); - foreach ($rules as $rule => $replacement) { - if (preg_match($rule, $name)) { - return preg_replace($rule, $replacement, $name); - } - } - return $name . 's'; - } - - /** - * Converts a CamelCase name into space-separated words. - * For example, 'PostTag' will be converted to 'Post Tag'. - * @param string $name the string to be converted - * @param boolean $ucwords whether to capitalize the first letter in each word - * @return string the resulting words - */ - public static function camel2words($name, $ucwords = true) - { - $label = trim(strtolower(str_replace(array('-', '_', '.'), ' ', preg_replace('/(? * @since 2.0 */ -class CVarDumper +class VarDumper extends base\VarDumper { - private static $_objects; - private static $_output; - private static $_depth; - - /** - * Displays a variable. - * This method achieves the similar functionality as var_dump and print_r - * but is more robust when handling complex objects such as Yii controllers. - * @param mixed $var variable to be dumped - * @param integer $depth maximum depth that the dumper should go into the variable. Defaults to 10. - * @param boolean $highlight whether the result should be syntax-highlighted - */ - public static function dump($var, $depth = 10, $highlight = false) - { - echo self::dumpAsString($var, $depth, $highlight); - } - - /** - * Dumps a variable in terms of a string. - * This method achieves the similar functionality as var_dump and print_r - * but is more robust when handling complex objects such as Yii controllers. - * @param mixed $var variable to be dumped - * @param integer $depth maximum depth that the dumper should go into the variable. Defaults to 10. - * @param boolean $highlight whether the result should be syntax-highlighted - * @return string the string representation of the variable - */ - public static function dumpAsString($var, $depth = 10, $highlight = false) - { - self::$_output = ''; - self::$_objects = array(); - self::$_depth = $depth; - self::dumpInternal($var, 0); - if ($highlight) { - $result = highlight_string("/', '', $result, 1); - } - return self::$_output; - } - - /* - * @param mixed $var variable to be dumped - * @param integer $level depth level - */ - private static function dumpInternal($var, $level) - { - switch (gettype($var)) { - case 'boolean': - self::$_output .= $var ? 'true' : 'false'; - break; - case 'integer': - self::$_output .= "$var"; - break; - case 'double': - self::$_output .= "$var"; - break; - case 'string': - self::$_output .= "'" . addslashes($var) . "'"; - break; - case 'resource': - self::$_output .= '{resource}'; - break; - case 'NULL': - self::$_output .= "null"; - break; - case 'unknown type': - self::$_output .= '{unknown}'; - break; - case 'array': - if (self::$_depth <= $level) { - self::$_output .= 'array(...)'; - } elseif (empty($var)) { - self::$_output .= 'array()'; - } else { - $keys = array_keys($var); - $spaces = str_repeat(' ', $level * 4); - self::$_output .= "array\n" . $spaces . '('; - foreach ($keys as $key) { - self::$_output .= "\n" . $spaces . ' '; - self::dumpInternal($key, 0); - self::$_output .= ' => '; - self::dumpInternal($var[$key], $level + 1); - } - self::$_output .= "\n" . $spaces . ')'; - } - break; - case 'object': - if (($id = array_search($var, self::$_objects, true)) !== false) { - self::$_output .= get_class($var) . '#' . ($id + 1) . '(...)'; - } elseif (self::$_depth <= $level) { - self::$_output .= get_class($var) . '(...)'; - } else { - $id = self::$_objects[] = $var; - $className = get_class($var); - $members = (array)$var; - $spaces = str_repeat(' ', $level * 4); - self::$_output .= "$className#$id\n" . $spaces . '('; - foreach ($members as $key => $value) { - $keyDisplay = strtr(trim($key), array("\0" => ':')); - self::$_output .= "\n" . $spaces . " [$keyDisplay] => "; - self::dumpInternal($value, $level + 1); - } - self::$_output .= "\n" . $spaces . ')'; - } - break; - } - } } \ No newline at end of file diff --git a/framework/helpers/base/ArrayHelper.php b/framework/helpers/base/ArrayHelper.php new file mode 100644 index 0000000..9870542 --- /dev/null +++ b/framework/helpers/base/ArrayHelper.php @@ -0,0 +1,340 @@ + + * @since 2.0 + */ +class ArrayHelper +{ + /** + * Merges two or more arrays into one recursively. + * If each array has an element with the same string key value, the latter + * will overwrite the former (different from array_merge_recursive). + * Recursive merging will be conducted if both arrays have an element of array + * type and are having the same key. + * For integer-keyed elements, the elements from the latter array will + * be appended to the former array. + * @param array $a array to be merged to + * @param array $b array to be merged from. You can specify additional + * arrays via third argument, fourth argument etc. + * @return array the merged array (the original arrays are not changed.) + */ + public static function merge($a, $b) + { + $args = func_get_args(); + $res = array_shift($args); + while ($args !== array()) { + $next = array_shift($args); + foreach ($next as $k => $v) { + if (is_integer($k)) { + isset($res[$k]) ? $res[] = $v : $res[$k] = $v; + } elseif (is_array($v) && isset($res[$k]) && is_array($res[$k])) { + $res[$k] = self::merge($res[$k], $v); + } else { + $res[$k] = $v; + } + } + } + return $res; + } + + /** + * Retrieves the value of an array element or object property with the given key or property name. + * If the key does not exist in the array, the default value will be returned instead. + * + * Below are some usage examples, + * + * ~~~ + * // working with array + * $username = \yii\helpers\ArrayHelper::getValue($_POST, 'username'); + * // working with object + * $username = \yii\helpers\ArrayHelper::getValue($user, 'username'); + * // working with anonymous function + * $fullName = \yii\helpers\ArrayHelper::getValue($user, function($user, $defaultValue) { + * return $user->firstName . ' ' . $user->lastName; + * }); + * ~~~ + * + * @param array|object $array array or object to extract value from + * @param string|\Closure $key key name of the array element, or property name of the object, + * or an anonymous function returning the value. The anonymous function signature should be: + * `function($array, $defaultValue)`. + * @param mixed $default the default value to be returned if the specified key does not exist + * @return mixed the value of the + */ + public static function getValue($array, $key, $default = null) + { + if ($key instanceof \Closure) { + return $key($array, $default); + } elseif (is_array($array)) { + return isset($array[$key]) || array_key_exists($key, $array) ? $array[$key] : $default; + } else { + return $array->$key; + } + } + + /** + * Indexes an array according to a specified key. + * The input array should be multidimensional or an array of objects. + * + * The key can be a key name of the sub-array, a property name of object, or an anonymous + * function which returns the key value given an array element. + * + * If a key value is null, the corresponding array element will be discarded and not put in the result. + * + * For example, + * + * ~~~ + * $array = array( + * array('id' => '123', 'data' => 'abc'), + * array('id' => '345', 'data' => 'def'), + * ); + * $result = ArrayHelper::index($array, 'id'); + * // the result is: + * // array( + * // '123' => array('id' => '123', 'data' => 'abc'), + * // '345' => array('id' => '345', 'data' => 'def'), + * // ) + * + * // using anonymous function + * $result = ArrayHelper::index($array, function(element) { + * return $element['id']; + * }); + * ~~~ + * + * @param array $array the array that needs to be indexed + * @param string|\Closure $key the column name or anonymous function whose result will be used to index the array + * @return array the indexed array + */ + public static function index($array, $key) + { + $result = array(); + foreach ($array as $element) { + $value = static::getValue($element, $key); + $result[$value] = $element; + } + return $result; + } + + /** + * Returns the values of a specified column in an array. + * The input array should be multidimensional or an array of objects. + * + * For example, + * + * ~~~ + * $array = array( + * array('id' => '123', 'data' => 'abc'), + * array('id' => '345', 'data' => 'def'), + * ); + * $result = ArrayHelper::getColumn($array, 'id'); + * // the result is: array( '123', '345') + * + * // using anonymous function + * $result = ArrayHelper::getColumn($array, function(element) { + * return $element['id']; + * }); + * ~~~ + * + * @param array $array + * @param string|\Closure $name + * @param boolean $keepKeys whether to maintain the array keys. If false, the resulting array + * will be re-indexed with integers. + * @return array the list of column values + */ + public static function getColumn($array, $name, $keepKeys = true) + { + $result = array(); + if ($keepKeys) { + foreach ($array as $k => $element) { + $result[$k] = static::getValue($element, $name); + } + } else { + foreach ($array as $element) { + $result[] = static::getValue($element, $name); + } + } + + return $result; + } + + /** + * Builds a map (key-value pairs) from a multidimensional array or an array of objects. + * The `$from` and `$to` parameters specify the key names or property names to set up the map. + * Optionally, one can further group the map according to a grouping field `$group`. + * + * For example, + * + * ~~~ + * $array = array( + * array('id' => '123', 'name' => 'aaa', 'class' => 'x'), + * array('id' => '124', 'name' => 'bbb', 'class' => 'x'), + * array('id' => '345', 'name' => 'ccc', 'class' => 'y'), + * ); + * + * $result = ArrayHelper::map($array, 'id', 'name'); + * // the result is: + * // array( + * // '123' => 'aaa', + * // '124' => 'bbb', + * // '345' => 'ccc', + * // ) + * + * $result = ArrayHelper::map($array, 'id', 'name', 'class'); + * // the result is: + * // array( + * // 'x' => array( + * // '123' => 'aaa', + * // '124' => 'bbb', + * // ), + * // 'y' => array( + * // '345' => 'ccc', + * // ), + * // ) + * ~~~ + * + * @param array $array + * @param string|\Closure $from + * @param string|\Closure $to + * @param string|\Closure $group + * @return array + */ + public static function map($array, $from, $to, $group = null) + { + $result = array(); + foreach ($array as $element) { + $key = static::getValue($element, $from); + $value = static::getValue($element, $to); + if ($group !== null) { + $result[static::getValue($element, $group)][$key] = $value; + } else { + $result[$key] = $value; + } + } + return $result; + } + + /** + * Sorts an array of objects or arrays (with the same structure) by one or several keys. + * @param array $array the array to be sorted. The array will be modified after calling this method. + * @param string|\Closure|array $key the key(s) to be sorted by. This refers to a key name of the sub-array + * elements, a property name of the objects, or an anonymous function returning the values for comparison + * purpose. The anonymous function signature should be: `function($item)`. + * To sort by multiple keys, provide an array of keys here. + * @param boolean|array $ascending whether to sort in ascending or descending order. When + * sorting by multiple keys with different ascending orders, use an array of ascending flags. + * @param integer|array $sortFlag the PHP sort flag. Valid values include: + * `SORT_REGULAR`, `SORT_NUMERIC`, `SORT_STRING`, and `SORT_STRING | SORT_FLAG_CASE`. The last + * value is for sorting strings in case-insensitive manner. Please refer to + * See [PHP manual](http://php.net/manual/en/function.sort.php) for more details. + * When sorting by multiple keys with different sort flags, use an array of sort flags. + * @throws InvalidParamException if the $ascending or $sortFlag parameters do not have + * correct number of elements as that of $key. + */ + public static function multisort(&$array, $key, $ascending = true, $sortFlag = SORT_REGULAR) + { + $keys = is_array($key) ? $key : array($key); + if (empty($keys) || empty($array)) { + return; + } + $n = count($keys); + if (is_scalar($ascending)) { + $ascending = array_fill(0, $n, $ascending); + } elseif (count($ascending) !== $n) { + throw new InvalidParamException('The length of $ascending parameter must be the same as that of $keys.'); + } + if (is_scalar($sortFlag)) { + $sortFlag = array_fill(0, $n, $sortFlag); + } elseif (count($sortFlag) !== $n) { + throw new InvalidParamException('The length of $ascending parameter must be the same as that of $keys.'); + } + $args = array(); + foreach ($keys as $i => $key) { + $flag = $sortFlag[$i]; + if ($flag == (SORT_STRING | SORT_FLAG_CASE)) { + $flag = SORT_STRING; + $column = array(); + foreach (static::getColumn($array, $key) as $k => $value) { + $column[$k] = strtolower($value); + } + $args[] = $column; + } else { + $args[] = static::getColumn($array, $key); + } + $args[] = $ascending[$i] ? SORT_ASC : SORT_DESC; + $args[] = $flag; + } + $args[] = &$array; + call_user_func_array('array_multisort', $args); + } + + /** + * Encodes special characters in an array of strings into HTML entities. + * Both the array keys and values will be encoded. + * If a value is an array, this method will also encode it recursively. + * @param array $data data to be encoded + * @param boolean $valuesOnly whether to encode array values only. If false, + * both the array keys and array values will be encoded. + * @param string $charset the charset that the data is using. If not set, + * [[\yii\base\Application::charset]] will be used. + * @return array the encoded data + * @see http://www.php.net/manual/en/function.htmlspecialchars.php + */ + public static function htmlEncode($data, $valuesOnly = true, $charset = null) + { + if ($charset === null) { + $charset = Yii::$app->charset; + } + $d = array(); + foreach ($data as $key => $value) { + if (!$valuesOnly && is_string($key)) { + $key = htmlspecialchars($key, ENT_QUOTES, $charset); + } + if (is_string($value)) { + $d[$key] = htmlspecialchars($value, ENT_QUOTES, $charset); + } elseif (is_array($value)) { + $d[$key] = static::htmlEncode($value, $charset); + } + } + return $d; + } + + /** + * Decodes HTML entities into the corresponding characters in an array of strings. + * Both the array keys and values will be decoded. + * If a value is an array, this method will also decode it recursively. + * @param array $data data to be decoded + * @param boolean $valuesOnly whether to decode array values only. If false, + * both the array keys and array values will be decoded. + * @return array the decoded data + * @see http://www.php.net/manual/en/function.htmlspecialchars-decode.php + */ + public static function htmlDecode($data, $valuesOnly = true) + { + $d = array(); + foreach ($data as $key => $value) { + if (!$valuesOnly && is_string($key)) { + $key = htmlspecialchars_decode($key, ENT_QUOTES); + } + if (is_string($value)) { + $d[$key] = htmlspecialchars_decode($value, ENT_QUOTES); + } elseif (is_array($value)) { + $d[$key] = static::htmlDecode($value); + } + } + return $d; + } +} \ No newline at end of file diff --git a/framework/helpers/base/ConsoleColor.php b/framework/helpers/base/ConsoleColor.php new file mode 100644 index 0000000..5e7f577 --- /dev/null +++ b/framework/helpers/base/ConsoleColor.php @@ -0,0 +1,470 @@ + + * @since 2.0 + */ +class ConsoleColor +{ + const FG_BLACK = 30; + const FG_RED = 31; + const FG_GREEN = 32; + const FG_YELLOW = 33; + const FG_BLUE = 34; + const FG_PURPLE = 35; + const FG_CYAN = 36; + const FG_GREY = 37; + + const BG_BLACK = 40; + const BG_RED = 41; + const BG_GREEN = 42; + const BG_YELLOW = 43; + const BG_BLUE = 44; + const BG_PURPLE = 45; + const BG_CYAN = 46; + const BG_GREY = 47; + + const BOLD = 1; + const ITALIC = 3; + const UNDERLINE = 4; + const BLINK = 5; + const NEGATIVE = 7; + const CONCEALED = 8; + const CROSSED_OUT = 9; + const FRAMED = 51; + const ENCIRCLED = 52; + const OVERLINED = 53; + + /** + * Moves the terminal cursor up by sending ANSI control code CUU to the terminal. + * If the cursor is already at the edge of the screen, this has no effect. + * @param integer $rows number of rows the cursor should be moved up + */ + public static function moveCursorUp($rows=1) + { + echo "\033[" . (int) $rows . 'A'; + } + + /** + * Moves the terminal cursor down by sending ANSI control code CUD to the terminal. + * If the cursor is already at the edge of the screen, this has no effect. + * @param integer $rows number of rows the cursor should be moved down + */ + public static function moveCursorDown($rows=1) + { + echo "\033[" . (int) $rows . 'B'; + } + + /** + * Moves the terminal cursor forward by sending ANSI control code CUF to the terminal. + * If the cursor is already at the edge of the screen, this has no effect. + * @param integer $steps number of steps the cursor should be moved forward + */ + public static function moveCursorForward($steps=1) + { + echo "\033[" . (int) $steps . 'C'; + } + + /** + * Moves the terminal cursor backward by sending ANSI control code CUB to the terminal. + * If the cursor is already at the edge of the screen, this has no effect. + * @param integer $steps number of steps the cursor should be moved backward + */ + public static function moveCursorBackward($steps=1) + { + echo "\033[" . (int) $steps . 'D'; + } + + /** + * Moves the terminal cursor to the beginning of the next line by sending ANSI control code CNL to the terminal. + * @param integer $lines number of lines the cursor should be moved down + */ + public static function moveCursorNextLine($lines=1) + { + echo "\033[" . (int) $lines . 'E'; + } + + /** + * Moves the terminal cursor to the beginning of the previous line by sending ANSI control code CPL to the terminal. + * @param integer $lines number of lines the cursor should be moved up + */ + public static function moveCursorPrevLine($lines=1) + { + echo "\033[" . (int) $lines . 'F'; + } + + /** + * Moves the cursor to an absolute position given as column and row by sending ANSI control code CUP or CHA to the terminal. + * @param integer $column 1-based column number, 1 is the left edge of the screen. + * @param integer|null $row 1-based row number, 1 is the top edge of the screen. if not set, will move cursor only in current line. + */ + public static function moveCursorTo($column, $row=null) + { + if ($row === null) { + echo "\033[" . (int) $column . 'G'; + } else { + echo "\033[" . (int) $row . ';' . (int) $column . 'H'; + } + } + + /** + * Scrolls whole page up by sending ANSI control code SU to the terminal. + * New lines are added at the bottom. This is not supported by ANSI.SYS used in windows. + * @param int $lines number of lines to scroll up + */ + public static function scrollUp($lines=1) + { + echo "\033[".(int)$lines."S"; + } + + /** + * Scrolls whole page down by sending ANSI control code SD to the terminal. + * New lines are added at the top. This is not supported by ANSI.SYS used in windows. + * @param int $lines number of lines to scroll down + */ + public static function scrollDown($lines=1) + { + echo "\033[".(int)$lines."T"; + } + + /** + * Saves the current cursor position by sending ANSI control code SCP to the terminal. + * Position can then be restored with {@link restoreCursorPosition}. + */ + public static function saveCursorPosition() + { + echo "\033[s"; + } + + /** + * Restores the cursor position saved with {@link saveCursorPosition} by sending ANSI control code RCP to the terminal. + */ + public static function restoreCursorPosition() + { + echo "\033[u"; + } + + /** + * Hides the cursor by sending ANSI DECTCEM code ?25l to the terminal. + * Use {@link showCursor} to bring it back. + * Do not forget to show cursor when your application exits. Cursor might stay hidden in terminal after exit. + */ + public static function hideCursor() + { + echo "\033[?25l"; + } + + /** + * Will show a cursor again when it has been hidden by {@link hideCursor} by sending ANSI DECTCEM code ?25h to the terminal. + */ + public static function showCursor() + { + echo "\033[?25h"; + } + + /** + * Clears entire screen content by sending ANSI control code ED with argument 2 to the terminal. + * Cursor position will not be changed. + * **Note:** ANSI.SYS implementation used in windows will reset cursor position to upper left corner of the screen. + */ + public static function clearScreen() + { + echo "\033[2J"; + } + + /** + * Clears text from cursor to the beginning of the screen by sending ANSI control code ED with argument 1 to the terminal. + * Cursor position will not be changed. + */ + public static function clearScreenBeforeCursor() + { + echo "\033[1J"; + } + + /** + * Clears text from cursor to the end of the screen by sending ANSI control code ED with argument 0 to the terminal. + * Cursor position will not be changed. + */ + public static function clearScreenAfterCursor() + { + echo "\033[0J"; + } + + /** + * Clears the line, the cursor is currently on by sending ANSI control code EL with argument 2 to the terminal. + * Cursor position will not be changed. + */ + public static function clearLine() + { + echo "\033[2K"; + } + + /** + * Clears text from cursor position to the beginning of the line by sending ANSI control code EL with argument 1 to the terminal. + * Cursor position will not be changed. + */ + public static function clearLineBeforeCursor() + { + echo "\033[1K"; + } + + /** + * Clears text from cursor position to the end of the line by sending ANSI control code EL with argument 0 to the terminal. + * Cursor position will not be changed. + */ + public static function clearLineAfterCursor() + { + echo "\033[0K"; + } + + /** + * Will send ANSI format for following output + * + * You can pass any of the FG_*, BG_* and TEXT_* constants and also xterm256ColorBg + * TODO: documentation + */ + public static function ansiStyle() + { + echo "\033[" . implode(';', func_get_args()) . 'm'; + } + + /** + * Will return a string formatted with the given ANSI style + * + * See {@link ansiStyle} for possible arguments. + * @param string $string the string to be formatted + * @return string + */ + public static function ansiStyleString($string) + { + $args = func_get_args(); + array_shift($args); + $code = implode(';', $args); + return "\033[0m" . ($code !== '' ? "\033[" . $code . "m" : '') . $string."\033[0m"; + } + + //const COLOR_XTERM256 = 38;// http://en.wikipedia.org/wiki/Talk:ANSI_escape_code#xterm-256colors + public static function xterm256ColorFg($i) // TODO naming! + { + return '38;5;'.$i; + } + + public static function xterm256ColorBg($i) // TODO naming! + { + return '48;5;'.$i; + } + + /** + * Usage: list($w, $h) = ConsoleHelper::getScreenSize(); + * + * @return array + */ + public static function getScreenSize() + { + // TODO implement + return array(150,50); + } + + /** + * resets any ansi style set by previous method {@link ansiStyle} + * Any output after this is will have default text style. + */ + public static function reset() + { + echo "\033[0m"; + } + + /** + * Strips ANSI control codes from a string + * + * @param string $string String to strip + * @return string + */ + public static function strip($string) + { + return preg_replace('/\033\[[\d;]+m/', '', $string); // TODO currently only strips color + } + + // TODO refactor and review + public static function ansiToHtml($string) + { + $tags = 0; + return preg_replace_callback('/\033\[[\d;]+m/', function($ansi) use (&$tags) { + $styleA = array(); + foreach(explode(';', $ansi) as $controlCode) + { + switch($controlCode) + { + case static::FG_BLACK: $style = array('color' => '#000000'); break; + case static::FG_BLUE: $style = array('color' => '#000078'); break; + case static::FG_CYAN: $style = array('color' => '#007878'); break; + case static::FG_GREEN: $style = array('color' => '#007800'); break; + case static::FG_GREY: $style = array('color' => '#787878'); break; + case static::FG_PURPLE: $style = array('color' => '#780078'); break; + case static::FG_RED: $style = array('color' => '#780000'); break; + case static::FG_YELLOW: $style = array('color' => '#787800'); break; + case static::BG_BLACK: $style = array('background-color' => '#000000'); break; + case static::BG_BLUE: $style = array('background-color' => '#000078'); break; + case static::BG_CYAN: $style = array('background-color' => '#007878'); break; + case static::BG_GREEN: $style = array('background-color' => '#007800'); break; + case static::BG_GREY: $style = array('background-color' => '#787878'); break; + case static::BG_PURPLE: $style = array('background-color' => '#780078'); break; + case static::BG_RED: $style = array('background-color' => '#780000'); break; + case static::BG_YELLOW: $style = array('background-color' => '#787800'); break; + case static::BOLD: $style = array('font-weight' => 'bold'); break; + case static::ITALIC: $style = array('font-style' => 'italic'); break; + case static::UNDERLINE: $style = array('text-decoration' => array('underline')); break; + case static::OVERLINED: $style = array('text-decoration' => array('overline')); break; + case static::CROSSED_OUT:$style = array('text-decoration' => array('line-through')); break; + case static::BLINK: $style = array('text-decoration' => array('blink')); break; + case static::NEGATIVE: // ??? + case static::CONCEALED: + case static::ENCIRCLED: + case static::FRAMED: + // TODO allow resetting codes + break; + case 0: // ansi reset + $return = ''; + for($n=$tags; $tags>0; $tags--) { + $return .= ''; + } + return $return; + } + + $styleA = ArrayHelper::merge($styleA, $style); + } + $styleString[] = array(); + foreach($styleA as $name => $content) { + if ($name === 'text-decoration') { + $content = implode(' ', $content); + } + $styleString[] = $name.':'.$content; + } + $tags++; + return ' $ds, '\\' => $ds)), $ds); + } + + /** + * Returns the localized version of a specified file. + * + * The searching is based on the specified language code. In particular, + * a file with the same name will be looked for under the subdirectory + * whose name is same as the language code. For example, given the file "path/to/view.php" + * and language code "zh_cn", the localized file will be looked for as + * "path/to/zh_cn/view.php". If the file is not found, the original file + * will be returned. + * + * If the target and the source language codes are the same, + * the original file will be returned. + * + * For consistency, it is recommended that the language code is given + * in lower case and in the format of LanguageID_RegionID (e.g. "en_us"). + * + * @param string $file the original file + * @param string $language the target language that the file should be localized to. + * If not set, the value of [[\yii\base\Application::language]] will be used. + * @param string $sourceLanguage the language that the original file is in. + * If not set, the value of [[\yii\base\Application::sourceLanguage]] will be used. + * @return string the matching localized file, or the original file if the localized version is not found. + * If the target and the source language codes are the same, the original file will be returned. + */ + public static function localize($file, $language = null, $sourceLanguage = null) + { + if ($language === null) { + $language = \Yii::$app->language; + } + if ($sourceLanguage === null) { + $sourceLanguage = \Yii::$app->sourceLanguage; + } + if ($language === $sourceLanguage) { + return $file; + } + $desiredFile = dirname($file) . DIRECTORY_SEPARATOR . $sourceLanguage . DIRECTORY_SEPARATOR . basename($file); + return is_file($desiredFile) ? $desiredFile : $file; + } + + /** + * Determines the MIME type of the specified file. + * This method will first try to determine the MIME type based on + * [finfo_open](http://php.net/manual/en/function.finfo-open.php). If this doesn't work, it will + * fall back to [[getMimeTypeByExtension()]]. + * @param string $file the file name. + * @param string $magicFile name of the optional magic database file, usually something like `/path/to/magic.mime`. + * This will be passed as the second parameter to [finfo_open](http://php.net/manual/en/function.finfo-open.php). + * @param boolean $checkExtension whether to use the file extension to determine the MIME type in case + * `finfo_open()` cannot determine it. + * @return string the MIME type (e.g. `text/plain`). Null is returned if the MIME type cannot be determined. + */ + public static function getMimeType($file, $magicFile = null, $checkExtension = true) + { + if (function_exists('finfo_open')) { + $info = finfo_open(FILEINFO_MIME_TYPE, $magicFile); + if ($info && ($result = finfo_file($info, $file)) !== false) { + return $result; + } + } + + return $checkExtension ? self::getMimeTypeByExtension($file) : null; + } + + /** + * Determines the MIME type based on the extension name of the specified file. + * This method will use a local map between extension names and MIME types. + * @param string $file the file name. + * @param string $magicFile the path of the file that contains all available MIME type information. + * If this is not set, the default file aliased by `@yii/util/mimeTypes.php` will be used. + * @return string the MIME type. Null is returned if the MIME type cannot be determined. + */ + public static function getMimeTypeByExtension($file, $magicFile = null) + { + if ($magicFile === null) { + $magicFile = \Yii::getAlias('@yii/util/mimeTypes.php'); + } + $mimeTypes = require($magicFile); + if (($ext = pathinfo($file, PATHINFO_EXTENSION)) !== '') { + $ext = strtolower($ext); + if (isset($mimeTypes[$ext])) { + return $mimeTypes[$ext]; + } + } + return null; + } + + /** + * Copies a list of files from one place to another. + * @param array $fileList the list of files to be copied (name=>spec). + * The array keys are names displayed during the copy process, and array values are specifications + * for files to be copied. Each array value must be an array of the following structure: + *
    + *
  • source: required, the full path of the file/directory to be copied from
  • + *
  • target: required, the full path of the file/directory to be copied to
  • + *
  • callback: optional, the callback to be invoked when copying a file. The callback function + * should be declared as follows: + *
    +	 *   function foo($source,$params)
    +	 *   
    + * where $source parameter is the source file path, and the content returned + * by the function will be saved into the target file.
  • + *
  • params: optional, the parameters to be passed to the callback
  • + *
+ * @see buildFileList + */ + public static function copyFiles($fileList) + { + $overwriteAll = false; + foreach($fileList as $name=>$file) { + $source = strtr($file['source'], '/\\', DIRECTORY_SEPARATOR); + $target = strtr($file['target'], '/\\', DIRECTORY_SEPARATOR); + $callback = isset($file['callback']) ? $file['callback'] : null; + $params = isset($file['params']) ? $file['params'] : null; + + if(is_dir($source)) { + try { + self::ensureDirectory($target); + } + catch (Exception $e) { + mkdir($target, true, 0777); + } + continue; + } + + if($callback !== null) { + $content = call_user_func($callback, $source, $params); + } + else { + $content = file_get_contents($source); + } + if(is_file($target)) { + if($content === file_get_contents($target)) { + echo " unchanged $name\n"; + continue; + } + if($overwriteAll) { + echo " overwrite $name\n"; + } + else { + echo " exist $name\n"; + echo " ...overwrite? [Yes|No|All|Quit] "; + $answer = trim(fgets(STDIN)); + if(!strncasecmp($answer, 'q', 1)) { + return; + } + elseif(!strncasecmp($answer, 'y', 1)) { + echo " overwrite $name\n"; + } + elseif(!strncasecmp($answer, 'a', 1)) { + echo " overwrite $name\n"; + $overwriteAll = true; + } + else { + echo " skip $name\n"; + continue; + } + } + } + else { + try { + self::ensureDirectory(dirname($target)); + } + catch (Exception $e) { + mkdir(dirname($target), true, 0777); + } + echo " generate $name\n"; + } + file_put_contents($target, $content); + } + } + + /** + * Builds the file list of a directory. + * This method traverses through the specified directory and builds + * a list of files and subdirectories that the directory contains. + * The result of this function can be passed to {@link copyFiles}. + * @param string $sourceDir the source directory + * @param string $targetDir the target directory + * @param string $baseDir base directory + * @param array $ignoreFiles list of the names of files that should + * be ignored in list building process. Argument available since 1.1.11. + * @param array $renameMap hash array of file names that should be + * renamed. Example value: array('1.old.txt'=>'2.new.txt'). + * @return array the file list (see {@link copyFiles}) + */ + public static function buildFileList($sourceDir, $targetDir, $baseDir='', $ignoreFiles=array(), $renameMap=array()) + { + $list = array(); + $handle = opendir($sourceDir); + while(($file = readdir($handle)) !== false) { + if(in_array($file, array('.', '..', '.svn', '.gitignore')) || in_array($file, $ignoreFiles)) { + continue; + } + $sourcePath = $sourceDir.DIRECTORY_SEPARATOR.$file; + $targetPath = $targetDir.DIRECTORY_SEPARATOR.strtr($file, $renameMap); + $name = $baseDir === '' ? $file : $baseDir.'/'.$file; + $list[$name] = array( + 'source' => $sourcePath, + 'target' => $targetPath, + ); + if(is_dir($sourcePath)) { + $list = array_merge($list, self::buildFileList($sourcePath, $targetPath, $name, $ignoreFiles, $renameMap)); + } + } + closedir($handle); + return $list; + } +} \ No newline at end of file diff --git a/framework/helpers/base/Html.php b/framework/helpers/base/Html.php new file mode 100644 index 0000000..5e7f4ad --- /dev/null +++ b/framework/helpers/base/Html.php @@ -0,0 +1,981 @@ + + * @since 2.0 + */ +class Html +{ + /** + * @var boolean whether to close void (empty) elements. Defaults to true. + * @see voidElements + */ + public static $closeVoidElements = true; + /** + * @var array list of void elements (element name => 1) + * @see closeVoidElements + * @see http://www.w3.org/TR/html-markup/syntax.html#void-element + */ + public static $voidElements = array( + 'area' => 1, + 'base' => 1, + 'br' => 1, + 'col' => 1, + 'command' => 1, + 'embed' => 1, + 'hr' => 1, + 'img' => 1, + 'input' => 1, + 'keygen' => 1, + 'link' => 1, + 'meta' => 1, + 'param' => 1, + 'source' => 1, + 'track' => 1, + 'wbr' => 1, + ); + /** + * @var boolean whether to show the values of boolean attributes in element tags. + * If false, only the attribute names will be generated. + * @see booleanAttributes + */ + public static $showBooleanAttributeValues = true; + /** + * @var array list of boolean attributes. The presence of a boolean attribute on + * an element represents the true value, and the absence of the attribute represents the false value. + * @see showBooleanAttributeValues + * @see http://www.w3.org/TR/html5/infrastructure.html#boolean-attributes + */ + public static $booleanAttributes = array( + 'async' => 1, + 'autofocus' => 1, + 'autoplay' => 1, + 'checked' => 1, + 'controls' => 1, + 'declare' => 1, + 'default' => 1, + 'defer' => 1, + 'disabled' => 1, + 'formnovalidate' => 1, + 'hidden' => 1, + 'ismap' => 1, + 'loop' => 1, + 'multiple' => 1, + 'muted' => 1, + 'nohref' => 1, + 'noresize' => 1, + 'novalidate' => 1, + 'open' => 1, + 'readonly' => 1, + 'required' => 1, + 'reversed' => 1, + 'scoped' => 1, + 'seamless' => 1, + 'selected' => 1, + 'typemustmatch' => 1, + ); + /** + * @var array the preferred order of attributes in a tag. This mainly affects the order of the attributes + * that are rendered by [[renderAttributes()]]. + */ + public static $attributeOrder = array( + 'type', + 'id', + 'class', + 'name', + 'value', + + 'href', + 'src', + 'action', + 'method', + + 'selected', + 'checked', + 'readonly', + 'disabled', + 'multiple', + + 'size', + 'maxlength', + 'width', + 'height', + 'rows', + 'cols', + + 'alt', + 'title', + 'rel', + 'media', + ); + + /** + * Encodes special characters into HTML entities. + * The [[yii\base\Application::charset|application charset]] will be used for encoding. + * @param string $content the content to be encoded + * @return string the encoded content + * @see decode + * @see http://www.php.net/manual/en/function.htmlspecialchars.php + */ + public static function encode($content) + { + return htmlspecialchars($content, ENT_QUOTES, Yii::$app->charset); + } + + /** + * Decodes special HTML entities back to the corresponding characters. + * This is the opposite of [[encode()]]. + * @param string $content the content to be decoded + * @return string the decoded content + * @see encode + * @see http://www.php.net/manual/en/function.htmlspecialchars-decode.php + */ + public static function decode($content) + { + return htmlspecialchars_decode($content, ENT_QUOTES); + } + + /** + * Generates a complete HTML tag. + * @param string $name the tag name + * @param string $content the content to be enclosed between the start and end tags. It will not be HTML-encoded. + * If this is coming from end users, you should consider [[encode()]] it to prevent XSS attacks. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated HTML tag + * @see beginTag + * @see endTag + */ + public static function tag($name, $content = '', $options = array()) + { + $html = '<' . $name . static::renderTagAttributes($options); + if (isset(static::$voidElements[strtolower($name)])) { + return $html . (static::$closeVoidElements ? ' />' : '>'); + } else { + return $html . ">$content"; + } + } + + /** + * Generates a start tag. + * @param string $name the tag name + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated start tag + * @see endTag + * @see tag + */ + public static function beginTag($name, $options = array()) + { + return '<' . $name . static::renderTagAttributes($options) . '>'; + } + + /** + * Generates an end tag. + * @param string $name the tag name + * @return string the generated end tag + * @see beginTag + * @see tag + */ + public static function endTag($name) + { + return ""; + } + + /** + * Encloses the given content within a CDATA tag. + * @param string $content the content to be enclosed within the CDATA tag + * @return string the CDATA tag with the enclosed content. + */ + public static function cdata($content) + { + return ''; + } + + /** + * Generates a style tag. + * @param string $content the style content + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * If the options does not contain "type", a "type" attribute with value "text/css" will be used. + * @return string the generated style tag + */ + public static function style($content, $options = array()) + { + if (!isset($options['type'])) { + $options['type'] = 'text/css'; + } + return static::tag('style', "/**/", $options); + } + + /** + * Generates a script tag. + * @param string $content the script content + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * If the options does not contain "type", a "type" attribute with value "text/javascript" will be rendered. + * @return string the generated script tag + */ + public static function script($content, $options = array()) + { + if (!isset($options['type'])) { + $options['type'] = 'text/javascript'; + } + return static::tag('script', "/**/", $options); + } + + /** + * Generates a link tag that refers to an external CSS file. + * @param array|string $url the URL of the external CSS file. This parameter will be processed by [[url()]]. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated link tag + * @see url + */ + public static function cssFile($url, $options = array()) + { + $options['rel'] = 'stylesheet'; + $options['type'] = 'text/css'; + $options['href'] = static::url($url); + return static::tag('link', '', $options); + } + + /** + * Generates a script tag that refers to an external JavaScript file. + * @param string $url the URL of the external JavaScript file. This parameter will be processed by [[url()]]. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated script tag + * @see url + */ + public static function jsFile($url, $options = array()) + { + $options['type'] = 'text/javascript'; + $options['src'] = static::url($url); + return static::tag('script', '', $options); + } + + /** + * Generates a form start tag. + * @param array|string $action the form action URL. This parameter will be processed by [[url()]]. + * @param string $method the form submission method, either "post" or "get" (case-insensitive) + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated form start tag. + * @see endForm + */ + public static function beginForm($action = '', $method = 'post', $options = array()) + { + $action = static::url($action); + + // query parameters in the action are ignored for GET method + // we use hidden fields to add them back + $hiddens = array(); + if (!strcasecmp($method, 'get') && ($pos = strpos($action, '?')) !== false) { + foreach (explode('&', substr($action, $pos + 1)) as $pair) { + if (($pos1 = strpos($pair, '=')) !== false) { + $hiddens[] = static::hiddenInput(urldecode(substr($pair, 0, $pos1)), urldecode(substr($pair, $pos1 + 1))); + } else { + $hiddens[] = static::hiddenInput(urldecode($pair), ''); + } + } + $action = substr($action, 0, $pos); + } + + $options['action'] = $action; + $options['method'] = $method; + $form = static::beginTag('form', $options); + if ($hiddens !== array()) { + $form .= "\n" . implode("\n", $hiddens); + } + + return $form; + } + + /** + * Generates a form end tag. + * @return string the generated tag + * @see beginForm + */ + public static function endForm() + { + return ''; + } + + /** + * Generates a hyperlink tag. + * @param string $text link body. It will NOT be HTML-encoded. Therefore you can pass in HTML code + * such as an image tag. If this is is coming from end users, you should consider [[encode()]] + * it to prevent XSS attacks. + * @param array|string|null $url the URL for the hyperlink tag. This parameter will be processed by [[url()]] + * and will be used for the "href" attribute of the tag. If this parameter is null, the "href" attribute + * will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated hyperlink + * @see url + */ + public static function a($text, $url = null, $options = array()) + { + if ($url !== null) { + $options['href'] = static::url($url); + } + return static::tag('a', $text, $options); + } + + /** + * Generates a mailto hyperlink. + * @param string $text link body. It will NOT be HTML-encoded. Therefore you can pass in HTML code + * such as an image tag. If this is is coming from end users, you should consider [[encode()]] + * it to prevent XSS attacks. + * @param string $email email address. If this is null, the first parameter (link body) will be treated + * as the email address and used. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated mailto link + */ + public static function mailto($text, $email = null, $options = array()) + { + return static::a($text, 'mailto:' . ($email === null ? $text : $email), $options); + } + + /** + * Generates an image tag. + * @param string $src the image URL. This parameter will be processed by [[url()]]. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated image tag + */ + public static function img($src, $options = array()) + { + $options['src'] = static::url($src); + if (!isset($options['alt'])) { + $options['alt'] = ''; + } + return static::tag('img', null, $options); + } + + /** + * Generates a label tag. + * @param string $content label text. It will NOT be HTML-encoded. Therefore you can pass in HTML code + * such as an image tag. If this is is coming from end users, you should consider [[encode()]] + * it to prevent XSS attacks. + * @param string $for the ID of the HTML element that this label is associated with. + * If this is null, the "for" attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated label tag + */ + public static function label($content, $for = null, $options = array()) + { + $options['for'] = $for; + return static::tag('label', $content, $options); + } + + /** + * Generates a button tag. + * @param string $name the name attribute. If it is null, the name attribute will not be generated. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param string $content the content enclosed within the button tag. It will NOT be HTML-encoded. + * Therefore you can pass in HTML code such as an image tag. If this is is coming from end users, + * you should consider [[encode()]] it to prevent XSS attacks. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * If the options does not contain "type", a "type" attribute with value "button" will be rendered. + * @return string the generated button tag + */ + public static function button($name = null, $value = null, $content = 'Button', $options = array()) + { + $options['name'] = $name; + $options['value'] = $value; + if (!isset($options['type'])) { + $options['type'] = 'button'; + } + return static::tag('button', $content, $options); + } + + /** + * Generates a submit button tag. + * @param string $name the name attribute. If it is null, the name attribute will not be generated. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param string $content the content enclosed within the button tag. It will NOT be HTML-encoded. + * Therefore you can pass in HTML code such as an image tag. If this is is coming from end users, + * you should consider [[encode()]] it to prevent XSS attacks. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated submit button tag + */ + public static function submitButton($name = null, $value = null, $content = 'Submit', $options = array()) + { + $options['type'] = 'submit'; + return static::button($name, $value, $content, $options); + } + + /** + * Generates a reset button tag. + * @param string $name the name attribute. If it is null, the name attribute will not be generated. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param string $content the content enclosed within the button tag. It will NOT be HTML-encoded. + * Therefore you can pass in HTML code such as an image tag. If this is is coming from end users, + * you should consider [[encode()]] it to prevent XSS attacks. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated reset button tag + */ + public static function resetButton($name = null, $value = null, $content = 'Reset', $options = array()) + { + $options['type'] = 'reset'; + return static::button($name, $value, $content, $options); + } + + /** + * Generates an input type of the given type. + * @param string $type the type attribute. + * @param string $name the name attribute. If it is null, the name attribute will not be generated. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated input tag + */ + public static function input($type, $name = null, $value = null, $options = array()) + { + $options['type'] = $type; + $options['name'] = $name; + $options['value'] = $value; + return static::tag('input', null, $options); + } + + /** + * Generates an input button. + * @param string $name the name attribute. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated button tag + */ + public static function buttonInput($name, $value = 'Button', $options = array()) + { + return static::input('button', $name, $value, $options); + } + + /** + * Generates a submit input button. + * @param string $name the name attribute. If it is null, the name attribute will not be generated. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated button tag + */ + public static function submitInput($name = null, $value = 'Submit', $options = array()) + { + return static::input('submit', $name, $value, $options); + } + + /** + * Generates a reset input button. + * @param string $name the name attribute. If it is null, the name attribute will not be generated. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the attributes of the button tag. The values will be HTML-encoded using [[encode()]]. + * Attributes whose value is null will be ignored and not put in the tag returned. + * @return string the generated button tag + */ + public static function resetInput($name = null, $value = 'Reset', $options = array()) + { + return static::input('reset', $name, $value, $options); + } + + /** + * Generates a text input field. + * @param string $name the name attribute. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated button tag + */ + public static function textInput($name, $value = null, $options = array()) + { + return static::input('text', $name, $value, $options); + } + + /** + * Generates a hidden input field. + * @param string $name the name attribute. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated button tag + */ + public static function hiddenInput($name, $value = null, $options = array()) + { + return static::input('hidden', $name, $value, $options); + } + + /** + * Generates a password input field. + * @param string $name the name attribute. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated button tag + */ + public static function passwordInput($name, $value = null, $options = array()) + { + return static::input('password', $name, $value, $options); + } + + /** + * Generates a file input field. + * To use a file input field, you should set the enclosing form's "enctype" attribute to + * be "multipart/form-data". After the form is submitted, the uploaded file information + * can be obtained via $_FILES[$name] (see PHP documentation). + * @param string $name the name attribute. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated button tag + */ + public static function fileInput($name, $value = null, $options = array()) + { + return static::input('file', $name, $value, $options); + } + + /** + * Generates a text area input. + * @param string $name the input name + * @param string $value the input value. Note that it will be encoded using [[encode()]]. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated text area tag + */ + public static function textarea($name, $value = '', $options = array()) + { + $options['name'] = $name; + return static::tag('textarea', static::encode($value), $options); + } + + /** + * Generates a radio button input. + * @param string $name the name attribute. + * @param boolean $checked whether the radio button should be checked. + * @param string $value the value attribute. If it is null, the value attribute will not be rendered. + * @param array $options the tag options in terms of name-value pairs. The following options are supported: + * + * - uncheck: string, the value associated with the uncheck state of the radio button. When this attribute + * is present, a hidden input will be generated so that if the radio button is not checked and is submitted, + * the value of this attribute will still be submitted to the server via the hidden input. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated radio button tag + */ + public static function radio($name, $checked = false, $value = '1', $options = array()) + { + $options['checked'] = $checked; + $options['value'] = $value; + if (isset($options['uncheck'])) { + // add a hidden field so that if the radio button is not selected, it still submits a value + $hidden = static::hiddenInput($name, $options['uncheck']); + unset($options['uncheck']); + } else { + $hidden = ''; + } + return $hidden . static::input('radio', $name, $value, $options); + } + + /** + * Generates a checkbox input. + * @param string $name the name attribute. + * @param boolean $checked whether the checkbox should be checked. + * @param string $value the value attribute. If it is null, the value attribute will not be rendered. + * @param array $options the tag options in terms of name-value pairs. The following options are supported: + * + * - uncheck: string, the value associated with the uncheck state of the checkbox. When this attribute + * is present, a hidden input will be generated so that if the checkbox is not checked and is submitted, + * the value of this attribute will still be submitted to the server via the hidden input. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated checkbox tag + */ + public static function checkbox($name, $checked = false, $value = '1', $options = array()) + { + $options['checked'] = $checked; + $options['value'] = $value; + if (isset($options['uncheck'])) { + // add a hidden field so that if the checkbox is not selected, it still submits a value + $hidden = static::hiddenInput($name, $options['uncheck']); + unset($options['uncheck']); + } else { + $hidden = ''; + } + return $hidden . static::input('checkbox', $name, $value, $options); + } + + /** + * Generates a drop-down list. + * @param string $name the input name + * @param string $selection the selected value + * @param array $items the option data items. The array keys are option values, and the array values + * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). + * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. + * If you have a list of data models, you may convert them into the format described above using + * [[\yii\helpers\ArrayHelper::map()]]. + * + * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in + * the labels will also be HTML-encoded. + * @param array $options the tag options in terms of name-value pairs. The following options are supported: + * + * - prompt: string, a prompt text to be displayed as the first option; + * - options: array, the attributes for the select option tags. The array keys must be valid option values, + * and the array values are the extra attributes for the corresponding option tags. For example, + * + * ~~~ + * array( + * 'value1' => array('disabled' => true), + * 'value2' => array('label' => 'value 2'), + * ); + * ~~~ + * + * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', + * except that the array keys represent the optgroup labels specified in $items. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated drop-down list tag + */ + public static function dropDownList($name, $selection = null, $items = array(), $options = array()) + { + $options['name'] = $name; + $selectOptions = static::renderSelectOptions($selection, $items, $options); + return static::tag('select', "\n" . $selectOptions . "\n", $options); + } + + /** + * Generates a list box. + * @param string $name the input name + * @param string|array $selection the selected value(s) + * @param array $items the option data items. The array keys are option values, and the array values + * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). + * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. + * If you have a list of data models, you may convert them into the format described above using + * [[\yii\helpers\ArrayHelper::map()]]. + * + * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in + * the labels will also be HTML-encoded. + * @param array $options the tag options in terms of name-value pairs. The following options are supported: + * + * - prompt: string, a prompt text to be displayed as the first option; + * - options: array, the attributes for the select option tags. The array keys must be valid option values, + * and the array values are the extra attributes for the corresponding option tags. For example, + * + * ~~~ + * array( + * 'value1' => array('disabled' => true), + * 'value2' => array('label' => 'value 2'), + * ); + * ~~~ + * + * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', + * except that the array keys represent the optgroup labels specified in $items. + * - unselect: string, the value that will be submitted when no option is selected. + * When this attribute is set, a hidden field will be generated so that if no option is selected in multiple + * mode, we can still obtain the posted unselect value. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated list box tag + */ + public static function listBox($name, $selection = null, $items = array(), $options = array()) + { + if (!isset($options['size'])) { + $options['size'] = 4; + } + if (isset($options['multiple']) && $options['multiple'] && substr($name, -2) !== '[]') { + $name .= '[]'; + } + $options['name'] = $name; + if (isset($options['unselect'])) { + // add a hidden field so that if the list box has no option being selected, it still submits a value + if (substr($name, -2) === '[]') { + $name = substr($name, 0, -2); + } + $hidden = static::hiddenInput($name, $options['unselect']); + unset($options['unselect']); + } else { + $hidden = ''; + } + $selectOptions = static::renderSelectOptions($selection, $items, $options); + return $hidden . static::tag('select', "\n" . $selectOptions . "\n", $options); + } + + /** + * Generates a list of checkboxes. + * A checkbox list allows multiple selection, like [[listBox()]]. + * As a result, the corresponding submitted value is an array. + * @param string $name the name attribute of each checkbox. + * @param string|array $selection the selected value(s). + * @param array $items the data item used to generate the checkboxes. + * The array keys are the labels, while the array values are the corresponding checkbox values. + * Note that the labels will NOT be HTML-encoded, while the values will. + * @param array $options options (name => config) for the checkbox list. The following options are supported: + * + * - unselect: string, the value that should be submitted when none of the checkboxes is selected. + * By setting this option, a hidden input will be generated. + * - separator: string, the HTML code that separates items. + * - item: callable, a callback that can be used to customize the generation of the HTML code + * corresponding to a single item in $items. The signature of this callback must be: + * + * ~~~ + * function ($index, $label, $name, $checked, $value) + * ~~~ + * + * where $index is the zero-based index of the checkbox in the whole list; $label + * is the label for the checkbox; and $name, $value and $checked represent the name, + * value and the checked status of the checkbox input. + * @return string the generated checkbox list + */ + public static function checkboxList($name, $selection = null, $items = array(), $options = array()) + { + if (substr($name, -2) !== '[]') { + $name .= '[]'; + } + + $formatter = isset($options['item']) ? $options['item'] : null; + $lines = array(); + $index = 0; + foreach ($items as $value => $label) { + $checked = $selection !== null && + (!is_array($selection) && !strcmp($value, $selection) + || is_array($selection) && in_array($value, $selection)); + if ($formatter !== null) { + $lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value); + } else { + $lines[] = static::label(static::checkbox($name, $checked, $value) . ' ' . $label); + } + $index++; + } + + if (isset($options['unselect'])) { + // add a hidden field so that if the list box has no option being selected, it still submits a value + $name2 = substr($name, -2) === '[]' ? substr($name, 0, -2) : $name; + $hidden = static::hiddenInput($name2, $options['unselect']); + } else { + $hidden = ''; + } + $separator = isset($options['separator']) ? $options['separator'] : "\n"; + + return $hidden . implode($separator, $lines); + } + + /** + * Generates a list of radio buttons. + * A radio button list is like a checkbox list, except that it only allows single selection. + * @param string $name the name attribute of each radio button. + * @param string|array $selection the selected value(s). + * @param array $items the data item used to generate the radio buttons. + * The array keys are the labels, while the array values are the corresponding radio button values. + * Note that the labels will NOT be HTML-encoded, while the values will. + * @param array $options options (name => config) for the radio button list. The following options are supported: + * + * - unselect: string, the value that should be submitted when none of the radio buttons is selected. + * By setting this option, a hidden input will be generated. + * - separator: string, the HTML code that separates items. + * - item: callable, a callback that can be used to customize the generation of the HTML code + * corresponding to a single item in $items. The signature of this callback must be: + * + * ~~~ + * function ($index, $label, $name, $checked, $value) + * ~~~ + * + * where $index is the zero-based index of the radio button in the whole list; $label + * is the label for the radio button; and $name, $value and $checked represent the name, + * value and the checked status of the radio button input. + * @return string the generated radio button list + */ + public static function radioList($name, $selection = null, $items = array(), $options = array()) + { + $formatter = isset($options['item']) ? $options['item'] : null; + $lines = array(); + $index = 0; + foreach ($items as $value => $label) { + $checked = $selection !== null && + (!is_array($selection) && !strcmp($value, $selection) + || is_array($selection) && in_array($value, $selection)); + if ($formatter !== null) { + $lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value); + } else { + $lines[] = static::label(static::radio($name, $checked, $value) . ' ' . $label); + } + $index++; + } + + $separator = isset($options['separator']) ? $options['separator'] : "\n"; + if (isset($options['unselect'])) { + // add a hidden field so that if the list box has no option being selected, it still submits a value + $hidden = static::hiddenInput($name, $options['unselect']); + } else { + $hidden = ''; + } + + return $hidden . implode($separator, $lines); + } + + /** + * Renders the option tags that can be used by [[dropDownList()]] and [[listBox()]]. + * @param string|array $selection the selected value(s). This can be either a string for single selection + * or an array for multiple selections. + * @param array $items the option data items. The array keys are option values, and the array values + * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). + * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. + * If you have a list of data models, you may convert them into the format described above using + * [[\yii\helpers\ArrayHelper::map()]]. + * + * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in + * the labels will also be HTML-encoded. + * @param array $tagOptions the $options parameter that is passed to the [[dropDownList()]] or [[listBox()]] call. + * This method will take out these elements, if any: "prompt", "options" and "groups". See more details + * in [[dropDownList()]] for the explanation of these elements. + * + * @return string the generated list options + */ + public static function renderSelectOptions($selection, $items, &$tagOptions = array()) + { + $lines = array(); + if (isset($tagOptions['prompt'])) { + $prompt = str_replace(' ', ' ', static::encode($tagOptions['prompt'])); + $lines[] = static::tag('option', $prompt, array('value' => '')); + } + + $options = isset($tagOptions['options']) ? $tagOptions['options'] : array(); + $groups = isset($tagOptions['groups']) ? $tagOptions['groups'] : array(); + unset($tagOptions['prompt'], $tagOptions['options'], $tagOptions['groups']); + + foreach ($items as $key => $value) { + if (is_array($value)) { + $groupAttrs = isset($groups[$key]) ? $groups[$key] : array(); + $groupAttrs['label'] = $key; + $attrs = array('options' => $options, 'groups' => $groups); + $content = static::renderSelectOptions($selection, $value, $attrs); + $lines[] = static::tag('optgroup', "\n" . $content . "\n", $groupAttrs); + } else { + $attrs = isset($options[$key]) ? $options[$key] : array(); + $attrs['value'] = $key; + $attrs['selected'] = $selection !== null && + (!is_array($selection) && !strcmp($key, $selection) + || is_array($selection) && in_array($key, $selection)); + $lines[] = static::tag('option', str_replace(' ', ' ', static::encode($value)), $attrs); + } + } + + return implode("\n", $lines); + } + + /** + * Renders the HTML tag attributes. + * Boolean attributes such as s 'checked', 'disabled', 'readonly', will be handled specially + * according to [[booleanAttributes]] and [[showBooleanAttributeValues]]. + * @param array $attributes attributes to be rendered. The attribute values will be HTML-encoded using [[encode()]]. + * Attributes whose value is null will be ignored and not put in the rendering result. + * @return string the rendering result. If the attributes are not empty, they will be rendered + * into a string with a leading white space (such that it can be directly appended to the tag name + * in a tag. If there is no attribute, an empty string will be returned. + */ + public static function renderTagAttributes($attributes) + { + if (count($attributes) > 1) { + $sorted = array(); + foreach (static::$attributeOrder as $name) { + if (isset($attributes[$name])) { + $sorted[$name] = $attributes[$name]; + } + } + $attributes = array_merge($sorted, $attributes); + } + + $html = ''; + foreach ($attributes as $name => $value) { + if (isset(static::$booleanAttributes[strtolower($name)])) { + if ($value || strcasecmp($name, $value) === 0) { + $html .= static::$showBooleanAttributeValues ? " $name=\"$name\"" : " $name"; + } + } elseif ($value !== null) { + $html .= " $name=\"" . static::encode($value) . '"'; + } + } + return $html; + } + + /** + * Normalizes the input parameter to be a valid URL. + * + * If the input parameter + * + * - is an empty string: the currently requested URL will be returned; + * - is a non-empty string: it will be processed by [[Yii::getAlias()]] and returned; + * - is an array: the first array element is considered a route, while the rest of the name-value + * pairs are treated as the parameters to be used for URL creation using [[\yii\web\Controller::createUrl()]]. + * For example: `array('post/index', 'page' => 2)`, `array('index')`. + * + * @param array|string $url the parameter to be used to generate a valid URL + * @return string the normalized URL + * @throws InvalidParamException if the parameter is invalid. + */ + public static function url($url) + { + if (is_array($url)) { + if (isset($url[0])) { + $route = $url[0]; + $params = array_splice($url, 1); + if (Yii::$app->controller !== null) { + return Yii::$app->controller->createUrl($route, $params); + } else { + return Yii::$app->getUrlManager()->createUrl($route, $params); + } + } else { + throw new InvalidParamException('The array specifying a URL must contain at least one element.'); + } + } elseif ($url === '') { + return Yii::$app->getRequest()->getUrl(); + } else { + return Yii::getAlias($url); + } + } +} diff --git a/framework/helpers/base/SecurityHelper.php b/framework/helpers/base/SecurityHelper.php new file mode 100644 index 0000000..6ba48ba --- /dev/null +++ b/framework/helpers/base/SecurityHelper.php @@ -0,0 +1,272 @@ + + * @author Tom Worster + * @since 2.0 + */ +class SecurityHelper +{ + /** + * Encrypts data. + * @param string $data data to be encrypted. + * @param string $key the encryption secret key + * @return string the encrypted data + * @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized + * @see decrypt() + */ + public static function encrypt($data, $key) + { + $module = static::openCryptModule(); + $key = StringHelper::substr($key, 0, mcrypt_enc_get_key_size($module)); + srand(); + $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($module), MCRYPT_RAND); + mcrypt_generic_init($module, $key, $iv); + $encrypted = $iv . mcrypt_generic($module, $data); + mcrypt_generic_deinit($module); + mcrypt_module_close($module); + return $encrypted; + } + + /** + * Decrypts data + * @param string $data data to be decrypted. + * @param string $key the decryption secret key + * @return string the decrypted data + * @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized + * @see encrypt() + */ + public static function decrypt($data, $key) + { + $module = static::openCryptModule(); + $key = StringHelper::substr($key, 0, mcrypt_enc_get_key_size($module)); + $ivSize = mcrypt_enc_get_iv_size($module); + $iv = StringHelper::substr($data, 0, $ivSize); + mcrypt_generic_init($module, $key, $iv); + $decrypted = mdecrypt_generic($module, StringHelper::substr($data, $ivSize, StringHelper::strlen($data))); + mcrypt_generic_deinit($module); + mcrypt_module_close($module); + return rtrim($decrypted, "\0"); + } + + /** + * Prefixes data with a keyed hash value so that it can later be detected if it is tampered. + * @param string $data the data to be protected + * @param string $key the secret key to be used for generating hash + * @param string $algorithm the hashing algorithm (e.g. "md5", "sha1", "sha256", etc.). Call PHP "hash_algos()" + * function to see the supported hashing algorithms on your system. + * @return string the data prefixed with the keyed hash + * @see validateData() + * @see getSecretKey() + */ + public static function hashData($data, $key, $algorithm = 'sha256') + { + return hash_hmac($algorithm, $data, $key) . $data; + } + + /** + * Validates if the given data is tampered. + * @param string $data the data to be validated. The data must be previously + * generated by [[hashData()]]. + * @param string $key the secret key that was previously used to generate the hash for the data in [[hashData()]]. + * @param string $algorithm the hashing algorithm (e.g. "md5", "sha1", "sha256", etc.). Call PHP "hash_algos()" + * function to see the supported hashing algorithms on your system. This must be the same + * as the value passed to [[hashData()]] when generating the hash for the data. + * @return string the real data with the hash stripped off. False if the data is tampered. + * @see hashData() + */ + public static function validateData($data, $key, $algorithm = 'sha256') + { + $hashSize = StringHelper::strlen(hash_hmac($algorithm, 'test', $key)); + $n = StringHelper::strlen($data); + if ($n >= $hashSize) { + $hash = StringHelper::substr($data, 0, $hashSize); + $data2 = StringHelper::substr($data, $hashSize, $n - $hashSize); + return $hash === hash_hmac($algorithm, $data2, $key) ? $data2 : false; + } else { + return false; + } + } + + /** + * Returns a secret key associated with the specified name. + * If the secret key does not exist, a random key will be generated + * and saved in the file "keys.php" under the application's runtime directory + * so that the same secret key can be returned in future requests. + * @param string $name the name that is associated with the secret key + * @param integer $length the length of the key that should be generated if not exists + * @return string the secret key associated with the specified name + */ + public static function getSecretKey($name, $length = 32) + { + static $keys; + $keyFile = Yii::$app->getRuntimePath() . '/keys.php'; + if ($keys === null) { + $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); + file_put_contents($keyFile, " 30) { + throw new InvalidParamException('Hash is invalid.'); + } + + $test = crypt($password, $hash); + $n = strlen($test); + if (strlen($test) < 32 || $n !== strlen($hash)) { + return false; + } + + // Use a for-loop to compare two strings to prevent timing attacks. See: + // http://codereview.stackexchange.com/questions/13512 + $check = 0; + for ($i = 0; $i < $n; ++$i) { + $check |= (ord($test[$i]) ^ ord($hash[$i])); + } + + return $check === 0; + } + + /** + * Generates a salt that can be used to generate a password hash. + * + * The PHP [crypt()](http://php.net/manual/en/function.crypt.php) built-in function + * requires, for the Blowfish hash algorithm, a salt string in a specific format: + * "$2a$", "$2x$" or "$2y$", a two digit cost parameter, "$", and 22 characters + * from the alphabet "./0-9A-Za-z". + * + * @param integer $cost the cost parameter + * @return string the random salt value. + * @throws InvalidParamException if the cost parameter is not between 4 and 30 + */ + protected static function generateSalt($cost = 13) + { + $cost = (int)$cost; + if ($cost < 4 || $cost > 30) { + throw new InvalidParamException('Cost must be between 4 and 31.'); + } + + // Get 20 * 8bits of pseudo-random entropy from mt_rand(). + $rand = ''; + for ($i = 0; $i < 20; ++$i) { + $rand .= chr(mt_rand(0, 255)); + } + + // Add the microtime for a little more entropy. + $rand .= microtime(); + // Mix the bits cryptographically into a 20-byte binary string. + $rand = sha1($rand, true); + // Form the prefix that specifies Blowfish algorithm and cost parameter. + $salt = sprintf("$2y$%02d$", $cost); + // Append the random salt data in the required base64 format. + $salt .= str_replace('+', '.', substr(base64_encode($rand), 0, 22)); + return $salt; + } +} \ No newline at end of file diff --git a/framework/helpers/base/StringHelper.php b/framework/helpers/base/StringHelper.php new file mode 100644 index 0000000..cb4b09b --- /dev/null +++ b/framework/helpers/base/StringHelper.php @@ -0,0 +1,125 @@ + + * @author Alex Makarov + * @since 2.0 + */ +class StringHelper +{ + /** + * Returns the number of bytes in the given string. + * This method ensures the string is treated as a byte array. + * It will use `mb_strlen()` if it is available. + * @param string $string the string being measured for length + * @return integer the number of bytes in the given string. + */ + public static function strlen($string) + { + return function_exists('mb_strlen') ? mb_strlen($string, '8bit') : strlen($string); + } + + /** + * Returns the portion of string specified by the start and length parameters. + * This method ensures the string is treated as a byte array. + * It will use `mb_substr()` if it is available. + * @param string $string the input string. Must be one character or longer. + * @param integer $start the starting position + * @param integer $length the desired portion length + * @return string the extracted part of string, or FALSE on failure or an empty string. + * @see http://www.php.net/manual/en/function.substr.php + */ + public static function substr($string, $start, $length) + { + return function_exists('mb_substr') ? mb_substr($string, $start, $length, '8bit') : substr($string, $start, $length); + } + + /** + * Converts a word to its plural form. + * Note that this is for English only! + * For example, 'apple' will become 'apples', and 'child' will become 'children'. + * @param string $name the word to be pluralized + * @return string the pluralized word + */ + public static function pluralize($name) + { + static $rules = array( + '/(m)ove$/i' => '\1oves', + '/(f)oot$/i' => '\1eet', + '/(c)hild$/i' => '\1hildren', + '/(h)uman$/i' => '\1umans', + '/(m)an$/i' => '\1en', + '/(s)taff$/i' => '\1taff', + '/(t)ooth$/i' => '\1eeth', + '/(p)erson$/i' => '\1eople', + '/([m|l])ouse$/i' => '\1ice', + '/(x|ch|ss|sh|us|as|is|os)$/i' => '\1es', + '/([^aeiouy]|qu)y$/i' => '\1ies', + '/(?:([^f])fe|([lr])f)$/i' => '\1\2ves', + '/(shea|lea|loa|thie)f$/i' => '\1ves', + '/([ti])um$/i' => '\1a', + '/(tomat|potat|ech|her|vet)o$/i' => '\1oes', + '/(bu)s$/i' => '\1ses', + '/(ax|test)is$/i' => '\1es', + '/s$/' => 's', + ); + foreach ($rules as $rule => $replacement) { + if (preg_match($rule, $name)) { + return preg_replace($rule, $replacement, $name); + } + } + return $name . 's'; + } + + /** + * Converts a CamelCase name into space-separated words. + * For example, 'PostTag' will be converted to 'Post Tag'. + * @param string $name the string to be converted + * @param boolean $ucwords whether to capitalize the first letter in each word + * @return string the resulting words + */ + public static function camel2words($name, $ucwords = true) + { + $label = trim(strtolower(str_replace(array('-', '_', '.'), ' ', preg_replace('/(? + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\helpers\base; + +/** + * VarDumper is intended to replace the buggy PHP function var_dump and print_r. + * It can correctly identify the recursively referenced objects in a complex + * object structure. It also has a recursive depth control to avoid indefinite + * recursive display of some peculiar variables. + * + * VarDumper can be used as follows, + * + * ~~~ + * VarDumper::dump($var); + * ~~~ + * + * @author Qiang Xue + * @since 2.0 + */ +class VarDumper +{ + private static $_objects; + private static $_output; + private static $_depth; + + /** + * Displays a variable. + * This method achieves the similar functionality as var_dump and print_r + * but is more robust when handling complex objects such as Yii controllers. + * @param mixed $var variable to be dumped + * @param integer $depth maximum depth that the dumper should go into the variable. Defaults to 10. + * @param boolean $highlight whether the result should be syntax-highlighted + */ + public static function dump($var, $depth = 10, $highlight = false) + { + echo self::dumpAsString($var, $depth, $highlight); + } + + /** + * Dumps a variable in terms of a string. + * This method achieves the similar functionality as var_dump and print_r + * but is more robust when handling complex objects such as Yii controllers. + * @param mixed $var variable to be dumped + * @param integer $depth maximum depth that the dumper should go into the variable. Defaults to 10. + * @param boolean $highlight whether the result should be syntax-highlighted + * @return string the string representation of the variable + */ + public static function dumpAsString($var, $depth = 10, $highlight = false) + { + self::$_output = ''; + self::$_objects = array(); + self::$_depth = $depth; + self::dumpInternal($var, 0); + if ($highlight) { + $result = highlight_string("/', '', $result, 1); + } + return self::$_output; + } + + /* + * @param mixed $var variable to be dumped + * @param integer $level depth level + */ + private static function dumpInternal($var, $level) + { + switch (gettype($var)) { + case 'boolean': + self::$_output .= $var ? 'true' : 'false'; + break; + case 'integer': + self::$_output .= "$var"; + break; + case 'double': + self::$_output .= "$var"; + break; + case 'string': + self::$_output .= "'" . addslashes($var) . "'"; + break; + case 'resource': + self::$_output .= '{resource}'; + break; + case 'NULL': + self::$_output .= "null"; + break; + case 'unknown type': + self::$_output .= '{unknown}'; + break; + case 'array': + if (self::$_depth <= $level) { + self::$_output .= 'array(...)'; + } elseif (empty($var)) { + self::$_output .= 'array()'; + } else { + $keys = array_keys($var); + $spaces = str_repeat(' ', $level * 4); + self::$_output .= "array\n" . $spaces . '('; + foreach ($keys as $key) { + self::$_output .= "\n" . $spaces . ' '; + self::dumpInternal($key, 0); + self::$_output .= ' => '; + self::dumpInternal($var[$key], $level + 1); + } + self::$_output .= "\n" . $spaces . ')'; + } + break; + case 'object': + if (($id = array_search($var, self::$_objects, true)) !== false) { + self::$_output .= get_class($var) . '#' . ($id + 1) . '(...)'; + } elseif (self::$_depth <= $level) { + self::$_output .= get_class($var) . '(...)'; + } else { + $id = self::$_objects[] = $var; + $className = get_class($var); + $members = (array)$var; + $spaces = str_repeat(' ', $level * 4); + self::$_output .= "$className#$id\n" . $spaces . '('; + foreach ($members as $key => $value) { + $keyDisplay = strtr(trim($key), array("\0" => ':')); + self::$_output .= "\n" . $spaces . " [$keyDisplay] => "; + self::dumpInternal($value, $level + 1); + } + self::$_output .= "\n" . $spaces . ')'; + } + break; + } + } +} \ No newline at end of file diff --git a/framework/helpers/base/mimeTypes.php b/framework/helpers/base/mimeTypes.php new file mode 100644 index 0000000..ffdba4b --- /dev/null +++ b/framework/helpers/base/mimeTypes.php @@ -0,0 +1,187 @@ + 'application/postscript', + 'aif' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'anx' => 'application/annodex', + 'asc' => 'text/plain', + 'au' => 'audio/basic', + 'avi' => 'video/x-msvideo', + 'axa' => 'audio/annodex', + 'axv' => 'video/annodex', + 'bcpio' => 'application/x-bcpio', + 'bin' => 'application/octet-stream', + 'bmp' => 'image/bmp', + 'c' => 'text/plain', + 'cc' => 'text/plain', + 'ccad' => 'application/clariscad', + 'cdf' => 'application/x-netcdf', + 'class' => 'application/octet-stream', + 'cpio' => 'application/x-cpio', + 'cpt' => 'application/mac-compactpro', + 'csh' => 'application/x-csh', + 'css' => 'text/css', + 'dcr' => 'application/x-director', + 'dir' => 'application/x-director', + 'dms' => 'application/octet-stream', + 'doc' => 'application/msword', + 'drw' => 'application/drafting', + 'dvi' => 'application/x-dvi', + 'dwg' => 'application/acad', + 'dxf' => 'application/dxf', + 'dxr' => 'application/x-director', + 'eps' => 'application/postscript', + 'etx' => 'text/x-setext', + 'exe' => 'application/octet-stream', + 'ez' => 'application/andrew-inset', + 'f' => 'text/plain', + 'f90' => 'text/plain', + 'flac' => 'audio/flac', + 'fli' => 'video/x-fli', + 'flv' => 'video/x-flv', + 'gif' => 'image/gif', + 'gtar' => 'application/x-gtar', + 'gz' => 'application/x-gzip', + 'h' => 'text/plain', + 'hdf' => 'application/x-hdf', + 'hh' => 'text/plain', + 'hqx' => 'application/mac-binhex40', + 'htm' => 'text/html', + 'html' => 'text/html', + 'ice' => 'x-conference/x-cooltalk', + 'ief' => 'image/ief', + 'iges' => 'model/iges', + 'igs' => 'model/iges', + 'ips' => 'application/x-ipscript', + 'ipx' => 'application/x-ipix', + 'jpe' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'js' => 'application/x-javascript', + 'kar' => 'audio/midi', + 'latex' => 'application/x-latex', + 'lha' => 'application/octet-stream', + 'lsp' => 'application/x-lisp', + 'lzh' => 'application/octet-stream', + 'm' => 'text/plain', + 'man' => 'application/x-troff-man', + 'me' => 'application/x-troff-me', + 'mesh' => 'model/mesh', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mif' => 'application/vnd.mif', + 'mime' => 'www/mime', + 'mov' => 'video/quicktime', + 'movie' => 'video/x-sgi-movie', + 'mp2' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'mpe' => 'video/mpeg', + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mpga' => 'audio/mpeg', + 'ms' => 'application/x-troff-ms', + 'msh' => 'model/mesh', + 'nc' => 'application/x-netcdf', + 'oga' => 'audio/ogg', + 'ogg' => 'audio/ogg', + 'ogv' => 'video/ogg', + 'ogx' => 'application/ogg', + 'oda' => 'application/oda', + 'pbm' => 'image/x-portable-bitmap', + 'pdb' => 'chemical/x-pdb', + 'pdf' => 'application/pdf', + 'pgm' => 'image/x-portable-graymap', + 'pgn' => 'application/x-chess-pgn', + 'png' => 'image/png', + 'pnm' => 'image/x-portable-anymap', + 'pot' => 'application/mspowerpoint', + 'ppm' => 'image/x-portable-pixmap', + 'pps' => 'application/mspowerpoint', + 'ppt' => 'application/mspowerpoint', + 'ppz' => 'application/mspowerpoint', + 'pre' => 'application/x-freelance', + 'prt' => 'application/pro_eng', + 'ps' => 'application/postscript', + 'qt' => 'video/quicktime', + 'ra' => 'audio/x-realaudio', + 'ram' => 'audio/x-pn-realaudio', + 'ras' => 'image/cmu-raster', + 'rgb' => 'image/x-rgb', + 'rm' => 'audio/x-pn-realaudio', + 'roff' => 'application/x-troff', + 'rpm' => 'audio/x-pn-realaudio-plugin', + 'rtf' => 'text/rtf', + 'rtx' => 'text/richtext', + 'scm' => 'application/x-lotusscreencam', + 'set' => 'application/set', + 'sgm' => 'text/sgml', + 'sgml' => 'text/sgml', + 'sh' => 'application/x-sh', + 'shar' => 'application/x-shar', + 'silo' => 'model/mesh', + 'sit' => 'application/x-stuffit', + 'skd' => 'application/x-koan', + 'skm' => 'application/x-koan', + 'skp' => 'application/x-koan', + 'skt' => 'application/x-koan', + 'smi' => 'application/smil', + 'smil' => 'application/smil', + 'snd' => 'audio/basic', + 'sol' => 'application/solids', + 'spl' => 'application/x-futuresplash', + 'spx' => 'audio/ogg', + 'src' => 'application/x-wais-source', + 'step' => 'application/STEP', + 'stl' => 'application/SLA', + 'stp' => 'application/STEP', + 'sv4cpio' => 'application/x-sv4cpio', + 'sv4crc' => 'application/x-sv4crc', + 'swf' => 'application/x-shockwave-flash', + 't' => 'application/x-troff', + 'tar' => 'application/x-tar', + 'tcl' => 'application/x-tcl', + 'tex' => 'application/x-tex', + 'texi' => 'application/x-texinfo', + 'texinfo' => 'application/x-texinfo', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'tr' => 'application/x-troff', + 'tsi' => 'audio/TSP-audio', + 'tsp' => 'application/dsptype', + 'tsv' => 'text/tab-separated-values', + 'txt' => 'text/plain', + 'unv' => 'application/i-deas', + 'ustar' => 'application/x-ustar', + 'vcd' => 'application/x-cdlink', + 'vda' => 'application/vda', + 'viv' => 'video/vnd.vivo', + 'vivo' => 'video/vnd.vivo', + 'vrml' => 'model/vrml', + 'wav' => 'audio/x-wav', + 'wrl' => 'model/vrml', + 'xbm' => 'image/x-xbitmap', + 'xlc' => 'application/vnd.ms-excel', + 'xll' => 'application/vnd.ms-excel', + 'xlm' => 'application/vnd.ms-excel', + 'xls' => 'application/vnd.ms-excel', + 'xlw' => 'application/vnd.ms-excel', + 'xml' => 'application/xml', + 'xpm' => 'image/x-xpixmap', + 'xspf' => 'application/xspf+xml', + 'xwd' => 'image/x-xwindowdump', + 'xyz' => 'chemical/x-pdb', + 'zip' => 'application/zip', +); diff --git a/framework/helpers/mimeTypes.php b/framework/helpers/mimeTypes.php deleted file mode 100644 index ffdba4b..0000000 --- a/framework/helpers/mimeTypes.php +++ /dev/null @@ -1,187 +0,0 @@ - 'application/postscript', - 'aif' => 'audio/x-aiff', - 'aifc' => 'audio/x-aiff', - 'aiff' => 'audio/x-aiff', - 'anx' => 'application/annodex', - 'asc' => 'text/plain', - 'au' => 'audio/basic', - 'avi' => 'video/x-msvideo', - 'axa' => 'audio/annodex', - 'axv' => 'video/annodex', - 'bcpio' => 'application/x-bcpio', - 'bin' => 'application/octet-stream', - 'bmp' => 'image/bmp', - 'c' => 'text/plain', - 'cc' => 'text/plain', - 'ccad' => 'application/clariscad', - 'cdf' => 'application/x-netcdf', - 'class' => 'application/octet-stream', - 'cpio' => 'application/x-cpio', - 'cpt' => 'application/mac-compactpro', - 'csh' => 'application/x-csh', - 'css' => 'text/css', - 'dcr' => 'application/x-director', - 'dir' => 'application/x-director', - 'dms' => 'application/octet-stream', - 'doc' => 'application/msword', - 'drw' => 'application/drafting', - 'dvi' => 'application/x-dvi', - 'dwg' => 'application/acad', - 'dxf' => 'application/dxf', - 'dxr' => 'application/x-director', - 'eps' => 'application/postscript', - 'etx' => 'text/x-setext', - 'exe' => 'application/octet-stream', - 'ez' => 'application/andrew-inset', - 'f' => 'text/plain', - 'f90' => 'text/plain', - 'flac' => 'audio/flac', - 'fli' => 'video/x-fli', - 'flv' => 'video/x-flv', - 'gif' => 'image/gif', - 'gtar' => 'application/x-gtar', - 'gz' => 'application/x-gzip', - 'h' => 'text/plain', - 'hdf' => 'application/x-hdf', - 'hh' => 'text/plain', - 'hqx' => 'application/mac-binhex40', - 'htm' => 'text/html', - 'html' => 'text/html', - 'ice' => 'x-conference/x-cooltalk', - 'ief' => 'image/ief', - 'iges' => 'model/iges', - 'igs' => 'model/iges', - 'ips' => 'application/x-ipscript', - 'ipx' => 'application/x-ipix', - 'jpe' => 'image/jpeg', - 'jpeg' => 'image/jpeg', - 'jpg' => 'image/jpeg', - 'js' => 'application/x-javascript', - 'kar' => 'audio/midi', - 'latex' => 'application/x-latex', - 'lha' => 'application/octet-stream', - 'lsp' => 'application/x-lisp', - 'lzh' => 'application/octet-stream', - 'm' => 'text/plain', - 'man' => 'application/x-troff-man', - 'me' => 'application/x-troff-me', - 'mesh' => 'model/mesh', - 'mid' => 'audio/midi', - 'midi' => 'audio/midi', - 'mif' => 'application/vnd.mif', - 'mime' => 'www/mime', - 'mov' => 'video/quicktime', - 'movie' => 'video/x-sgi-movie', - 'mp2' => 'audio/mpeg', - 'mp3' => 'audio/mpeg', - 'mpe' => 'video/mpeg', - 'mpeg' => 'video/mpeg', - 'mpg' => 'video/mpeg', - 'mpga' => 'audio/mpeg', - 'ms' => 'application/x-troff-ms', - 'msh' => 'model/mesh', - 'nc' => 'application/x-netcdf', - 'oga' => 'audio/ogg', - 'ogg' => 'audio/ogg', - 'ogv' => 'video/ogg', - 'ogx' => 'application/ogg', - 'oda' => 'application/oda', - 'pbm' => 'image/x-portable-bitmap', - 'pdb' => 'chemical/x-pdb', - 'pdf' => 'application/pdf', - 'pgm' => 'image/x-portable-graymap', - 'pgn' => 'application/x-chess-pgn', - 'png' => 'image/png', - 'pnm' => 'image/x-portable-anymap', - 'pot' => 'application/mspowerpoint', - 'ppm' => 'image/x-portable-pixmap', - 'pps' => 'application/mspowerpoint', - 'ppt' => 'application/mspowerpoint', - 'ppz' => 'application/mspowerpoint', - 'pre' => 'application/x-freelance', - 'prt' => 'application/pro_eng', - 'ps' => 'application/postscript', - 'qt' => 'video/quicktime', - 'ra' => 'audio/x-realaudio', - 'ram' => 'audio/x-pn-realaudio', - 'ras' => 'image/cmu-raster', - 'rgb' => 'image/x-rgb', - 'rm' => 'audio/x-pn-realaudio', - 'roff' => 'application/x-troff', - 'rpm' => 'audio/x-pn-realaudio-plugin', - 'rtf' => 'text/rtf', - 'rtx' => 'text/richtext', - 'scm' => 'application/x-lotusscreencam', - 'set' => 'application/set', - 'sgm' => 'text/sgml', - 'sgml' => 'text/sgml', - 'sh' => 'application/x-sh', - 'shar' => 'application/x-shar', - 'silo' => 'model/mesh', - 'sit' => 'application/x-stuffit', - 'skd' => 'application/x-koan', - 'skm' => 'application/x-koan', - 'skp' => 'application/x-koan', - 'skt' => 'application/x-koan', - 'smi' => 'application/smil', - 'smil' => 'application/smil', - 'snd' => 'audio/basic', - 'sol' => 'application/solids', - 'spl' => 'application/x-futuresplash', - 'spx' => 'audio/ogg', - 'src' => 'application/x-wais-source', - 'step' => 'application/STEP', - 'stl' => 'application/SLA', - 'stp' => 'application/STEP', - 'sv4cpio' => 'application/x-sv4cpio', - 'sv4crc' => 'application/x-sv4crc', - 'swf' => 'application/x-shockwave-flash', - 't' => 'application/x-troff', - 'tar' => 'application/x-tar', - 'tcl' => 'application/x-tcl', - 'tex' => 'application/x-tex', - 'texi' => 'application/x-texinfo', - 'texinfo' => 'application/x-texinfo', - 'tif' => 'image/tiff', - 'tiff' => 'image/tiff', - 'tr' => 'application/x-troff', - 'tsi' => 'audio/TSP-audio', - 'tsp' => 'application/dsptype', - 'tsv' => 'text/tab-separated-values', - 'txt' => 'text/plain', - 'unv' => 'application/i-deas', - 'ustar' => 'application/x-ustar', - 'vcd' => 'application/x-cdlink', - 'vda' => 'application/vda', - 'viv' => 'video/vnd.vivo', - 'vivo' => 'video/vnd.vivo', - 'vrml' => 'model/vrml', - 'wav' => 'audio/x-wav', - 'wrl' => 'model/vrml', - 'xbm' => 'image/x-xbitmap', - 'xlc' => 'application/vnd.ms-excel', - 'xll' => 'application/vnd.ms-excel', - 'xlm' => 'application/vnd.ms-excel', - 'xls' => 'application/vnd.ms-excel', - 'xlw' => 'application/vnd.ms-excel', - 'xml' => 'application/xml', - 'xpm' => 'image/x-xpixmap', - 'xspf' => 'application/xspf+xml', - 'xwd' => 'image/x-xwindowdump', - 'xyz' => 'chemical/x-pdb', - 'zip' => 'application/zip', -); From f52bc48576968e2ba407061a8169ad958677f7cb Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sat, 13 Apr 2013 23:16:11 -0400 Subject: [PATCH 09/53] use error_log to log fatal errors. --- framework/base/Application.php | 137 ++++++++++++++++------------------------- 1 file changed, 54 insertions(+), 83 deletions(-) diff --git a/framework/base/Application.php b/framework/base/Application.php index 1053210..b9f01d7 100644 --- a/framework/base/Application.php +++ b/framework/base/Application.php @@ -13,36 +13,6 @@ use yii\helpers\FileHelper; /** * Application is the base class for all application classes. * - * An application serves as the global context that the user request - * is being processed. It manages a set of application components that - * provide specific functionalities to the whole application. - * - * The core application components provided by Application are the following: - *
    - *
  • {@link getErrorHandler errorHandler}: handles PHP errors and - * uncaught exceptions. This application component is dynamically loaded when needed.
  • - *
  • {@link getSecurityManager securityManager}: provides security-related - * services, such as hashing, encryption. This application component is dynamically - * loaded when needed.
  • - *
  • {@link getStatePersister statePersister}: provides global state - * persistence method. This application component is dynamically loaded when needed.
  • - *
  • {@link getCache cache}: provides caching feature. This application component is - * disabled by default.
  • - *
- * - * Application will undergo the following life cycles when processing a user request: - *
    - *
  1. load application configuration;
  2. - *
  3. set up class autoloader and error handling;
  4. - *
  5. load static application components;
  6. - *
  7. {@link beforeRequest}: preprocess the user request; `beforeRequest` event raised.
  8. - *
  9. {@link processRequest}: process the user request;
  10. - *
  11. {@link afterRequest}: postprocess the user request; `afterRequest` event raised.
  12. - *
- * - * Starting from lifecycle 3, if a PHP error or an uncaught exception occurs, - * the application will switch to its error handling logic and jump to step 6 afterwards. - * * @author Qiang Xue * @since 2.0 */ @@ -157,30 +127,6 @@ class Application extends Module } /** - * Handles fatal PHP errors - */ - public function handleFatalError() - { - if (YII_ENABLE_ERROR_HANDLER) { - $error = error_get_last(); - - if (ErrorException::isFatalError($error)) { - unset($this->_memoryReserve); - $exception = new ErrorException($error['message'], $error['type'], $error['type'], $error['file'], $error['line']); - $this->logException($exception); - - if (($handler = $this->getErrorHandler()) !== null) { - @$handler->handle($exception); - } else { - $this->renderException($exception); - } - - exit(1); - } - } - } - - /** * Runs the application. * This is the main entrance of an application. * @return integer the exit status (0 means normal, non-zero values mean abnormal) @@ -384,6 +330,45 @@ class Application extends Module } /** + * Handles uncaught PHP exceptions. + * + * This method is implemented as a PHP exception handler. It requires + * that constant YII_ENABLE_ERROR_HANDLER be defined true. + * + * @param \Exception $exception exception that is not caught + */ + public function handleException($exception) + { + // disable error capturing to avoid recursive errors while handling exceptions + restore_error_handler(); + restore_exception_handler(); + + try { + $this->logException($exception); + + if (($handler = $this->getErrorHandler()) !== null) { + $handler->handle($exception); + } else { + $this->renderException($exception); + } + + $this->end(1); + + } catch (\Exception $e) { + // exception could be thrown in end() or ErrorHandler::handle() + $msg = (string)$e; + $msg .= "\nPrevious exception:\n"; + $msg .= (string)$exception; + if (YII_DEBUG) { + echo $msg; + } + $msg .= "\n\$_SERVER = " . var_export($_SERVER, true); + error_log($msg); + exit(1); + } + } + + /** * Handles PHP execution errors such as warnings, notices. * * This method is used as a PHP error handler. It will simply raise an `ErrorException`. @@ -414,41 +399,27 @@ class Application extends Module } /** - * Handles uncaught PHP exceptions. - * - * This method is implemented as a PHP exception handler. It requires - * that constant YII_ENABLE_ERROR_HANDLER be defined true. - * - * @param \Exception $exception exception that is not caught + * Handles fatal PHP errors */ - public function handleException($exception) + public function handleFatalError() { - // disable error capturing to avoid recursive errors while handling exceptions - restore_error_handler(); - restore_exception_handler(); - - try { - $this->logException($exception); + if (YII_ENABLE_ERROR_HANDLER) { + $error = error_get_last(); - if (($handler = $this->getErrorHandler()) !== null) { - $handler->handle($exception); - } else { - $this->renderException($exception); - } + if (ErrorException::isFatalError($error)) { + unset($this->_memoryReserve); + $exception = new ErrorException($error['message'], $error['type'], $error['type'], $error['file'], $error['line']); + // use error_log because it's too late to use Yii log + error_log($exception); - $this->end(1); + if (($handler = $this->getErrorHandler()) !== null) { + @$handler->handle($exception); + } else { + $this->renderException($exception); + } - } catch (\Exception $e) { - // exception could be thrown in end() or ErrorHandler::handle() - $msg = (string)$e; - $msg .= "\nPrevious exception:\n"; - $msg .= (string)$exception; - if (YII_DEBUG) { - echo $msg; + exit(1); } - $msg .= "\n\$_SERVER = " . var_export($_SERVER, true); - error_log($msg); - exit(1); } } From 34c0794f0097588d50a032818964b027751ce038 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sun, 14 Apr 2013 22:50:35 -0400 Subject: [PATCH 10/53] script WIP --- framework/base/View.php | 9 +-- framework/base/ViewContent.php | 176 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 173 insertions(+), 12 deletions(-) diff --git a/framework/base/View.php b/framework/base/View.php index d1a3c5f..247dfd2 100644 --- a/framework/base/View.php +++ b/framework/base/View.php @@ -37,7 +37,7 @@ class View extends Component /** * @var ViewContent */ - public $content; + public $page; /** * @var mixed custom parameters that are shared among view templates. */ @@ -88,10 +88,10 @@ class View extends Component if (is_array($this->theme)) { $this->theme = Yii::createObject($this->theme); } - if (is_array($this->content)) { - $this->content = Yii::createObject($this->content); + if (is_array($this->page)) { + $this->page = Yii::createObject($this->page); } else { - $this->content = new ViewContent; + $this->page = new ViewContent; } } @@ -166,7 +166,6 @@ class View extends Component } else { $output = $this->renderPhpFile($viewFile, $params); } - $output = $this->content->populate($output); $this->afterRender($viewFile, $output); } diff --git a/framework/base/ViewContent.php b/framework/base/ViewContent.php index cea3c7c..9bdeaef 100644 --- a/framework/base/ViewContent.php +++ b/framework/base/ViewContent.php @@ -8,6 +8,7 @@ namespace yii\base; use Yii; +use yii\helpers\Html; /** * @author Qiang Xue @@ -19,6 +20,10 @@ class ViewContent extends Component const POS_BEGIN = 2; const POS_END = 3; + const TOKEN_HEAD = ''; + const TOKEN_BODY_BEGIN = ''; + const TOKEN_BODY_END = ''; + /** * @var array * @@ -44,7 +49,7 @@ class ViewContent extends Component * ) * ~~~ */ - public $bundles; + public $assetBundles; public $title; public $metaTags; public $linkTags; @@ -57,11 +62,6 @@ class ViewContent extends Component public $jsInBody; public $jsFilesInBody; - public function populate($content) - { - return $content; - } - public function reset() { $this->title = null; @@ -77,7 +77,169 @@ class ViewContent extends Component $this->jsFilesInBody = null; } - public function renderScripts($pos) + public function begin() + { + ob_start(); + ob_implicit_flush(false); + } + + public function end() + { + $content = ob_get_clean(); + echo $this->populate($content); + } + + public function beginBody() + { + echo self::TOKEN_BODY_BEGIN; + } + + public function endBody() + { + echo self::TOKEN_BODY_END; + } + + public function head() + { + echo self::TOKEN_HEAD; + } + + public function requireAssetBundle($name) + { + if (!isset($this->assetBundles[$name])) { + $bundle = Yii::$app->assets->getBundle($name); + if ($bundle !== null) { + $this->assetBundles[$name] = $bundle; + } else { + throw new InvalidConfigException("Unknown asset bundle: $name"); + } + foreach ($bundle->depends as $d) { + $this->requireAssetBundle($d); + } + } + } + + public function registerMetaTag($options, $key = null) + { + if ($key === null) { + $this->metaTags[] = Html::tag('meta', '', $options); + } else { + $this->metaTags[$key] = Html::tag('meta', '', $options); + } + } + + public function registerLinkTag($options, $key = null) + { + if ($key === null) { + $this->linkTags[] = Html::tag('link', '', $options); + } else { + $this->linkTags[$key] = Html::tag('link', '', $options); + } + } + + public function registerCss($css, $options = array(), $key = null) + { + $key = $key ?: $css; + $this->css[$key] = Html::style($css, $options); + } + + public function registerCssFile($url, $options = array(), $key = null) + { + $key = $key ?: $url; + $this->cssFiles[$key] = Html::cssFile($url, $options); + } + + public function registerJs($js, $position = self::POS_END, $options = array(), $key = null) + { + $key = $key ?: $js; + $html = Html::script($js, $options); + if ($position == self::POS_END) { + $this->js[$key] = $html; + } elseif ($position == self::POS_HEAD) { + $this->jsInHead[$key] = $html; + } elseif ($position == self::POS_BEGIN) { + $this->jsInBody[$key] = $html; + } else { + throw new InvalidParamException("Unknown position: $position"); + } + } + + public function registerJsFile($url, $position = self::POS_END, $options = array(), $key = null) + { + $key = $key ?: $url; + $html = Html::jsFile($url, $options); + if ($position == self::POS_END) { + $this->jsFiles[$key] = $html; + } elseif ($position == self::POS_HEAD) { + $this->jsFilesInHead[$key] = $html; + } elseif ($position == self::POS_BEGIN) { + $this->jsFilesInBody[$key] = $html; + } else { + throw new InvalidParamException("Unknown position: $position"); + } + } + + + protected function populate($content) + { + $this->expandAssetBundles(); + return strtr($content, array( + self::TOKEN_HEAD => $this->getHeadHtml(), + self::TOKEN_BODY_BEGIN => $this->getBodyBeginHtml(), + self::TOKEN_BODY_END => $this->getBodyEndHtml(), + )); + } + + protected function expandAssetBundles() + { + + } + + protected function getHeadHtml() + { + $lines = array(); + if (!empty($this->metaTags)) { + $lines[] = implode("\n", $this->cssFiles); + } + if (!empty($this->linkTags)) { + $lines[] = implode("\n", $this->cssFiles); + } + if (!empty($this->cssFiles)) { + $lines[] = implode("\n", $this->cssFiles); + } + if (!empty($this->css)) { + $lines[] = implode("\n", $this->css); + } + if (!empty($this->jsFilesInHead)) { + $lines[] = implode("\n", $this->jsFilesInHead); + } + if (!empty($this->jsInHead)) { + $lines[] = implode("\n", $this->jsInHead); + } + return implode("\n", $lines); + } + + protected function getBodyBeginHtml() + { + $lines = array(); + if (!empty($this->jsFilesInBody)) { + $lines[] = implode("\n", $this->jsFilesInBody); + } + if (!empty($this->jsInHead)) { + $lines[] = implode("\n", $this->jsInBody); + } + return implode("\n", $lines); + } + + protected function getBodyEndHtml() { + $lines = array(); + if (!empty($this->jsFiles)) { + $lines[] = implode("\n", $this->jsFiles); + } + if (!empty($this->js)) { + $lines[] = implode("\n", $this->js); + } + return implode("\n", $lines); } } \ No newline at end of file From c49222f79961c5aa279d11383b53263ec46fcee4 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Mon, 15 Apr 2013 15:57:34 -0400 Subject: [PATCH 11/53] script WIP --- framework/base/View.php | 2 +- framework/base/ViewContent.php | 63 ++++----- framework/web/AssetBundle.php | 68 ++++++++++ framework/web/AssetManager.php | 288 +++++++++++++++++++---------------------- 4 files changed, 226 insertions(+), 195 deletions(-) create mode 100644 framework/web/AssetBundle.php diff --git a/framework/base/View.php b/framework/base/View.php index 247dfd2..a794e08 100644 --- a/framework/base/View.php +++ b/framework/base/View.php @@ -241,7 +241,7 @@ class View extends Component { if (!empty($this->cacheStack)) { $n = count($this->dynamicPlaceholders); - $placeholder = ""; + $placeholder = ""; $this->addDynamicPlaceholder($placeholder, $statements); return $placeholder; } else { diff --git a/framework/base/ViewContent.php b/framework/base/ViewContent.php index 9bdeaef..797dba2 100644 --- a/framework/base/ViewContent.php +++ b/framework/base/ViewContent.php @@ -25,30 +25,9 @@ class ViewContent extends Component const TOKEN_BODY_END = ''; /** - * @var array - * - * Each asset bundle should be declared with the following structure: - * - * ~~~ - * array( - * 'basePath' => '...', - * 'baseUrl' => '...', // if missing, the bundle will be published to the "www/assets" folder - * 'js' => array( - * 'js/main.js', - * 'js/menu.js', - * 'js/base.js' => self::POS_HEAD, - * 'css' => array( - * 'css/main.css', - * 'css/menu.css', - * ), - * 'depends' => array( - * 'jquery', - * 'yii', - * 'yii/treeview', - * ), - * ) - * ~~~ + * @var \yii\web\AssetManager */ + public $assetManager; public $assetBundles; public $title; public $metaTags; @@ -62,6 +41,14 @@ class ViewContent extends Component public $jsInBody; public $jsFilesInBody; + public function init() + { + parent::init(); + if ($this->assetManager === null) { + $this->assetManager = Yii::$app->getAssetManager(); + } + } + public function reset() { $this->title = null; @@ -104,21 +91,22 @@ class ViewContent extends Component echo self::TOKEN_HEAD; } - public function requireAssetBundle($name) + public function registerAssetBundle($name) { if (!isset($this->assetBundles[$name])) { - $bundle = Yii::$app->assets->getBundle($name); + $bundle = $this->assetManager->getBundle($name); if ($bundle !== null) { - $this->assetBundles[$name] = $bundle; + $this->assetBundles[$name] = false; + $bundle->registerWith($this); + $this->assetBundles[$name] = true; } else { throw new InvalidConfigException("Unknown asset bundle: $name"); } - foreach ($bundle->depends as $d) { - $this->requireAssetBundle($d); - } + } elseif ($this->assetBundles[$name] === false) { + throw new InvalidConfigException("A cyclic dependency is detected for bundle '$name'."); } } - + public function registerMetaTag($options, $key = null) { if ($key === null) { @@ -149,8 +137,10 @@ class ViewContent extends Component $this->cssFiles[$key] = Html::cssFile($url, $options); } - public function registerJs($js, $position = self::POS_END, $options = array(), $key = null) + public function registerJs($js, $options = array(), $key = null) { + $position = isset($options['position']) ? $options['position'] : self::POS_END; + unset($options['position']); $key = $key ?: $js; $html = Html::script($js, $options); if ($position == self::POS_END) { @@ -164,8 +154,10 @@ class ViewContent extends Component } } - public function registerJsFile($url, $position = self::POS_END, $options = array(), $key = null) + public function registerJsFile($url, $options = array(), $key = null) { + $position = isset($options['position']) ? $options['position'] : self::POS_END; + unset($options['position']); $key = $key ?: $url; $html = Html::jsFile($url, $options); if ($position == self::POS_END) { @@ -178,11 +170,9 @@ class ViewContent extends Component throw new InvalidParamException("Unknown position: $position"); } } - protected function populate($content) { - $this->expandAssetBundles(); return strtr($content, array( self::TOKEN_HEAD => $this->getHeadHtml(), self::TOKEN_BODY_BEGIN => $this->getBodyBeginHtml(), @@ -190,11 +180,6 @@ class ViewContent extends Component )); } - protected function expandAssetBundles() - { - - } - protected function getHeadHtml() { $lines = array(); diff --git a/framework/web/AssetBundle.php b/framework/web/AssetBundle.php new file mode 100644 index 0000000..3e4541e --- /dev/null +++ b/framework/web/AssetBundle.php @@ -0,0 +1,68 @@ + '...', + * 'baseUrl' => '...', // if missing, the bundle will be published to the "www/assets" folder + * 'js' => array( + * 'js/main.js', + * 'js/menu.js', + * 'js/base.js' => self::POS_HEAD, + * 'css' => array( + * 'css/main.css', + * 'css/menu.css', + * ), + * 'depends' => array( + * 'jquery', + * 'yii', + * 'yii/treeview', + * ), + * ) + * ~~~ + * @author Qiang Xue + * @since 2.0 + */ +class AssetBundle extends Object +{ + public $basePath; + public $baseUrl; // if missing, the bundle will be published to the "www/assets" folder + public $js = array(); + public $css = array(); + public $depends = array(); + + /** + * @param \yii\base\ViewContent $content + */ + public function registerWith($content) + { + foreach ($this->depends as $name) { + $content->registerAssetBundle($name); + } + foreach ($this->js as $js => $options) { + if (is_array($options)) { + $content->registerJsFile($js, $options); + } else { + $content->registerJsFile($options); + } + } + foreach ($this->css as $css => $options) { + if (is_array($options)) { + $content->registerCssFile($css, $options); + } else { + $content->registerCssFile($options); + } + } + } +} \ No newline at end of file diff --git a/framework/web/AssetManager.php b/framework/web/AssetManager.php index 60f4c07..3cb1e83 100644 --- a/framework/web/AssetManager.php +++ b/framework/web/AssetManager.php @@ -1,140 +1,121 @@ * @link http://www.yiiframework.com/ - * @copyright Copyright © 2008-2011 Yii Software LLC + * @copyright Copyright (c) 2008 Yii Software LLC * @license http://www.yiiframework.com/license/ */ +namespace yii\web; + +use Yii; +use yii\base\Component; +use yii\base\InvalidConfigException; +use yii\base\InvalidParamException; /** - * CAssetManager is a Web application component that manages private files (called assets) and makes them accessible by Web clients. - * - * It achieves this goal by copying assets to a Web-accessible directory - * and returns the corresponding URL for accessing them. - * - * To publish an asset, simply call {@link publish()}. - * - * The Web-accessible directory holding the published files is specified - * by {@link setBasePath basePath}, which defaults to the "assets" directory - * under the directory containing the application entry script file. - * The property {@link setBaseUrl baseUrl} refers to the URL for accessing - * the {@link setBasePath basePath}. - * - * @property string $basePath The root directory storing the published asset files. Defaults to 'WebRoot/assets'. - * @property string $baseUrl The base url that the published asset files can be accessed. - * Note, the ending slashes are stripped off. Defaults to '/AppBaseUrl/assets'. * * @author Qiang Xue - * @version $Id$ - * @package system.web - * @since 1.0 + * @since 2.0 */ -class CAssetManager extends CApplicationComponent +class AssetManager extends Component { /** - * Default web accessible base path for storing private files + * @var array list of asset bundles. The keys are the bundle names, and the values are the configuration + * arrays for creating [[AssetBundle]] objects. Besides the bundles listed here, the asset manager + * may look for bundles declared in extensions. For more details, please refer to [[getBundle()]]. + */ + public $bundles; + /** + * @var + */ + public $bundleMap; + /** + * @return string the root directory storing the published asset files. + */ + public $basePath = '@wwwroot/assets'; + /** + * @return string the base URL through which the published asset files can be accessed. */ - const DEFAULT_BASEPATH='assets'; + public $baseUrl = '@www/assets'; /** * @var boolean whether to use symbolic link to publish asset files. Defaults to false, meaning - * asset files are copied to public folders. Using symbolic links has the benefit that the published - * assets will always be consistent with the source assets. This is especially useful during development. + * asset files are copied to [[basePath]]. Using symbolic links has the benefit that the published + * assets will always be consistent with the source assets and there is no copy operation required. + * This is especially useful during development. * * However, there are special requirements for hosting environments in order to use symbolic links. * In particular, symbolic links are supported only on Linux/Unix, and Windows Vista/2008 or greater. - * The latter requires PHP 5.3 or greater. * * Moreover, some Web servers need to be properly configured so that the linked assets are accessible * to Web users. For example, for Apache Web server, the following configuration directive should be added * for the Web folder: - *
-	 * Options FollowSymLinks
-	 * 
* - * @since 1.1.5 + * ~~~ + * Options FollowSymLinks + * ~~~ */ - public $linkAssets=false; + public $linkAssets = false; /** * @var array list of directories and files which should be excluded from the publishing process. * Defaults to exclude '.svn' and '.gitignore' files only. This option has no effect if {@link linkAssets} is enabled. * @since 1.1.6 **/ - public $excludeFiles=array('.svn','.gitignore'); + public $excludeFiles = array('.svn', '.gitignore'); /** * @var integer the permission to be set for newly generated asset files. * This value will be used by PHP chmod function. * Defaults to 0666, meaning the file is read-writable by all users. * @since 1.1.8 */ - public $newFileMode=0666; + public $newFileMode = 0666; /** * @var integer the permission to be set for newly generated asset directories. * This value will be used by PHP chmod function. * Defaults to 0777, meaning the directory can be read, written and executed by all users. * @since 1.1.8 */ - public $newDirMode=0777; - /** - * @var string base web accessible path for storing private files - */ - private $_basePath; - /** - * @var string base URL for accessing the publishing directory. - */ - private $_baseUrl; + public $newDirMode = 0777; /** * @var array published assets */ - private $_published=array(); + private $_published = array(); - /** - * @return string the root directory storing the published asset files. Defaults to 'WebRoot/assets'. - */ - public function getBasePath() + public function init() { - if($this->_basePath===null) - { - $request=\Yii::$app->getRequest(); - $this->setBasePath(dirname($request->getScriptFile()).DIRECTORY_SEPARATOR.self::DEFAULT_BASEPATH); + parent::init(); + $this->basePath = Yii::getAlias($this->basePath); + if (!is_dir($this->basePath)) { + throw new InvalidConfigException("The directory does not exist: {$this->basePath}"); + } elseif (!is_writable($this->basePath)) { + throw new InvalidConfigException("The directory is not writable by the Web process: {$this->basePath}"); + } else { + $this->base = realpath($this->basePath); } - return $this->_basePath; - } - - /** - * Sets the root directory storing published asset files. - * @param string $value the root directory storing published asset files - * @throws CException if the base path is invalid - */ - public function setBasePath($value) - { - if(($basePath=realpath($value))!==false && is_dir($basePath) && is_writable($basePath)) - $this->_basePath=$basePath; - else - throw new CException(Yii::t('yii|CAssetManager.basePath "{path}" is invalid. Please make sure the directory exists and is writable by the Web server process.', - array('{path}'=>$value))); + $this->baseUrl = rtrim(Yii::getAlias($this->getBaseUrl), '/'); } /** - * @return string the base url that the published asset files can be accessed. - * Note, the ending slashes are stripped off. Defaults to '/AppBaseUrl/assets'. + * @param string $name + * @return AssetBundle + * @throws InvalidParamException */ - public function getBaseUrl() + public function getBundle($name) { - if($this->_baseUrl===null) - { - $request=\Yii::$app->getRequest(); - $this->setBaseUrl($request->getBaseUrl().'/'.self::DEFAULT_BASEPATH); + if (!isset($this->bundles[$name])) { + $manifest = Yii::getAlias("@{$name}/assets.php", false); + if ($manifest === false) { + throw new InvalidParamException("Unable to find the asset bundle: $name"); + } + $this->bundles[$name] = require($manifest); } - return $this->_baseUrl; - } - - /** - * @param string $value the base url that the published asset files can be accessed - */ - public function setBaseUrl($value) - { - $this->_baseUrl=rtrim($value,'/'); + if (is_array($this->bundles[$name])) { + $config = $this->bundles[$name]; + if (!isset($config['class'])) { + $config['class'] = 'yii\\web\\AssetBundle'; + $this->bundles[$name] = Yii::createObject($config); + } + } + return $this->bundles[$name]; } /** @@ -173,69 +154,65 @@ class CAssetManager extends CApplicationComponent * @return string an absolute URL to the published asset * @throws CException if the asset to be published does not exist. */ - public function publish($path,$hashByName=false,$level=-1,$forceCopy=false) + public function publish($path, $hashByName = false, $level = -1, $forceCopy = false) { - if(isset($this->_published[$path])) + if (isset($this->_published[$path])) { return $this->_published[$path]; - else if(($src=realpath($path))!==false) - { - if(is_file($src)) - { - $dir=$this->hash($hashByName ? basename($src) : dirname($src).filemtime($src)); - $fileName=basename($src); - $dstDir=$this->getBasePath().DIRECTORY_SEPARATOR.$dir; - $dstFile=$dstDir.DIRECTORY_SEPARATOR.$fileName; + } else { + if (($src = realpath($path)) !== false) { + if (is_file($src)) { + $dir = $this->hash($hashByName ? basename($src) : dirname($src) . filemtime($src)); + $fileName = basename($src); + $dstDir = $this->getBasePath() . DIRECTORY_SEPARATOR . $dir; + $dstFile = $dstDir . DIRECTORY_SEPARATOR . $fileName; - if($this->linkAssets) - { - if(!is_file($dstFile)) - { - if(!is_dir($dstDir)) - { - mkdir($dstDir); - @chmod($dstDir, $this->newDirMode); + if ($this->linkAssets) { + if (!is_file($dstFile)) { + if (!is_dir($dstDir)) { + mkdir($dstDir); + @chmod($dstDir, $this->newDirMode); + } + symlink($src, $dstFile); + } + } else { + if (@filemtime($dstFile) < @filemtime($src)) { + if (!is_dir($dstDir)) { + mkdir($dstDir); + @chmod($dstDir, $this->newDirMode); + } + copy($src, $dstFile); + @chmod($dstFile, $this->newFileMode); } - symlink($src,$dstFile); - } - } - else if(@filemtime($dstFile)<@filemtime($src)) - { - if(!is_dir($dstDir)) - { - mkdir($dstDir); - @chmod($dstDir, $this->newDirMode); } - copy($src,$dstFile); - @chmod($dstFile, $this->newFileMode); - } - return $this->_published[$path]=$this->getBaseUrl()."/$dir/$fileName"; - } - else if(is_dir($src)) - { - $dir=$this->hash($hashByName ? basename($src) : $src.filemtime($src)); - $dstDir=$this->getBasePath().DIRECTORY_SEPARATOR.$dir; + return $this->_published[$path] = $this->getBaseUrl() . "/$dir/$fileName"; + } else { + if (is_dir($src)) { + $dir = $this->hash($hashByName ? basename($src) : $src . filemtime($src)); + $dstDir = $this->getBasePath() . DIRECTORY_SEPARATOR . $dir; - if($this->linkAssets) - { - if(!is_dir($dstDir)) - symlink($src,$dstDir); - } - else if(!is_dir($dstDir) || $forceCopy) - { - CFileHelper::copyDirectory($src,$dstDir,array( - 'exclude'=>$this->excludeFiles, - 'level'=>$level, - 'newDirMode'=>$this->newDirMode, - 'newFileMode'=>$this->newFileMode, - )); - } + if ($this->linkAssets) { + if (!is_dir($dstDir)) { + symlink($src, $dstDir); + } + } else { + if (!is_dir($dstDir) || $forceCopy) { + CFileHelper::copyDirectory($src, $dstDir, array( + 'exclude' => $this->excludeFiles, + 'level' => $level, + 'newDirMode' => $this->newDirMode, + 'newFileMode' => $this->newFileMode, + )); + } + } - return $this->_published[$path]=$this->getBaseUrl().'/'.$dir; + return $this->_published[$path] = $this->getBaseUrl() . '/' . $dir; + } + } } } throw new CException(Yii::t('yii|The asset "{asset}" to be published does not exist.', - array('{asset}'=>$path))); + array('{asset}' => $path))); } /** @@ -249,18 +226,18 @@ class CAssetManager extends CApplicationComponent * different extensions. * @return string the published file path. False if the file or directory does not exist */ - public function getPublishedPath($path,$hashByName=false) + public function getPublishedPath($path, $hashByName = false) { - if(($path=realpath($path))!==false) - { - $base=$this->getBasePath().DIRECTORY_SEPARATOR; - if(is_file($path)) - return $base . $this->hash($hashByName ? basename($path) : dirname($path).filemtime($path)) . DIRECTORY_SEPARATOR . basename($path); - else - return $base . $this->hash($hashByName ? basename($path) : $path.filemtime($path)); - } - else + if (($path = realpath($path)) !== false) { + $base = $this->getBasePath() . DIRECTORY_SEPARATOR; + if (is_file($path)) { + return $base . $this->hash($hashByName ? basename($path) : dirname($path) . filemtime($path)) . DIRECTORY_SEPARATOR . basename($path); + } else { + return $base . $this->hash($hashByName ? basename($path) : $path . filemtime($path)); + } + } else { return false; + } } /** @@ -274,19 +251,20 @@ class CAssetManager extends CApplicationComponent * different extensions. * @return string the published URL for the file or directory. False if the file or directory does not exist. */ - public function getPublishedUrl($path,$hashByName=false) + public function getPublishedUrl($path, $hashByName = false) { - if(isset($this->_published[$path])) + if (isset($this->_published[$path])) { return $this->_published[$path]; - if(($path=realpath($path))!==false) - { - if(is_file($path)) - return $this->getBaseUrl().'/'.$this->hash($hashByName ? basename($path) : dirname($path).filemtime($path)).'/'.basename($path); - else - return $this->getBaseUrl().'/'.$this->hash($hashByName ? basename($path) : $path.filemtime($path)); } - else + if (($path = realpath($path)) !== false) { + if (is_file($path)) { + return $this->getBaseUrl() . '/' . $this->hash($hashByName ? basename($path) : dirname($path) . filemtime($path)) . '/' . basename($path); + } else { + return $this->getBaseUrl() . '/' . $this->hash($hashByName ? basename($path) : $path . filemtime($path)); + } + } else { return false; + } } /** @@ -297,6 +275,6 @@ class CAssetManager extends CApplicationComponent */ protected function hash($path) { - return sprintf('%x',crc32($path.Yii::getVersion())); + return sprintf('%x', crc32($path . Yii::getVersion())); } } From 8f7757a25c963afaa0c366129e9812f04bed8853 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Tue, 16 Apr 2013 00:17:15 +0400 Subject: [PATCH 12/53] conditions cleanup --- framework/helpers/base/Html.php | 2 +- framework/widgets/ActiveForm.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/helpers/base/Html.php b/framework/helpers/base/Html.php index 5e7f4ad..bb1fed3 100644 --- a/framework/helpers/base/Html.php +++ b/framework/helpers/base/Html.php @@ -728,7 +728,7 @@ class Html if (!isset($options['size'])) { $options['size'] = 4; } - if (isset($options['multiple']) && $options['multiple'] && substr($name, -2) !== '[]') { + if (!empty($options['multiple']) && substr($name, -2) !== '[]') { $name .= '[]'; } $options['name'] = $name; diff --git a/framework/widgets/ActiveForm.php b/framework/widgets/ActiveForm.php index 48bc181..8192bc0 100644 --- a/framework/widgets/ActiveForm.php +++ b/framework/widgets/ActiveForm.php @@ -64,7 +64,7 @@ class ActiveForm extends Widget $models = array($models); } - $showAll = isset($options['showAll']) && $options['showAll']; + $showAll = !empty($options['showAll']); $lines = array(); /** @var $model Model */ foreach ($models as $model) { From ac5b25e3f7514763ff60edfff9bb23346f0ec509 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Tue, 16 Apr 2013 00:52:24 +0400 Subject: [PATCH 13/53] fixed typos --- framework/caching/ZendDataCache.php | 2 +- framework/console/controllers/AppController.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/caching/ZendDataCache.php b/framework/caching/ZendDataCache.php index 669733d..5b41a8d 100644 --- a/framework/caching/ZendDataCache.php +++ b/framework/caching/ZendDataCache.php @@ -10,7 +10,7 @@ namespace yii\caching; /** * ZendDataCache provides Zend data caching in terms of an application component. * - * To use this application component, the [Zend Data Cache PHP extensionn](http://www.zend.com/en/products/server/) + * To use this application component, the [Zend Data Cache PHP extension](http://www.zend.com/en/products/server/) * must be loaded. * * See [[Cache]] for common cache operations that ZendDataCache supports. diff --git a/framework/console/controllers/AppController.php b/framework/console/controllers/AppController.php index 93ef5f5..2c32c54 100644 --- a/framework/console/controllers/AppController.php +++ b/framework/console/controllers/AppController.php @@ -159,7 +159,7 @@ class AppController extends Controller * @param string $pathTo path to file we want to get relative path for * @param string $varName variable name w/o $ to replace value with relative path for * - * @return string target file contetns + * @return string target file contents */ public function replaceRelativePath($source, $pathTo, $varName) { From 9f2b44fc21c0dc6572cdda930dae96276cd80426 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Tue, 16 Apr 2013 00:53:07 +0400 Subject: [PATCH 14/53] phpdoc --- framework/base/ErrorHandler.php | 14 ++++++++++++++ framework/base/Response.php | 18 ++++++++++++++++++ framework/caching/DbDependency.php | 1 + framework/caching/MemCache.php | 2 +- framework/helpers/base/VarDumper.php | 2 +- framework/validators/CaptchaValidator.php | 1 + framework/widgets/ActiveForm.php | 16 +++++++++++++++- 7 files changed, 51 insertions(+), 3 deletions(-) diff --git a/framework/base/ErrorHandler.php b/framework/base/ErrorHandler.php index a2f372c..dc83474 100644 --- a/framework/base/ErrorHandler.php +++ b/framework/base/ErrorHandler.php @@ -51,6 +51,7 @@ class ErrorHandler extends Component /** + * Handles exception * @param \Exception $exception */ public function handle($exception) @@ -64,6 +65,10 @@ class ErrorHandler extends Component $this->renderException($exception); } + /** + * Renders exception + * @param \Exception $exception + */ protected function renderException($exception) { if ($this->errorAction !== null) { @@ -196,6 +201,10 @@ class ErrorHandler extends Component echo '
' . $output . '
'; } + /** + * Renders calls stack trace + * @param array $trace + */ public function renderTrace($trace) { $count = 0; @@ -233,6 +242,11 @@ class ErrorHandler extends Component echo ''; } + /** + * Converts special characters to HTML entities + * @param string $text text to encode + * @return string + */ public function htmlEncode($text) { return htmlspecialchars($text, ENT_QUOTES, \Yii::$app->charset); diff --git a/framework/base/Response.php b/framework/base/Response.php index a53fd61..af91a20 100644 --- a/framework/base/Response.php +++ b/framework/base/Response.php @@ -13,27 +13,45 @@ namespace yii\base; */ class Response extends Component { + /** + * Starts output buffering + */ public function beginOutput() { ob_start(); ob_implicit_flush(false); } + /** + * Returns contents of the output buffer and discards it + * @return string output buffer contents + */ public function endOutput() { return ob_get_clean(); } + /** + * Returns contents of the output buffer + * @return string output buffer contents + */ public function getOutput() { return ob_get_contents(); } + /** + * Discards the output buffer + */ public function cleanOutput() { ob_clean(); } + /** + * Discards the output buffer + * @param boolean $all if true recursively discards all output buffers used + */ public function removeOutput($all = true) { if ($all) { diff --git a/framework/caching/DbDependency.php b/framework/caching/DbDependency.php index cbe0ae1..7d45223 100644 --- a/framework/caching/DbDependency.php +++ b/framework/caching/DbDependency.php @@ -52,6 +52,7 @@ class DbDependency extends Dependency /** * Generates the data needed to determine if dependency has been changed. * This method returns the value of the global state. + * @throws InvalidConfigException * @return mixed the data needed to determine if dependency has been changed. */ protected function generateDependencyData() diff --git a/framework/caching/MemCache.php b/framework/caching/MemCache.php index df07b8e..20aff21 100644 --- a/framework/caching/MemCache.php +++ b/framework/caching/MemCache.php @@ -106,7 +106,7 @@ class MemCache extends Cache /** * Returns the underlying memcache (or memcached) object. * @return \Memcache|\Memcached the memcache (or memcached) object used by this cache component. - * @throws Exception if memcache or memcached extension is not loaded + * @throws InvalidConfigException if memcache or memcached extension is not loaded */ public function getMemcache() { diff --git a/framework/helpers/base/VarDumper.php b/framework/helpers/base/VarDumper.php index 5942cd8..fe15d98 100644 --- a/framework/helpers/base/VarDumper.php +++ b/framework/helpers/base/VarDumper.php @@ -64,7 +64,7 @@ class VarDumper return self::$_output; } - /* + /** * @param mixed $var variable to be dumped * @param integer $level depth level */ diff --git a/framework/validators/CaptchaValidator.php b/framework/validators/CaptchaValidator.php index 3b4745b..ebb0039 100644 --- a/framework/validators/CaptchaValidator.php +++ b/framework/validators/CaptchaValidator.php @@ -68,6 +68,7 @@ class CaptchaValidator extends Validator /** * Returns the CAPTCHA action object. + * @throws InvalidConfigException * @return CaptchaAction the action object */ public function getCaptchaAction() diff --git a/framework/widgets/ActiveForm.php b/framework/widgets/ActiveForm.php index 8192bc0..83506dd 100644 --- a/framework/widgets/ActiveForm.php +++ b/framework/widgets/ActiveForm.php @@ -39,10 +39,16 @@ class ActiveForm extends Widget public $errorMessageClass = 'yii-error-message'; /** * @var string the default CSS class that indicates an input has error. - * This is */ public $errorClass = 'yii-error'; + /** + * @var string the default CSS class that indicates an input validated successfully. + */ public $successClass = 'yii-success'; + + /** + * @var string the default CSS class that indicates an input is currently being validated. + */ public $validatingClass = 'yii-validating'; /** * @var boolean whether to enable client-side data validation. Defaults to false. @@ -127,6 +133,14 @@ class ActiveForm extends Widget return Html::label($label, $for, $options); } + /** + * @param string $type + * @param Model $model + * @param string $attribute + * @param array $options + * + * @return string + */ public function input($type, $model, $attribute, $options = array()) { $value = $this->getAttributeValue($model, $attribute); From f2284948949d7c01b3971a7c464f2150aa2f3185 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Mon, 15 Apr 2013 16:57:54 -0400 Subject: [PATCH 15/53] script IWP --- framework/assets.php | 5 +++++ framework/web/AssetManager.php | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 framework/assets.php diff --git a/framework/assets.php b/framework/assets.php new file mode 100644 index 0000000..5efa94a --- /dev/null +++ b/framework/assets.php @@ -0,0 +1,5 @@ + __DIR__ . '/web/assets', +); \ No newline at end of file diff --git a/framework/web/AssetManager.php b/framework/web/AssetManager.php index 3cb1e83..ccf6dd8 100644 --- a/framework/web/AssetManager.php +++ b/framework/web/AssetManager.php @@ -96,10 +96,11 @@ class AssetManager extends Component /** * @param string $name + * @param boolean $publish * @return AssetBundle * @throws InvalidParamException */ - public function getBundle($name) + public function getBundle($name, $publish = true) { if (!isset($this->bundles[$name])) { $manifest = Yii::getAlias("@{$name}/assets.php", false); From 1e21bae999707cb356bc84cac41c7736bf6df727 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Tue, 16 Apr 2013 01:01:23 +0400 Subject: [PATCH 16/53] Removed Response::removeOutput, moved recursive buffer cleanup into Response::cleanOutput --- framework/base/Response.php | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/framework/base/Response.php b/framework/base/Response.php index af91a20..396b073 100644 --- a/framework/base/Response.php +++ b/framework/base/Response.php @@ -42,17 +42,9 @@ class Response extends Component /** * Discards the output buffer - */ - public function cleanOutput() - { - ob_clean(); - } - - /** - * Discards the output buffer * @param boolean $all if true recursively discards all output buffers used */ - public function removeOutput($all = true) + public function cleanOutput($all = true) { if ($all) { for ($level = ob_get_level(); $level > 0; --$level) { From 9edc942caf1817d7f47bdc121479ab54e7c48c72 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Mon, 15 Apr 2013 17:10:16 -0400 Subject: [PATCH 17/53] script WIP --- framework/web/AssetManager.php | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/web/AssetManager.php b/framework/web/AssetManager.php index ccf6dd8..6c32687 100644 --- a/framework/web/AssetManager.php +++ b/framework/web/AssetManager.php @@ -116,6 +116,7 @@ class AssetManager extends Component $this->bundles[$name] = Yii::createObject($config); } } + // todo: publish bundle return $this->bundles[$name]; } From 0416e01414ccf1261f5fc8f88ff059f0857813bb Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Mon, 15 Apr 2013 22:59:13 -0400 Subject: [PATCH 18/53] script WIP --- framework/YiiBase.php | 30 ++++++++++++++++++++++++++++-- framework/base/Application.php | 16 ++++++++++++++-- framework/base/ViewContent.php | 3 ++- framework/web/Application.php | 12 ++++++++++++ framework/web/AssetManager.php | 27 ++++++++++++++++++++++----- 5 files changed, 78 insertions(+), 10 deletions(-) diff --git a/framework/YiiBase.php b/framework/YiiBase.php index fb2967a..c32cc28 100644 --- a/framework/YiiBase.php +++ b/framework/YiiBase.php @@ -198,6 +198,32 @@ class YiiBase } /** + * Returns the root alias part of a given alias. + * A root alias is an alias that has been registered via [[setAlias()]] previously. + * If a given alias matches multiple root aliases, the longest one will be returned. + * @param string $alias the alias + * @return string|boolean the root alias, or false if no root alias is found + */ + public static function getRootAlias($alias) + { + $pos = strpos($alias, '/'); + $root = $pos === false ? $alias : substr($alias, 0, $pos); + + if (isset(self::$aliases[$root])) { + if (is_string(self::$aliases[$root])) { + return $root; + } else { + foreach (self::$aliases[$root] as $name => $path) { + if (strpos($alias . '/', $name . '/') === 0) { + return $name; + } + } + } + } + return false; + } + + /** * Registers a path alias. * * A path alias is a short name representing a long path (a file path, a URL, etc.) @@ -222,13 +248,13 @@ class YiiBase * - a path alias (e.g. `@yii/base`). In this case, the path alias will be converted into the * actual path first by calling [[getAlias()]]. * - * @throws InvalidParamException the alias does not start with '@', or if $path is an invalid alias. + * @throws InvalidParamException if $path is an invalid alias. * @see getAlias */ public static function setAlias($alias, $path) { if (strncmp($alias, '@', 1)) { - throw new InvalidParamException('The alias must start with the "@" character.'); + $alias = '@' . $alias; } $pos = strpos($alias, '/'); $root = $pos === false ? $alias : substr($alias, 0, $pos); diff --git a/framework/base/Application.php b/framework/base/Application.php index b9f01d7..fd6527f 100644 --- a/framework/base/Application.php +++ b/framework/base/Application.php @@ -56,6 +56,11 @@ class Application extends Module * If this is false, layout will be disabled. */ public $layout = 'main'; + /** + * @var array list of installed extensions. The array keys are the extension names, and the array + * values are the corresponding extension root source directories or path aliases. + */ + public $extensions = array(); private $_ended = false; @@ -81,12 +86,19 @@ class Application extends Module if (isset($config['basePath'])) { $this->setBasePath($config['basePath']); + Yii::setAlias('@app', $this->getBasePath()); unset($config['basePath']); - Yii::$aliases['@app'] = $this->getBasePath(); } else { throw new InvalidConfigException('The "basePath" configuration is required.'); } + if (isset($config['extensions'])) { + foreach ($config['extensions'] as $name => $path) { + Yii::setAlias("@$name", $path); + } + unset($config['extensions']); + } + $this->registerErrorHandlers(); $this->registerCoreComponents(); @@ -206,7 +218,7 @@ class Application extends Module */ public function getVendorPath() { - if ($this->_vendorPath !== null) { + if ($this->_vendorPath === null) { $this->setVendorPath($this->getBasePath() . DIRECTORY_SEPARATOR . 'vendor'); } return $this->_vendorPath; diff --git a/framework/base/ViewContent.php b/framework/base/ViewContent.php index 797dba2..8b4e835 100644 --- a/framework/base/ViewContent.php +++ b/framework/base/ViewContent.php @@ -28,6 +28,7 @@ class ViewContent extends Component * @var \yii\web\AssetManager */ public $assetManager; + public $assetBundles; public $title; public $metaTags; @@ -45,7 +46,7 @@ class ViewContent extends Component { parent::init(); if ($this->assetManager === null) { - $this->assetManager = Yii::$app->getAssetManager(); + $this->assetManager = Yii::$app->getAssets(); } } diff --git a/framework/web/Application.php b/framework/web/Application.php index f9b615d..32f6479 100644 --- a/framework/web/Application.php +++ b/framework/web/Application.php @@ -98,6 +98,15 @@ class Application extends \yii\base\Application } /** + * Returns the asset manager. + * @return AssetManager the asset manager component + */ + public function getAssets() + { + return $this->getComponent('user'); + } + + /** * Registers the core application components. * @see setComponents */ @@ -117,6 +126,9 @@ class Application extends \yii\base\Application 'user' => array( 'class' => 'yii\web\User', ), + 'assets' => array( + 'class' => 'yii\web\AssetManager', + ), )); } } diff --git a/framework/web/AssetManager.php b/framework/web/AssetManager.php index 6c32687..1f82e7c 100644 --- a/framework/web/AssetManager.php +++ b/framework/web/AssetManager.php @@ -91,7 +91,13 @@ class AssetManager extends Component } else { $this->base = realpath($this->basePath); } - $this->baseUrl = rtrim(Yii::getAlias($this->getBaseUrl), '/'); + $this->baseUrl = rtrim(Yii::getAlias($this->baseUrl), '/'); + + foreach (require(YII_PATH . '/assets.php') as $name => $bundle) { + if (!isset($this->bundles[$name])) { + $this->bundles[$name] = $bundle; + } + } } /** @@ -103,11 +109,18 @@ class AssetManager extends Component public function getBundle($name, $publish = true) { if (!isset($this->bundles[$name])) { - $manifest = Yii::getAlias("@{$name}/assets.php", false); - if ($manifest === false) { + $rootAlias = Yii::getRootAlias("@$name"); + if ($rootAlias !== false) { + $manifest = Yii::getAlias("$rootAlias/assets.php", false); + if ($manifest !== false && is_file($manifest)) { + foreach (require($manifest) as $bn => $config) { + $this->bundles[$bn] = $config; + } + } + } + if (!isset($this->bundles[$name])) { throw new InvalidParamException("Unable to find the asset bundle: $name"); } - $this->bundles[$name] = require($manifest); } if (is_array($this->bundles[$name])) { $config = $this->bundles[$name]; @@ -116,7 +129,11 @@ class AssetManager extends Component $this->bundles[$name] = Yii::createObject($config); } } - // todo: publish bundle + + if ($publish) { + + } + return $this->bundles[$name]; } From ee2d93b1813244e46c238ca955a52d5eb59f8d30 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 16 Apr 2013 07:59:31 -0400 Subject: [PATCH 19/53] scripts WIP --- framework/base/ViewContent.php | 2 +- framework/web/AssetBundle.php | 20 +++++++++++++++++++- framework/web/AssetManager.php | 16 +++++++++++++++- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/framework/base/ViewContent.php b/framework/base/ViewContent.php index 8b4e835..a2951e7 100644 --- a/framework/base/ViewContent.php +++ b/framework/base/ViewContent.php @@ -98,7 +98,7 @@ class ViewContent extends Component $bundle = $this->assetManager->getBundle($name); if ($bundle !== null) { $this->assetBundles[$name] = false; - $bundle->registerWith($this); + $bundle->registerAssets($this); $this->assetBundles[$name] = true; } else { throw new InvalidConfigException("Unknown asset bundle: $name"); diff --git a/framework/web/AssetBundle.php b/framework/web/AssetBundle.php index 3e4541e..26fbb34 100644 --- a/framework/web/AssetBundle.php +++ b/framework/web/AssetBundle.php @@ -42,10 +42,18 @@ class AssetBundle extends Object public $css = array(); public $depends = array(); + public function mapTo($target) + { + $this->depends = array($target); + $this->js = $this->css = array(); + $this->basePath = null; + $this->baseUrl = null; + } + /** * @param \yii\base\ViewContent $content */ - public function registerWith($content) + public function registerAssets($content) { foreach ($this->depends as $name) { $content->registerAssetBundle($name); @@ -65,4 +73,14 @@ class AssetBundle extends Object } } } + + /** + * @param \yii\web\AssetManager $assetManager + */ + public function publish($assetManager) + { + if ($this->basePath !== null && $this->baseUrl === null) { + return; + } + } } \ No newline at end of file diff --git a/framework/web/AssetManager.php b/framework/web/AssetManager.php index 1f82e7c..4f44f0c 100644 --- a/framework/web/AssetManager.php +++ b/framework/web/AssetManager.php @@ -129,9 +129,23 @@ class AssetManager extends Component $this->bundles[$name] = Yii::createObject($config); } } + /** @var $bundle AssetBundle */ + $bundle = $this->bundles[$name]; + if (isset($this->bundleMap[$name]) && is_string($this->bundleMap[$name])) { + $target = $this->bundleMap[$name]; + if (!isset($this->bundles[$target])) { + if (isset($this->bundleMap[$target])) { + $this->bundles[$target] = $this->bundleMap[$target]; + } else { + throw new InvalidConfigException("Asset bundle '$name' is mapped to an unknown bundle: $target"); + } + } + $bundle->mapTo($target); + unset($this->bundleMap[$name]); + } if ($publish) { - + $bundle->publish($this); } return $this->bundles[$name]; From 5b412b8f84f7351889b9b046bc484d98b9ec017b Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 16 Apr 2013 17:42:31 -0400 Subject: [PATCH 20/53] script WIP --- framework/base/Application.php | 11 +- framework/base/Controller.php | 4 +- framework/base/Module.php | 14 +- framework/base/Theme.php | 6 +- framework/base/Widget.php | 2 +- framework/console/controllers/AppController.php | 121 +++++++++++++++- framework/helpers/base/FileHelper.php | 177 ++---------------------- framework/validators/FileValidator.php | 2 +- framework/web/AssetBundle.php | 65 ++++++--- framework/web/AssetManager.php | 115 +++++++-------- 10 files changed, 251 insertions(+), 266 deletions(-) diff --git a/framework/base/Application.php b/framework/base/Application.php index fd6527f..e0d0237 100644 --- a/framework/base/Application.php +++ b/framework/base/Application.php @@ -202,11 +202,11 @@ class Application extends Module */ public function setRuntimePath($path) { - $p = FileHelper::ensureDirectory($path); - if (is_writable($p)) { - $this->_runtimePath = $p; + $path = Yii::getAlias($path); + if (is_dir($path) && is_writable($path)) { + $this->_runtimePath = $path; } else { - throw new InvalidConfigException("Runtime path must be writable by the Web server process: $path"); + throw new InvalidConfigException("Runtime path must be a directory writable by the Web server process: $path"); } } @@ -227,11 +227,10 @@ class Application extends Module /** * Sets the directory that stores vendor files. * @param string $path the directory that stores vendor files. - * @throws InvalidConfigException if the directory does not exist */ public function setVendorPath($path) { - $this->_vendorPath = FileHelper::ensureDirectory($path); + $this->_vendorPath = Yii::getAlias($path); } /** diff --git a/framework/base/Controller.php b/framework/base/Controller.php index 6ff68da..d11d19b 100644 --- a/framework/base/Controller.php +++ b/framework/base/Controller.php @@ -428,7 +428,7 @@ class Controller extends Component $file = $this->getViewPath() . DIRECTORY_SEPARATOR . $view; } - return FileHelper::getExtension($file) === '' ? $file . '.php' : $file; + return pathinfo($file, PATHINFO_EXTENSION) === '' ? $file . '.php' : $file; } /** @@ -463,7 +463,7 @@ class Controller extends Component $file = $module->getLayoutPath() . DIRECTORY_SEPARATOR . $view; } - if (FileHelper::getExtension($file) === '') { + if (pathinfo($file, PATHINFO_EXTENSION) === '') { $file .= '.php'; } return $file; diff --git a/framework/base/Module.php b/framework/base/Module.php index 0b2bd16..ee97614 100644 --- a/framework/base/Module.php +++ b/framework/base/Module.php @@ -211,7 +211,13 @@ abstract class Module extends Component */ public function setBasePath($path) { - $this->_basePath = FileHelper::ensureDirectory($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"); + } } /** @@ -236,7 +242,7 @@ abstract class Module extends Component */ public function setControllerPath($path) { - $this->_controllerPath = FileHelper::ensureDirectory($path); + $this->_controllerPath = Yii::getAlias($path); } /** @@ -259,7 +265,7 @@ abstract class Module extends Component */ public function setViewPath($path) { - $this->_viewPath = FileHelper::ensureDirectory($path); + $this->_viewPath = Yii::getAlias($path); } /** @@ -282,7 +288,7 @@ abstract class Module extends Component */ public function setLayoutPath($path) { - $this->_layoutPath = FileHelper::ensureDirectory($path); + $this->_layoutPath = Yii::getAlias($path); } /** diff --git a/framework/base/Theme.php b/framework/base/Theme.php index e529a63..a60d56e 100644 --- a/framework/base/Theme.php +++ b/framework/base/Theme.php @@ -61,10 +61,10 @@ class Theme extends Component parent::init(); if (empty($this->pathMap)) { if ($this->basePath !== null) { - $this->basePath = FileHelper::ensureDirectory($this->basePath); + $this->basePath = Yii::getAlias($this->basePath); $this->pathMap = array(Yii::$app->getBasePath() => $this->basePath); } else { - throw new InvalidConfigException("Theme::basePath must be set."); + throw new InvalidConfigException('The "basePath" property must be set.'); } } $paths = array(); @@ -75,7 +75,7 @@ class Theme extends Component } $this->pathMap = $paths; if ($this->baseUrl === null) { - throw new InvalidConfigException("Theme::baseUrl must be set."); + throw new InvalidConfigException('The "baseUrl" property must be set.'); } else { $this->baseUrl = rtrim(Yii::getAlias($this->baseUrl), '/'); } diff --git a/framework/base/Widget.php b/framework/base/Widget.php index c6667fa..13e6d30 100644 --- a/framework/base/Widget.php +++ b/framework/base/Widget.php @@ -131,6 +131,6 @@ class Widget extends Component $file = $this->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/'); } - return FileHelper::getExtension($file) === '' ? $file . '.php' : $file; + return pathinfo($file, PATHINFO_EXTENSION) === '' ? $file . '.php' : $file; } } \ No newline at end of file diff --git a/framework/console/controllers/AppController.php b/framework/console/controllers/AppController.php index 2c32c54..237ba0f 100644 --- a/framework/console/controllers/AppController.php +++ b/framework/console/controllers/AppController.php @@ -86,7 +86,7 @@ class AppController extends Controller $sourceDir = $this->getSourceDir(); $config = $this->getConfig(); - $list = FileHelper::buildFileList($sourceDir, $path); + $list = $this->buildFileList($sourceDir, $path); if(is_array($config)) { foreach($config as $file => $settings) { @@ -96,7 +96,7 @@ class AppController extends Controller } } - FileHelper::copyFiles($list); + $this->copyFiles($list); if(is_array($config)) { foreach($config as $file => $settings) { @@ -204,4 +204,121 @@ class AppController extends Controller return '__DIR__.\''.$up.'/'.basename($path1).'\''; } + + + /** + * Copies a list of files from one place to another. + * @param array $fileList the list of files to be copied (name=>spec). + * The array keys are names displayed during the copy process, and array values are specifications + * for files to be copied. Each array value must be an array of the following structure: + *
    + *
  • source: required, the full path of the file/directory to be copied from
  • + *
  • target: required, the full path of the file/directory to be copied to
  • + *
  • callback: optional, the callback to be invoked when copying a file. The callback function + * should be declared as follows: + *
    +	 *   function foo($source,$params)
    +	 *   
    + * where $source parameter is the source file path, and the content returned + * by the function will be saved into the target file.
  • + *
  • params: optional, the parameters to be passed to the callback
  • + *
+ * @see buildFileList + */ + protected function copyFiles($fileList) + { + $overwriteAll = false; + foreach($fileList as $name=>$file) { + $source = strtr($file['source'], '/\\', DIRECTORY_SEPARATOR); + $target = strtr($file['target'], '/\\', DIRECTORY_SEPARATOR); + $callback = isset($file['callback']) ? $file['callback'] : null; + $params = isset($file['params']) ? $file['params'] : null; + + if(is_dir($source)) { + if (!is_dir($target)) { + mkdir($target, 0777, true); + } + continue; + } + + if($callback !== null) { + $content = call_user_func($callback, $source, $params); + } + else { + $content = file_get_contents($source); + } + if(is_file($target)) { + if($content === file_get_contents($target)) { + echo " unchanged $name\n"; + continue; + } + if($overwriteAll) { + echo " overwrite $name\n"; + } + else { + echo " exist $name\n"; + echo " ...overwrite? [Yes|No|All|Quit] "; + $answer = trim(fgets(STDIN)); + if(!strncasecmp($answer, 'q', 1)) { + return; + } + elseif(!strncasecmp($answer, 'y', 1)) { + echo " overwrite $name\n"; + } + elseif(!strncasecmp($answer, 'a', 1)) { + echo " overwrite $name\n"; + $overwriteAll = true; + } + else { + echo " skip $name\n"; + continue; + } + } + } + else { + if (!is_dir(dirname($target))) { + mkdir(dirname($target), 0777, true); + } + echo " generate $name\n"; + } + file_put_contents($target, $content); + } + } + + /** + * Builds the file list of a directory. + * This method traverses through the specified directory and builds + * a list of files and subdirectories that the directory contains. + * The result of this function can be passed to {@link copyFiles}. + * @param string $sourceDir the source directory + * @param string $targetDir the target directory + * @param string $baseDir base directory + * @param array $ignoreFiles list of the names of files that should + * be ignored in list building process. Argument available since 1.1.11. + * @param array $renameMap hash array of file names that should be + * renamed. Example value: array('1.old.txt'=>'2.new.txt'). + * @return array the file list (see {@link copyFiles}) + */ + protected function buildFileList($sourceDir, $targetDir, $baseDir='', $ignoreFiles=array(), $renameMap=array()) + { + $list = array(); + $handle = opendir($sourceDir); + while(($file = readdir($handle)) !== false) { + if(in_array($file, array('.', '..', '.svn', '.gitignore')) || in_array($file, $ignoreFiles)) { + continue; + } + $sourcePath = $sourceDir.DIRECTORY_SEPARATOR.$file; + $targetPath = $targetDir.DIRECTORY_SEPARATOR.strtr($file, $renameMap); + $name = $baseDir === '' ? $file : $baseDir.'/'.$file; + $list[$name] = array( + 'source' => $sourcePath, + 'target' => $targetPath, + ); + if(is_dir($sourcePath)) { + $list = array_merge($list, self::buildFileList($sourcePath, $targetPath, $name, $ignoreFiles, $renameMap)); + } + } + closedir($handle); + return $list; + } } \ No newline at end of file diff --git a/framework/helpers/base/FileHelper.php b/framework/helpers/base/FileHelper.php index 7dd5543..5bab36f 100644 --- a/framework/helpers/base/FileHelper.php +++ b/framework/helpers/base/FileHelper.php @@ -9,8 +9,7 @@ namespace yii\helpers\base; -use yii\base\Exception; -use yii\base\InvalidConfigException; +use Yii; /** * Filesystem helper @@ -22,35 +21,6 @@ use yii\base\InvalidConfigException; class FileHelper { /** - * Returns the extension name of a file path. - * For example, the path "path/to/something.php" would return "php". - * @param string $path the file path - * @return string the extension name without the dot character. - */ - public static function getExtension($path) - { - return pathinfo($path, PATHINFO_EXTENSION); - } - - /** - * Checks the given path and ensures it is a directory. - * This method will call `realpath()` to "normalize" the given path. - * If the given path does not refer to an existing directory, an exception will be thrown. - * @param string $path the given path. This can also be a path alias. - * @return string the normalized path - * @throws InvalidConfigException if the path does not refer to an existing directory. - */ - public static function ensureDirectory($path) - { - $p = \Yii::getAlias($path); - if (($p = realpath($p)) !== false && is_dir($p)) { - return $p; - } else { - throw new InvalidConfigException('Directory does not exist: ' . $path); - } - } - - /** * Normalizes a file/directory path. * After normalization, the directory separators in the path will be `DIRECTORY_SEPARATOR`, * and any trailing directory separators will be removed. For example, '/home\demo/' on Linux @@ -69,17 +39,14 @@ class FileHelper * * The searching is based on the specified language code. In particular, * a file with the same name will be looked for under the subdirectory - * whose name is same as the language code. For example, given the file "path/to/view.php" - * and language code "zh_cn", the localized file will be looked for as - * "path/to/zh_cn/view.php". If the file is not found, the original file + * whose name is the same as the language code. For example, given the file "path/to/view.php" + * and language code "zh_CN", the localized file will be looked for as + * "path/to/zh_CN/view.php". If the file is not found, the original file * will be returned. * * If the target and the source language codes are the same, * the original file will be returned. * - * For consistency, it is recommended that the language code is given - * in lower case and in the format of LanguageID_RegionID (e.g. "en_us"). - * * @param string $file the original file * @param string $language the target language that the file should be localized to. * If not set, the value of [[\yii\base\Application::language]] will be used. @@ -91,10 +58,10 @@ class FileHelper public static function localize($file, $language = null, $sourceLanguage = null) { if ($language === null) { - $language = \Yii::$app->language; + $language = Yii::$app->language; } if ($sourceLanguage === null) { - $sourceLanguage = \Yii::$app->sourceLanguage; + $sourceLanguage = Yii::$app->sourceLanguage; } if ($language === $sourceLanguage) { return $file; @@ -120,6 +87,7 @@ class FileHelper if (function_exists('finfo_open')) { $info = finfo_open(FILEINFO_MIME_TYPE, $magicFile); if ($info && ($result = finfo_file($info, $file)) !== false) { + finfo_close($info); return $result; } } @@ -137,138 +105,21 @@ class FileHelper */ public static function getMimeTypeByExtension($file, $magicFile = null) { + static $mimeTypes = array(); if ($magicFile === null) { - $magicFile = \Yii::getAlias('@yii/util/mimeTypes.php'); + $magicFile = __DIR__ . '/mimeTypes.php'; + } + if (!isset($mimeTypes[$magicFile])) { + $mimeTypes[$magicFile] = require($magicFile); } - $mimeTypes = require($magicFile); if (($ext = pathinfo($file, PATHINFO_EXTENSION)) !== '') { $ext = strtolower($ext); - if (isset($mimeTypes[$ext])) { - return $mimeTypes[$ext]; + if (isset($mimeTypes[$magicFile][$ext])) { + return $mimeTypes[$magicFile][$ext]; } } return null; } - /** - * Copies a list of files from one place to another. - * @param array $fileList the list of files to be copied (name=>spec). - * The array keys are names displayed during the copy process, and array values are specifications - * for files to be copied. Each array value must be an array of the following structure: - *
    - *
  • source: required, the full path of the file/directory to be copied from
  • - *
  • target: required, the full path of the file/directory to be copied to
  • - *
  • callback: optional, the callback to be invoked when copying a file. The callback function - * should be declared as follows: - *
    -	 *   function foo($source,$params)
    -	 *   
    - * where $source parameter is the source file path, and the content returned - * by the function will be saved into the target file.
  • - *
  • params: optional, the parameters to be passed to the callback
  • - *
- * @see buildFileList - */ - public static function copyFiles($fileList) - { - $overwriteAll = false; - foreach($fileList as $name=>$file) { - $source = strtr($file['source'], '/\\', DIRECTORY_SEPARATOR); - $target = strtr($file['target'], '/\\', DIRECTORY_SEPARATOR); - $callback = isset($file['callback']) ? $file['callback'] : null; - $params = isset($file['params']) ? $file['params'] : null; - - if(is_dir($source)) { - try { - self::ensureDirectory($target); - } - catch (Exception $e) { - mkdir($target, true, 0777); - } - continue; - } - - if($callback !== null) { - $content = call_user_func($callback, $source, $params); - } - else { - $content = file_get_contents($source); - } - if(is_file($target)) { - if($content === file_get_contents($target)) { - echo " unchanged $name\n"; - continue; - } - if($overwriteAll) { - echo " overwrite $name\n"; - } - else { - echo " exist $name\n"; - echo " ...overwrite? [Yes|No|All|Quit] "; - $answer = trim(fgets(STDIN)); - if(!strncasecmp($answer, 'q', 1)) { - return; - } - elseif(!strncasecmp($answer, 'y', 1)) { - echo " overwrite $name\n"; - } - elseif(!strncasecmp($answer, 'a', 1)) { - echo " overwrite $name\n"; - $overwriteAll = true; - } - else { - echo " skip $name\n"; - continue; - } - } - } - else { - try { - self::ensureDirectory(dirname($target)); - } - catch (Exception $e) { - mkdir(dirname($target), true, 0777); - } - echo " generate $name\n"; - } - file_put_contents($target, $content); - } - } - /** - * Builds the file list of a directory. - * This method traverses through the specified directory and builds - * a list of files and subdirectories that the directory contains. - * The result of this function can be passed to {@link copyFiles}. - * @param string $sourceDir the source directory - * @param string $targetDir the target directory - * @param string $baseDir base directory - * @param array $ignoreFiles list of the names of files that should - * be ignored in list building process. Argument available since 1.1.11. - * @param array $renameMap hash array of file names that should be - * renamed. Example value: array('1.old.txt'=>'2.new.txt'). - * @return array the file list (see {@link copyFiles}) - */ - public static function buildFileList($sourceDir, $targetDir, $baseDir='', $ignoreFiles=array(), $renameMap=array()) - { - $list = array(); - $handle = opendir($sourceDir); - while(($file = readdir($handle)) !== false) { - if(in_array($file, array('.', '..', '.svn', '.gitignore')) || in_array($file, $ignoreFiles)) { - continue; - } - $sourcePath = $sourceDir.DIRECTORY_SEPARATOR.$file; - $targetPath = $targetDir.DIRECTORY_SEPARATOR.strtr($file, $renameMap); - $name = $baseDir === '' ? $file : $baseDir.'/'.$file; - $list[$name] = array( - 'source' => $sourcePath, - 'target' => $targetPath, - ); - if(is_dir($sourcePath)) { - $list = array_merge($list, self::buildFileList($sourcePath, $targetPath, $name, $ignoreFiles, $renameMap)); - } - } - closedir($handle); - return $list; - } } \ No newline at end of file diff --git a/framework/validators/FileValidator.php b/framework/validators/FileValidator.php index c104c05..b3de0b2 100644 --- a/framework/validators/FileValidator.php +++ b/framework/validators/FileValidator.php @@ -175,7 +175,7 @@ class FileValidator extends Validator if ($this->minSize !== null && $file->getSize() < $this->minSize) { $this->addError($object, $attribute, $this->tooSmall, array('{file}' => $file->getName(), '{limit}' => $this->minSize)); } - if (!empty($this->types) && !in_array(strtolower(FileHelper::getExtension($file->getName())), $this->types, true)) { + if (!empty($this->types) && !in_array(strtolower(pathinfo($file->getName(), PATHINFO_EXTENSION)), $this->types, true)) { $this->addError($object, $attribute, $this->wrongType, array('{file}' => $file->getName(), '{extensions}' => implode(', ', $this->types))); } break; diff --git a/framework/web/AssetBundle.php b/framework/web/AssetBundle.php index 26fbb34..d3e29d8 100644 --- a/framework/web/AssetBundle.php +++ b/framework/web/AssetBundle.php @@ -7,6 +7,7 @@ namespace yii\web; +use Yii; use yii\base\Object; /** @@ -36,18 +37,47 @@ use yii\base\Object; */ class AssetBundle extends Object { + /** + * @var string the root directory of the asset files. If this is not set, + * the assets are considered to be located under a Web-accessible folder already + * and no asset publishing will be performed. + */ public $basePath; - public $baseUrl; // if missing, the bundle will be published to the "www/assets" folder + /** + * @var string the base URL that will be prefixed to the asset files. + * This property must be set if [[basePath]] is not set. + * When this property is not set, it will be initialized as the base URL + * that the assets are published to. + */ + public $baseUrl; + /** + * @var array list of JavaScript files that this bundle contains. Each JavaScript file can + * be specified in one of the three formats: + * + * - a relative path: a path relative to [[basePath]] if [[basePath]] is set, + * or a URL relative to [[baseUrl]] if [[basePath]] is not set; + * - an absolute URL; + * - a path alias that can be resolved into a relative path or an absolute URL. + * + * Note that you should not use backward slashes "\" to specify JavaScript files. + * + * A JavaScript file can be associated with the options: // todo + */ public $js = array(); public $css = array(); + /** + * @var array list of the bundle names that this bundle depends on + */ public $depends = array(); - public function mapTo($target) + public function init() { - $this->depends = array($target); - $this->js = $this->css = array(); - $this->basePath = null; - $this->baseUrl = null; + if ($this->baseUrl !== null) { + $this->baseUrl = rtrim(Yii::getAlias($this->baseUrl), '/'); + } + if ($this->basePath !== null) { + $this->basePath = rtrim(Yii::getAlias($this->basePath), '/\\'); + } } /** @@ -59,18 +89,18 @@ class AssetBundle extends Object $content->registerAssetBundle($name); } foreach ($this->js as $js => $options) { - if (is_array($options)) { - $content->registerJsFile($js, $options); - } else { - $content->registerJsFile($options); + $js = is_string($options) ? $options : $js; + if (strpos($js, '//') !== 0 && strpos($js, '://') === false) { + $js = $this->baseUrl . '/' . ltrim($js, '/'); } + $content->registerJsFile($js, is_array($options) ? $options : array()); } foreach ($this->css as $css => $options) { - if (is_array($options)) { - $content->registerCssFile($css, $options); - } else { - $content->registerCssFile($options); + $css = is_string($options) ? $options : $css; + if (strpos($css, '//') !== 0 && strpos($css, '://') === false) { + $css = $this->baseUrl . '/' . ltrim($css, '/'); } + $content->registerCssFile($css, is_array($options) ? $options : array()); } } @@ -79,8 +109,11 @@ class AssetBundle extends Object */ public function publish($assetManager) { - if ($this->basePath !== null && $this->baseUrl === null) { - return; + if ($this->basePath !== null) { + $baseUrl = $assetManager->publish($this->basePath); + if ($this->baseUrl === null) { + $this->baseUrl = $baseUrl; + } } } } \ No newline at end of file diff --git a/framework/web/AssetManager.php b/framework/web/AssetManager.php index 4f44f0c..1390f47 100644 --- a/framework/web/AssetManager.php +++ b/framework/web/AssetManager.php @@ -26,10 +26,6 @@ class AssetManager extends Component */ public $bundles; /** - * @var - */ - public $bundleMap; - /** * @return string the root directory storing the published asset files. */ public $basePath = '@wwwroot/assets'; @@ -89,7 +85,7 @@ class AssetManager extends Component } elseif (!is_writable($this->basePath)) { throw new InvalidConfigException("The directory is not writable by the Web process: {$this->basePath}"); } else { - $this->base = realpath($this->basePath); + $this->basePath = realpath($this->basePath); } $this->baseUrl = rtrim(Yii::getAlias($this->baseUrl), '/'); @@ -131,18 +127,6 @@ class AssetManager extends Component } /** @var $bundle AssetBundle */ $bundle = $this->bundles[$name]; - if (isset($this->bundleMap[$name]) && is_string($this->bundleMap[$name])) { - $target = $this->bundleMap[$name]; - if (!isset($this->bundles[$target])) { - if (isset($this->bundleMap[$target])) { - $this->bundles[$target] = $this->bundleMap[$target]; - } else { - throw new InvalidConfigException("Asset bundle '$name' is mapped to an unknown bundle: $target"); - } - } - $bundle->mapTo($target); - unset($this->bundleMap[$name]); - } if ($publish) { $bundle->publish($this); @@ -191,61 +175,56 @@ class AssetManager extends Component { if (isset($this->_published[$path])) { return $this->_published[$path]; - } else { - if (($src = realpath($path)) !== false) { - if (is_file($src)) { - $dir = $this->hash($hashByName ? basename($src) : dirname($src) . filemtime($src)); - $fileName = basename($src); - $dstDir = $this->getBasePath() . DIRECTORY_SEPARATOR . $dir; - $dstFile = $dstDir . DIRECTORY_SEPARATOR . $fileName; + } - if ($this->linkAssets) { - if (!is_file($dstFile)) { - if (!is_dir($dstDir)) { - mkdir($dstDir); - @chmod($dstDir, $this->newDirMode); - } - symlink($src, $dstFile); - } - } else { - if (@filemtime($dstFile) < @filemtime($src)) { - if (!is_dir($dstDir)) { - mkdir($dstDir); - @chmod($dstDir, $this->newDirMode); - } - copy($src, $dstFile); - @chmod($dstFile, $this->newFileMode); - } - } + $src = realpath($path); + if ($src === false) { + throw new InvalidParamException("The file or directory to be published does not exist: $path"); + } + + if (is_file($src)) { + $dir = $this->hash($hashByName ? basename($src) : dirname($src) . filemtime($src)); + $fileName = basename($src); + $dstDir = $this->basePath . DIRECTORY_SEPARATOR . $dir; + $dstFile = $dstDir . DIRECTORY_SEPARATOR . $fileName; - return $this->_published[$path] = $this->getBaseUrl() . "/$dir/$fileName"; - } else { - if (is_dir($src)) { - $dir = $this->hash($hashByName ? basename($src) : $src . filemtime($src)); - $dstDir = $this->getBasePath() . DIRECTORY_SEPARATOR . $dir; + if (!is_dir($dstDir)) { + @mkdir($dstDir, $this->newDirMode, true); + } - if ($this->linkAssets) { - if (!is_dir($dstDir)) { - symlink($src, $dstDir); - } - } else { - if (!is_dir($dstDir) || $forceCopy) { - CFileHelper::copyDirectory($src, $dstDir, array( - 'exclude' => $this->excludeFiles, - 'level' => $level, - 'newDirMode' => $this->newDirMode, - 'newFileMode' => $this->newFileMode, - )); - } - } - return $this->_published[$path] = $this->getBaseUrl() . '/' . $dir; - } + if ($this->linkAssets) { + if (!is_file($dstFile)) { + symlink($src, $dstFile); + } + } elseif (@filemtime($dstFile) < @filemtime($src)) { + copy($src, $dstFile); + @chmod($dstFile, $this->newFileMode); + } + + $url = $this->baseUrl . "/$dir/$fileName"; + } else { + $dir = $this->hash($hashByName ? basename($src) : $src . filemtime($src)); + $dstDir = $this->basePath . DIRECTORY_SEPARATOR . $dir; + + if ($this->linkAssets) { + if (!is_dir($dstDir)) { + symlink($src, $dstDir); + } + } else { + if (!is_dir($dstDir) || $forceCopy) { + FileHelper::copyDirectory($src, $dstDir, array( + 'exclude' => $this->excludeFiles, + 'level' => $level, + 'newDirMode' => $this->newDirMode, + 'newFileMode' => $this->newFileMode, + )); } } + + $url = $this->baseUrl . '/' . $dir; } - throw new CException(Yii::t('yii|The asset "{asset}" to be published does not exist.', - array('{asset}' => $path))); + return $this->_published[$path] = $url; } /** @@ -262,7 +241,7 @@ class AssetManager extends Component public function getPublishedPath($path, $hashByName = false) { if (($path = realpath($path)) !== false) { - $base = $this->getBasePath() . DIRECTORY_SEPARATOR; + $base = $this->basePath . DIRECTORY_SEPARATOR; if (is_file($path)) { return $base . $this->hash($hashByName ? basename($path) : dirname($path) . filemtime($path)) . DIRECTORY_SEPARATOR . basename($path); } else { @@ -291,9 +270,9 @@ class AssetManager extends Component } if (($path = realpath($path)) !== false) { if (is_file($path)) { - return $this->getBaseUrl() . '/' . $this->hash($hashByName ? basename($path) : dirname($path) . filemtime($path)) . '/' . basename($path); + return $this->baseUrl . '/' . $this->hash($hashByName ? basename($path) : dirname($path) . filemtime($path)) . '/' . basename($path); } else { - return $this->getBaseUrl() . '/' . $this->hash($hashByName ? basename($path) : $path . filemtime($path)); + return $this->baseUrl . '/' . $this->hash($hashByName ? basename($path) : $path . filemtime($path)); } } else { return false; From 8f9cfa2c304f347ac66ba32c7dfdea00feae0bb3 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 16 Apr 2013 21:26:19 -0400 Subject: [PATCH 21/53] draft of script/asset management is done. --- framework/helpers/base/FileHelper.php | 52 +++++++++++++++- framework/web/AssetBundle.php | 40 ++++++------ framework/web/AssetManager.php | 111 +++++++++++++++------------------- 3 files changed, 116 insertions(+), 87 deletions(-) diff --git a/framework/helpers/base/FileHelper.php b/framework/helpers/base/FileHelper.php index 5bab36f..d3804fb 100644 --- a/framework/helpers/base/FileHelper.php +++ b/framework/helpers/base/FileHelper.php @@ -86,9 +86,12 @@ class FileHelper { if (function_exists('finfo_open')) { $info = finfo_open(FILEINFO_MIME_TYPE, $magicFile); - if ($info && ($result = finfo_file($info, $file)) !== false) { + if ($info) { + $result = finfo_file($info, $file); finfo_close($info); - return $result; + if ($result !== false) { + return $result; + } } } @@ -122,4 +125,49 @@ class FileHelper } + /** + * Copies a whole directory as another one. + * The files and sub-directories will also be copied over. + * @param string $src the source directory + * @param string $dst the destination directory + * @param array $options options for directory copy. Valid options are: + * + * - dirMode: integer, the permission to be set for newly copied directories. Defaults to 0777. + * - fileMode: integer, the permission to be set for newly copied files. Defaults to the current environment setting. + * - filter: callback, a PHP callback that is called for every sub-directory and file to + * determine if it should be copied. The signature of the callback should be: + * + * ~~~ + * // $path is the file/directory path to be copied + * function ($path) { + * // return a boolean indicating if $path should be copied + * } + * ~~~ + */ + public static function copyDirectory($src, $dst, $options = array()) + { + if (!is_dir($dst)) { + mkdir($dst, isset($options['dirMode']) ? $options['dirMode'] : 0777, true); + } + + $handle = opendir($src); + while (($file = readdir($handle)) !== false) { + if ($file === '.' || $file === '..') { + continue; + } + $srcPath = $src . DIRECTORY_SEPARATOR . $file; + if (!isset($options['filter']) || call_user_func($options['filter'], $srcPath)) { + $dstPath = $dst . DIRECTORY_SEPARATOR . $file; + if (is_file($srcPath)) { + copy($srcPath, $dstPath); + if (isset($options['fileMode'])) { + chmod($dstPath, $options['fileMode']); + } + } else { + static::copyDirectory($srcPath, $dstPath, $options); + } + } + } + closedir($handle); + } } \ No newline at end of file diff --git a/framework/web/AssetBundle.php b/framework/web/AssetBundle.php index d3e29d8..a6b69d2 100644 --- a/framework/web/AssetBundle.php +++ b/framework/web/AssetBundle.php @@ -11,27 +11,6 @@ use Yii; use yii\base\Object; /** - * Each asset bundle should be declared with the following structure: - * - * ~~~ - * array( - * 'basePath' => '...', - * 'baseUrl' => '...', // if missing, the bundle will be published to the "www/assets" folder - * 'js' => array( - * 'js/main.js', - * 'js/menu.js', - * 'js/base.js' => self::POS_HEAD, - * 'css' => array( - * 'css/main.css', - * 'css/menu.css', - * ), - * 'depends' => array( - * 'jquery', - * 'yii', - * 'yii/treeview', - * ), - * ) - * ~~~ * @author Qiang Xue * @since 2.0 */ @@ -61,9 +40,26 @@ class AssetBundle extends Object * * Note that you should not use backward slashes "\" to specify JavaScript files. * - * A JavaScript file can be associated with the options: // todo + * Each JavaScript file may be associated with options. In this case, the array key + * should be the JavaScript file path, while the corresponding array value should + * be the option array. The options will be passed to [[ViewContent::registerJsFile()]]. */ public $js = array(); + /** + * @var array list of CSS files that this bundle contains. Each CSS file can + * be specified in one of the three formats: + * + * - a relative path: a path relative to [[basePath]] if [[basePath]] is set, + * or a URL relative to [[baseUrl]] if [[basePath]] is not set; + * - an absolute URL; + * - a path alias that can be resolved into a relative path or an absolute URL. + * + * Note that you should not use backward slashes "\" to specify CSS files. + * + * Each CSS file may be associated with options. In this case, the array key + * should be the CSS file path, while the corresponding array value should + * be the option array. The options will be passed to [[ViewContent::registerCssFile()]]. + */ public $css = array(); /** * @var array list of the bundle names that this bundle depends on diff --git a/framework/web/AssetManager.php b/framework/web/AssetManager.php index 1390f47..c06d6b2 100644 --- a/framework/web/AssetManager.php +++ b/framework/web/AssetManager.php @@ -11,6 +11,7 @@ use Yii; use yii\base\Component; use yii\base\InvalidConfigException; use yii\base\InvalidParamException; +use yii\helpers\FileHelper; /** * @@ -58,24 +59,22 @@ class AssetManager extends Component **/ public $excludeFiles = array('.svn', '.gitignore'); /** - * @var integer the permission to be set for newly generated asset files. - * This value will be used by PHP chmod function. - * Defaults to 0666, meaning the file is read-writable by all users. - * @since 1.1.8 + * @var integer the permission to be set for newly published asset files. + * This value will be used by PHP chmod() function. + * If not set, the permission will be determined by the current environment. */ - public $newFileMode = 0666; + public $fileMode; /** * @var integer the permission to be set for newly generated asset directories. - * This value will be used by PHP chmod function. + * This value will be used by PHP chmod() function. * Defaults to 0777, meaning the directory can be read, written and executed by all users. - * @since 1.1.8 */ - public $newDirMode = 0777; + public $dirMode = 0777; + /** - * @var array published assets + * Initializes the component. + * @throws InvalidConfigException if [[basePath]] is invalid */ - private $_published = array(); - public function init() { parent::init(); @@ -136,16 +135,22 @@ class AssetManager extends Component } /** + * @var array published assets + */ + private $_published = array(); + + /** * Publishes a file or a directory. - * This method will copy the specified asset to a web accessible directory - * and return the URL for accessing the published asset. - *
    - *
  • If the asset is a file, its file modification time will be checked - * to avoid unnecessary file copying;
  • - *
  • If the asset is a directory, all files and subdirectories under it will - * be published recursively. Note, in case $forceCopy is false the method only checks the - * existence of the target directory to avoid repetitive copying.
  • - *
+ * + * This method will copy the specified file or directory to [[basePath]] so that + * it can be accessed via the Web server. + * + * If the asset is a file, its file modification time will be checked to avoid + * unnecessary file copying. + * + * If the asset is a directory, all files and subdirectories under it will be published recursively. + * Note, in case $forceCopy is false the method only checks the existence of the target + * directory to avoid repetitive copying (which is very expensive). * * Note: On rare scenario, a race condition can develop that will lead to a * one-time-manifestation of a non-critical problem in the creation of the directory @@ -155,23 +160,14 @@ class AssetManager extends Component * discussion: http://code.google.com/p/yii/issues/detail?id=2579 * * @param string $path the asset (file or directory) to be published - * @param boolean $hashByName whether the published directory should be named as the hashed basename. - * If false, the name will be the hash taken from dirname of the path being published and path mtime. - * Defaults to false. Set true if the path being published is shared among - * different extensions. - * @param integer $level level of recursive copying when the asset is a directory. - * Level -1 means publishing all subdirectories and files; - * Level 0 means publishing only the files DIRECTLY under the directory; - * level N means copying those directories that are within N levels. - * @param boolean $forceCopy whether we should copy the asset file or directory even if it is already published before. - * This parameter is set true mainly during development stage when the original - * assets are being constantly changed. The consequence is that the performance + * @param boolean $forceCopy whether the asset should ALWAYS be copied even if it is found + * in the target directory. This parameter is mainly useful during the development stage + * when the original assets are being constantly changed. The consequence is that the performance * is degraded, which is not a concern during development, however. - * This parameter has been available since version 1.1.2. * @return string an absolute URL to the published asset - * @throws CException if the asset to be published does not exist. + * @throws InvalidParamException if the asset to be published does not exist. */ - public function publish($path, $hashByName = false, $level = -1, $forceCopy = false) + public function publish($path, $forceCopy = false) { if (isset($this->_published[$path])) { return $this->_published[$path]; @@ -183,13 +179,13 @@ class AssetManager extends Component } if (is_file($src)) { - $dir = $this->hash($hashByName ? basename($src) : dirname($src) . filemtime($src)); + $dir = $this->hash(dirname($src) . filemtime($src)); $fileName = basename($src); $dstDir = $this->basePath . DIRECTORY_SEPARATOR . $dir; $dstFile = $dstDir . DIRECTORY_SEPARATOR . $fileName; if (!is_dir($dstDir)) { - @mkdir($dstDir, $this->newDirMode, true); + @mkdir($dstDir, $this->dirMode, true); } @@ -197,29 +193,26 @@ class AssetManager extends Component if (!is_file($dstFile)) { symlink($src, $dstFile); } - } elseif (@filemtime($dstFile) < @filemtime($src)) { + } elseif (@filemtime($dstFile) < @filemtime($src) || $forceCopy) { copy($src, $dstFile); - @chmod($dstFile, $this->newFileMode); + if ($this->fileMode !== null) { + @chmod($dstFile, $this->fileMode); + } } $url = $this->baseUrl . "/$dir/$fileName"; } else { - $dir = $this->hash($hashByName ? basename($src) : $src . filemtime($src)); + $dir = $this->hash($src . filemtime($src)); $dstDir = $this->basePath . DIRECTORY_SEPARATOR . $dir; - if ($this->linkAssets) { if (!is_dir($dstDir)) { symlink($src, $dstDir); } - } else { - if (!is_dir($dstDir) || $forceCopy) { - FileHelper::copyDirectory($src, $dstDir, array( - 'exclude' => $this->excludeFiles, - 'level' => $level, - 'newDirMode' => $this->newDirMode, - 'newFileMode' => $this->newFileMode, - )); - } + } elseif (!is_dir($dstDir) || $forceCopy) { + FileHelper::copyDirectory($src, $dstDir, array( + 'dirMode' => $this->dirMode, + 'fileMode' => $this->fileMode, + )); } $url = $this->baseUrl . '/' . $dir; @@ -232,20 +225,16 @@ class AssetManager extends Component * This method does not perform any publishing. It merely tells you * if the file or directory is published, where it will go. * @param string $path directory or file path being published - * @param boolean $hashByName whether the published directory should be named as the hashed basename. - * If false, the name will be the hash taken from dirname of the path being published and path mtime. - * Defaults to false. Set true if the path being published is shared among - * different extensions. * @return string the published file path. False if the file or directory does not exist */ - public function getPublishedPath($path, $hashByName = false) + public function getPublishedPath($path) { if (($path = realpath($path)) !== false) { $base = $this->basePath . DIRECTORY_SEPARATOR; if (is_file($path)) { - return $base . $this->hash($hashByName ? basename($path) : dirname($path) . filemtime($path)) . DIRECTORY_SEPARATOR . basename($path); + return $base . $this->hash(dirname($path) . filemtime($path)) . DIRECTORY_SEPARATOR . basename($path); } else { - return $base . $this->hash($hashByName ? basename($path) : $path . filemtime($path)); + return $base . $this->hash($path . filemtime($path)); } } else { return false; @@ -257,22 +246,18 @@ class AssetManager extends Component * This method does not perform any publishing. It merely tells you * if the file path is published, what the URL will be to access it. * @param string $path directory or file path being published - * @param boolean $hashByName whether the published directory should be named as the hashed basename. - * If false, the name will be the hash taken from dirname of the path being published and path mtime. - * Defaults to false. Set true if the path being published is shared among - * different extensions. * @return string the published URL for the file or directory. False if the file or directory does not exist. */ - public function getPublishedUrl($path, $hashByName = false) + public function getPublishedUrl($path) { if (isset($this->_published[$path])) { return $this->_published[$path]; } if (($path = realpath($path)) !== false) { if (is_file($path)) { - return $this->baseUrl . '/' . $this->hash($hashByName ? basename($path) : dirname($path) . filemtime($path)) . '/' . basename($path); + return $this->baseUrl . '/' . $this->hash(dirname($path) . filemtime($path)) . '/' . basename($path); } else { - return $this->baseUrl . '/' . $this->hash($hashByName ? basename($path) : $path . filemtime($path)); + return $this->baseUrl . '/' . $this->hash($path . filemtime($path)); } } else { return false; From 82535b254de8f0a65b7e74bfdca6a32b4d941034 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Wed, 17 Apr 2013 20:28:21 -0400 Subject: [PATCH 22/53] ... --- framework/web/AssetBundle.php | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/framework/web/AssetBundle.php b/framework/web/AssetBundle.php index a6b69d2..e811dc6 100644 --- a/framework/web/AssetBundle.php +++ b/framework/web/AssetBundle.php @@ -17,14 +17,21 @@ use yii\base\Object; class AssetBundle extends Object { /** - * @var string the root directory of the asset files. If this is not set, - * the assets are considered to be located under a Web-accessible folder already - * and no asset publishing will be performed. + * @var string the root directory of the source asset files. If this is set, + * the source asset files will be published to [[basePath]] when the bundle + * is being used the first time. + */ + public $sourcePath; + /** + * @var string the root directory of the public asset files. If this is not set + * while [[sourcePath]] is set, a default value will be set by [[AssetManager]] + * when it publishes the source asset files. If you set this property, please + * make sure the directory is Web accessible. */ public $basePath; /** * @var string the base URL that will be prefixed to the asset files. - * This property must be set if [[basePath]] is not set. + * This property must be set if you set [[basePath]] explicitly. * When this property is not set, it will be initialized as the base URL * that the assets are published to. */ @@ -33,12 +40,11 @@ class AssetBundle extends Object * @var array list of JavaScript files that this bundle contains. Each JavaScript file can * be specified in one of the three formats: * - * - a relative path: a path relative to [[basePath]] if [[basePath]] is set, - * or a URL relative to [[baseUrl]] if [[basePath]] is not set; + * - a relative file path: a path relative to [[basePath]];, * - an absolute URL; * - a path alias that can be resolved into a relative path or an absolute URL. * - * Note that you should not use backward slashes "\" to specify JavaScript files. + * Note that only forward slashes "/" should be used as directory separators. * * Each JavaScript file may be associated with options. In this case, the array key * should be the JavaScript file path, while the corresponding array value should @@ -49,12 +55,11 @@ class AssetBundle extends Object * @var array list of CSS files that this bundle contains. Each CSS file can * be specified in one of the three formats: * - * - a relative path: a path relative to [[basePath]] if [[basePath]] is set, - * or a URL relative to [[baseUrl]] if [[basePath]] is not set; + * - a relative file path: a path relative to [[basePath]];, * - an absolute URL; * - a path alias that can be resolved into a relative path or an absolute URL. * - * Note that you should not use backward slashes "\" to specify CSS files. + * Note that only forward slashes "/" should be used as directory separators. * * Each CSS file may be associated with options. In this case, the array key * should be the CSS file path, while the corresponding array value should @@ -71,8 +76,8 @@ class AssetBundle extends Object if ($this->baseUrl !== null) { $this->baseUrl = rtrim(Yii::getAlias($this->baseUrl), '/'); } - if ($this->basePath !== null) { - $this->basePath = rtrim(Yii::getAlias($this->basePath), '/\\'); + if ($this->sourcePath !== null) { + $this->sourcePath = rtrim(Yii::getAlias($this->sourcePath), '/\\'); } } @@ -105,8 +110,8 @@ class AssetBundle extends Object */ public function publish($assetManager) { - if ($this->basePath !== null) { - $baseUrl = $assetManager->publish($this->basePath); + if ($this->sourcePath !== null) { + $baseUrl = $assetManager->publish($this->sourcePath); if ($this->baseUrl === null) { $this->baseUrl = $baseUrl; } From db2392cdda65a061c543d704d1e793ef0d0d98af Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 18 Apr 2013 08:45:41 -0400 Subject: [PATCH 23/53] script WIP --- framework/base/ViewContent.php | 2 +- framework/web/AssetBundle.php | 122 +++++++++++++++++++++++++++-------------- framework/web/AssetManager.php | 17 ++---- framework/web/Sort.php | 2 +- 4 files changed, 87 insertions(+), 56 deletions(-) diff --git a/framework/base/ViewContent.php b/framework/base/ViewContent.php index a2951e7..dedd3bb 100644 --- a/framework/base/ViewContent.php +++ b/framework/base/ViewContent.php @@ -98,7 +98,7 @@ class ViewContent extends Component $bundle = $this->assetManager->getBundle($name); if ($bundle !== null) { $this->assetBundles[$name] = false; - $bundle->registerAssets($this); + $bundle->registerAssets($this, $this->assetManager); $this->assetBundles[$name] = true; } else { throw new InvalidConfigException("Unknown asset bundle: $name"); diff --git a/framework/web/AssetBundle.php b/framework/web/AssetBundle.php index e811dc6..066e248 100644 --- a/framework/web/AssetBundle.php +++ b/framework/web/AssetBundle.php @@ -8,6 +8,7 @@ namespace yii\web; use Yii; +use yii\base\InvalidConfigException; use yii\base\Object; /** @@ -17,34 +18,51 @@ use yii\base\Object; class AssetBundle extends Object { /** - * @var string the root directory of the source asset files. If this is set, - * the source asset files will be published to [[basePath]] when the bundle - * is being used the first time. + * @var string the root directory of the source asset files. A source asset file + * is a file that is part of your source code repository of your Web application. + * + * You must set this property if the directory containing the source asset files + * is not Web accessible (this is usually the case for extensions). + * + * By setting this property, the asset manager will publish the source asset files + * to a Web-accessible directory [[basePath]]. + * + * You can use either a directory or an alias of the directory. */ public $sourcePath; /** - * @var string the root directory of the public asset files. If this is not set - * while [[sourcePath]] is set, a default value will be set by [[AssetManager]] - * when it publishes the source asset files. If you set this property, please - * make sure the directory is Web accessible. + * @var string the Web-accessible directory that contains the asset files in this bundle. + * + * If [[sourcePath]] is set, this property will be *overwritten* by [[AssetManager]] + * when it publishes the asset files from [[sourcePath]]. + * + * If the bundle contains any assets that are specified in terms of relative file path, + * then this property must be set either manually or automatically (by asset manager via + * asset publishing). + * + * You can use either a directory or an alias of the directory. */ public $basePath; /** - * @var string the base URL that will be prefixed to the asset files. - * This property must be set if you set [[basePath]] explicitly. - * When this property is not set, it will be initialized as the base URL - * that the assets are published to. + * @var string the base URL that will be prefixed to the asset files for them to + * be accessed via Web server. + * + * If [[sourcePath]] is set, this property will be *overwritten* by [[AssetManager]] + * when it publishes the asset files from [[sourcePath]]. + * + * If the bundle contains any assets that are specified in terms of relative file path, + * then this property must be set either manually or automatically (by asset manager via + * asset publishing). + * + * You can use either a URL or an alias of the URL. */ public $baseUrl; /** * @var array list of JavaScript files that this bundle contains. Each JavaScript file can - * be specified in one of the three formats: - * - * - a relative file path: a path relative to [[basePath]];, - * - an absolute URL; - * - a path alias that can be resolved into a relative path or an absolute URL. + * be either a file path (without leading slash) relative to [[basePath]] or a URL representing + * an external JavaScript file. * - * Note that only forward slashes "/" should be used as directory separators. + * Note that only forward slash "/" can be used as directory separator. * * Each JavaScript file may be associated with options. In this case, the array key * should be the JavaScript file path, while the corresponding array value should @@ -53,13 +71,10 @@ class AssetBundle extends Object public $js = array(); /** * @var array list of CSS files that this bundle contains. Each CSS file can - * be specified in one of the three formats: + * be either a file path (without leading slash) relative to [[basePath]] or a URL representing + * an external CSS file. * - * - a relative file path: a path relative to [[basePath]];, - * - an absolute URL; - * - a path alias that can be resolved into a relative path or an absolute URL. - * - * Note that only forward slashes "/" should be used as directory separators. + * Note that only forward slash "/" can be used as directory separator. * * Each CSS file may be associated with options. In this case, the array key * should be the CSS file path, while the corresponding array value should @@ -71,50 +86,73 @@ class AssetBundle extends Object */ public $depends = array(); + /** + * Initializes the bundle. + */ public function init() { - if ($this->baseUrl !== null) { - $this->baseUrl = rtrim(Yii::getAlias($this->baseUrl), '/'); - } if ($this->sourcePath !== null) { $this->sourcePath = rtrim(Yii::getAlias($this->sourcePath), '/\\'); } + if ($this->basePath !== null) { + $this->basePath = rtrim(Yii::getAlias($this->basePath), '/\\'); + } + if ($this->baseUrl !== null) { + $this->baseUrl = rtrim(Yii::getAlias($this->baseUrl), '/'); + } } /** - * @param \yii\base\ViewContent $content + * @param \yii\base\ViewContent $page + * @param AssetManager $am + * @throws InvalidConfigException */ - public function registerAssets($content) + public function registerAssets($page, $am) { foreach ($this->depends as $name) { - $content->registerAssetBundle($name); + $page->registerAssetBundle($name); + } + + if ($this->sourcePath !== null) { + list ($this->basePath, $this->baseUrl) = $am->publish($this->sourcePath); } + foreach ($this->js as $js => $options) { $js = is_string($options) ? $options : $js; - if (strpos($js, '//') !== 0 && strpos($js, '://') === false) { - $js = $this->baseUrl . '/' . ltrim($js, '/'); + if (strpos($js, '/') !== 0 && strpos($js, '://') === false) { + if (isset($this->basePath, $this->baseUrl)) { + $js = $this->processAsset(ltrim($js, '/'), $this->basePath, $this->baseUrl); + } else { + throw new InvalidConfigException('Both of the "baseUrl" and "basePath" properties must be set.'); + } } - $content->registerJsFile($js, is_array($options) ? $options : array()); + $page->registerJsFile($js, is_array($options) ? $options : array()); } foreach ($this->css as $css => $options) { $css = is_string($options) ? $options : $css; if (strpos($css, '//') !== 0 && strpos($css, '://') === false) { - $css = $this->baseUrl . '/' . ltrim($css, '/'); + if (isset($this->basePath, $this->baseUrl)) { + $css = $this->processAsset(ltrim($css, '/'), $this->basePath, $this->baseUrl); + } else { + throw new InvalidConfigException('Both of the "baseUrl" and "basePath" properties must be set.'); + } } - $content->registerCssFile($css, is_array($options) ? $options : array()); + $page->registerCssFile($css, is_array($options) ? $options : array()); } } /** - * @param \yii\web\AssetManager $assetManager + * Processes the given asset file and returns a URL to the processed one. + * This method can be overwritten to support various types of asset files, such as LESS, Sass, TypeScript. + * + * Note that if the asset file is converted into another file, the new file must reside under the same + * directory as the given asset file. + * + * @param string $asset the asset file path to be processed. + * @return string the processed asset file path. */ - public function publish($assetManager) + protected function processAsset($asset, $basePath, $baseUrl) { - if ($this->sourcePath !== null) { - $baseUrl = $assetManager->publish($this->sourcePath); - if ($this->baseUrl === null) { - $this->baseUrl = $baseUrl; - } - } + return $this->baseUrl . '/' . $asset; } } \ No newline at end of file diff --git a/framework/web/AssetManager.php b/framework/web/AssetManager.php index c06d6b2..9a121d8 100644 --- a/framework/web/AssetManager.php +++ b/framework/web/AssetManager.php @@ -26,6 +26,7 @@ class AssetManager extends Component * may look for bundles declared in extensions. For more details, please refer to [[getBundle()]]. */ public $bundles; + public $bundleClass; /** * @return string the root directory storing the published asset files. */ @@ -97,11 +98,10 @@ class AssetManager extends Component /** * @param string $name - * @param boolean $publish * @return AssetBundle * @throws InvalidParamException */ - public function getBundle($name, $publish = true) + public function getBundle($name) { if (!isset($this->bundles[$name])) { $rootAlias = Yii::getRootAlias("@$name"); @@ -124,12 +124,6 @@ class AssetManager extends Component $this->bundles[$name] = Yii::createObject($config); } } - /** @var $bundle AssetBundle */ - $bundle = $this->bundles[$name]; - - if ($publish) { - $bundle->publish($this); - } return $this->bundles[$name]; } @@ -164,7 +158,7 @@ class AssetManager extends Component * in the target directory. This parameter is mainly useful during the development stage * when the original assets are being constantly changed. The consequence is that the performance * is degraded, which is not a concern during development, however. - * @return string an absolute URL to the published asset + * @return array the path (directory or file path) and the URL that the asset is published as. * @throws InvalidParamException if the asset to be published does not exist. */ public function publish($path, $forceCopy = false) @@ -200,7 +194,7 @@ class AssetManager extends Component } } - $url = $this->baseUrl . "/$dir/$fileName"; + return $this->_published[$path] = array($dstFile, $this->baseUrl . "/$dir/$fileName"); } else { $dir = $this->hash($src . filemtime($src)); $dstDir = $this->basePath . DIRECTORY_SEPARATOR . $dir; @@ -215,9 +209,8 @@ class AssetManager extends Component )); } - $url = $this->baseUrl . '/' . $dir; + return $this->_published[$path] = array($dstDir, $this->baseUrl . '/' . $dir); } - return $this->_published[$path] = $url; } /** diff --git a/framework/web/Sort.php b/framework/web/Sort.php index 7cfeeca..e5c2451 100644 --- a/framework/web/Sort.php +++ b/framework/web/Sort.php @@ -216,7 +216,7 @@ class Sort extends \yii\base\Object $url = $this->createUrl($attribute); - return Html::link($label, $url, $htmlOptions); + return Html::a($label, $url, $htmlOptions); } private $_attributeOrders; From a3d058574e6914de604c18c09e9a3c4966bee714 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 18 Apr 2013 11:26:39 -0400 Subject: [PATCH 24/53] refactored FileHelper::copyDirectory() --- framework/helpers/base/FileHelper.php | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/framework/helpers/base/FileHelper.php b/framework/helpers/base/FileHelper.php index d3804fb..478f978 100644 --- a/framework/helpers/base/FileHelper.php +++ b/framework/helpers/base/FileHelper.php @@ -124,7 +124,6 @@ class FileHelper return null; } - /** * Copies a whole directory as another one. * The files and sub-directories will also be copied over. @@ -134,15 +133,12 @@ class FileHelper * * - dirMode: integer, the permission to be set for newly copied directories. Defaults to 0777. * - fileMode: integer, the permission to be set for newly copied files. Defaults to the current environment setting. - * - filter: callback, a PHP callback that is called for every sub-directory and file to - * determine if it should be copied. The signature of the callback should be: - * - * ~~~ - * // $path is the file/directory path to be copied - * function ($path) { - * // return a boolean indicating if $path should be copied - * } - * ~~~ + * - beforeCopy: callback, a PHP callback that is called before copying each sub-directory or file. + * If the callback returns false, the copy operation for the sub-directory or file will be cancelled. + * The signature of the callback should be: `function ($from, $to)`, where `$from` is the sub-directory or + * file to be copied from, while `$to` is the copy target. + * - afterCopy: callback, a PHP callback that is called after a sub-directory or file is successfully copied. + * The signature of the callback is similar to that of `beforeCopy`. */ public static function copyDirectory($src, $dst, $options = array()) { @@ -155,16 +151,19 @@ class FileHelper if ($file === '.' || $file === '..') { continue; } - $srcPath = $src . DIRECTORY_SEPARATOR . $file; - if (!isset($options['filter']) || call_user_func($options['filter'], $srcPath)) { - $dstPath = $dst . DIRECTORY_SEPARATOR . $file; - if (is_file($srcPath)) { - copy($srcPath, $dstPath); + $from = $src . DIRECTORY_SEPARATOR . $file; + $to = $dst . DIRECTORY_SEPARATOR . $file; + if (!isset($options['beforeCopy']) || call_user_func($options['beforeCopy'], $from, $to)) { + if (is_file($from)) { + copy($from, $to); if (isset($options['fileMode'])) { - chmod($dstPath, $options['fileMode']); + chmod($to, $options['fileMode']); } } else { - static::copyDirectory($srcPath, $dstPath, $options); + static::copyDirectory($from, $to, $options); + } + if (isset($options['afterCopy'])) { + call_user_func($options['afterCopy'], $from, $to); } } } From 4db5041d66ab59a5cd8981d28e99f0a047da929f Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Thu, 18 Apr 2013 20:43:27 +0400 Subject: [PATCH 25/53] removed metions of 1.1 --- framework/web/AssetManager.php | 1 - framework/web/Response.php | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/framework/web/AssetManager.php b/framework/web/AssetManager.php index 9a121d8..55c54d4 100644 --- a/framework/web/AssetManager.php +++ b/framework/web/AssetManager.php @@ -56,7 +56,6 @@ class AssetManager extends Component /** * @var array list of directories and files which should be excluded from the publishing process. * Defaults to exclude '.svn' and '.gitignore' files only. This option has no effect if {@link linkAssets} is enabled. - * @since 1.1.6 **/ public $excludeFiles = array('.svn', '.gitignore'); /** diff --git a/framework/web/Response.php b/framework/web/Response.php index da2482f..1d604e9 100644 --- a/framework/web/Response.php +++ b/framework/web/Response.php @@ -112,8 +112,8 @@ class Response extends \yii\base\Response *
  • mimeType: mime type of the file, if not set it will be guessed automatically based on the file name, if set to null no content-type header will be sent.
  • *
  • xHeader: appropriate x-sendfile header, defaults to "X-Sendfile"
  • *
  • terminate: whether to terminate the current application after calling this method, defaults to true
  • - *
  • forceDownload: specifies whether the file will be downloaded or shown inline, defaults to true. (Since version 1.1.9.)
  • - *
  • addHeaders: an array of additional http headers in header-value pairs (available since version 1.1.10)
  • + *
  • forceDownload: specifies whether the file will be downloaded or shown inline, defaults to true
  • + *
  • addHeaders: an array of additional http headers in header-value pairs
  • * */ public function xSendFile($filePath, $options = array()) From c006ebeb4900fa9b03bb5b4828c83b8d161c2cf7 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Thu, 18 Apr 2013 20:46:37 +0400 Subject: [PATCH 26/53] added .hgignore to list of typically ignored files --- framework/console/controllers/AppController.php | 4 ++-- framework/web/AssetManager.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/framework/console/controllers/AppController.php b/framework/console/controllers/AppController.php index 237ba0f..a47acfe 100644 --- a/framework/console/controllers/AppController.php +++ b/framework/console/controllers/AppController.php @@ -294,7 +294,7 @@ class AppController extends Controller * @param string $targetDir the target directory * @param string $baseDir base directory * @param array $ignoreFiles list of the names of files that should - * be ignored in list building process. Argument available since 1.1.11. + * be ignored in list building process. * @param array $renameMap hash array of file names that should be * renamed. Example value: array('1.old.txt'=>'2.new.txt'). * @return array the file list (see {@link copyFiles}) @@ -304,7 +304,7 @@ class AppController extends Controller $list = array(); $handle = opendir($sourceDir); while(($file = readdir($handle)) !== false) { - if(in_array($file, array('.', '..', '.svn', '.gitignore')) || in_array($file, $ignoreFiles)) { + if(in_array($file, array('.', '..', '.svn', '.gitignore', '.hgignore')) || in_array($file, $ignoreFiles)) { continue; } $sourcePath = $sourceDir.DIRECTORY_SEPARATOR.$file; diff --git a/framework/web/AssetManager.php b/framework/web/AssetManager.php index 55c54d4..8788253 100644 --- a/framework/web/AssetManager.php +++ b/framework/web/AssetManager.php @@ -55,9 +55,9 @@ class AssetManager extends Component public $linkAssets = false; /** * @var array list of directories and files which should be excluded from the publishing process. - * Defaults to exclude '.svn' and '.gitignore' files only. This option has no effect if {@link linkAssets} is enabled. + * Defaults to exclude '.svn', '.gitignore' and '.hgignore' files only. This option has no effect if {@link linkAssets} is enabled. **/ - public $excludeFiles = array('.svn', '.gitignore'); + public $excludeFiles = array('.svn', '.gitignore', '.hgignore'); /** * @var integer the permission to be set for newly published asset files. * This value will be used by PHP chmod() function. From fadb528f2e367d0eff289173567bb6b4a7f0bfef Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 18 Apr 2013 12:58:45 -0400 Subject: [PATCH 27/53] script WIP --- framework/helpers/base/FileHelper.php | 2 +- framework/web/AssetBundle.php | 19 +------- framework/web/AssetManager.php | 86 +++++++++++++++++++++++++---------- 3 files changed, 64 insertions(+), 43 deletions(-) diff --git a/framework/helpers/base/FileHelper.php b/framework/helpers/base/FileHelper.php index 478f978..2f62f43 100644 --- a/framework/helpers/base/FileHelper.php +++ b/framework/helpers/base/FileHelper.php @@ -157,7 +157,7 @@ class FileHelper if (is_file($from)) { copy($from, $to); if (isset($options['fileMode'])) { - chmod($to, $options['fileMode']); + @chmod($to, $options['fileMode']); } } else { static::copyDirectory($from, $to, $options); diff --git a/framework/web/AssetBundle.php b/framework/web/AssetBundle.php index 066e248..5b04637 100644 --- a/framework/web/AssetBundle.php +++ b/framework/web/AssetBundle.php @@ -121,7 +121,7 @@ class AssetBundle extends Object $js = is_string($options) ? $options : $js; if (strpos($js, '/') !== 0 && strpos($js, '://') === false) { if (isset($this->basePath, $this->baseUrl)) { - $js = $this->processAsset(ltrim($js, '/'), $this->basePath, $this->baseUrl); + $js = $am->processAsset(ltrim($js, '/'), $this->basePath, $this->baseUrl); } else { throw new InvalidConfigException('Both of the "baseUrl" and "basePath" properties must be set.'); } @@ -132,7 +132,7 @@ class AssetBundle extends Object $css = is_string($options) ? $options : $css; if (strpos($css, '//') !== 0 && strpos($css, '://') === false) { if (isset($this->basePath, $this->baseUrl)) { - $css = $this->processAsset(ltrim($css, '/'), $this->basePath, $this->baseUrl); + $css = $am->processAsset(ltrim($css, '/'), $this->basePath, $this->baseUrl); } else { throw new InvalidConfigException('Both of the "baseUrl" and "basePath" properties must be set.'); } @@ -140,19 +140,4 @@ class AssetBundle extends Object $page->registerCssFile($css, is_array($options) ? $options : array()); } } - - /** - * Processes the given asset file and returns a URL to the processed one. - * This method can be overwritten to support various types of asset files, such as LESS, Sass, TypeScript. - * - * Note that if the asset file is converted into another file, the new file must reside under the same - * directory as the given asset file. - * - * @param string $asset the asset file path to be processed. - * @return string the processed asset file path. - */ - protected function processAsset($asset, $basePath, $baseUrl) - { - return $this->baseUrl . '/' . $asset; - } } \ No newline at end of file diff --git a/framework/web/AssetManager.php b/framework/web/AssetManager.php index 9a121d8..30f4c11 100644 --- a/framework/web/AssetManager.php +++ b/framework/web/AssetManager.php @@ -21,12 +21,10 @@ use yii\helpers\FileHelper; class AssetManager extends Component { /** - * @var array list of asset bundles. The keys are the bundle names, and the values are the configuration - * arrays for creating [[AssetBundle]] objects. Besides the bundles listed here, the asset manager - * may look for bundles declared in extensions. For more details, please refer to [[getBundle()]]. + * @var array list of available asset bundles. The keys are the bundle names, and the values are the configuration + * arrays for creating the [[AssetBundle]] objects. */ public $bundles; - public $bundleClass; /** * @return string the root directory storing the published asset files. */ @@ -54,12 +52,6 @@ class AssetManager extends Component */ public $linkAssets = false; /** - * @var array list of directories and files which should be excluded from the publishing process. - * Defaults to exclude '.svn' and '.gitignore' files only. This option has no effect if {@link linkAssets} is enabled. - * @since 1.1.6 - **/ - public $excludeFiles = array('.svn', '.gitignore'); - /** * @var integer the permission to be set for newly published asset files. * This value will be used by PHP chmod() function. * If not set, the permission will be determined by the current environment. @@ -97,9 +89,21 @@ class AssetManager extends Component } /** - * @param string $name - * @return AssetBundle - * @throws InvalidParamException + * Returns the named bundle. + * This method will first look for the bundle in [[bundles]]. If not found, + * it will attempt to find the bundle from an installed extension using the following procedure: + * + * 1. Convert the bundle into a path alias; + * 2. Determine the root alias and use it to locate the bundle manifest file "assets.php"; + * 3. Look for the bundle in the manifest file. + * + * For example, given the bundle name "foo/button", the method will first convert it + * into the path alias "@foo/button"; since "@foo" is the root alias, it will look + * for the bundle manifest file "@foo/assets.php". The manifest file should declare + * the bundles used by the "foo/button" extension. + * + * @param string $name the bundle name + * @return AssetBundle the loaded bundle object. Null is returned if the bundle does not exist. */ public function getBundle($name) { @@ -114,7 +118,7 @@ class AssetManager extends Component } } if (!isset($this->bundles[$name])) { - throw new InvalidParamException("Unable to find the asset bundle: $name"); + return null; } } if (is_array($this->bundles[$name])) { @@ -129,6 +133,20 @@ class AssetManager extends Component } /** + * Processes the given asset file and returns a URL to the processed one. + * This method can be overwritten to support various types of asset files, such as LESS, Sass, TypeScript. + * @param string $asset the asset file path to be processed. The file path is relative + * to $basePath, and it may contain forward slashes to indicate sub-directories (e.g. "js/main.js"). + * @param string $basePath the directory that contains the asset file. + * @param string $baseUrl the corresponding URL of $basePath. + * @return string the processed asset file path. + */ + public function processAsset($asset, $basePath, $baseUrl) + { + return $baseUrl . '/' . $asset; + } + + /** * @var array published assets */ private $_published = array(); @@ -154,14 +172,26 @@ class AssetManager extends Component * discussion: http://code.google.com/p/yii/issues/detail?id=2579 * * @param string $path the asset (file or directory) to be published - * @param boolean $forceCopy whether the asset should ALWAYS be copied even if it is found - * in the target directory. This parameter is mainly useful during the development stage - * when the original assets are being constantly changed. The consequence is that the performance - * is degraded, which is not a concern during development, however. + * @param array $options the options to be applied when publishing a directory. + * The following options are supported: + * + * - beforeCopy: callback, a PHP callback that is called before copying each sub-directory or file. + * This option is used only when publishing a directory. If the callback returns false, the copy + * operation for the sub-directory or file will be cancelled. + * The signature of the callback should be: `function ($from, $to)`, where `$from` is the sub-directory or + * file to be copied from, while `$to` is the copy target. + * - afterCopy: callback, a PHP callback that is called after a sub-directory or file is successfully copied. + * This option is used only when publishing a directory. The signature of the callback is similar to that + * of `beforeCopy`. + * - forceCopy: boolean, whether the directory being published should be copied even if + * it is found in the target directory. This option is used only when publishing a directory. + * You may want to set this to be true during the development stage to make sure the published + * directory is always up-to-date. Do not set this to true on production servers as it will + * significantly degrade the performance. * @return array the path (directory or file path) and the URL that the asset is published as. * @throws InvalidParamException if the asset to be published does not exist. */ - public function publish($path, $forceCopy = false) + public function publish($path, $options = array()) { if (isset($this->_published[$path])) { return $this->_published[$path]; @@ -179,15 +209,14 @@ class AssetManager extends Component $dstFile = $dstDir . DIRECTORY_SEPARATOR . $fileName; if (!is_dir($dstDir)) { - @mkdir($dstDir, $this->dirMode, true); + mkdir($dstDir, $this->dirMode, true); } - if ($this->linkAssets) { if (!is_file($dstFile)) { symlink($src, $dstFile); } - } elseif (@filemtime($dstFile) < @filemtime($src) || $forceCopy) { + } elseif (@filemtime($dstFile) < @filemtime($src)) { copy($src, $dstFile); if ($this->fileMode !== null) { @chmod($dstFile, $this->fileMode); @@ -202,11 +231,18 @@ class AssetManager extends Component if (!is_dir($dstDir)) { symlink($src, $dstDir); } - } elseif (!is_dir($dstDir) || $forceCopy) { - FileHelper::copyDirectory($src, $dstDir, array( + } elseif (!is_dir($dstDir) || !empty($options['forceCopy'])) { + $opts = array( 'dirMode' => $this->dirMode, 'fileMode' => $this->fileMode, - )); + ); + if (isset($options['beforeCopy'])) { + $opts['beforeCopy'] = $options['beforeCopy']; + } + if (isset($options['afterCopy'])) { + $opts['afterCopy'] = $options['afterCopy']; + } + FileHelper::copyDirectory($src, $dstDir, $opts); } return $this->_published[$path] = array($dstDir, $this->baseUrl . '/' . $dir); From bab3222907764537687d0ca1cc717e33fb9acd9c Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 18 Apr 2013 15:29:30 -0400 Subject: [PATCH 28/53] Added doc. --- framework/web/AssetBundle.php | 7 ++++++- framework/web/AssetManager.php | 4 ++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/framework/web/AssetBundle.php b/framework/web/AssetBundle.php index 5b04637..76d901f 100644 --- a/framework/web/AssetBundle.php +++ b/framework/web/AssetBundle.php @@ -85,6 +85,11 @@ class AssetBundle extends Object * @var array list of the bundle names that this bundle depends on */ public $depends = array(); + /** + * @var array the options to be passed to [[AssetManager::publish()]] when the asset bundle + * is being published. + */ + public $publishOption = array(); /** * Initializes the bundle. @@ -114,7 +119,7 @@ class AssetBundle extends Object } if ($this->sourcePath !== null) { - list ($this->basePath, $this->baseUrl) = $am->publish($this->sourcePath); + list ($this->basePath, $this->baseUrl) = $am->publish($this->sourcePath, $this->publishOption); } foreach ($this->js as $js => $options) { diff --git a/framework/web/AssetManager.php b/framework/web/AssetManager.php index 9f01173..5375d08 100644 --- a/framework/web/AssetManager.php +++ b/framework/web/AssetManager.php @@ -164,6 +164,10 @@ class AssetManager extends Component * Note, in case $forceCopy is false the method only checks the existence of the target * directory to avoid repetitive copying (which is very expensive). * + * By default, when publishing a directory, subdirectories and files whose name starts with a dot "." + * will NOT be published. If you want to change this behavior, you may specify the "beforeCopy" option + * as explained in the `$options` parameter. + * * Note: On rare scenario, a race condition can develop that will lead to a * one-time-manifestation of a non-critical problem in the creation of the directory * that holds the published assets. This problem can be avoided altogether by 'requesting' From d22e8ea34b589830d896d703396b3912fdf186b7 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 18 Apr 2013 15:46:01 -0400 Subject: [PATCH 29/53] Added YiiBase::importNamespaces(). --- framework/YiiBase.php | 24 ++++++++++++++++++++++++ framework/base/Application.php | 12 ------------ framework/web/AssetBundle.php | 4 ++-- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/framework/YiiBase.php b/framework/YiiBase.php index c32cc28..9d501b1 100644 --- a/framework/YiiBase.php +++ b/framework/YiiBase.php @@ -139,6 +139,30 @@ class YiiBase } /** + * Imports a set of namespaces. + * + * By importing a namespace, the method will create an alias for the directory corresponding + * to the namespace. For example, if "foo\bar" is a namespace associated with the directory + * "path/to/foo/bar", then an alias "@foo/bar" will be created for this directory. + * + * This method is typically invoked in the bootstrap file to import the namespaces of + * the installed extensions. By default, Composer, when installing new extensions, will + * generate such a mapping file which can be loaded and passed to this method. + * + * @param array $namespaces the namespaces to be imported. The keys are the namespaces, + * and the values are the corresponding directories. + */ + public static function importNamespaces($namespaces) + { + foreach ($namespaces as $name => $path) { + if ($name !== '') { + $name = '@' . str_replace('\\', '/', $name); + static::setAlias($name, $path); + } + } + } + + /** * Translates a path alias into an actual path. * * The translation is done according to the following procedure: diff --git a/framework/base/Application.php b/framework/base/Application.php index e0d0237..88dfcb9 100644 --- a/framework/base/Application.php +++ b/framework/base/Application.php @@ -56,11 +56,6 @@ class Application extends Module * If this is false, layout will be disabled. */ public $layout = 'main'; - /** - * @var array list of installed extensions. The array keys are the extension names, and the array - * values are the corresponding extension root source directories or path aliases. - */ - public $extensions = array(); private $_ended = false; @@ -92,13 +87,6 @@ class Application extends Module throw new InvalidConfigException('The "basePath" configuration is required.'); } - if (isset($config['extensions'])) { - foreach ($config['extensions'] as $name => $path) { - Yii::setAlias("@$name", $path); - } - unset($config['extensions']); - } - $this->registerErrorHandlers(); $this->registerCoreComponents(); diff --git a/framework/web/AssetBundle.php b/framework/web/AssetBundle.php index 76d901f..124f5e6 100644 --- a/framework/web/AssetBundle.php +++ b/framework/web/AssetBundle.php @@ -89,7 +89,7 @@ class AssetBundle extends Object * @var array the options to be passed to [[AssetManager::publish()]] when the asset bundle * is being published. */ - public $publishOption = array(); + public $publishOptions = array(); /** * Initializes the bundle. @@ -119,7 +119,7 @@ class AssetBundle extends Object } if ($this->sourcePath !== null) { - list ($this->basePath, $this->baseUrl) = $am->publish($this->sourcePath, $this->publishOption); + list ($this->basePath, $this->baseUrl) = $am->publish($this->sourcePath, $this->publishOptions); } foreach ($this->js as $js => $options) { From 1f47ea9781fd85dc08bcae78da924c6996f12e90 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 18 Apr 2013 15:55:28 -0400 Subject: [PATCH 30/53] Added asset processor concept. --- framework/web/AssetManager.php | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/framework/web/AssetManager.php b/framework/web/AssetManager.php index 5375d08..72dd06b 100644 --- a/framework/web/AssetManager.php +++ b/framework/web/AssetManager.php @@ -26,6 +26,13 @@ class AssetManager extends Component */ public $bundles; /** + * @var array list of asset processors. An asset processor will convert a special type of asset files + * (e.g. LESS, Sass, TypeScript) into JS or CSS files. The array keys are the file extension names + * (e.g. "less", "sass", "ts"), and the array values are the corresponding configuration arrays + * for creating the processor objects. + */ + public $processors; + /** * @return string the root directory storing the published asset files. */ public $basePath = '@wwwroot/assets'; @@ -143,7 +150,15 @@ class AssetManager extends Component */ public function processAsset($asset, $basePath, $baseUrl) { - return $baseUrl . '/' . $asset; + $ext = pathinfo($asset, PATHINFO_EXTENSION); + if (isset($this->processors[$ext])) { + if (is_array($this->processors[$ext])) { + $this->processors[$ext] = Yii::createObject($this->processors[$ext]); + } + return $this->processors[$ext]->process($asset, $basePath, $baseUrl); + } else { + return $baseUrl . '/' . $asset; + } } /** From 990489354d3690193a0ace75d0979e1289ee2a42 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Thu, 18 Apr 2013 23:41:46 +0400 Subject: [PATCH 31/53] corrected exception class --- framework/base/Module.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/base/Module.php b/framework/base/Module.php index ee97614..d99778d 100644 --- a/framework/base/Module.php +++ b/framework/base/Module.php @@ -207,7 +207,7 @@ abstract class Module extends Component * 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 Exception if the directory does not exist. + * @throws InvalidParamException if the directory does not exist. */ public function setBasePath($path) { From 77233cf39edae7ea727b998fd5b4705366273fae Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Fri, 19 Apr 2013 00:38:42 +0400 Subject: [PATCH 32/53] removed dev comment --- framework/base/Application.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/framework/base/Application.php b/framework/base/Application.php index 88dfcb9..027dec8 100644 --- a/framework/base/Application.php +++ b/framework/base/Application.php @@ -60,8 +60,7 @@ class Application extends Module private $_ended = false; /** - * @var string Used to reserve memory for fatal error handler. This memory - * reserve can be removed if it's OK to write to PHP log only in this particular case. + * @var string Used to reserve memory for fatal error handler. */ private $_memoryReserve; From a701697c96e58ce6c15b9e17bd16a8f7ac5d0092 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Fri, 19 Apr 2013 01:58:23 +0400 Subject: [PATCH 33/53] fixed component name --- framework/web/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/web/Application.php b/framework/web/Application.php index 32f6479..90f7292 100644 --- a/framework/web/Application.php +++ b/framework/web/Application.php @@ -103,7 +103,7 @@ class Application extends \yii\base\Application */ public function getAssets() { - return $this->getComponent('user'); + return $this->getComponent('assets'); } /** From 90b2a54f2de2a7967f4b2abeb3c54a17b34fb917 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Fri, 19 Apr 2013 02:30:46 +0400 Subject: [PATCH 34/53] updated default app template --- framework/console/webapp/default/index.php | 8 ++++---- framework/console/webapp/default/protected/config/main.php | 4 ++++ framework/console/webapp/default/protected/views/layouts/main.php | 7 ++++--- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/framework/console/webapp/default/index.php b/framework/console/webapp/default/index.php index 461b364..b84e257 100644 --- a/framework/console/webapp/default/index.php +++ b/framework/console/webapp/default/index.php @@ -1,10 +1,10 @@ run(); \ No newline at end of file diff --git a/framework/console/webapp/default/protected/config/main.php b/framework/console/webapp/default/protected/config/main.php index 1e3f981..795811e 100644 --- a/framework/console/webapp/default/protected/config/main.php +++ b/framework/console/webapp/default/protected/config/main.php @@ -1,5 +1,6 @@ 'webapp', 'name' => 'My Web Application', 'components' => array( @@ -12,5 +13,8 @@ return array( 'password' => '', ), */ + 'cache' => array( + 'class' => 'yii\caching\DummyCache', + ), ), ); \ No newline at end of file diff --git a/framework/console/webapp/default/protected/views/layouts/main.php b/framework/console/webapp/default/protected/views/layouts/main.php index 197b4a2..a4215dc 100644 --- a/framework/console/webapp/default/protected/views/layouts/main.php +++ b/framework/console/webapp/default/protected/views/layouts/main.php @@ -1,11 +1,12 @@ + - + - <?php echo $this->context->pageTitle?> + <?php echo Html::encode($this->page->title)?> -

    context->pageTitle?>

    +

    page->title)?>

    From c32def86184f2a5128bf8224c485f7b75004aaf7 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 18 Apr 2013 18:59:27 -0400 Subject: [PATCH 35/53] turn asset manager into a getter. --- framework/base/ViewContent.php | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/framework/base/ViewContent.php b/framework/base/ViewContent.php index dedd3bb..e5a1a06 100644 --- a/framework/base/ViewContent.php +++ b/framework/base/ViewContent.php @@ -24,11 +24,6 @@ class ViewContent extends Component const TOKEN_BODY_BEGIN = ''; const TOKEN_BODY_END = ''; - /** - * @var \yii\web\AssetManager - */ - public $assetManager; - public $assetBundles; public $title; public $metaTags; @@ -42,14 +37,6 @@ class ViewContent extends Component public $jsInBody; public $jsFilesInBody; - public function init() - { - parent::init(); - if ($this->assetManager === null) { - $this->assetManager = Yii::$app->getAssets(); - } - } - public function reset() { $this->title = null; @@ -64,6 +51,21 @@ class ViewContent extends Component $this->jsInBody = null; $this->jsFilesInBody = null; } + + private $_assetManager; + + /** + * @return \yii\web\AssetManager + */ + public function getAssetManager() + { + return $this->_assetManager ?: Yii::$app->getAssets(); + } + + public function setAssetManager($value) + { + $this->_assetManager = $value; + } public function begin() { @@ -95,10 +97,11 @@ class ViewContent extends Component public function registerAssetBundle($name) { if (!isset($this->assetBundles[$name])) { - $bundle = $this->assetManager->getBundle($name); + $am = $this->getAssetManager(); + $bundle = $am->getBundle($name); if ($bundle !== null) { $this->assetBundles[$name] = false; - $bundle->registerAssets($this, $this->assetManager); + $bundle->registerAssets($this, $am); $this->assetBundles[$name] = true; } else { throw new InvalidConfigException("Unknown asset bundle: $name"); From 2a12fdbcdf35f5cbc678d0d9beddae6f382203db Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Fri, 19 Apr 2013 03:51:08 +0400 Subject: [PATCH 36/53] safer exception rendering --- framework/base/ErrorHandler.php | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/framework/base/ErrorHandler.php b/framework/base/ErrorHandler.php index dc83474..91d5982 100644 --- a/framework/base/ErrorHandler.php +++ b/framework/base/ErrorHandler.php @@ -81,15 +81,20 @@ class ErrorHandler extends Component if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') { \Yii::$app->renderException($exception); } else { - $view = new View; - if (!YII_DEBUG || $exception instanceof UserException) { - $viewName = $this->errorView; - } else { - $viewName = $this->exceptionView; + try { + $view = new View; + if (!YII_DEBUG || $exception instanceof UserException) { + $viewName = $this->errorView; + } else { + $viewName = $this->exceptionView; + } + echo $view->renderFile($viewName, array( + 'exception' => $exception, + ), $this); + } + catch (\Exception $e) { + \Yii::$app->renderException($e); } - echo $view->renderFile($viewName, array( - 'exception' => $exception, - ), $this); } } else { \Yii::$app->renderException($exception); From 767a78f8b8925563cd5c11e115813e84c7387f2a Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Fri, 19 Apr 2013 04:12:30 +0400 Subject: [PATCH 37/53] better handling of errors during rendering an error --- framework/base/Application.php | 2 +- framework/base/ErrorHandler.php | 27 +++++++++++++++------------ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/framework/base/Application.php b/framework/base/Application.php index 027dec8..c498a8e 100644 --- a/framework/base/Application.php +++ b/framework/base/Application.php @@ -411,7 +411,7 @@ class Application extends Module error_log($exception); if (($handler = $this->getErrorHandler()) !== null) { - @$handler->handle($exception); + $handler->handle($exception); } else { $this->renderException($exception); } diff --git a/framework/base/ErrorHandler.php b/framework/base/ErrorHandler.php index 91d5982..f9818ac 100644 --- a/framework/base/ErrorHandler.php +++ b/framework/base/ErrorHandler.php @@ -81,19 +81,22 @@ class ErrorHandler extends Component if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') { \Yii::$app->renderException($exception); } else { - try { - $view = new View; - if (!YII_DEBUG || $exception instanceof UserException) { - $viewName = $this->errorView; - } else { - $viewName = $this->exceptionView; - } - echo $view->renderFile($viewName, array( - 'exception' => $exception, - ), $this); + if(YII_DEBUG) { + ini_set('display_errors', 1); } - catch (\Exception $e) { - \Yii::$app->renderException($e); + + $view = new View; + if (!YII_DEBUG || $exception instanceof UserException) { + $viewName = $this->errorView; + } else { + $viewName = $this->exceptionView; + } + echo $view->renderFile($viewName, array( + 'exception' => $exception, + ), $this); + + if(YII_DEBUG) { + ini_set('display_errors', 0); } } } else { From 0a41b006ee319b3c3c27eef469faf94371dbe913 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Fri, 19 Apr 2013 04:22:36 +0400 Subject: [PATCH 38/53] removed unnecessary code, added comment about displaying errors --- framework/base/ErrorHandler.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/framework/base/ErrorHandler.php b/framework/base/ErrorHandler.php index f9818ac..98a061d 100644 --- a/framework/base/ErrorHandler.php +++ b/framework/base/ErrorHandler.php @@ -81,6 +81,8 @@ class ErrorHandler extends Component if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') { \Yii::$app->renderException($exception); } else { + // if there is an error during error rendering it's useful to + // display PHP error in debug mode instead of a blank screen if(YII_DEBUG) { ini_set('display_errors', 1); } @@ -94,10 +96,6 @@ class ErrorHandler extends Component echo $view->renderFile($viewName, array( 'exception' => $exception, ), $this); - - if(YII_DEBUG) { - ini_set('display_errors', 0); - } } } else { \Yii::$app->renderException($exception); From 30e6d28b7746992b651a21111d452ee084a372d0 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 18 Apr 2013 23:22:59 -0400 Subject: [PATCH 39/53] Finished asset converter. --- framework/base/ViewContent.php | 1 + framework/web/AssetBundle.php | 6 ++++-- framework/web/AssetConverter.php | 45 +++++++++++++++++++++++++++++++++++++++ framework/web/AssetManager.php | 39 ++++++++++++++------------------- framework/web/IAssetConverter.php | 17 +++++++++++++++ 5 files changed, 83 insertions(+), 25 deletions(-) create mode 100644 framework/web/AssetConverter.php create mode 100644 framework/web/IAssetConverter.php diff --git a/framework/base/ViewContent.php b/framework/base/ViewContent.php index e5a1a06..c7a3bc4 100644 --- a/framework/base/ViewContent.php +++ b/framework/base/ViewContent.php @@ -39,6 +39,7 @@ class ViewContent extends Component public function reset() { + $this->assetBundles = null; $this->title = null; $this->metaTags = null; $this->linkTags = null; diff --git a/framework/web/AssetBundle.php b/framework/web/AssetBundle.php index 124f5e6..f82173b 100644 --- a/framework/web/AssetBundle.php +++ b/framework/web/AssetBundle.php @@ -122,11 +122,13 @@ class AssetBundle extends Object list ($this->basePath, $this->baseUrl) = $am->publish($this->sourcePath, $this->publishOptions); } + $converter = $am->getConverter(); + foreach ($this->js as $js => $options) { $js = is_string($options) ? $options : $js; if (strpos($js, '/') !== 0 && strpos($js, '://') === false) { if (isset($this->basePath, $this->baseUrl)) { - $js = $am->processAsset(ltrim($js, '/'), $this->basePath, $this->baseUrl); + $js = $converter->convert(ltrim($js, '/'), $this->basePath, $this->baseUrl); } else { throw new InvalidConfigException('Both of the "baseUrl" and "basePath" properties must be set.'); } @@ -137,7 +139,7 @@ class AssetBundle extends Object $css = is_string($options) ? $options : $css; if (strpos($css, '//') !== 0 && strpos($css, '://') === false) { if (isset($this->basePath, $this->baseUrl)) { - $css = $am->processAsset(ltrim($css, '/'), $this->basePath, $this->baseUrl); + $css = $converter->convert(ltrim($css, '/'), $this->basePath, $this->baseUrl); } else { throw new InvalidConfigException('Both of the "baseUrl" and "basePath" properties must be set.'); } diff --git a/framework/web/AssetConverter.php b/framework/web/AssetConverter.php new file mode 100644 index 0000000..26cc59b --- /dev/null +++ b/framework/web/AssetConverter.php @@ -0,0 +1,45 @@ + + * @since 2.0 + */ +class AssetConverter extends Component implements IAssetConverter +{ + public $commands = array( + 'less' => array('css', 'lessc %s %s'), + 'scss' => array('css', 'sass %s %s'), + 'sass' => array('css', 'sass %s %s'), + 'styl' => array('js', 'stylus < %s > %s'), + ); + + public function convert($asset, $basePath, $baseUrl) + { + $pos = strrpos($asset, '.'); + if ($pos !== false) { + $ext = substr($asset, $pos + 1); + if (isset($this->commands[$ext])) { + list ($ext, $command) = $this->commands[$ext]; + $result = substr($asset, 0, $pos + 1) . $ext; + if (@filemtime("$basePath/$result") < filemtime("$basePath/$asset")) { + $output = array(); + $command = sprintf($command, "$basePath/$asset", "$basePath/$result"); + exec($command, $output); + Yii::info("Converted $asset into $result: " . implode("\n", $output), __METHOD__); + return "$baseUrl/$result"; + } + } + } + return "$baseUrl/$asset"; + } +} \ No newline at end of file diff --git a/framework/web/AssetManager.php b/framework/web/AssetManager.php index 72dd06b..c4ad385 100644 --- a/framework/web/AssetManager.php +++ b/framework/web/AssetManager.php @@ -26,13 +26,6 @@ class AssetManager extends Component */ public $bundles; /** - * @var array list of asset processors. An asset processor will convert a special type of asset files - * (e.g. LESS, Sass, TypeScript) into JS or CSS files. The array keys are the file extension names - * (e.g. "less", "sass", "ts"), and the array values are the corresponding configuration arrays - * for creating the processor objects. - */ - public $processors; - /** * @return string the root directory storing the published asset files. */ public $basePath = '@wwwroot/assets'; @@ -139,26 +132,26 @@ class AssetManager extends Component return $this->bundles[$name]; } + private $_converter; + /** - * Processes the given asset file and returns a URL to the processed one. - * This method can be overwritten to support various types of asset files, such as LESS, Sass, TypeScript. - * @param string $asset the asset file path to be processed. The file path is relative - * to $basePath, and it may contain forward slashes to indicate sub-directories (e.g. "js/main.js"). - * @param string $basePath the directory that contains the asset file. - * @param string $baseUrl the corresponding URL of $basePath. - * @return string the processed asset file path. + * @return IAssetConverter */ - public function processAsset($asset, $basePath, $baseUrl) + public function getConverter() { - $ext = pathinfo($asset, PATHINFO_EXTENSION); - if (isset($this->processors[$ext])) { - if (is_array($this->processors[$ext])) { - $this->processors[$ext] = Yii::createObject($this->processors[$ext]); - } - return $this->processors[$ext]->process($asset, $basePath, $baseUrl); - } else { - return $baseUrl . '/' . $asset; + if ($this->_converter === null) { + $this->_converter = Yii::createObject(array( + 'class' => 'yii\\web\\AssetConverter', + )); + } elseif (is_array($this->_converter) || is_string($this->_converter)) { + $this->_converter = Yii::createObject($this->_converter); } + return $this->_converter; + } + + public function setConverter($value) + { + $this->_converter = $value; } /** diff --git a/framework/web/IAssetConverter.php b/framework/web/IAssetConverter.php new file mode 100644 index 0000000..994cb2f --- /dev/null +++ b/framework/web/IAssetConverter.php @@ -0,0 +1,17 @@ + + * @since 2.0 + */ +interface IAssetConverter +{ + public function convert($asset, $basePath, $baseUrl); +} \ No newline at end of file From 41ea94853bb73f67f1bbf16a855c3fb3f14fbc1a Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Fri, 19 Apr 2013 15:19:29 -0400 Subject: [PATCH 40/53] refactoring and documentation for asset/script management. --- framework/base/View.php | 366 +++++++++++++++++++++++++++++++++++++++-- framework/base/ViewContent.php | 235 -------------------------- framework/web/Application.php | 6 +- framework/web/AssetBundle.php | 38 +++-- framework/web/AssetManager.php | 10 +- 5 files changed, 388 insertions(+), 267 deletions(-) delete mode 100644 framework/base/ViewContent.php diff --git a/framework/base/View.php b/framework/base/View.php index a794e08..b791743 100644 --- a/framework/base/View.php +++ b/framework/base/View.php @@ -10,6 +10,7 @@ namespace yii\base; use Yii; use yii\base\Application; use yii\helpers\FileHelper; +use yii\helpers\Html; /** * View represents a view object in the MVC pattern. @@ -22,22 +23,48 @@ use yii\helpers\FileHelper; class View extends Component { /** - * @event Event an event that is triggered by [[renderFile()]] right before it renders a view file. + * @event ViewEvent an event that is triggered by [[renderFile()]] right before it renders a view file. */ const EVENT_BEFORE_RENDER = 'beforeRender'; /** - * @event Event an event that is triggered by [[renderFile()]] right after it renders a view file. + * @event ViewEvent an event that is triggered by [[renderFile()]] right after it renders a view file. */ const EVENT_AFTER_RENDER = 'afterRender'; /** - * @var object the object that owns this view. This can be a controller, a widget, or any other object. + * The location of registered JavaScript code block or files. + * This means the location is in the head section. */ - public $context; + const POS_HEAD = 1; + /** + * The location of registered JavaScript code block or files. + * This means the location is at the beginning of the body section. + */ + const POS_BEGIN = 2; + /** + * The location of registered JavaScript code block or files. + * This means the location is at the end of the body section. + */ + const POS_END = 3; + /** + * This is internally used as the placeholder for receiving the content registered for the head section. + */ + const PL_HEAD = ''; + /** + * This is internally used as the placeholder for receiving the content registered for the beginning of the body section. + */ + const PL_BODY_BEGIN = ''; /** - * @var ViewContent + * This is internally used as the placeholder for receiving the content registered for the end of the body section. */ - public $page; + const PL_BODY_END = ''; + + + /** + * @var object the context under which the [[renderFile()]] method is being invoked. + * This can be a controller, a widget, or any other object. + */ + public $context; /** * @var mixed custom parameters that are shared among view templates. */ @@ -48,32 +75,75 @@ class View extends Component */ public $renderer; /** - * @var Theme|array the theme object or the configuration array for creating the theme. + * @var Theme|array the theme object or the configuration array for creating the theme object. * If not set, it means theming is not enabled. */ public $theme; /** * @var array a list of named output blocks. The keys are the block names and the values * are the corresponding block content. You can call [[beginBlock()]] and [[endBlock()]] - * to capture small fragments of a view. They can be later accessed at somewhere else + * to capture small fragments of a view. They can be later accessed somewhere else * through this property. */ public $blocks; /** * @var Widget[] the widgets that are currently being rendered (not ended). This property - * is maintained by [[beginWidget()]] and [[endWidget()]] methods. Do not modify it. + * is maintained by [[beginWidget()]] and [[endWidget()]] methods. Do not modify it directly. + * @internal */ public $widgetStack = array(); /** * @var array a list of currently active fragment cache widgets. This property - * is used internally to implement the content caching feature. Do not modify it. + * is used internally to implement the content caching feature. Do not modify it directly. + * @internal */ public $cacheStack = array(); /** * @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. + * is used internally to implement the content caching feature. Do not modify it directly. + * @internal */ public $dynamicPlaceholders = array(); + /** + * @var array the registered asset bundles. The keys are the bundle names, and the values + * are the corresponding [[AssetBundle]] objects. + * @see registerAssetBundle + */ + public $assetBundles; + /** + * @var string the page title + */ + public $title; + /** + * @var array the registered meta tags. + * @see registerMetaTag + */ + public $metaTags; + /** + * @var array the registered link tags. + * @see registerLinkTag + */ + public $linkTags; + /** + * @var array the registered CSS code blocks. + * @see registerCss + */ + public $css; + /** + * @var array the registered CSS files. + * @see registerCssFile + */ + public $cssFiles; + /** + * @var array the registered JS code blocks + * @see registerJs + */ + public $js; + /** + * @var array the registered JS files. + * @see registerJsFile + */ + public $jsFiles; /** @@ -88,11 +158,6 @@ class View extends Component if (is_array($this->theme)) { $this->theme = Yii::createObject($this->theme); } - if (is_array($this->page)) { - $this->page = Yii::createObject($this->page); - } else { - $this->page = new ViewContent; - } } /** @@ -445,4 +510,273 @@ class View extends Component { $this->endWidget(); } + + + private $_assetManager; + + /** + * Registers the asset manager being used by this view object. + * @return \yii\web\AssetManager the asset manager. Defaults to the "assetManager" application component. + */ + public function getAssetManager() + { + return $this->_assetManager ?: Yii::$app->getAssetManager(); + } + + /** + * Sets the asset manager. + * @param \yii\web\AssetManager $value the asset manager + */ + public function setAssetManager($value) + { + $this->_assetManager = $value; + } + + /** + * Marks the beginning of an HTML page. + */ + public function beginPage() + { + ob_start(); + ob_implicit_flush(false); + } + + /** + * Marks the ending of an HTML page. + */ + public function endPage() + { + $content = ob_get_clean(); + echo strtr($content, array( + self::PL_HEAD => $this->renderHeadHtml(), + self::PL_BODY_BEGIN => $this->renderBodyBeginHtml(), + self::PL_BODY_END => $this->renderBodyEndHtml(), + )); + + unset( + $this->assetBundles, + $this->metaTags, + $this->linkTags, + $this->css, + $this->cssFiles, + $this->js, + $this->jsFiles + ); + } + + /** + * Marks the beginning of an HTML body section. + */ + public function beginBody() + { + echo self::PL_BODY_BEGIN; + } + + /** + * Marks the ending of an HTML body section. + */ + public function endBody() + { + echo self::PL_BODY_END; + } + + /** + * Marks the position of an HTML head section. + */ + public function head() + { + echo self::PL_HEAD; + } + + /** + * Registers the named asset bundle. + * All dependent asset bundles will be registered. + * @param string $name the name of the asset bundle. + * @throws InvalidConfigException if the asset bundle does not exist or a cyclic dependency is detected + */ + public function registerAssetBundle($name) + { + if (!isset($this->assetBundles[$name])) { + $am = $this->getAssetManager(); + $bundle = $am->getBundle($name); + if ($bundle !== null) { + $this->assetBundles[$name] = false; + $bundle->registerAssets($this); + $this->assetBundles[$name] = true; + } else { + throw new InvalidConfigException("Unknown asset bundle: $name"); + } + } elseif ($this->assetBundles[$name] === false) { + throw new InvalidConfigException("A cyclic dependency is detected for bundle '$name'."); + } + } + + /** + * Registers a meta tag. + * @param array $options the HTML attributes for the meta tag. + * @param string $key the key that identifies the meta tag. If two meta tags are registered + * with the same key, the latter will overwrite the former. If this is null, the new meta tag + * will be appended to the existing ones. + */ + public function registerMetaTag($options, $key = null) + { + if ($key === null) { + $this->metaTags[] = Html::tag('meta', '', $options); + } else { + $this->metaTags[$key] = Html::tag('meta', '', $options); + } + } + + /** + * Registers a link tag. + * @param array $options the HTML attributes for the link tag. + * @param string $key the key that identifies the link tag. If two link tags are registered + * with the same key, the latter will overwrite the former. If this is null, the new link tag + * will be appended to the existing ones. + */ + public function registerLinkTag($options, $key = null) + { + if ($key === null) { + $this->linkTags[] = Html::tag('link', '', $options); + } else { + $this->linkTags[$key] = Html::tag('link', '', $options); + } + } + + /** + * Registers a CSS code block. + * @param string $css the CSS code block to be registered + * @param array $options the HTML attributes for the style tag. + * @param string $key the key that identifies the CSS code block. If null, it will use + * $css as the key. If two CSS code blocks are registered with the same key, the latter + * will overwrite the former. + */ + public function registerCss($css, $options = array(), $key = null) + { + $key = $key ?: $css; + $this->css[$key] = Html::style($css, $options); + } + + /** + * Registers a CSS file. + * @param string $url the CSS file to be registered. + * @param array $options the HTML attributes for the link tag. + * @param string $key the key that identifies the CSS script file. If null, it will use + * $url as the key. If two CSS files are registered with the same key, the latter + * will overwrite the former. + */ + public function registerCssFile($url, $options = array(), $key = null) + { + $key = $key ?: $url; + $this->cssFiles[$key] = Html::cssFile($url, $options); + } + + /** + * Registers a JS code block. + * @param string $js the JS code block to be registered + * @param array $options the HTML attributes for the script tag. A special option + * named "position" is supported which specifies where the JS script tag should be inserted + * in a page. The possible values of "position" are: + * + * - [[POS_HEAD]]: in the head section + * - [[POS_BEGIN]]: at the beginning of the body section + * - [[POS_END]]: at the end of the body section + * + * @param string $key the key that identifies the JS code block. If null, it will use + * $js as the key. If two JS code blocks are registered with the same key, the latter + * will overwrite the former. + */ + public function registerJs($js, $options = array(), $key = null) + { + $position = isset($options['position']) ? $options['position'] : self::POS_END; + unset($options['position']); + $key = $key ?: $js; + $this->js[$position][$key] = Html::script($js, $options); + } + + /** + * Registers a JS file. + * @param string $url the JS file to be registered. + * @param array $options the HTML attributes for the script tag. A special option + * named "position" is supported which specifies where the JS script tag should be inserted + * in a page. The possible values of "position" are: + * + * - [[POS_HEAD]]: in the head section + * - [[POS_BEGIN]]: at the beginning of the body section + * - [[POS_END]]: at the end of the body section + * + * @param string $key the key that identifies the JS script file. If null, it will use + * $url as the key. If two JS files are registered with the same key, the latter + * will overwrite the former. + */ + public function registerJsFile($url, $options = array(), $key = null) + { + $position = isset($options['position']) ? $options['position'] : self::POS_END; + unset($options['position']); + $key = $key ?: $url; + $this->jsFiles[$position][$key] = Html::jsFile($url, $options); + } + + /** + * Renders the content to be inserted in the head section. + * The content is rendered using the registered meta tags, link tags, CSS/JS code blocks and files. + * @return string the rendered content + */ + protected function renderHeadHtml() + { + $lines = array(); + if (!empty($this->metaTags)) { + $lines[] = implode("\n", $this->cssFiles); + } + if (!empty($this->linkTags)) { + $lines[] = implode("\n", $this->cssFiles); + } + if (!empty($this->cssFiles)) { + $lines[] = implode("\n", $this->cssFiles); + } + if (!empty($this->css)) { + $lines[] = implode("\n", $this->css); + } + if (!empty($this->jsFiles[self::POS_HEAD])) { + $lines[] = implode("\n", $this->jsFiles[self::POS_HEAD]); + } + if (!empty($this->js[self::POS_HEAD])) { + $lines[] = implode("\n", $this->js[self::POS_HEAD]); + } + return implode("\n", $lines); + } + + /** + * Renders the content to be inserted at the beginning of the body section. + * The content is rendered using the registered JS code blocks and files. + * @return string the rendered content + */ + protected function renderBodyBeginHtml() + { + $lines = array(); + if (!empty($this->jsFiles[self::POS_BEGIN])) { + $lines[] = implode("\n", $this->jsFiles[self::POS_BEGIN]); + } + if (!empty($this->js[self::POS_BEGIN])) { + $lines[] = implode("\n", $this->js[self::POS_BEGIN]); + } + return implode("\n", $lines); + } + + /** + * Renders the content to be inserted at the end of the body section. + * The content is rendered using the registered JS code blocks and files. + * @return string the rendered content + */ + protected function renderBodyEndHtml() + { + $lines = array(); + if (!empty($this->jsFiles[self::POS_END])) { + $lines[] = implode("\n", $this->jsFiles[self::POS_END]); + } + if (!empty($this->js[self::POS_END])) { + $lines[] = implode("\n", $this->js[self::POS_END]); + } + return implode("\n", $lines); + } } \ No newline at end of file diff --git a/framework/base/ViewContent.php b/framework/base/ViewContent.php deleted file mode 100644 index c7a3bc4..0000000 --- a/framework/base/ViewContent.php +++ /dev/null @@ -1,235 +0,0 @@ - - * @since 2.0 - */ -class ViewContent extends Component -{ - const POS_HEAD = 1; - const POS_BEGIN = 2; - const POS_END = 3; - - const TOKEN_HEAD = ''; - const TOKEN_BODY_BEGIN = ''; - const TOKEN_BODY_END = ''; - - public $assetBundles; - public $title; - public $metaTags; - public $linkTags; - public $css; - public $cssFiles; - public $js; - public $jsFiles; - public $jsInHead; - public $jsFilesInHead; - public $jsInBody; - public $jsFilesInBody; - - public function reset() - { - $this->assetBundles = null; - $this->title = null; - $this->metaTags = null; - $this->linkTags = null; - $this->css = null; - $this->cssFiles = null; - $this->js = null; - $this->jsFiles = null; - $this->jsInHead = null; - $this->jsFilesInHead = null; - $this->jsInBody = null; - $this->jsFilesInBody = null; - } - - private $_assetManager; - - /** - * @return \yii\web\AssetManager - */ - public function getAssetManager() - { - return $this->_assetManager ?: Yii::$app->getAssets(); - } - - public function setAssetManager($value) - { - $this->_assetManager = $value; - } - - public function begin() - { - ob_start(); - ob_implicit_flush(false); - } - - public function end() - { - $content = ob_get_clean(); - echo $this->populate($content); - } - - public function beginBody() - { - echo self::TOKEN_BODY_BEGIN; - } - - public function endBody() - { - echo self::TOKEN_BODY_END; - } - - public function head() - { - echo self::TOKEN_HEAD; - } - - public function registerAssetBundle($name) - { - if (!isset($this->assetBundles[$name])) { - $am = $this->getAssetManager(); - $bundle = $am->getBundle($name); - if ($bundle !== null) { - $this->assetBundles[$name] = false; - $bundle->registerAssets($this, $am); - $this->assetBundles[$name] = true; - } else { - throw new InvalidConfigException("Unknown asset bundle: $name"); - } - } elseif ($this->assetBundles[$name] === false) { - throw new InvalidConfigException("A cyclic dependency is detected for bundle '$name'."); - } - } - - public function registerMetaTag($options, $key = null) - { - if ($key === null) { - $this->metaTags[] = Html::tag('meta', '', $options); - } else { - $this->metaTags[$key] = Html::tag('meta', '', $options); - } - } - - public function registerLinkTag($options, $key = null) - { - if ($key === null) { - $this->linkTags[] = Html::tag('link', '', $options); - } else { - $this->linkTags[$key] = Html::tag('link', '', $options); - } - } - - public function registerCss($css, $options = array(), $key = null) - { - $key = $key ?: $css; - $this->css[$key] = Html::style($css, $options); - } - - public function registerCssFile($url, $options = array(), $key = null) - { - $key = $key ?: $url; - $this->cssFiles[$key] = Html::cssFile($url, $options); - } - - public function registerJs($js, $options = array(), $key = null) - { - $position = isset($options['position']) ? $options['position'] : self::POS_END; - unset($options['position']); - $key = $key ?: $js; - $html = Html::script($js, $options); - if ($position == self::POS_END) { - $this->js[$key] = $html; - } elseif ($position == self::POS_HEAD) { - $this->jsInHead[$key] = $html; - } elseif ($position == self::POS_BEGIN) { - $this->jsInBody[$key] = $html; - } else { - throw new InvalidParamException("Unknown position: $position"); - } - } - - public function registerJsFile($url, $options = array(), $key = null) - { - $position = isset($options['position']) ? $options['position'] : self::POS_END; - unset($options['position']); - $key = $key ?: $url; - $html = Html::jsFile($url, $options); - if ($position == self::POS_END) { - $this->jsFiles[$key] = $html; - } elseif ($position == self::POS_HEAD) { - $this->jsFilesInHead[$key] = $html; - } elseif ($position == self::POS_BEGIN) { - $this->jsFilesInBody[$key] = $html; - } else { - throw new InvalidParamException("Unknown position: $position"); - } - } - - protected function populate($content) - { - return strtr($content, array( - self::TOKEN_HEAD => $this->getHeadHtml(), - self::TOKEN_BODY_BEGIN => $this->getBodyBeginHtml(), - self::TOKEN_BODY_END => $this->getBodyEndHtml(), - )); - } - - protected function getHeadHtml() - { - $lines = array(); - if (!empty($this->metaTags)) { - $lines[] = implode("\n", $this->cssFiles); - } - if (!empty($this->linkTags)) { - $lines[] = implode("\n", $this->cssFiles); - } - if (!empty($this->cssFiles)) { - $lines[] = implode("\n", $this->cssFiles); - } - if (!empty($this->css)) { - $lines[] = implode("\n", $this->css); - } - if (!empty($this->jsFilesInHead)) { - $lines[] = implode("\n", $this->jsFilesInHead); - } - if (!empty($this->jsInHead)) { - $lines[] = implode("\n", $this->jsInHead); - } - return implode("\n", $lines); - } - - protected function getBodyBeginHtml() - { - $lines = array(); - if (!empty($this->jsFilesInBody)) { - $lines[] = implode("\n", $this->jsFilesInBody); - } - if (!empty($this->jsInHead)) { - $lines[] = implode("\n", $this->jsInBody); - } - return implode("\n", $lines); - } - - protected function getBodyEndHtml() - { - $lines = array(); - if (!empty($this->jsFiles)) { - $lines[] = implode("\n", $this->jsFiles); - } - if (!empty($this->js)) { - $lines[] = implode("\n", $this->js); - } - return implode("\n", $lines); - } -} \ No newline at end of file diff --git a/framework/web/Application.php b/framework/web/Application.php index 90f7292..3387044 100644 --- a/framework/web/Application.php +++ b/framework/web/Application.php @@ -101,9 +101,9 @@ class Application extends \yii\base\Application * Returns the asset manager. * @return AssetManager the asset manager component */ - public function getAssets() + public function getAssetManager() { - return $this->getComponent('assets'); + return $this->getComponent('assetManager'); } /** @@ -126,7 +126,7 @@ class Application extends \yii\base\Application 'user' => array( 'class' => 'yii\web\User', ), - 'assets' => array( + 'assetManager' => array( 'class' => 'yii\web\AssetManager', ), )); diff --git a/framework/web/AssetBundle.php b/framework/web/AssetBundle.php index f82173b..4108b07 100644 --- a/framework/web/AssetBundle.php +++ b/framework/web/AssetBundle.php @@ -12,6 +12,14 @@ use yii\base\InvalidConfigException; use yii\base\Object; /** + * AssetBundle represents a collection of asset files, such as CSS, JS, images. + * + * Each asset bundle has a unique name that globally identifies it among all asset bundles + * used in an application. + * + * An asset bundle can depend on other asset bundles. When registering an asset bundle + * with a view, all its dependent asset bundles will be automatically registered. + * * @author Qiang Xue * @since 2.0 */ @@ -66,7 +74,7 @@ class AssetBundle extends Object * * Each JavaScript file may be associated with options. In this case, the array key * should be the JavaScript file path, while the corresponding array value should - * be the option array. The options will be passed to [[ViewContent::registerJsFile()]]. + * be the option array. The options will be passed to [[View::registerJsFile()]]. */ public $js = array(); /** @@ -78,7 +86,7 @@ class AssetBundle extends Object * * Each CSS file may be associated with options. In this case, the array key * should be the CSS file path, while the corresponding array value should - * be the option array. The options will be passed to [[ViewContent::registerCssFile()]]. + * be the option array. The options will be passed to [[View::registerCssFile()]]. */ public $css = array(); /** @@ -108,14 +116,20 @@ class AssetBundle extends Object } /** - * @param \yii\base\ViewContent $page - * @param AssetManager $am - * @throws InvalidConfigException + * Registers the CSS and JS files with the given view. + * This method will first register all dependent asset bundles. + * It will then try to convert non-CSS or JS files (e.g. LESS, Sass) into the corresponding + * CSS or JS files using [[AssetManager::converter|asset converter]]. + * @param \yii\base\View $view the view that the asset files to be registered with. + * @throws InvalidConfigException if [[baseUrl]] or [[basePath]] is not set when the bundle + * contains internal CSS or JS files. */ - public function registerAssets($page, $am) + public function registerAssets($view) { + $am = $view->getAssetManager(); + foreach ($this->depends as $name) { - $page->registerAssetBundle($name); + $view->registerAssetBundle($name); } if ($this->sourcePath !== null) { @@ -128,23 +142,23 @@ class AssetBundle extends Object $js = is_string($options) ? $options : $js; if (strpos($js, '/') !== 0 && strpos($js, '://') === false) { if (isset($this->basePath, $this->baseUrl)) { - $js = $converter->convert(ltrim($js, '/'), $this->basePath, $this->baseUrl); + $js = $converter->convert($js, $this->basePath, $this->baseUrl); } else { throw new InvalidConfigException('Both of the "baseUrl" and "basePath" properties must be set.'); } } - $page->registerJsFile($js, is_array($options) ? $options : array()); + $view->registerJsFile($js, is_array($options) ? $options : array()); } foreach ($this->css as $css => $options) { $css = is_string($options) ? $options : $css; - if (strpos($css, '//') !== 0 && strpos($css, '://') === false) { + if (strpos($css, '/') !== 0 && strpos($css, '://') === false) { if (isset($this->basePath, $this->baseUrl)) { - $css = $converter->convert(ltrim($css, '/'), $this->basePath, $this->baseUrl); + $css = $converter->convert($css, $this->basePath, $this->baseUrl); } else { throw new InvalidConfigException('Both of the "baseUrl" and "basePath" properties must be set.'); } } - $page->registerCssFile($css, is_array($options) ? $options : array()); + $view->registerCssFile($css, is_array($options) ? $options : array()); } } } \ No newline at end of file diff --git a/framework/web/AssetManager.php b/framework/web/AssetManager.php index c4ad385..95dcbd2 100644 --- a/framework/web/AssetManager.php +++ b/framework/web/AssetManager.php @@ -14,6 +14,7 @@ use yii\base\InvalidParamException; use yii\helpers\FileHelper; /** + * AssetManager manages asset bundles and asset publishing. * * @author Qiang Xue * @since 2.0 @@ -135,7 +136,8 @@ class AssetManager extends Component private $_converter; /** - * @return IAssetConverter + * Returns the asset converter. + * @return IAssetConverter the asset converter. */ public function getConverter() { @@ -149,6 +151,12 @@ class AssetManager extends Component return $this->_converter; } + /** + * Sets the asset converter. + * @param array|IAssetConverter $value the asset converter. This can be either + * an object implementing the [[IAssetConverter]] interface, or a configuration + * array that can be used to create the asset converter object. + */ public function setConverter($value) { $this->_converter = $value; From c7cf1026c0dfbea2eda74c24633f7777220729c9 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 20 Apr 2013 01:40:37 +0400 Subject: [PATCH 41/53] adjusted default app layout --- framework/console/webapp/default/protected/views/layouts/main.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/console/webapp/default/protected/views/layouts/main.php b/framework/console/webapp/default/protected/views/layouts/main.php index a4215dc..5c883e6 100644 --- a/framework/console/webapp/default/protected/views/layouts/main.php +++ b/framework/console/webapp/default/protected/views/layouts/main.php @@ -3,10 +3,10 @@ - <?php echo Html::encode($this->page->title)?> + <?php echo Html::encode($this->title)?> -

    page->title)?>

    +

    title)?>

    From 304122ebe4e4ffaba958ec6b27012724cb3b1232 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Fri, 19 Apr 2013 20:44:59 -0400 Subject: [PATCH 42/53] Fixed bug in yiic.php. Refactoring AssetConverter. --- framework/console/Application.php | 1 + framework/console/controllers/ScriptController.php | 26 +++++++++++++++++++++ framework/web/AssetConverter.php | 27 ++++++++++++++++++---- framework/web/IAssetConverter.php | 10 ++++++++ framework/yiic.php | 7 +++--- 5 files changed, 62 insertions(+), 9 deletions(-) create mode 100644 framework/console/controllers/ScriptController.php diff --git a/framework/console/Application.php b/framework/console/Application.php index 574495b..e185cc5 100644 --- a/framework/console/Application.php +++ b/framework/console/Application.php @@ -129,6 +129,7 @@ class Application extends \yii\base\Application 'migrate' => 'yii\console\controllers\MigrateController', 'app' => 'yii\console\controllers\AppController', 'cache' => 'yii\console\controllers\CacheController', + 'script' => 'yii\console\controllers\ScriptController', ); } diff --git a/framework/console/controllers/ScriptController.php b/framework/console/controllers/ScriptController.php new file mode 100644 index 0000000..7ca498b --- /dev/null +++ b/framework/console/controllers/ScriptController.php @@ -0,0 +1,26 @@ + + * @since 2.0 + */ +class ScriptController extends Controller +{ + public $defaultAction = 'combo'; + + public function actionCombo($configFile) + { + + } +} \ No newline at end of file diff --git a/framework/web/AssetConverter.php b/framework/web/AssetConverter.php index 26cc59b..8340be5 100644 --- a/framework/web/AssetConverter.php +++ b/framework/web/AssetConverter.php @@ -11,18 +11,32 @@ use Yii; use yii\base\Component; /** + * AssetConverter supports conversion of several popular script formats into JS or CSS scripts. + * * @author Qiang Xue * @since 2.0 */ class AssetConverter extends Component implements IAssetConverter { + /** + * @var array the commands that are used to perform the asset conversion. + * The keys are the asset file extension names, and the values are the corresponding + * target script types (either "css" or "js") and the commands used for the conversion. + */ public $commands = array( - 'less' => array('css', 'lessc %s %s'), - 'scss' => array('css', 'sass %s %s'), - 'sass' => array('css', 'sass %s %s'), - 'styl' => array('js', 'stylus < %s > %s'), + 'less' => array('css', 'lessc {from} {to}'), + 'scss' => array('css', 'sass {from} {to}'), + 'sass' => array('css', 'sass {from} {to}'), + 'styl' => array('js', 'stylus < {from} > {to}'), ); + /** + * Converts a given asset file into a CSS or JS file. + * @param string $asset the asset file path, relative to $basePath + * @param string $basePath the directory the $asset is relative to. + * @param string $baseUrl the URL corresponding to $basePath + * @return string the URL to the converted asset file. + */ public function convert($asset, $basePath, $baseUrl) { $pos = strrpos($asset, '.'); @@ -33,7 +47,10 @@ class AssetConverter extends Component implements IAssetConverter $result = substr($asset, 0, $pos + 1) . $ext; if (@filemtime("$basePath/$result") < filemtime("$basePath/$asset")) { $output = array(); - $command = sprintf($command, "$basePath/$asset", "$basePath/$result"); + $command = strtr($command, array( + '{from}' => "$basePath/$asset", + '{to}' => "$basePath/$result", + )); exec($command, $output); Yii::info("Converted $asset into $result: " . implode("\n", $output), __METHOD__); return "$baseUrl/$result"; diff --git a/framework/web/IAssetConverter.php b/framework/web/IAssetConverter.php index 994cb2f..4334d3e 100644 --- a/framework/web/IAssetConverter.php +++ b/framework/web/IAssetConverter.php @@ -8,10 +8,20 @@ namespace yii\web; /** + * The IAssetConverter interface must be implemented by asset converter classes. + * * @author Qiang Xue * @since 2.0 */ interface IAssetConverter { + /** + * Converts a given asset file into a CSS or JS file. + * @param string $asset the asset file path, relative to $basePath + * @param string $basePath the directory the $asset is relative to. + * @param string $baseUrl the URL corresponding to $basePath + * @return string the URL to the converted asset file. If the given asset does not + * need conversion, "$baseUrl/$asset" should be returned. + */ public function convert($asset, $basePath, $baseUrl); } \ No newline at end of file diff --git a/framework/yiic.php b/framework/yiic.php index 0db69bb..3872e2f 100644 --- a/framework/yiic.php +++ b/framework/yiic.php @@ -14,10 +14,9 @@ defined('STDIN') or define('STDIN', fopen('php://stdin', 'r')); require(__DIR__ . '/yii.php'); -$id = 'yiic'; -$basePath = __DIR__ . '/console'; - -$application = new yii\console\Application($id, $basePath, array( +$application = new yii\console\Application(array( + 'id' => 'yiic', + 'basePath' => __DIR__ . '/console', 'controllerPath' => '@yii/console/controllers', )); $application->run(); From 7775e927e12e2ba6a81d7046df0e228df2b1b608 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Mon, 22 Apr 2013 06:50:22 -0400 Subject: [PATCH 43/53] script command WIP --- framework/console/controllers/ScriptController.php | 156 ++++++++++++++++++++- framework/web/AssetBundle.php | 15 +- 2 files changed, 165 insertions(+), 6 deletions(-) diff --git a/framework/console/controllers/ScriptController.php b/framework/console/controllers/ScriptController.php index 7ca498b..44a5818 100644 --- a/framework/console/controllers/ScriptController.php +++ b/framework/console/controllers/ScriptController.php @@ -17,10 +17,160 @@ use yii\console\Controller; */ class ScriptController extends Controller { - public $defaultAction = 'combo'; + public $defaultAction = 'compress'; - public function actionCombo($configFile) + public $bundles = array(); + public $extensions = array(); + /** + * @var array + * ~~~ + * 'all' => array( + * 'css' => 'all.css', + * 'js' => 'js.css', + * 'depends' => array( ... ), + * ) + * ~~~ + */ + public $targets = array(); + public $basePath; + public $baseUrl; + public $publishOptions = array(); + + public function actionCompress($configFile, $bundleFile) + { + $this->loadConfiguration($configFile); + $bundles = $this->loadBundles($this->bundles, $this->extensions); + $this->publishBundles($bundles, $this->publishOptions); + $timestamp = time(); + $targets = array(); + foreach ($this->targets as $name => $target) { + $target['basePath'] = $this->basePath; + $target['baseUrl'] = $this->baseUrl; + if (isset($target['js'])) { + $this->buildTarget($target, 'js', $bundles, $timestamp); + } + if (isset($target['css'])) { + $this->buildTarget($target, 'css', $bundles, $timestamp); + } + $targets[$name] = $target; + } + + $targets = $this->adjustDependency($targets, $bundles); + $array = var_export($targets, true); + $version = date('Y-m-d H:i:s', time()); + file_put_contents($bundleFile, << $value) { + if (property_exists($this, $name)) { + $this->$name = $value; + } else { + throw new Exception("Unknown configuration: $name"); + } + } + + if (!isset($this->basePath)) { + throw new Exception("Please specify the 'basePath' option."); + } + if (!is_dir($this->basePath)) { + throw new Exception("The 'basePath' directory does not exist: {$this->basePath}"); + } + if (!isset($this->baseUrl)) { + throw new Exception("Please specify the 'baseUrl' option."); + } + $this->publishOptions['basePath'] = $this->basePath; + $this->publishOptions['baseUrl'] = $this->baseUrl; + } + + protected function loadBundles($bundles, $extensions) + { + $result = array(); + foreach ($bundles as $name => $bundle) { + $bundle['class'] = 'yii\\web\\AssetBundle'; + $result[$name] = Yii::createObject($bundle); + } + foreach ($extensions as $path) { + $manifest = $path . '/assets.php'; + if (!is_file($manifest)) { + continue; + } + foreach (require($manifest) as $name => $bundle) { + if (!isset($result[$name])) { + $bundle['class'] = 'yii\\web\\AssetBundle'; + $result[$name] = Yii::createObject($bundle); + } + } + } + return $result; + } + + /** + * @param \yii\web\AssetBundle[] $bundles + * @param array $options + */ + protected function publishBundles($bundles, $options) { - + if (!isset($options['class'])) { + $options['class'] = 'yii\\web\\AssetManager'; + } + $am = Yii::createObject($options); + foreach ($bundles as $bundle) { + $bundle->publish($am); + } + } + + /** + * @param array $target + * @param string $type either "js" or "css" + * @param \yii\web\AssetBundle[] $bundles + * @param integer $timestamp + * @throws Exception + */ + protected function buildTarget(&$target, $type, $bundles, $timestamp) + { + $outputFile = strtr($target[$type], array( + '{ts}' => $timestamp, + )); + $inputFiles = array(); + foreach ($target['depends'] as $name) { + if (isset($bundles[$name])) { + foreach ($bundles[$name]->$type as $file) { + $inputFiles[] = $bundles[$name]->basePath . '/' . $file; + } + } else { + throw new Exception("Unknown bundle: $name"); + } + } + if ($type === 'js') { + $this->compressJsFiles($inputFiles, $target['basePath'] . '/' . $outputFile); + } else { + $this->compressCssFiles($inputFiles, $target['basePath'] . '/' . $outputFile); + } + $target[$type] = array($outputFile); + } + + protected function adjustDependency($targets, $bundles) + { + return $targets; + } + + protected function compressJsFiles($inputFiles, $outputFile) + { + + } + + protected function compressCssFiles($inputFiles, $outputFile) + { + } } \ No newline at end of file diff --git a/framework/web/AssetBundle.php b/framework/web/AssetBundle.php index 4108b07..9fc2bbf 100644 --- a/framework/web/AssetBundle.php +++ b/framework/web/AssetBundle.php @@ -132,9 +132,7 @@ class AssetBundle extends Object $view->registerAssetBundle($name); } - if ($this->sourcePath !== null) { - list ($this->basePath, $this->baseUrl) = $am->publish($this->sourcePath, $this->publishOptions); - } + $this->publish($am); $converter = $am->getConverter(); @@ -161,4 +159,15 @@ class AssetBundle extends Object $view->registerCssFile($css, is_array($options) ? $options : array()); } } + + /** + * Publishes the asset bundle if its source code is not under Web-accessible directory. + * @param AssetManager $am the asset manager to perform the asset publishing + */ + public function publish($am) + { + if ($this->sourcePath !== null) { + list ($this->basePath, $this->baseUrl) = $am->publish($this->sourcePath, $this->publishOptions); + } + } } \ No newline at end of file From f21499dd9b10de70438e11ad1099de87337b13bf Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Mon, 22 Apr 2013 16:03:27 -0400 Subject: [PATCH 44/53] script WIP --- framework/base/View.php | 4 +- framework/console/controllers/ScriptController.php | 158 ++++++++++++++++----- framework/web/AssetBundle.php | 69 ++++----- 3 files changed, 157 insertions(+), 74 deletions(-) diff --git a/framework/base/View.php b/framework/base/View.php index b791743..10a7053 100644 --- a/framework/base/View.php +++ b/framework/base/View.php @@ -592,7 +592,7 @@ class View extends Component * Registers the named asset bundle. * All dependent asset bundles will be registered. * @param string $name the name of the asset bundle. - * @throws InvalidConfigException if the asset bundle does not exist or a cyclic dependency is detected + * @throws InvalidConfigException if the asset bundle does not exist or a circular dependency is detected */ public function registerAssetBundle($name) { @@ -607,7 +607,7 @@ class View extends Component throw new InvalidConfigException("Unknown asset bundle: $name"); } } elseif ($this->assetBundles[$name] === false) { - throw new InvalidConfigException("A cyclic dependency is detected for bundle '$name'."); + throw new InvalidConfigException("A circular dependency is detected for bundle '$name'."); } } diff --git a/framework/console/controllers/ScriptController.php b/framework/console/controllers/ScriptController.php index 44a5818..ab57f92 100644 --- a/framework/console/controllers/ScriptController.php +++ b/framework/console/controllers/ScriptController.php @@ -32,41 +32,26 @@ class ScriptController extends Controller * ~~~ */ public $targets = array(); - public $basePath; - public $baseUrl; - public $publishOptions = array(); + public $assetManager = array(); public function actionCompress($configFile, $bundleFile) { $this->loadConfiguration($configFile); $bundles = $this->loadBundles($this->bundles, $this->extensions); - $this->publishBundles($bundles, $this->publishOptions); + $targets = $this->loadTargets($this->targets, $bundles); +// $this->publishBundles($bundles, $this->publishOptions); $timestamp = time(); - $targets = array(); - foreach ($this->targets as $name => $target) { - $target['basePath'] = $this->basePath; - $target['baseUrl'] = $this->baseUrl; - if (isset($target['js'])) { + foreach ($targets as $target) { + if (!empty($target->js)) { $this->buildTarget($target, 'js', $bundles, $timestamp); } - if (isset($target['css'])) { + if (!empty($target->css)) { $this->buildTarget($target, 'css', $bundles, $timestamp); } - $targets[$name] = $target; } $targets = $this->adjustDependency($targets, $bundles); - $array = var_export($targets, true); - $version = date('Y-m-d H:i:s', time()); - file_put_contents($bundleFile, <<saveTargets($targets, $bundleFile); } protected function loadConfiguration($configFile) @@ -75,21 +60,16 @@ EOD if (property_exists($this, $name)) { $this->$name = $value; } else { - throw new Exception("Unknown configuration: $name"); + throw new Exception("Unknown configuration option: $name"); } } - if (!isset($this->basePath)) { - throw new Exception("Please specify the 'basePath' option."); - } - if (!is_dir($this->basePath)) { - throw new Exception("The 'basePath' directory does not exist: {$this->basePath}"); + if (!isset($this->assetManager['basePath'])) { + throw new Exception("Please specify 'basePath' for the 'assetManager' option."); } - if (!isset($this->baseUrl)) { - throw new Exception("Please specify the 'baseUrl' option."); + if (!isset($this->assetManager['baseUrl'])) { + throw new Exception("Please specify 'baseUrl' for the 'assetManager' option."); } - $this->publishOptions['basePath'] = $this->basePath; - $this->publishOptions['baseUrl'] = $this->baseUrl; } protected function loadBundles($bundles, $extensions) @@ -114,6 +94,33 @@ EOD return $result; } + protected function loadTargets($targets, $bundles) + { + $registered = array(); + foreach ($bundles as $name => $bundle) { + $this->registerBundle($bundles, $name, $registered); + } + $bundleOrders = array_combine(array_keys($registered), range(0, count($bundles) - 1)); + foreach ($targets as $name => $target) { + if (!isset($target['basePath'])) { + throw new Exception("Please specify 'basePath' for the '$name' target."); + } + if (!isset($target['baseUrl'])) { + throw new Exception("Please specify 'baseUrl' for the '$name' target."); + } + usort($target['depends'], function ($a, $b) use ($bundleOrders) { + if ($bundleOrders[$a] == $bundleOrders[$b]) { + return 0; + } else { + return $bundleOrders[$a] > $bundleOrders[$b] ? 1 : -1; + } + }); + $target['class'] = 'yii\\web\\AssetBundle'; + $targets[$name] = Yii::createObject($target); + } + return $targets; + } + /** * @param \yii\web\AssetBundle[] $bundles * @param array $options @@ -130,19 +137,20 @@ EOD } /** - * @param array $target + * @param \yii\web\AssetBundle $target * @param string $type either "js" or "css" * @param \yii\web\AssetBundle[] $bundles * @param integer $timestamp * @throws Exception */ - protected function buildTarget(&$target, $type, $bundles, $timestamp) + protected function buildTarget($target, $type, $bundles, $timestamp) { - $outputFile = strtr($target[$type], array( + $outputFile = strtr($target->$type, array( '{ts}' => $timestamp, )); $inputFiles = array(); - foreach ($target['depends'] as $name) { + + foreach ($target->depends as $name) { if (isset($bundles[$name])) { foreach ($bundles[$name]->$type as $file) { $inputFiles[] = $bundles[$name]->basePath . '/' . $file; @@ -152,18 +160,90 @@ EOD } } if ($type === 'js') { - $this->compressJsFiles($inputFiles, $target['basePath'] . '/' . $outputFile); + $this->compressJsFiles($inputFiles, $target->basePath . '/' . $outputFile); } else { - $this->compressCssFiles($inputFiles, $target['basePath'] . '/' . $outputFile); + $this->compressCssFiles($inputFiles, $target->basePath . '/' . $outputFile); } - $target[$type] = array($outputFile); + $target->$type = array($outputFile); } protected function adjustDependency($targets, $bundles) { + $map = array(); + foreach ($targets as $name => $target) { + foreach ($target->depends as $bundle) { + if (!isset($map[$bundle])) { + $map[$bundle] = $name; + } else { + throw new Exception("Bundle '$bundle' is found in both target '{$map[$bundle]}' and '$name'."); + } + } + } + + foreach ($targets as $name => $target) { + $depends = array(); + foreach ($target->depends as $bn) { + foreach ($bundles[$bn]->depends as $bundle) { + $depends[$map[$bundle]] = true; + } + } + unset($depends[$name]); + $target->depends = array_keys($depends); + } + + // detect possible circular dependencies + foreach ($targets as $name => $target) { + $registered = array(); + $this->registerBundle($targets, $name, $registered); + } + + foreach ($map as $bundle => $target) { + $targets[$bundle] = Yii::createObject(array( + 'class' => 'yii\\web\\AssetBundle', + 'depends' => array($target), + )); + } return $targets; } + protected function registerBundle($bundles, $name, &$registered) + { + if (!isset($registered[$name])) { + $registered[$name] = false; + $bundle = $bundles[$name]; + foreach ($bundle->depends as $depend) { + $this->registerBundle($bundles, $depend, $registered); + } + unset($registered[$name]); + $registered[$name] = true; + } elseif ($registered[$name] === false) { + throw new Exception("A circular dependency is detected for target '$name'."); + } + } + + protected function saveTargets($targets, $bundleFile) + { + $array = array(); + foreach ($targets as $name => $target) { + foreach (array('js', 'css', 'depends', 'basePath', 'baseUrl') as $prop) { + if (!empty($target->$prop)) { + $array[$name][$prop] = $target->$prop; + } + } + } + $array = var_export($array, true); + $version = date('Y-m-d H:i:s', time()); + file_put_contents($bundleFile, <<getAssetManager(); - foreach ($this->depends as $name) { $view->registerAssetBundle($name); } - $this->publish($am); + $this->publish($view->getAssetManager()); - $converter = $am->getConverter(); + foreach ($this->js as $js) { + $view->registerJsFile($js, $this->jsOptions); + } + foreach ($this->css as $css) { + $view->registerCssFile($css, $this->cssOptions); + } + } - foreach ($this->js as $js => $options) { - $js = is_string($options) ? $options : $js; + /** + * Publishes the asset bundle if its source code is not under Web-accessible directory. + * @param AssetManager $am the asset manager to perform the asset publishing + * @throws InvalidConfigException if [[baseUrl]] or [[basePath]] is not set when the bundle + * contains internal CSS or JS files. + */ + public function publish($am) + { + if ($this->sourcePath !== null) { + list ($this->basePath, $this->baseUrl) = $am->publish($this->sourcePath, $this->publishOptions); + } + $converter = $am->getConverter(); + foreach ($this->js as $i => $js) { if (strpos($js, '/') !== 0 && strpos($js, '://') === false) { if (isset($this->basePath, $this->baseUrl)) { - $js = $converter->convert($js, $this->basePath, $this->baseUrl); + $this->js[$i] = $converter->convert($js, $this->basePath, $this->baseUrl); } else { throw new InvalidConfigException('Both of the "baseUrl" and "basePath" properties must be set.'); } } - $view->registerJsFile($js, is_array($options) ? $options : array()); } - foreach ($this->css as $css => $options) { - $css = is_string($options) ? $options : $css; + foreach ($this->css as $i => $css) { if (strpos($css, '/') !== 0 && strpos($css, '://') === false) { if (isset($this->basePath, $this->baseUrl)) { - $css = $converter->convert($css, $this->basePath, $this->baseUrl); + $this->css[$i] = $converter->convert($css, $this->basePath, $this->baseUrl); } else { throw new InvalidConfigException('Both of the "baseUrl" and "basePath" properties must be set.'); } } - $view->registerCssFile($css, is_array($options) ? $options : array()); - } - } - - /** - * Publishes the asset bundle if its source code is not under Web-accessible directory. - * @param AssetManager $am the asset manager to perform the asset publishing - */ - public function publish($am) - { - if ($this->sourcePath !== null) { - list ($this->basePath, $this->baseUrl) = $am->publish($this->sourcePath, $this->publishOptions); } } } \ No newline at end of file From e132ed8d185a12edf4154a45af67ac34995ea9d0 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Mon, 22 Apr 2013 17:22:38 -0400 Subject: [PATCH 45/53] initial implementation of asset command. --- framework/console/Application.php | 2 +- framework/console/controllers/AssetController.php | 353 +++++++++++++++++++++ framework/console/controllers/ScriptController.php | 256 --------------- 3 files changed, 354 insertions(+), 257 deletions(-) create mode 100644 framework/console/controllers/AssetController.php delete mode 100644 framework/console/controllers/ScriptController.php diff --git a/framework/console/Application.php b/framework/console/Application.php index e185cc5..2f28cac 100644 --- a/framework/console/Application.php +++ b/framework/console/Application.php @@ -129,7 +129,7 @@ class Application extends \yii\base\Application 'migrate' => 'yii\console\controllers\MigrateController', 'app' => 'yii\console\controllers\AppController', 'cache' => 'yii\console\controllers\CacheController', - 'script' => 'yii\console\controllers\ScriptController', + 'asset' => 'yii\console\controllers\AssetController', ); } diff --git a/framework/console/controllers/AssetController.php b/framework/console/controllers/AssetController.php new file mode 100644 index 0000000..71a2cae --- /dev/null +++ b/framework/console/controllers/AssetController.php @@ -0,0 +1,353 @@ + + * @since 2.0 + */ +class AssetController extends Controller +{ + public $defaultAction = 'compress'; + + public $bundles = array(); + public $extensions = array(); + /** + * @var array + * ~~~ + * 'all' => array( + * 'css' => 'all.css', + * 'js' => 'js.css', + * 'depends' => array( ... ), + * ) + * ~~~ + */ + public $targets = array(); + public $assetManager = array(); + public $jsCompressor = 'java -jar compiler.jar --js {from} --js_output_file {to}'; + public $cssCompressor = 'java -jar yuicompressor.jar {from} -o {to}'; + + public function actionCompress($configFile, $bundleFile) + { + $this->loadConfiguration($configFile); + $bundles = $this->loadBundles($this->bundles, $this->extensions); + $targets = $this->loadTargets($this->targets, $bundles); + $this->publishBundles($bundles, $this->publishOptions); + $timestamp = time(); + foreach ($targets as $target) { + if (!empty($target->js)) { + $this->buildTarget($target, 'js', $bundles, $timestamp); + } + if (!empty($target->css)) { + $this->buildTarget($target, 'css', $bundles, $timestamp); + } + } + + $targets = $this->adjustDependency($targets, $bundles); + $this->saveTargets($targets, $bundleFile); + } + + protected function loadConfiguration($configFile) + { + foreach (require($configFile) as $name => $value) { + if (property_exists($this, $name)) { + $this->$name = $value; + } else { + throw new Exception("Unknown configuration option: $name"); + } + } + + if (!isset($this->assetManager['basePath'])) { + throw new Exception("Please specify 'basePath' for the 'assetManager' option."); + } + if (!isset($this->assetManager['baseUrl'])) { + throw new Exception("Please specify 'baseUrl' for the 'assetManager' option."); + } + } + + protected function loadBundles($bundles, $extensions) + { + $result = array(); + foreach ($bundles as $name => $bundle) { + $bundle['class'] = 'yii\\web\\AssetBundle'; + $result[$name] = Yii::createObject($bundle); + } + foreach ($extensions as $path) { + $manifest = $path . '/assets.php'; + if (!is_file($manifest)) { + continue; + } + foreach (require($manifest) as $name => $bundle) { + if (!isset($result[$name])) { + $bundle['class'] = 'yii\\web\\AssetBundle'; + $result[$name] = Yii::createObject($bundle); + } + } + } + return $result; + } + + protected function loadTargets($targets, $bundles) + { + // build the dependency order of bundles + $registered = array(); + foreach ($bundles as $name => $bundle) { + $this->registerBundle($bundles, $name, $registered); + } + $bundleOrders = array_combine(array_keys($registered), range(0, count($bundles) - 1)); + + // fill up the target which has empty 'depends'. + $referenced = array(); + foreach ($targets as $name => $target) { + if (empty($target['depends'])) { + if (!isset($all)) { + $all = $name; + } else { + throw new Exception("Only one target can have empty 'depends' option. Found two now: $all, $name"); + } + } else { + foreach ($target['depends'] as $bundle) { + if (!isset($referenced[$bundle])) { + $referenced[$bundle] = $name; + } else { + throw new Exception("Target '{$referenced[$bundle]}' and '$name' cannot contain the bundle '$bundle' at the same time."); + } + } + } + } + if (isset($all)) { + $targets[$all]['depends'] = array_diff(array_keys($registered), array_keys($referenced)); + } + + // adjust the 'depends' order for each target according to the dependency order of bundles + // create an AssetBundle object for each target + foreach ($targets as $name => $target) { + if (!isset($target['basePath'])) { + throw new Exception("Please specify 'basePath' for the '$name' target."); + } + if (!isset($target['baseUrl'])) { + throw new Exception("Please specify 'baseUrl' for the '$name' target."); + } + usort($target['depends'], function ($a, $b) use ($bundleOrders) { + if ($bundleOrders[$a] == $bundleOrders[$b]) { + return 0; + } else { + return $bundleOrders[$a] > $bundleOrders[$b] ? 1 : -1; + } + }); + $target['class'] = 'yii\\web\\AssetBundle'; + $targets[$name] = Yii::createObject($target); + } + return $targets; + } + + /** + * @param \yii\web\AssetBundle[] $bundles + * @param array $options + */ + protected function publishBundles($bundles, $options) + { + if (!isset($options['class'])) { + $options['class'] = 'yii\\web\\AssetManager'; + } + $am = Yii::createObject($options); + foreach ($bundles as $bundle) { + $bundle->publish($am); + } + } + + /** + * @param \yii\web\AssetBundle $target + * @param string $type either "js" or "css" + * @param \yii\web\AssetBundle[] $bundles + * @param integer $timestamp + * @throws Exception + */ + protected function buildTarget($target, $type, $bundles, $timestamp) + { + $outputFile = strtr($target->$type, array( + '{ts}' => $timestamp, + )); + $inputFiles = array(); + + foreach ($target->depends as $name) { + if (isset($bundles[$name])) { + foreach ($bundles[$name]->$type as $file) { + $inputFiles[] = $bundles[$name]->basePath . '/' . $file; + } + } else { + throw new Exception("Unknown bundle: $name"); + } + } + if ($type === 'js') { + $this->compressJsFiles($inputFiles, $target->basePath . '/' . $outputFile); + } else { + $this->compressCssFiles($inputFiles, $target->basePath . '/' . $outputFile); + } + $target->$type = array($outputFile); + } + + protected function adjustDependency($targets, $bundles) + { + $map = array(); + foreach ($targets as $name => $target) { + foreach ($target->depends as $bundle) { + $map[$bundle] = $name; + } + } + + foreach ($targets as $name => $target) { + $depends = array(); + foreach ($target->depends as $bn) { + foreach ($bundles[$bn]->depends as $bundle) { + $depends[$map[$bundle]] = true; + } + } + unset($depends[$name]); + $target->depends = array_keys($depends); + } + + // detect possible circular dependencies + foreach ($targets as $name => $target) { + $registered = array(); + $this->registerBundle($targets, $name, $registered); + } + + foreach ($map as $bundle => $target) { + $targets[$bundle] = Yii::createObject(array( + 'class' => 'yii\\web\\AssetBundle', + 'depends' => array($target), + )); + } + return $targets; + } + + protected function registerBundle($bundles, $name, &$registered) + { + if (!isset($registered[$name])) { + $registered[$name] = false; + $bundle = $bundles[$name]; + foreach ($bundle->depends as $depend) { + $this->registerBundle($bundles, $depend, $registered); + } + unset($registered[$name]); + $registered[$name] = true; + } elseif ($registered[$name] === false) { + throw new Exception("A circular dependency is detected for target '$name'."); + } + } + + protected function saveTargets($targets, $bundleFile) + { + $array = array(); + foreach ($targets as $name => $target) { + foreach (array('js', 'css', 'depends', 'basePath', 'baseUrl') as $prop) { + if (!empty($target->$prop)) { + $array[$name][$prop] = $target->$prop; + } + } + } + $array = var_export($array, true); + $version = date('Y-m-d H:i:s', time()); + file_put_contents($bundleFile, <<jsCompressor)) { + $tmpFile = $outputFile . '.tmp'; + $this->combineJsFiles($inputFiles, $tmpFile); + $log = shell_exec(strtr($this->jsCompressor, array( + '{from}' => $tmpFile, + '{to}' => $outputFile, + ))); + @unlink($tmpFile); + } else { + $log = call_user_func($this->jsCompressor, $this, $inputFiles, $outputFile); + } + } + + protected function compressCssFiles($inputFiles, $outputFile) + { + if (is_string($this->cssCompressor)) { + $tmpFile = $outputFile . '.tmp'; + $this->combineCssFiles($inputFiles, $tmpFile); + $log = shell_exec(strtr($this->cssCompressor, array( + '{from}' => $inputFiles, + '{to}' => $outputFile, + ))); + } else { + $log = call_user_func($this->cssCompressor, $this, $inputFiles, $outputFile); + } + } + + public function combineJsFiles($files, $tmpFile) + { + $content = ''; + foreach ($files as $file) { + $content .= "/*** BEGIN FILE: $file ***/\n" + . file_get_contents($file) + . "/*** END FILE: $file ***/\n"; + } + file_put_contents($tmpFile, $content); + } + + public function combineCssFiles($files, $tmpFile) + { + // todo: adjust url() references in CSS files + $content = ''; + foreach ($files as $file) { + $content .= "/*** BEGIN FILE: $file ***/\n" + . file_get_contents($file) + . "/*** END FILE: $file ***/\n"; + } + file_put_contents($tmpFile, $content); + } + + public function actionTemplate($configFile) + { + $template = << require('path/to/bundles.php'), + // + 'extensions' => require('path/to/namespaces.php'), + // + 'targets' => array( + 'all' => array( + 'basePath' => __DIR__, + 'baseUrl' => '/test', + 'js' => 'all-{ts}.js', + 'css' => 'all-{ts}.css', + ), + ), + + 'assetManager' => array( + 'basePath' => __DIR__, + 'baseUrl' => '/test', + ), +); +EOD; + file_put_contents($configFile, $template); + } +} \ No newline at end of file diff --git a/framework/console/controllers/ScriptController.php b/framework/console/controllers/ScriptController.php deleted file mode 100644 index ab57f92..0000000 --- a/framework/console/controllers/ScriptController.php +++ /dev/null @@ -1,256 +0,0 @@ - - * @since 2.0 - */ -class ScriptController extends Controller -{ - public $defaultAction = 'compress'; - - public $bundles = array(); - public $extensions = array(); - /** - * @var array - * ~~~ - * 'all' => array( - * 'css' => 'all.css', - * 'js' => 'js.css', - * 'depends' => array( ... ), - * ) - * ~~~ - */ - public $targets = array(); - public $assetManager = array(); - - public function actionCompress($configFile, $bundleFile) - { - $this->loadConfiguration($configFile); - $bundles = $this->loadBundles($this->bundles, $this->extensions); - $targets = $this->loadTargets($this->targets, $bundles); -// $this->publishBundles($bundles, $this->publishOptions); - $timestamp = time(); - foreach ($targets as $target) { - if (!empty($target->js)) { - $this->buildTarget($target, 'js', $bundles, $timestamp); - } - if (!empty($target->css)) { - $this->buildTarget($target, 'css', $bundles, $timestamp); - } - } - - $targets = $this->adjustDependency($targets, $bundles); - $this->saveTargets($targets, $bundleFile); - } - - protected function loadConfiguration($configFile) - { - foreach (require($configFile) as $name => $value) { - if (property_exists($this, $name)) { - $this->$name = $value; - } else { - throw new Exception("Unknown configuration option: $name"); - } - } - - if (!isset($this->assetManager['basePath'])) { - throw new Exception("Please specify 'basePath' for the 'assetManager' option."); - } - if (!isset($this->assetManager['baseUrl'])) { - throw new Exception("Please specify 'baseUrl' for the 'assetManager' option."); - } - } - - protected function loadBundles($bundles, $extensions) - { - $result = array(); - foreach ($bundles as $name => $bundle) { - $bundle['class'] = 'yii\\web\\AssetBundle'; - $result[$name] = Yii::createObject($bundle); - } - foreach ($extensions as $path) { - $manifest = $path . '/assets.php'; - if (!is_file($manifest)) { - continue; - } - foreach (require($manifest) as $name => $bundle) { - if (!isset($result[$name])) { - $bundle['class'] = 'yii\\web\\AssetBundle'; - $result[$name] = Yii::createObject($bundle); - } - } - } - return $result; - } - - protected function loadTargets($targets, $bundles) - { - $registered = array(); - foreach ($bundles as $name => $bundle) { - $this->registerBundle($bundles, $name, $registered); - } - $bundleOrders = array_combine(array_keys($registered), range(0, count($bundles) - 1)); - foreach ($targets as $name => $target) { - if (!isset($target['basePath'])) { - throw new Exception("Please specify 'basePath' for the '$name' target."); - } - if (!isset($target['baseUrl'])) { - throw new Exception("Please specify 'baseUrl' for the '$name' target."); - } - usort($target['depends'], function ($a, $b) use ($bundleOrders) { - if ($bundleOrders[$a] == $bundleOrders[$b]) { - return 0; - } else { - return $bundleOrders[$a] > $bundleOrders[$b] ? 1 : -1; - } - }); - $target['class'] = 'yii\\web\\AssetBundle'; - $targets[$name] = Yii::createObject($target); - } - return $targets; - } - - /** - * @param \yii\web\AssetBundle[] $bundles - * @param array $options - */ - protected function publishBundles($bundles, $options) - { - if (!isset($options['class'])) { - $options['class'] = 'yii\\web\\AssetManager'; - } - $am = Yii::createObject($options); - foreach ($bundles as $bundle) { - $bundle->publish($am); - } - } - - /** - * @param \yii\web\AssetBundle $target - * @param string $type either "js" or "css" - * @param \yii\web\AssetBundle[] $bundles - * @param integer $timestamp - * @throws Exception - */ - protected function buildTarget($target, $type, $bundles, $timestamp) - { - $outputFile = strtr($target->$type, array( - '{ts}' => $timestamp, - )); - $inputFiles = array(); - - foreach ($target->depends as $name) { - if (isset($bundles[$name])) { - foreach ($bundles[$name]->$type as $file) { - $inputFiles[] = $bundles[$name]->basePath . '/' . $file; - } - } else { - throw new Exception("Unknown bundle: $name"); - } - } - if ($type === 'js') { - $this->compressJsFiles($inputFiles, $target->basePath . '/' . $outputFile); - } else { - $this->compressCssFiles($inputFiles, $target->basePath . '/' . $outputFile); - } - $target->$type = array($outputFile); - } - - protected function adjustDependency($targets, $bundles) - { - $map = array(); - foreach ($targets as $name => $target) { - foreach ($target->depends as $bundle) { - if (!isset($map[$bundle])) { - $map[$bundle] = $name; - } else { - throw new Exception("Bundle '$bundle' is found in both target '{$map[$bundle]}' and '$name'."); - } - } - } - - foreach ($targets as $name => $target) { - $depends = array(); - foreach ($target->depends as $bn) { - foreach ($bundles[$bn]->depends as $bundle) { - $depends[$map[$bundle]] = true; - } - } - unset($depends[$name]); - $target->depends = array_keys($depends); - } - - // detect possible circular dependencies - foreach ($targets as $name => $target) { - $registered = array(); - $this->registerBundle($targets, $name, $registered); - } - - foreach ($map as $bundle => $target) { - $targets[$bundle] = Yii::createObject(array( - 'class' => 'yii\\web\\AssetBundle', - 'depends' => array($target), - )); - } - return $targets; - } - - protected function registerBundle($bundles, $name, &$registered) - { - if (!isset($registered[$name])) { - $registered[$name] = false; - $bundle = $bundles[$name]; - foreach ($bundle->depends as $depend) { - $this->registerBundle($bundles, $depend, $registered); - } - unset($registered[$name]); - $registered[$name] = true; - } elseif ($registered[$name] === false) { - throw new Exception("A circular dependency is detected for target '$name'."); - } - } - - protected function saveTargets($targets, $bundleFile) - { - $array = array(); - foreach ($targets as $name => $target) { - foreach (array('js', 'css', 'depends', 'basePath', 'baseUrl') as $prop) { - if (!empty($target->$prop)) { - $array[$name][$prop] = $target->$prop; - } - } - } - $array = var_export($array, true); - $version = date('Y-m-d H:i:s', time()); - file_put_contents($bundleFile, << Date: Mon, 22 Apr 2013 19:45:37 -0400 Subject: [PATCH 46/53] Added a basic app. --- app/assets/.gitignore | 1 + app/index.php | 9 ++++++ app/protected/config/main.php | 15 ++++++++++ app/protected/controllers/SiteController.php | 22 ++++++++++++++ app/protected/models/User.php | 43 ++++++++++++++++++++++++++++ app/protected/runtime/.gitignore | 1 + app/protected/views/layouts/main.php | 22 ++++++++++++++ app/protected/views/site/index.php | 17 +++++++++++ framework/web/UrlManager.php | 8 +++--- 9 files changed, 134 insertions(+), 4 deletions(-) create mode 100644 app/assets/.gitignore create mode 100644 app/index.php create mode 100644 app/protected/config/main.php create mode 100644 app/protected/controllers/SiteController.php create mode 100644 app/protected/models/User.php create mode 100644 app/protected/runtime/.gitignore create mode 100644 app/protected/views/layouts/main.php create mode 100644 app/protected/views/site/index.php diff --git a/app/assets/.gitignore b/app/assets/.gitignore new file mode 100644 index 0000000..72e8ffc --- /dev/null +++ b/app/assets/.gitignore @@ -0,0 +1 @@ +* diff --git a/app/index.php b/app/index.php new file mode 100644 index 0000000..8f98090 --- /dev/null +++ b/app/index.php @@ -0,0 +1,9 @@ +run(); diff --git a/app/protected/config/main.php b/app/protected/config/main.php new file mode 100644 index 0000000..e18ead8 --- /dev/null +++ b/app/protected/config/main.php @@ -0,0 +1,15 @@ + 'hello', + 'basePath' => dirname(__DIR__), + 'components' => array( + 'cache' => array( + 'class' => 'yii\caching\FileCache', + ), + 'user' => array( + 'class' => 'yii\web\User', + 'identityClass' => 'app\models\User', + ) + ), +); \ No newline at end of file diff --git a/app/protected/controllers/SiteController.php b/app/protected/controllers/SiteController.php new file mode 100644 index 0000000..58e9568 --- /dev/null +++ b/app/protected/controllers/SiteController.php @@ -0,0 +1,22 @@ +render('index'); + } + + public function actionLogin() + { + $user = app\models\User::findIdentity(100); + Yii::$app->getUser()->login($user); + Yii::$app->getResponse()->redirect(array('site/index')); + } + + public function actionLogout() + { + Yii::$app->getUser()->logout(); + Yii::$app->getResponse()->redirect(array('site/index')); + } +} \ No newline at end of file diff --git a/app/protected/models/User.php b/app/protected/models/User.php new file mode 100644 index 0000000..cebf1da --- /dev/null +++ b/app/protected/models/User.php @@ -0,0 +1,43 @@ + array( + 'id' => '100', + 'authKey' => 'test100key', + 'name' => 'admin', + ), + '101' => array( + 'id' => '101', + 'authKey' => 'test101key', + 'name' => 'demo', + ), + ); + + public static function findIdentity($id) + { + return isset(self::$users[$id]) ? new self(self::$users[$id]) : null; + } + + public function getId() + { + return $this->id; + } + + public function getAuthKey() + { + return $this->authKey; + } + + public function validateAuthKey($authKey) + { + return $this->authKey === $authKey; + } +} \ No newline at end of file diff --git a/app/protected/runtime/.gitignore b/app/protected/runtime/.gitignore new file mode 100644 index 0000000..72e8ffc --- /dev/null +++ b/app/protected/runtime/.gitignore @@ -0,0 +1 @@ +* diff --git a/app/protected/views/layouts/main.php b/app/protected/views/layouts/main.php new file mode 100644 index 0000000..092e665 --- /dev/null +++ b/app/protected/views/layouts/main.php @@ -0,0 +1,22 @@ + + + +beginPage(); ?> + + <?php echo Html::encode($this->title); ?> + head(); ?> + + +

    Welcome

    +beginBody(); ?> + +endBody(); ?> + +endPage(); ?> + diff --git a/app/protected/views/site/index.php b/app/protected/views/site/index.php new file mode 100644 index 0000000..3b83080 --- /dev/null +++ b/app/protected/views/site/index.php @@ -0,0 +1,17 @@ +title = 'Hello World'; + +$user = Yii::$app->getUser(); +if ($user->isGuest) { + echo Html::a('login', array('login')); +} else { + echo "You are logged in as " . $user->identity->name . "
    "; + echo Html::a('logout', array('logout')); +} +?> + + diff --git a/framework/web/UrlManager.php b/framework/web/UrlManager.php index 459e8e8..755d644 100644 --- a/framework/web/UrlManager.php +++ b/framework/web/UrlManager.php @@ -74,9 +74,6 @@ class UrlManager extends Component public function init() { parent::init(); - if (is_string($this->cache)) { - $this->cache = Yii::$app->getComponent($this->cache); - } $this->compileRules(); } @@ -88,6 +85,9 @@ class UrlManager extends Component if (!$this->enablePrettyUrl || $this->rules === array()) { return; } + if (is_string($this->cache)) { + $this->cache = Yii::$app->getComponent($this->cache); + } if ($this->cache instanceof Cache) { $key = $this->cache->buildKey(__CLASS__); $hash = md5(json_encode($this->rules)); @@ -104,7 +104,7 @@ class UrlManager extends Component $this->rules[$i] = Yii::createObject($rule); } - if ($this->cache instanceof Cache) { + if (isset($key, $hash)) { $this->cache->set($key, array($this->rules, $hash)); } } From 173706f5165a7c28717c9203b7ea1868991b7889 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Tue, 23 Apr 2013 14:11:40 +0400 Subject: [PATCH 47/53] updated expected exception message --- tests/unit/framework/base/ModelTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/framework/base/ModelTest.php b/tests/unit/framework/base/ModelTest.php index aa15230..f04e550 100644 --- a/tests/unit/framework/base/ModelTest.php +++ b/tests/unit/framework/base/ModelTest.php @@ -195,7 +195,7 @@ class ModelTest extends TestCase public function testCreateValidators() { - $this->setExpectedException('yii\base\InvalidConfigException', 'Invalid validation rule: a rule must be an array specifying both attribute names and validator type.'); + $this->setExpectedException('yii\base\InvalidConfigException', 'Invalid validation rule: a rule must specify both attribute names and validator type.'); $invalid = new InvalidRulesModel(); $invalid->createValidators(); From a9215ceddd74b9963784612d7c1e1b74fdd19b2d Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Tue, 23 Apr 2013 14:11:55 +0400 Subject: [PATCH 48/53] create a webapp in test bootstrap --- tests/unit/bootstrap.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/unit/bootstrap.php b/tests/unit/bootstrap.php index 4a388c6..8290bbe 100644 --- a/tests/unit/bootstrap.php +++ b/tests/unit/bootstrap.php @@ -9,4 +9,6 @@ require_once(__DIR__ . '/../../framework/yii.php'); Yii::setAlias('@yiiunit', __DIR__); +new \yii\web\Application(array('id' => 'testapp', 'basePath' => __DIR__)); + require_once(__DIR__ . '/TestCase.php'); From 3e2e4afa8560074797fce9cafc27577d13b78e57 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Tue, 23 Apr 2013 14:15:22 +0400 Subject: [PATCH 49/53] fixed DB quoting typo --- framework/db/Connection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/db/Connection.php b/framework/db/Connection.php index 695034a..797508a 100644 --- a/framework/db/Connection.php +++ b/framework/db/Connection.php @@ -522,7 +522,7 @@ class Connection extends Component if (isset($matches[3])) { return $db->quoteColumnName($matches[3]); } else { - return str_replace('%', $this->tablePrefix, $db->quoteTableName($matches[2])); + return str_replace('%', $db->tablePrefix, $db->quoteTableName($matches[2])); } }, $sql); } From 08be696434a2fbdf61f3a309fffc73c5e80785f2 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Tue, 23 Apr 2013 14:32:54 +0400 Subject: [PATCH 50/53] fixed Html test under Windows (line endings) --- tests/unit/framework/helpers/HtmlTest.php | 46 ++++++++++++++++++------------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/tests/unit/framework/helpers/HtmlTest.php b/tests/unit/framework/helpers/HtmlTest.php index bf0ca0a..4077043 100644 --- a/tests/unit/framework/helpers/HtmlTest.php +++ b/tests/unit/framework/helpers/HtmlTest.php @@ -22,6 +22,14 @@ class HtmlTest extends \yii\test\TestCase )); } + public function assertEqualsWithoutLE($expected, $actual) + { + $expected = str_replace("\r\n", "\n", $expected); + $actual = str_replace("\r\n", "\n", $actual); + + $this->assertEquals($expected, $actual); + } + public function tearDown() { Yii::$app = null; @@ -240,21 +248,21 @@ class HtmlTest extends \yii\test\TestCase EOD; - $this->assertEquals($expected, Html::dropDownList('test')); + $this->assertEqualsWithoutLE($expected, Html::dropDownList('test')); $expected = << EOD; - $this->assertEquals($expected, Html::dropDownList('test', null, $this->getDataItems())); + $this->assertEqualsWithoutLE($expected, Html::dropDownList('test', null, $this->getDataItems())); $expected = << EOD; - $this->assertEquals($expected, Html::dropDownList('test', 'value2', $this->getDataItems())); + $this->assertEqualsWithoutLE($expected, Html::dropDownList('test', 'value2', $this->getDataItems())); } public function testListBox() @@ -264,48 +272,48 @@ EOD; EOD; - $this->assertEquals($expected, Html::listBox('test')); + $this->assertEqualsWithoutLE($expected, Html::listBox('test')); $expected = << EOD; - $this->assertEquals($expected, Html::listBox('test', null, $this->getDataItems(), array('size' => 5))); + $this->assertEqualsWithoutLE($expected, Html::listBox('test', null, $this->getDataItems(), array('size' => 5))); $expected = << EOD; - $this->assertEquals($expected, Html::listBox('test', null, $this->getDataItems2())); + $this->assertEqualsWithoutLE($expected, Html::listBox('test', null, $this->getDataItems2())); $expected = << EOD; - $this->assertEquals($expected, Html::listBox('test', 'value2', $this->getDataItems())); + $this->assertEqualsWithoutLE($expected, Html::listBox('test', 'value2', $this->getDataItems())); $expected = << EOD; - $this->assertEquals($expected, Html::listBox('test', array('value1', 'value2'), $this->getDataItems())); + $this->assertEqualsWithoutLE($expected, Html::listBox('test', array('value1', 'value2'), $this->getDataItems())); $expected = << EOD; - $this->assertEquals($expected, Html::listBox('test', null, array(), array('multiple' => true))); + $this->assertEqualsWithoutLE($expected, Html::listBox('test', null, array(), array('multiple' => true))); $expected = << EOD; - $this->assertEquals($expected, Html::listBox('test', '', array(), array('unselect' => '0'))); + $this->assertEqualsWithoutLE($expected, Html::listBox('test', '', array(), array('unselect' => '0'))); } public function testCheckboxList() @@ -316,19 +324,19 @@ EOD; EOD; - $this->assertEquals($expected, Html::checkboxList('test', array('value2'), $this->getDataItems())); + $this->assertEqualsWithoutLE($expected, Html::checkboxList('test', array('value2'), $this->getDataItems())); $expected = << text1<> EOD; - $this->assertEquals($expected, Html::checkboxList('test', array('value2'), $this->getDataItems2())); + $this->assertEqualsWithoutLE($expected, Html::checkboxList('test', array('value2'), $this->getDataItems2())); $expected = <<
    EOD; - $this->assertEquals($expected, Html::checkboxList('test', array('value2'), $this->getDataItems(), array( + $this->assertEqualsWithoutLE($expected, Html::checkboxList('test', array('value2'), $this->getDataItems(), array( 'separator' => "
    \n", 'unselect' => '0', ))); @@ -337,7 +345,7 @@ EOD; 0 1 EOD; - $this->assertEquals($expected, Html::checkboxList('test', array('value2'), $this->getDataItems(), array( + $this->assertEqualsWithoutLE($expected, Html::checkboxList('test', array('value2'), $this->getDataItems(), array( 'item' => function ($index, $label, $name, $checked, $value) { return $index . Html::label($label . ' ' . Html::checkbox($name, $checked, $value)); } @@ -352,19 +360,19 @@ EOD; EOD; - $this->assertEquals($expected, Html::radioList('test', array('value2'), $this->getDataItems())); + $this->assertEqualsWithoutLE($expected, Html::radioList('test', array('value2'), $this->getDataItems())); $expected = << text1<> EOD; - $this->assertEquals($expected, Html::radioList('test', array('value2'), $this->getDataItems2())); + $this->assertEqualsWithoutLE($expected, Html::radioList('test', array('value2'), $this->getDataItems2())); $expected = <<
    EOD; - $this->assertEquals($expected, Html::radioList('test', array('value2'), $this->getDataItems(), array( + $this->assertEqualsWithoutLE($expected, Html::radioList('test', array('value2'), $this->getDataItems(), array( 'separator' => "
    \n", 'unselect' => '0', ))); @@ -373,7 +381,7 @@ EOD; 0 1 EOD; - $this->assertEquals($expected, Html::radioList('test', array('value2'), $this->getDataItems(), array( + $this->assertEqualsWithoutLE($expected, Html::radioList('test', array('value2'), $this->getDataItems(), array( 'item' => function ($index, $label, $name, $checked, $value) { return $index . Html::label($label . ' ' . Html::radio($name, $checked, $value)); } @@ -420,7 +428,7 @@ EOD; 'group12' => array('class' => 'group'), ), ); - $this->assertEquals($expected, Html::renderSelectOptions(array('value111', 'value1'), $data, $attributes)); + $this->assertEqualsWithoutLE($expected, Html::renderSelectOptions(array('value111', 'value1'), $data, $attributes)); } public function testRenderAttributes() From b0bf8f74068e867e1273d862e286ac7241cfb798 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Tue, 23 Apr 2013 14:40:52 +0400 Subject: [PATCH 51/53] fixed dbcache multiget --- framework/caching/DbCache.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/caching/DbCache.php b/framework/caching/DbCache.php index dee8c7a..befb523 100644 --- a/framework/caching/DbCache.php +++ b/framework/caching/DbCache.php @@ -124,7 +124,7 @@ class DbCache extends Cache $query = new Query; $query->select(array('id', 'data')) ->from($this->cacheTable) - ->where(array('id' => $keys)) + ->where(array('in', 'id', (array)$keys)) ->andWhere('([[expire]] = 0 OR [[expire]] > ' . time() . ')'); if ($this->db->enableQueryCache) { From 09dbaeb70094626067578e82c3071c14bdb0e8cb Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Tue, 23 Apr 2013 15:00:41 +0400 Subject: [PATCH 52/53] more assertions for cache test --- tests/unit/framework/caching/CacheTest.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/unit/framework/caching/CacheTest.php b/tests/unit/framework/caching/CacheTest.php index ad2fcf5..f9db4f4 100644 --- a/tests/unit/framework/caching/CacheTest.php +++ b/tests/unit/framework/caching/CacheTest.php @@ -16,9 +16,9 @@ abstract class CacheTest extends TestCase public function testSet() { $cache = $this->getCacheInstance(); - $cache->set('string_test', 'string_test'); - $cache->set('number_test', 42); - $cache->set('array_test', array('array_test' => 'array_test')); + $this->assertTrue($cache->set('string_test', 'string_test')); + $this->assertTrue($cache->set('number_test', 42)); + $this->assertTrue($cache->set('array_test', array('array_test' => 'array_test'))); $cache['arrayaccess_test'] = new \stdClass(); } @@ -45,7 +45,7 @@ abstract class CacheTest extends TestCase public function testExpire() { $cache = $this->getCacheInstance(); - $cache->set('expire_test', 'expire_test', 2); + $this->assertTrue($cache->set('expire_test', 'expire_test', 2)); sleep(1); $this->assertEquals('expire_test', $cache->get('expire_test')); sleep(2); @@ -57,11 +57,11 @@ abstract class CacheTest extends TestCase $cache = $this->getCacheInstance(); // should not change existing keys - $cache->add('number_test', 13); + $this->assertFalse($cache->add('number_test', 13)); $this->assertEquals(42, $cache->get('number_test')); // should store data is it's not there yet - $cache->add('add_test', 13); + $this->assertTrue($cache->add('add_test', 13)); $this->assertEquals(13, $cache->get('add_test')); } @@ -69,14 +69,14 @@ abstract class CacheTest extends TestCase { $cache = $this->getCacheInstance(); - $cache->delete('number_test'); + $this->assertTrue($cache->delete('number_test')); $this->assertEquals(null, $cache->get('number_test')); } public function testFlush() { $cache = $this->getCacheInstance(); - $cache->flush(); + $this->assertTrue($cache->flush()); $this->assertEquals(null, $cache->get('add_test')); } } From c09ace8e96dc3e977e53d8ec105aa05ba0bb8ec3 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Tue, 23 Apr 2013 15:10:18 +0400 Subject: [PATCH 53/53] added note about enabling APC cache for CLI --- framework/caching/ApcCache.php | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/caching/ApcCache.php b/framework/caching/ApcCache.php index dd954cc..391851d 100644 --- a/framework/caching/ApcCache.php +++ b/framework/caching/ApcCache.php @@ -11,6 +11,7 @@ namespace yii\caching; * ApcCache provides APC caching in terms of an application component. * * To use this application component, the [APC PHP extension](http://www.php.net/apc) must be loaded. + * In order to enable APC for CLI you should add "apc.enable_cli = 1" to your php.ini. * * See [[Cache]] for common cache operations that ApcCache supports. *