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(); ?>
+
+ 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/YiiBase.php b/framework/YiiBase.php
index 6df998b..9d501b1 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
*/
@@ -107,88 +105,86 @@ class YiiBase
}
/**
- * Imports a class or a directory.
- *
- * 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.
- *
- * 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.
- *
- * 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.
- *
- * 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
+ * Imports a class by its alias.
+ *
+ * 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).
+ *
+ * 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()`.
+ *
+ * You may import the same class multiple times. Only the first importing will count.
+ *
+ * @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;
+ } else {
+ $alias = static::getAlias($alias);
+ if (!isset(self::$_imported[$alias])) {
+ $className = basename($alias);
+ self::$_imported[$alias] = $className;
+ self::$classMap[$className] = $alias . '.php';
+ }
+ return self::$_imported[$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");
- self::$_imported[$alias] = $className;
- } else {
- self::$classMap[$className] = $path . DIRECTORY_SEPARATOR . "$className.php";
+ /**
+ * 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);
}
- return $className;
- } else {
- // a directory
- array_unshift(self::$classPath, $path);
- return self::$_imported[$alias] = $path;
}
}
/**
* 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:
+ *
+ * 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.
*
- * In case the given parameter is not an alias (i.e., not starting with '@'),
- * it will be returned back without change.
+ * 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.
@@ -198,18 +194,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 {
@@ -218,41 +222,96 @@ 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 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.
+ *
+ * A path alias must start with the character '@' so that it can be easily differentiated
+ * from non-alias paths.
+ *
+ * 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.
*
- * 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.
+ * Any trailing '/' and '\' characters in the given path will be trimmed.
*
- * @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
+ * @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 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)) {
+ $alias = '@' . $alias;
+ }
+ $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]);
+ }
}
}
/**
* 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
@@ -261,43 +320,58 @@ 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 && ($fullPath = stream_resolve_include_path($path)) !== false) {
+ $classFile = $fullPath;
+ }
+
+ 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");
}
}
@@ -305,16 +379,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:
*
@@ -357,7 +431,7 @@ class YiiBase
}
if (!class_exists($class, false)) {
- $class = static::import($class, true);
+ $class = static::import($class);
}
$class = ltrim($class, '\\');
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/base/Application.php b/framework/base/Application.php
index e1c1d60..c498a8e 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:
- *
- *
load application configuration;
- *
set up class autoloader and error handling;
- *
load static application components;
- *
{@link beforeRequest}: preprocess the user request; `beforeRequest` event raised.
- *
{@link processRequest}: process the user request;
- *
{@link afterRequest}: postprocess the user request; `afterRequest` event raised.
- *
- *
- * 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
*/
@@ -87,12 +57,10 @@ class Application extends Module
*/
public $layout = 'main';
- private $_runtimePath;
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;
@@ -112,8 +80,8 @@ 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.');
}
@@ -158,30 +126,6 @@ class Application extends Module
}
/**
- * Handles fatal PHP errors
- */
- public function handleFatalError()
- {
- if (YII_ENABLE_ERROR_HANDLER) {
- $error = error_get_last();
-
- if (ErrorException::isFatalErorr($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)
@@ -224,6 +168,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'.
@@ -243,12 +189,35 @@ 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");
+ }
+ }
+
+ 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.
+ */
+ public function setVendorPath($path)
+ {
+ $this->_vendorPath = Yii::getAlias($path);
}
/**
@@ -359,6 +328,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`.
@@ -389,41 +397,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);
}
}
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/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));
}
diff --git a/framework/base/ErrorHandler.php b/framework/base/ErrorHandler.php
index a2f372c..98a061d 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) {
@@ -76,6 +81,12 @@ 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);
+ }
+
$view = new View;
if (!YII_DEBUG || $exception instanceof UserException) {
$viewName = $this->errorView;
@@ -196,6 +207,10 @@ class ErrorHandler extends Component
echo '
' . $output . '
';
}
+ /**
+ * Renders calls stack trace
+ * @param array $trace
+ */
public function renderTrace($trace)
{
$count = 0;
@@ -233,6 +248,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/Module.php b/framework/base/Module.php
index 2ccf61d..d99778d 100644
--- a/framework/base/Module.php
+++ b/framework/base/Module.php
@@ -207,11 +207,17 @@ 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)
{
- $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,20 +288,7 @@ abstract class Module extends Component
*/
public function setLayoutPath($path)
{
- $this->_layoutPath = FileHelper::ensureDirectory($path);
- }
-
- /**
- * 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);
- }
+ $this->_layoutPath = Yii::getAlias($path);
}
/**
@@ -606,14 +599,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/Response.php b/framework/base/Response.php
index a53fd61..396b073 100644
--- a/framework/base/Response.php
+++ b/framework/base/Response.php
@@ -13,28 +13,38 @@ 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();
}
- public function cleanOutput()
- {
- ob_clean();
- }
-
- public function removeOutput($all = true)
+ /**
+ * Discards the output buffer
+ * @param boolean $all if true recursively discards all output buffers used
+ */
+ public function cleanOutput($all = true)
{
if ($all) {
for ($level = ob_get_level(); $level > 0; --$level) {
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/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/base/View.php b/framework/base/View.php
index 8b1f4ef..10a7053 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 $content;
+ 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,31 +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 clips. You can call [[beginClip()]] and [[endClip()]]
- * to capture small fragments of a view. They can be later accessed at somewhere else
+ * @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 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.
+ * 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;
/**
@@ -87,11 +158,6 @@ 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);
- } else {
- $this->content = new ViewContent;
- }
}
/**
@@ -165,7 +231,6 @@ class View extends Component
} else {
$output = $this->renderPhpFile($viewFile, $params);
}
- $output = $this->content->populate($output);
$this->afterRender($viewFile, $output);
}
@@ -241,7 +306,7 @@ class View extends Component
{
if (!empty($this->cacheStack)) {
$n = count($this->dynamicPlaceholders);
- $placeholder = "";
+ $placeholder = "";
$this->addDynamicPlaceholder($placeholder, $statements);
return $placeholder;
} else {
@@ -350,26 +415,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();
}
@@ -446,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 circular 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 circular 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 6a3b489..0000000
--- a/framework/base/ViewContent.php
+++ /dev/null
@@ -1,136 +0,0 @@
-
- * @since 2.0
- */
-class ViewContent extends Component
-{
- const POS_HEAD = 1;
- const POS_BEGIN = 2;
- const POS_END = 3;
-
- /**
- * @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',
- * ),
- * )
- * ~~~
- */
- public $bundles;
-
- public $title;
- public $metaTags;
- public $linkTags;
- public $css;
- public $js;
- public $cssFiles;
- public $jsFiles;
-
- public function populate($content)
- {
- return $content;
- }
-
- public function reset()
- {
- $this->title = null;
- $this->metaTags = null;
- $this->linkTags = null;
- $this->css = null;
- $this->js = null;
- $this->cssFiles = null;
- $this->jsFiles = null;
- }
-
- public function registerBundle($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/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/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.
*
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) {
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/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/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/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/Application.php b/framework/console/Application.php
index 574495b..2f28cac 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',
+ 'asset' => 'yii\console\controllers\AssetController',
);
}
diff --git a/framework/console/controllers/AppController.php b/framework/console/controllers/AppController.php
index 93ef5f5..a47acfe 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) {
@@ -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)
{
@@ -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.
+ * @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', '.hgignore')) || 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/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/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..5c883e6 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 @@
+
-
+
- context->pageTitle?>
+ title)?>
-
context->pageTitle?>
+
title)?>
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);
}
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 '';
- }, $string);
- }
-
- /**
- * TODO syntax copied from https://github.com/pear/Console_Color2/blob/master/Console/Color2.php
- *
- * Converts colorcodes in the format %y (for yellow) into ansi-control
- * codes. The conversion table is: ('bold' meaning 'light' on some
- * terminals). It's almost the same conversion table irssi uses.
- *
- * text text background
- * ------------------------------------------------
- * %k %K %0 black dark grey black
- * %r %R %1 red bold red red
- * %g %G %2 green bold green green
- * %y %Y %3 yellow bold yellow yellow
- * %b %B %4 blue bold blue blue
- * %m %M %5 magenta bold magenta magenta
- * %p %P magenta (think: purple)
- * %c %C %6 cyan bold cyan cyan
- * %w %W %7 white bold white white
- *
- * %F Blinking, Flashing
- * %U Underline
- * %8 Reverse
- * %_,%9 Bold
- *
- * %n Resets the color
- * %% A single %
- *
- * First param is the string to convert, second is an optional flag if
- * colors should be used. It defaults to true, if set to false, the
- * colorcodes will just be removed (And %% will be transformed into %)
- *
- * @param string $string String to convert
- * @param bool $colored Should the string be colored?
- *
- * @return string
- */
- public static function renderColoredString($string)
- {
- $colored = true;
-
-
- static $conversions = array ( // static so the array doesn't get built
- // everytime
- // %y - yellow, and so on... {{{
- '%y' => array('color' => 'yellow'),
- '%g' => array('color' => 'green' ),
- '%b' => array('color' => 'blue' ),
- '%r' => array('color' => 'red' ),
- '%p' => array('color' => 'purple'),
- '%m' => array('color' => 'purple'),
- '%c' => array('color' => 'cyan' ),
- '%w' => array('color' => 'grey' ),
- '%k' => array('color' => 'black' ),
- '%n' => array('color' => 'reset' ),
- '%Y' => array('color' => 'yellow', 'style' => 'light'),
- '%G' => array('color' => 'green', 'style' => 'light'),
- '%B' => array('color' => 'blue', 'style' => 'light'),
- '%R' => array('color' => 'red', 'style' => 'light'),
- '%P' => array('color' => 'purple', 'style' => 'light'),
- '%M' => array('color' => 'purple', 'style' => 'light'),
- '%C' => array('color' => 'cyan', 'style' => 'light'),
- '%W' => array('color' => 'grey', 'style' => 'light'),
- '%K' => array('color' => 'black', 'style' => 'light'),
- '%N' => array('color' => 'reset', 'style' => 'light'),
- '%3' => array('background' => 'yellow'),
- '%2' => array('background' => 'green' ),
- '%4' => array('background' => 'blue' ),
- '%1' => array('background' => 'red' ),
- '%5' => array('background' => 'purple'),
- '%6' => array('background' => 'cyan' ),
- '%7' => array('background' => 'grey' ),
- '%0' => array('background' => 'black' ),
- // Don't use this, I can't stand flashing text
- '%F' => array('style' => 'blink'),
- '%U' => array('style' => 'underline'),
- '%8' => array('style' => 'inverse'),
- '%9' => array('style' => 'bold'),
- '%_' => array('style' => 'bold')
- // }}}
- );
-
- if ($colored) {
- $string = str_replace('%%', '% ', $string);
- foreach ($conversions as $key => $value) {
- $string = str_replace($key, Console_Color::color($value),
- $string);
- }
- $string = str_replace('% ', '%', $string);
-
- } else {
- $string = preg_replace('/%((%)|.)/', '$2', $string);
- }
-
- return $string;
- }
-
- /**
- * Escapes % so they don't get interpreted as color codes
- *
- * @param string $string String to escape
- *
- * @access public
- * @return string
- */
- public static function escape($string)
- {
- return str_replace('%', '%%', $string);
- }
}
diff --git a/framework/helpers/FileHelper.php b/framework/helpers/FileHelper.php
index f850b98..3fb24e1 100644
--- a/framework/helpers/FileHelper.php
+++ b/framework/helpers/FileHelper.php
@@ -9,9 +9,6 @@
namespace yii\helpers;
-use yii\base\Exception;
-use yii\base\InvalidConfigException;
-
/**
* Filesystem helper
*
@@ -19,256 +16,6 @@ use yii\base\InvalidConfigException;
* @author Alex Makarov
* @since 2.0
*/
-class FileHelper
+class FileHelper extends base\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
- * will be normalized as '/home/demo'.
- * @param string $path the file/directory path to be normalized
- * @param string $ds the directory separator to be used in the normalized result. Defaults to `DIRECTORY_SEPARATOR`.
- * @return string the normalized file/directory path
- */
- public static function normalizePath($path, $ds = DIRECTORY_SEPARATOR)
- {
- return rtrim(strtr($path, array('/' => $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$name>";
- }
- }
-
- /**
- * 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 "$name>";
- }
-
- /**
- * 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 '';
+ }, $string);
+ }
+
+ /**
+ * TODO syntax copied from https://github.com/pear/Console_Color2/blob/master/Console/Color2.php
+ *
+ * Converts colorcodes in the format %y (for yellow) into ansi-control
+ * codes. The conversion table is: ('bold' meaning 'light' on some
+ * terminals). It's almost the same conversion table irssi uses.
+ *
+ * text text background
+ * ------------------------------------------------
+ * %k %K %0 black dark grey black
+ * %r %R %1 red bold red red
+ * %g %G %2 green bold green green
+ * %y %Y %3 yellow bold yellow yellow
+ * %b %B %4 blue bold blue blue
+ * %m %M %5 magenta bold magenta magenta
+ * %p %P magenta (think: purple)
+ * %c %C %6 cyan bold cyan cyan
+ * %w %W %7 white bold white white
+ *
+ * %F Blinking, Flashing
+ * %U Underline
+ * %8 Reverse
+ * %_,%9 Bold
+ *
+ * %n Resets the color
+ * %% A single %
+ *
*
- * @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');
- /**
- * @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;
- /**
- * @var string base web accessible path for storing private files
*/
- private $_basePath;
- /**
- * @var string base URL for accessing the publishing directory.
- */
- private $_baseUrl;
- /**
- * @var array published assets
- */
- private $_published=array();
+ public $dirMode = 0777;
/**
- * @return string the root directory storing the published asset files. Defaults to 'WebRoot/assets'.
+ * Initializes the component.
+ * @throws InvalidConfigException if [[basePath]] is invalid
*/
- 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->basePath = realpath($this->basePath);
+ }
+ $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;
+ }
}
- 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
+ * 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 return an array
+ * that lists the bundles used by the "foo/button" extension. The array format is the same as [[bundles]].
+ *
+ * @param string $name the bundle name
+ * @return AssetBundle the loaded bundle object. Null is returned if the bundle does not exist.
*/
- public function setBasePath($value)
+ public function getBundle($name)
{
- 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)));
+ if (!isset($this->bundles[$name])) {
+ $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])) {
+ return null;
+ }
+ }
+ 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];
}
+ private $_converter;
+
/**
- * @return string the base url that the published asset files can be accessed.
- * Note, the ending slashes are stripped off. Defaults to '/AppBaseUrl/assets'.
+ * Returns the asset converter.
+ * @return IAssetConverter the asset converter.
*/
- public function getBaseUrl()
+ public function getConverter()
{
- if($this->_baseUrl===null)
- {
- $request=\Yii::$app->getRequest();
- $this->setBaseUrl($request->getBaseUrl().'/'.self::DEFAULT_BASEPATH);
+ 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->_baseUrl;
+ return $this->_converter;
}
/**
- * @param string $value the base url that the published asset files can be accessed
+ * 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 setBaseUrl($value)
+ public function setConverter($value)
{
- $this->_baseUrl=rtrim($value,'/');
+ $this->_converter = $value;
}
/**
+ * @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).
+ *
+ * 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
@@ -157,85 +192,85 @@ class CAssetManager extends CApplicationComponent
* 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
- * 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.
+ * @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,$hashByName=false,$level=-1,$forceCopy=false)
+ public function publish($path, $options = array())
{
- 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;
+ }
- 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");
+ }
- return $this->_published[$path]=$this->getBaseUrl()."/$dir/$fileName";
+ if (is_file($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->dirMode, true);
}
- 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);
+ if ($this->linkAssets) {
+ if (!is_file($dstFile)) {
+ symlink($src, $dstFile);
}
- else if(!is_dir($dstDir) || $forceCopy)
- {
- CFileHelper::copyDirectory($src,$dstDir,array(
- 'exclude'=>$this->excludeFiles,
- 'level'=>$level,
- 'newDirMode'=>$this->newDirMode,
- 'newFileMode'=>$this->newFileMode,
- ));
+ } elseif (@filemtime($dstFile) < @filemtime($src)) {
+ copy($src, $dstFile);
+ if ($this->fileMode !== null) {
+ @chmod($dstFile, $this->fileMode);
}
+ }
- return $this->_published[$path]=$this->getBaseUrl().'/'.$dir;
+ return $this->_published[$path] = array($dstFile, $this->baseUrl . "/$dir/$fileName");
+ } else {
+ $dir = $this->hash($src . filemtime($src));
+ $dstDir = $this->basePath . DIRECTORY_SEPARATOR . $dir;
+ if ($this->linkAssets) {
+ if (!is_dir($dstDir)) {
+ symlink($src, $dstDir);
+ }
+ } elseif (!is_dir($dstDir) || !empty($options['forceCopy'])) {
+ $opts = array(
+ 'dirMode' => $this->dirMode,
+ 'fileMode' => $this->fileMode,
+ );
+ if (isset($options['beforeCopy'])) {
+ $opts['beforeCopy'] = $options['beforeCopy'];
+ } else {
+ $opts['beforeCopy'] = function ($from, $to) {
+ return strncmp(basename($from), '.', 1) !== 0;
+ };
+ }
+ if (isset($options['afterCopy'])) {
+ $opts['afterCopy'] = $options['afterCopy'];
+ }
+ FileHelper::copyDirectory($src, $dstDir, $opts);
}
+
+ return $this->_published[$path] = array($dstDir, $this->baseUrl . '/' . $dir);
}
- throw new CException(Yii::t('yii|The asset "{asset}" to be published does not exist.',
- array('{asset}'=>$path)));
}
/**
@@ -243,24 +278,20 @@ class CAssetManager extends CApplicationComponent
* 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->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->basePath . DIRECTORY_SEPARATOR;
+ if (is_file($path)) {
+ return $base . $this->hash(dirname($path) . filemtime($path)) . DIRECTORY_SEPARATOR . basename($path);
+ } else {
+ return $base . $this->hash($path . filemtime($path));
+ }
+ } else {
return false;
+ }
}
/**
@@ -268,25 +299,22 @@ class CAssetManager extends CApplicationComponent
* 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]))
+ 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->baseUrl . '/' . $this->hash(dirname($path) . filemtime($path)) . '/' . basename($path);
+ } else {
+ return $this->baseUrl . '/' . $this->hash($path . filemtime($path));
+ }
+ } else {
return false;
+ }
}
/**
@@ -297,6 +325,6 @@ class CAssetManager extends CApplicationComponent
*/
protected function hash($path)
{
- return sprintf('%x',crc32($path.Yii::getVersion()));
+ return sprintf('%x', crc32($path . Yii::getVersion()));
}
}
diff --git a/framework/web/IAssetConverter.php b/framework/web/IAssetConverter.php
new file mode 100644
index 0000000..4334d3e
--- /dev/null
+++ b/framework/web/IAssetConverter.php
@@ -0,0 +1,27 @@
+
+ * @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/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())
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;
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));
}
}
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);
}
}
diff --git a/framework/widgets/ActiveForm.php b/framework/widgets/ActiveForm.php
index 48bc181..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.
@@ -64,7 +70,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) {
@@ -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);
diff --git a/framework/widgets/Clip.php b/framework/widgets/Block.php
similarity index 54%
rename from framework/widgets/Clip.php
rename to framework/widgets/Block.php
index f321209..d6f7317 100644
--- a/framework/widgets/Clip.php
+++ b/framework/widgets/Block.php
@@ -7,28 +7,26 @@
namespace yii\widgets;
-use Yii;
use yii\base\Widget;
-use yii\base\View;
/**
* @author Qiang Xue
* @since 2.0
*/
-class Clip extends Widget
+class Block extends Widget
{
/**
- * @var string the ID of this clip.
+ * @var string the ID of this block.
*/
public $id;
/**
- * @var boolean whether to render the clip content in place. Defaults to false,
- * meaning the captured clip will not be displayed.
+ * @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 clip.
+ * Starts recording a block.
*/
public function init()
{
@@ -37,15 +35,15 @@ class Clip extends Widget
}
/**
- * Ends recording a clip.
- * This method stops output buffering and saves the rendering result as a named clip in the controller.
+ * Ends recording a block.
+ * This method stops output buffering and saves the rendering result as a named block in the controller.
*/
public function run()
{
- $clip = ob_get_clean();
- if ($this->renderClip) {
- echo $clip;
+ $block = ob_get_clean();
+ if ($this->renderInPlace) {
+ echo $block;
}
- $this->view->clips[$this->id] = $clip;
+ $this->view->blocks[$this->id] = $block;
}
}
\ 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();
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');
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()));
}
}
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();
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'));
}
}
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()