diff --git a/framework/base/Application.php b/framework/base/Application.php index 5a16283..e3fa808 100644 --- a/framework/base/Application.php +++ b/framework/base/Application.php @@ -73,7 +73,7 @@ use yii\base\Exception; * @author Qiang Xue * @since 2.0 */ -class Application extends Module +abstract class Application extends Module { /** * @var string the application name. Defaults to 'My Application'. @@ -95,7 +95,12 @@ class Application extends Module * to ensure errors and exceptions can be handled nicely. */ public $preload = array('errorHandler'); + /** + * @var Controller the currently active controller instance + */ + public $controller; + // todo public $localeDataPath = '@yii/i18n/data'; private $_runtimePath; @@ -118,9 +123,8 @@ class Application extends Module } /** - * Initializes the module. - * This method is called after the module is created and initialized with property values - * given in configuration. + * Initializes the application by loading components declared in [[preload]]. + * If you override this method, make sure the parent implementation is invoked. */ public function init() { @@ -129,16 +133,10 @@ class Application extends Module /** * Runs the application. - * This method loads static application components. Derived classes usually overrides this - * method to do more application-specific tasks. - * Remember to call the parent implementation so that static application components are loaded. + * This is the main entrance of an application. Derived classes must implement this method. + * @return integer the exit status (0 means normal, non-zero values mean abnormal) */ - public function run() - { - $this->beforeRequest(); - $this->processRequest(); - $this->afterRequest(); - } + abstract public function run(); /** * Terminates the application. @@ -167,20 +165,33 @@ class Application extends Module } /** - * Processes the request. - * This is the place where the actual request processing work is done. - * Derived classes should override this method. + * Raises the [[afterRequest]] event right AFTER the application processes the request. */ - public function processRequest() + public function afterRequest() { + $this->trigger('afterRequest'); } /** - * Raises the [[afterRequest]] event right AFTER the application processes the request. + * Processes the request. + * The request is represented in terms of a controller route and action parameters. + * @param string $route the route of the request. It may contain module ID, controller ID, and/or action ID. + * @param array $params parameters to be bound to the controller action. + * @return integer the exit status of the controller action (0 means normal, non-zero values mean abnormal) + * @throws Exception if the route cannot be resolved into a controller */ - public function afterRequest() + public function processRequest($route, $params = array()) { - $this->trigger('afterRequest'); + $result = $this->parseRoute($route); + if ($result === null) { + throw new Exception(\Yii::t('yii', 'Unable to resolve the request.')); + } + list($controller, $action) = $result; + $oldController = $this->controller; + $this->controller = $controller; + $status = $controller->run($action, $params); + $this->controller = $oldController; + return $status; } /** @@ -189,12 +200,10 @@ class Application extends Module */ public function getRuntimePath() { - if ($this->_runtimePath !== null) { - return $this->_runtimePath; - } else { + if ($this->_runtimePath === null) { $this->setRuntimePath($this->getBasePath() . DIRECTORY_SEPARATOR . 'runtime'); - return $this->_runtimePath; } + return $this->_runtimePath; } /** @@ -205,7 +214,7 @@ class Application extends Module public function setRuntimePath($path) { if (!is_dir($path) || !is_writable($path)) { - throw new \yii\base\Exception("Application runtime path \"$path\" is invalid. Please make sure it is a directory writable by the Web server process."); + throw new Exception("Application runtime path \"$path\" is invalid. Please make sure it is a directory writable by the Web server process."); } $this->_runtimePath = $path; } @@ -226,7 +235,7 @@ class Application extends Module * By default, [[language]] and [[sourceLanguage]] are the same. * Do not set this property unless your application needs to support multiple languages. * @param string $language the user language (e.g. 'en_US', 'zh_CN'). - * If it is null, the {@link sourceLanguage} will be used. + * If it is null, the [[sourceLanguage]] will be used. */ public function setLanguage($language) { @@ -256,40 +265,6 @@ class Application extends Module } /** - * 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 - * named as the locale ID. For example, given the file "path/to/view.php" - * and locale ID "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. - * - * For consistency, it is recommended that the locale ID is given - * in lower case and in the format of LanguageID_RegionID (e.g. "en_us"). - * - * @param string $srcFile the original file - * @param string $srcLanguage the language that the original file is in. If null, the application {@link sourceLanguage source language} is used. - * @param string $language the desired language that the file should be localized to. If null, the {@link getLanguage application language} will be used. - * @return string the matching localized file. The original file is returned if no localized version is found - * or if source language is the same as the desired language. - */ - public function findLocalizedFile($srcFile, $srcLanguage = null, $language = null) - { - if ($srcLanguage === null) { - $srcLanguage = $this->sourceLanguage; - } - if ($language === null) { - $language = $this->getLanguage(); - } - if ($language === $srcLanguage) { - return $srcFile; - } - $desiredFile = dirname($srcFile) . DIRECTORY_SEPARATOR . $language . DIRECTORY_SEPARATOR . basename($srcFile); - return is_file($desiredFile) ? $desiredFile : $srcFile; - } - - /** * Returns the locale instance. * @param string $localeID the locale ID (e.g. en_US). If null, the {@link getLanguage application language ID} will be used. * @return CLocale the locale instance @@ -346,15 +321,6 @@ class Application extends Module } /** - * Returns the state persister component. - * @return CStatePersister the state persister application component. - */ - public function getStatePersister() - { - return $this->getComponent('statePersister'); - } - - /** * Returns the cache component. * @return \yii\caching\Cache the cache application component. Null if the component is not enabled. */ @@ -420,9 +386,6 @@ class Application extends Module 'securityManager' => array( 'class' => 'yii\base\SecurityManager', ), - 'statePersister' => array( - 'class' => 'yii\base\StatePersister', - ), )); } } diff --git a/framework/base/Controller.php b/framework/base/Controller.php index 64e8b00..c94c84f 100644 --- a/framework/base/Controller.php +++ b/framework/base/Controller.php @@ -27,7 +27,7 @@ namespace yii\base; * @author Qiang Xue * @since 2.0 */ -abstract class Controller extends Component implements Initable +class Controller extends Component implements Initable { /** * @var string ID of this controller @@ -98,36 +98,33 @@ abstract class Controller extends Component implements Initable } /** - * Creates an action with the specified ID and runs it. - * If the action does not exist, [[missingAction()]] will be invoked. - * @param string $actionID action ID + * Runs the controller with the specified action and parameters. + * @param Action|string $action the action to be executed. This can be either an action object + * or the ID of the action. + * @param array $params the parameters to be passed to the action. + * If null, the result of [[getActionParams()]] will be used as action parameters. + * Note that the parameters must be name-value pairs with the names corresponding to + * the parameter names as declared by the action. * @return integer the exit status of the action. 0 means normal, other values mean abnormal. - * @see createAction - * @see runAction * @see missingAction + * @see createAction */ - public function run($actionID) + public function run($action, $params = null) { - if (($action = $this->createAction($actionID)) !== null) { - return $this->runAction($action); - } else { - $this->missingAction($actionID); - return 1; + if (is_string($action)) { + if (($a = $this->createAction($action)) !== null) { + $action = $a; + } else { + $this->missingAction($action); + return 1; + } } - } - /** - * Runs the action. - * @param Action $action action to run - * @return integer the exit status of the action. 0 means normal, other values mean abnormal. - */ - public function runAction($action) - { $priorAction = $this->action; $this->action = $action; $exitStatus = 1; if ($this->authorize($action)) { - $params = $action->normalizeParams($this->getActionParams()); + $params = $action->normalizeParams($params === null ? $this->getActionParams() : $params); if ($params !== false) { if ($this->beforeAction($action)) { $exitStatus = (int)call_user_func_array(array($action, 'run'), $params); @@ -223,17 +220,21 @@ abstract class Controller extends Component implements Initable * @param string $route the route of the new controller action. This can be an action ID, or a complete route * with module ID (optional in the current module), controller ID and action ID. If the former, * the action is assumed to be located within the current controller. + * @param array $params the parameters to be passed to the action. + * If null, the result of [[getActionParams()]] will be used as action parameters. + * Note that the parameters must be name-value pairs with the names corresponding to + * the parameter names as declared by the action. * @param boolean $exit whether to end the application after this call. Defaults to true. */ - public function forward($route, $exit = true) + public function forward($route, $params = array(), $exit = true) { if (strpos($route, '/') === false) { - $status = $this->run($route); + $status = $this->run($route, $params); } else { if ($route[0] !== '/' && !$this->module instanceof Application) { $route = '/' . $this->module->getUniqueId() . '/' . $route; } - $status = \Yii::$application->runController($route); + $status = \Yii::$application->dispatch($route, $params); } if ($exit) { \Yii::$application->end($status); diff --git a/framework/base/Module.php b/framework/base/Module.php index c183abf..eaa5adb 100644 --- a/framework/base/Module.php +++ b/framework/base/Module.php @@ -43,10 +43,54 @@ abstract class Module extends Component implements Initable * @var Module the parent module of this module. Null if this module does not have a parent. */ public $module; - - private $_basePath; - private $_modules = array(); - private $_components = array(); + /** + * @var array mapping from controller ID to controller configurations. + * Each name-value pair specifies the configuration of a single controller. + * A controller configuration can be either a string or an array. + * If the former, the string should be the class name or path alias of the controller. + * If the latter, the array must contain a 'class' element which specifies + * the controller's class name or path alias, and the rest of the name-value pairs + * in the array are used to initialize the corresponding controller properties. For example, + * + * ~~~ + * array( + * 'account' => '@application/controllers/UserController', + * 'article' => array( + * 'class' => '@application/controllers/PostController', + * 'pageTitle' => 'something new', + * ), + * ) + * ~~~ + */ + public $controllers = array(); + /** + * @return string the default route of this module. Defaults to 'default'. + * The route may consist of child module ID, controller ID, and/or action ID. + * For example, `help`, `post/create`, `admin/post/create`. + * If action ID is not given, it will take the default value as specified in + * [[Controller::defaultAction]]. + */ + public $defaultRoute = 'default'; + /** + * @var string the root directory of the module. + * @see getBasePath + * @see setBasePath + */ + protected $_basePath; + /** + * @var string the directory containing controller classes in the module. + * @see getControllerPath + * @see setControllerPath + */ + protected $_controllerPath; + /** + * @var array child modules of this module + */ + protected $_modules = array(); + /** + * @var array application components of this module + */ + protected $_components = array(); /** * Constructor. @@ -118,7 +162,8 @@ abstract class Module extends Component implements Initable /** * Returns the root directory of the module. - * @return string the root directory of the module. Defaults to the directory containing the module class file. + * It defaults to the directory containing the module class file. + * @return string the root directory of the module. */ public function getBasePath() { @@ -132,7 +177,7 @@ abstract class Module extends Component implements Initable /** * 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. + * @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. */ public function setBasePath($path) @@ -146,6 +191,36 @@ abstract class Module extends Component implements Initable } /** + * Returns the directory that contains the controller classes. + * Defaults to "[[basePath]]/controllers". + * @return string the directory that contains the controller classes. + */ + public function getControllerPath() + { + if ($this->_controllerPath !== null) { + return $this->_controllerPath; + } else { + return $this->_controllerPath = $this->getBasePath() . DIRECTORY_SEPARATOR . 'controllers'; + } + } + + /** + * Sets the directory that contains the controller classes. + * @param string $path the directory that contains the controller classes. + * This can be either a directory name or a path alias. + * @throws Exception if the directory is invalid + */ + public function setControllerPath($path) + { + $p = \Yii::getAlias($path); + if ($p === false || !is_dir($p)) { + throw new Exception('Invalid controller path: ' . $path); + } else { + $this->_controllerPath = realpath($p); + } + } + + /** * 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()]]. @@ -404,4 +479,83 @@ abstract class Module extends Component implements Initable $this->getComponent($id); } } + + /** + * Parses a given route into controller and action ID. + * The parsing process follows the following algorithm: + * + * 1. Get the first segment in route + * 2. If the segment matches + * - an ID in [[controllers]], create a controller instance using the corresponding configuration, + * and return the controller with the rest part of the route; + * - a controller class under [[controllerPath]], create the controller instance, and return it + * with the rest part of the route; + * - an ID in [[modules]], let the corresponding module to parse the rest part of the route. + * + * @param string $route the route which may consist module ID, controller ID and/or action ID (e.g. `post/create`) + * @return array|null the controller instance and action ID. Null if the route cannot be resolved. + */ + public function parseRoute($route) + { + if (($route = trim($route, '/')) === '') { + $route = $this->defaultRoute; + } + + if (($pos = strpos($route, '/')) !== false) { + $id = substr($route, 0, $pos); + $route = (string)substr($route, $pos + 1); + } else { + $id = $route; + $route = ''; + } + + $controller = $this->createController($id); + if ($controller !== null) { + return array($controller, $route); + } + + if (($module = $this->getModule($id)) !== null) { + return $module->parseRoute($route); + } + + return null; + } + + /** + * Creates a controller instance according to the specified controller ID. + * For security reasons, the controller ID must start with a lower-case letter + * and consist of word characters only. + * + * This method will first match the controller ID in [[controllers]]. If found, + * it will create an instance of controller using the corresponding configuration. + * If not found, it will look for a controller class named `XyzController` under + * the [[controllerPath]] directory, where `xyz` is the controller ID with the first + * letter in upper-case. + * @param string $id the controller ID + * @return Controller|null the created controller instance. Null if the controller ID is invalid. + */ + public function createController($id) + { + // Controller IDs must start with a lower-case letter and consist of word characters only + if (!preg_match('/^[a-z][a-zA-Z0-9_]*$/', $id)) { + return null; + } + + if (isset($this->controllers[$id])) { + return \Yii::createObject($this->controllers[$id], $id, $this); + } + + $className = ucfirst($id) . 'Controller'; + $classFile = $this->getControllerPath() . DIRECTORY_SEPARATOR . $className . '.php'; + if (is_file($classFile)) { + if (!class_exists($className, false)) { + require($classFile); + } + if (class_exists($className, false) && is_subclass_of($className, '\yii\base\Controller')) { + return $className::newInstance(array(), $id, $this); + } + } + + return null; + } } diff --git a/framework/base/SecurityManager.php b/framework/base/SecurityManager.php index b55cba3..53c6a9c 100644 --- a/framework/base/SecurityManager.php +++ b/framework/base/SecurityManager.php @@ -1,21 +1,22 @@ * @link http://www.yiiframework.com/ - * @copyright Copyright © 2008-2011 Yii Software LLC + * @copyright Copyright © 2008-2012 Yii Software LLC * @license http://www.yiiframework.com/license/ */ +namespace yii\base; + /** - * CSecurityManager provides private keys, hashing and encryption functions. + * SecurityManager provides private keys, hashing and encryption functions. * - * CSecurityManager is used by Yii components and applications for security-related purpose. + * SecurityManager is used by Yii components and applications for security-related purpose. * For example, it is used in cookie validation feature to prevent cookie data * from being tampered. * - * CSecurityManager is mainly used to protect data from being tampered and viewed. + * SecurityManager is mainly used to protect data from being tampered and viewed. * It can generate HMAC and encrypt the data. The private key used to generate HMAC * is set by {@link setValidationKey ValidationKey}. The key used to encrypt data is * specified by {@link setEncryptionKey EncryptionKey}. If the above keys are not @@ -30,7 +31,7 @@ * respectively, which uses 3DES encryption algorithm. Note, the PHP Mcrypt * extension must be installed and loaded. * - * CSecurityManager is a core application component that can be accessed via + * SecurityManager is a core application component that can be accessed via * {@link CApplication::getSecurityManager()}. * * @property string $validationKey The private key used to generate HMAC. @@ -44,10 +45,10 @@ * @package system.base * @since 1.0 */ -class CSecurityManager extends CApplicationComponent +class SecurityManager extends ApplicationComponent { - const STATE_VALIDATION_KEY='Yii.CSecurityManager.validationkey'; - const STATE_ENCRYPTION_KEY='Yii.CSecurityManager.encryptionkey'; + const STATE_VALIDATION_KEY = 'Yii.SecurityManager.validationkey'; + const STATE_ENCRYPTION_KEY = 'Yii.SecurityManager.encryptionkey'; /** * @var string the name of the hashing algorithm to be used by {@link computeHMAC}. @@ -57,7 +58,7 @@ class CSecurityManager extends CApplicationComponent * Defaults to 'sha1', meaning using SHA1 hash algorithm. * @since 1.1.3 */ - public $hashAlgorithm='sha1'; + public $hashAlgorithm = 'sha1'; /** * @var mixed the name of the crypt algorithm to be used by {@link encrypt} and {@link decrypt}. * This will be passed as the first parameter to {@link http://php.net/manual/en/function.mcrypt-module-open.php mcrypt_module_open}. @@ -68,7 +69,7 @@ class CSecurityManager extends CApplicationComponent * Defaults to 'des', meaning using DES crypt algorithm. * @since 1.1.3 */ - public $cryptAlgorithm='des'; + public $cryptAlgorithm = 'des'; private $_validationKey; private $_encryptionKey; @@ -77,7 +78,7 @@ class CSecurityManager extends CApplicationComponent public function init() { parent::init(); - $this->_mbstring=extension_loaded('mbstring'); + $this->_mbstring = extension_loaded('mbstring'); } /** @@ -85,7 +86,7 @@ class CSecurityManager extends CApplicationComponent */ protected function generateRandomKey() { - return sprintf('%08x%08x%08x%08x',mt_rand(),mt_rand(),mt_rand(),mt_rand()); + return sprintf('%08x%08x%08x%08x', mt_rand(), mt_rand(), mt_rand(), mt_rand()); } /** @@ -94,17 +95,17 @@ class CSecurityManager extends CApplicationComponent */ public function getValidationKey() { - if($this->_validationKey!==null) + if ($this->_validationKey !== null) { return $this->_validationKey; - else - { - if(($key=Yii::app()->getGlobalState(self::STATE_VALIDATION_KEY))!==null) + } + else { + if (($key = Yii::app()->getGlobalState(self::STATE_VALIDATION_KEY)) !== null) { $this->setValidationKey($key); - else - { - $key=$this->generateRandomKey(); + } + else { + $key = $this->generateRandomKey(); $this->setValidationKey($key); - Yii::app()->setGlobalState(self::STATE_VALIDATION_KEY,$key); + Yii::app()->setGlobalState(self::STATE_VALIDATION_KEY, $key); } return $this->_validationKey; } @@ -116,10 +117,12 @@ class CSecurityManager extends CApplicationComponent */ public function setValidationKey($value) { - if(!empty($value)) - $this->_validationKey=$value; - else - throw new CException(Yii::t('yii','CSecurityManager.validationKey cannot be empty.')); + if (!empty($value)) { + $this->_validationKey = $value; + } + else { + throw new CException(Yii::t('yii', 'SecurityManager.validationKey cannot be empty.')); + } } /** @@ -128,17 +131,17 @@ class CSecurityManager extends CApplicationComponent */ public function getEncryptionKey() { - if($this->_encryptionKey!==null) + if ($this->_encryptionKey !== null) { return $this->_encryptionKey; - else - { - if(($key=Yii::app()->getGlobalState(self::STATE_ENCRYPTION_KEY))!==null) + } + else { + if (($key = Yii::app()->getGlobalState(self::STATE_ENCRYPTION_KEY)) !== null) { $this->setEncryptionKey($key); - else - { - $key=$this->generateRandomKey(); + } + else { + $key = $this->generateRandomKey(); $this->setEncryptionKey($key); - Yii::app()->setGlobalState(self::STATE_ENCRYPTION_KEY,$key); + Yii::app()->setGlobalState(self::STATE_ENCRYPTION_KEY, $key); } return $this->_encryptionKey; } @@ -150,10 +153,12 @@ class CSecurityManager extends CApplicationComponent */ public function setEncryptionKey($value) { - if(!empty($value)) - $this->_encryptionKey=$value; - else - throw new CException(Yii::t('yii','CSecurityManager.encryptionKey cannot be empty.')); + if (!empty($value)) { + $this->_encryptionKey = $value; + } + else { + throw new CException(Yii::t('yii', 'SecurityManager.encryptionKey cannot be empty.')); + } } /** @@ -173,7 +178,7 @@ class CSecurityManager extends CApplicationComponent */ public function setValidation($value) { - $this->hashAlgorithm=$value; + $this->hashAlgorithm = $value; } /** @@ -183,14 +188,14 @@ class CSecurityManager extends CApplicationComponent * @return string the encrypted data * @throws CException if PHP Mcrypt extension is not loaded */ - public function encrypt($data,$key=null) + public function encrypt($data, $key = null) { - $module=$this->openCryptModule(); - $key=$this->substr($key===null ? md5($this->getEncryptionKey()) : $key,0,mcrypt_enc_get_key_size($module)); + $module = $this->openCryptModule(); + $key = $this->substr($key === null ? md5($this->getEncryptionKey()) : $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); + $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; @@ -203,17 +208,17 @@ class CSecurityManager extends CApplicationComponent * @return string the decrypted data * @throws CException if PHP Mcrypt extension is not loaded */ - public function decrypt($data,$key=null) + public function decrypt($data, $key = null) { - $module=$this->openCryptModule(); - $key=$this->substr($key===null ? md5($this->getEncryptionKey()) : $key,0,mcrypt_enc_get_key_size($module)); - $ivSize=mcrypt_enc_get_iv_size($module); - $iv=$this->substr($data,0,$ivSize); - mcrypt_generic_init($module,$key,$iv); - $decrypted=mdecrypt_generic($module,$this->substr($data,$ivSize,$this->strlen($data))); + $module = $this->openCryptModule(); + $key = $this->substr($key === null ? md5($this->getEncryptionKey()) : $key, 0, mcrypt_enc_get_key_size($module)); + $ivSize = mcrypt_enc_get_iv_size($module); + $iv = $this->substr($data, 0, $ivSize); + mcrypt_generic_init($module, $key, $iv); + $decrypted = mdecrypt_generic($module, $this->substr($data, $ivSize, $this->strlen($data))); mcrypt_generic_deinit($module); mcrypt_module_close($module); - return rtrim($decrypted,"\0"); + return rtrim($decrypted, "\0"); } /** @@ -223,20 +228,23 @@ class CSecurityManager extends CApplicationComponent */ protected function openCryptModule() { - if(extension_loaded('mcrypt')) - { - if(is_array($this->cryptAlgorithm)) - $module=@call_user_func_array('mcrypt_module_open',$this->cryptAlgorithm); - else - $module=@mcrypt_module_open($this->cryptAlgorithm,'', MCRYPT_MODE_CBC,''); + if (extension_loaded('mcrypt')) { + if (is_array($this->cryptAlgorithm)) { + $module = @call_user_func_array('mcrypt_module_open', $this->cryptAlgorithm); + } + else { + $module = @mcrypt_module_open($this->cryptAlgorithm, '', MCRYPT_MODE_CBC, ''); + } - if($module===false) - throw new CException(Yii::t('yii','Failed to initialize the mcrypt module.')); + if ($module === false) { + throw new CException(Yii::t('yii', 'Failed to initialize the mcrypt module.')); + } return $module; } - else - throw new CException(Yii::t('yii','CSecurityManager requires PHP mcrypt extension to be loaded in order to use data encryption feature.')); + else { + throw new CException(Yii::t('yii', 'SecurityManager requires PHP mcrypt extension to be loaded in order to use data encryption feature.')); + } } /** @@ -245,9 +253,9 @@ class CSecurityManager extends CApplicationComponent * @param string $key the private key to be used for generating HMAC. Defaults to null, meaning using {@link validationKey}. * @return string data prefixed with HMAC */ - public function hashData($data,$key=null) + public function hashData($data, $key = null) { - return $this->computeHMAC($data,$key).$data; + return $this->computeHMAC($data, $key) . $data; } /** @@ -258,17 +266,17 @@ class CSecurityManager extends CApplicationComponent * @return string the real data with HMAC stripped off. False if the data * is tampered. */ - public function validateData($data,$key=null) + public function validateData($data, $key = null) { - $len=$this->strlen($this->computeHMAC('test')); - if($this->strlen($data)>=$len) - { - $hmac=$this->substr($data,0,$len); - $data2=$this->substr($data,$len,$this->strlen($data)); - return $hmac===$this->computeHMAC($data2,$key)?$data2:false; + $len = $this->strlen($this->computeHMAC('test')); + if ($this->strlen($data) >= $len) { + $hmac = $this->substr($data, 0, $len); + $data2 = $this->substr($data, $len, $this->strlen($data)); + return $hmac === $this->computeHMAC($data2, $key) ? $data2 : false; } - else + else { return false; + } } /** @@ -277,29 +285,31 @@ class CSecurityManager extends CApplicationComponent * @param string $key the private key to be used for generating HMAC. Defaults to null, meaning using {@link validationKey}. * @return string the HMAC for the data */ - protected function computeHMAC($data,$key=null) + protected function computeHMAC($data, $key = null) { - if($key===null) - $key=$this->getValidationKey(); + if ($key === null) { + $key = $this->getValidationKey(); + } - if(function_exists('hash_hmac')) + if (function_exists('hash_hmac')) { return hash_hmac($this->hashAlgorithm, $data, $key); + } - if(!strcasecmp($this->hashAlgorithm,'sha1')) - { - $pack='H40'; - $func='sha1'; + if (!strcasecmp($this->hashAlgorithm, 'sha1')) { + $pack = 'H40'; + $func = 'sha1'; + } + else { + $pack = 'H32'; + $func = 'md5'; + } + if ($this->strlen($key) > 64) { + $key = pack($pack, $func($key)); } - else - { - $pack='H32'; - $func='md5'; + if ($this->strlen($key) < 64) { + $key = str_pad($key, 64, chr(0)); } - if($this->strlen($key) > 64) - $key=pack($pack, $func($key)); - if($this->strlen($key) < 64) - $key=str_pad($key, 64, chr(0)); - $key=$this->substr($key,0,64); + $key = $this->substr($key, 0, 64); return $func((str_repeat(chr(0x5C), 64) ^ $key) . pack($pack, $func((str_repeat(chr(0x36), 64) ^ $key) . $data))); } @@ -311,7 +321,7 @@ class CSecurityManager extends CApplicationComponent */ private function strlen($string) { - return $this->_mbstring ? mb_strlen($string,'8bit') : strlen($string); + return $this->_mbstring ? mb_strlen($string, '8bit') : strlen($string); } /** @@ -322,8 +332,8 @@ class CSecurityManager extends CApplicationComponent * @param int $length the desired portion length * @return string the extracted part of string, or FALSE on failure or an empty string. */ - private function substr($string,$start,$length) + private function substr($string, $start, $length) { - return $this->_mbstring ? mb_substr($string,$start,$length,'8bit') : substr($string,$start,$length); + return $this->_mbstring ? mb_substr($string, $start, $length, '8bit') : substr($string, $start, $length); } } diff --git a/framework/console/Application.php b/framework/console/Application.php index 167e008..8dcc476 100644 --- a/framework/console/Application.php +++ b/framework/console/Application.php @@ -2,7 +2,6 @@ /** * Console Application class file. * - * @author Qiang Xue * @link http://www.yiiframework.com/ * @copyright Copyright © 2008-2012 Yii Software LLC * @license http://www.yiiframework.com/license/ @@ -10,33 +9,37 @@ namespace yii\console; +use yii\base\Exception; + +// fcgi doesn't have STDIN defined by default +defined('STDIN') or define('STDIN', fopen('php://stdin', 'r')); + /** - * \yii\console\Application represents a console application. + * Application represents a console application. * - * \yii\console\Application extends {@link \yii\base\Application} by providing functionalities + * Application extends from [[yii\base\Application]] by providing functionalities that are * specific to console requests. In particular, it deals with console requests * through a command-based approach: - *
    - *
  • A console application consists of one or several possible user commands;
  • - *
  • Each user command is implemented as a class extending {@link \yii\console\Command};
  • - *
  • User specifies which command to run on the command line;
  • - *
  • The command processes the user request with the specified parameters.
  • - *
* - * The command classes reside in the directory {@link getCommandPath commandPath}. - * The name of the class follows the pattern: <command-name>Command, and its - * file name is the same the class name. For example, the 'ShellCommand' class defines - * a 'shell' command and the class file name is 'ShellCommand.php'. + * - A console application consists of one or several possible user commands; + * - Each user command is implemented as a class extending [[Command]]; + * - User specifies which command to run on the command line; + * - The command processes the user request with the specified parameters. + * + * The command classes reside in the directory specified by [[commandPath]]. + * The name of the class should be of the form `Command` (e.g. `HelpCommand`). * * To run the console application, enter the following on the command line: - *
- * php path/to/entry_script.php  [param 1] [param 2] ...
- * 
* - * You may use the following to see help instructions about a command: - *
- * php path/to/entry_script.php help 
- * 
+ * ~~~ + * yiic [param 1] [param 2] ... + * ~~~ + * + * You may use the following line to see help instructions about a command: + * + * ~~~ + * yiic help + * ~~~ * * @property string $commandPath The directory that contains the command classes. Defaults to 'protected/commands'. * @property CommandRunner $commandRunner The command runner. @@ -47,135 +50,148 @@ namespace yii\console; class Application extends \yii\base\Application { /** - * @var array mapping from command name to command configurations. - * Each command configuration can be either a string or an array. - * If the former, the string should be the file path of the command class. - * If the latter, the array must contain a 'class' element which specifies - * the command's class name or {@link \YiiBase::getPathOfAlias class path alias}. - * The rest name-value pairs in the array are used to initialize - * the corresponding command properties. For example, - *
-	 * array(
-	 *   'email'=>array(
-	 *      'class'=>'path.to.Mailer',
-	 *      'interval'=>3600,
-	 *   ),
-	 *   'log'=>'path/to/LoggerCommand.php',
-	 * )
-	 * 
+ * @var string the default route of this application. Defaults to 'help', + * meaning the `help` command. */ - public $commandMap=array(); - - private $_commandPath; - + public $defaultRoute = 'help'; /** - * @var \yii\console\CommandRunner + * @var boolean whether to enable the commands provided by the core framework. + * Defaults to true. */ - private $_runner; + public $enableCoreCommands = true; - /** - * Initializes the application by creating the command runner. - */ public function init() { - if(!isset($_SERVER['argv'])) - { - die('This script must be run from the command line.'); + parent::init(); + if ($this->enableCoreCommands) { + foreach ($this->coreCommands() as $id => $command) { + if (!isset($this->controllers[$id])) { + $this->controllers[$id] = $command; + } + } + } + // ensure we have the 'help' command so that we can list the available commands + if ($this->defaultRoute === 'help' && !isset($this->controllers['help'])) { + $this->controllers['help'] = 'yii\console\commands\HelpController'; } - $this->_runner=$this->createCommandRunner(); - $this->_runner->commands=$this->commandMap; - $this->_runner->addCommands($this->getCommandPath()); } /** - * Processes the user request. - * This method creates a console command runner to handle the particular user command. + * Runs the application. + * This is the main entrance of an application. + * @return integer the exit status (0 means normal, non-zero values mean abnormal) */ - public function processRequest() + public function run() { - $this->_runner->run($_SERVER['argv']); + if (!isset($_SERVER['argv'])) { + die('This script must be run from the command line.'); + } + list($route, $params) = $this->resolveRequest($_SERVER['argv']); + $this->beforeRequest(); + $status = $this->processRequest($route, $params); + $this->afterRequest(); + return $status; } /** - * Creates the command runner instance. - * @return CommandRunner the command runner + * Resolves the request. + * @param array $args the arguments passed via the command line + * @return array the controller route and the parameters for the controller action */ - protected function createCommandRunner() + protected function resolveRequest($args) { - return new CommandRunner(); - } + array_shift($args); // the 1st argument is the yiic script name - /** - * Displays the captured PHP error. - * This method displays the error in console mode when there is - * no active error handler. - * @param integer $code error code - * @param string $message error message - * @param string $file error file - * @param string $line error line - */ - public function displayError($code,$message,$file,$line) - { - echo "PHP Error[$code]: $message\n"; - echo " in file $file at line $line\n"; - $trace=debug_backtrace(); - // skip the first 4 stacks as they do not tell the error position - if(count($trace)>4) - $trace=array_slice($trace,4); - foreach($trace as $i=>$t) - { - if(!isset($t['file'])) - $t['file']='unknown'; - if(!isset($t['line'])) - $t['line']=0; - if(!isset($t['function'])) - $t['function']='unknown'; - echo "#$i {$t['file']}({$t['line']}): "; - if(isset($t['object']) && is_object($t['object'])) - echo get_class($t['object']).'->'; - echo "{$t['function']}()\n"; + if (isset($args[0])) { + $route = $args[0]; + array_shift($args); + } else { + $route = ''; + } + + $params = array(); + foreach ($args as $arg) { + if (preg_match('/^--(\w+)(=(.*))?$/', $arg, $matches)) { + $name = $matches[1]; + $params[$name] = isset($matches[3]) ? $matches[3] : true; + } else { + $params['args'][] = $arg; + } } + + return array($route, $params); } /** - * Displays the uncaught PHP exception. - * This method displays the exception in console mode when there is - * no active error handler. - * @param Exception $exception the uncaught exception + * Searches for commands under the specified directory. + * @param string $path the directory containing the command class files. + * @return array list of commands (command name=>command class file) */ - public function displayException($exception) + public function findCommands($path) { - echo $exception; + if (($dir = @opendir($path)) === false) + return array(); + $commands = array(); + while (($name = readdir($dir)) !== false) { + $file = $path . DIRECTORY_SEPARATOR . $name; + if (!strcasecmp(substr($name, -11), 'Command.php') && is_file($file)) + $commands[strtolower(substr($name, 0, -11))] = $file; + } + closedir($dir); + return $commands; } /** - * @return string the directory that contains the command classes. Defaults to 'protected/commands'. + * Adds commands from the specified command path. + * If a command already exists, the new one will be ignored. + * @param string $path the alias of the directory containing the command class files. */ - public function getCommandPath() + public function addCommands($path) { - $applicationCommandPath = $this->getBasePath().DIRECTORY_SEPARATOR.'commands'; - if($this->_commandPath===null && file_exists($applicationCommandPath)) - $this->setCommandPath($applicationCommandPath); - return $this->_commandPath; + if (($commands = $this->findCommands($path)) !== array()) { + foreach ($commands as $name => $file) { + if (!isset($this->commands[$name])) + $this->commands[$name] = $file; + } + } } /** - * @param string $value the directory that contains the command classes. - * @throws CException if the directory is invalid + * @param string $name command name (case-insensitive) + * @return CConsoleCommand the command object. Null if the name is invalid. */ - public function setCommandPath($value) + public function createCommand($name) { - if(($this->_commandPath=realpath($value))===false || !is_dir($this->_commandPath)) - throw new \yii\base\Exception(Yii::t('yii','The command path "{path}" is not a valid directory.', - array('{path}'=>$value))); + $name = strtolower($name); + if (isset($this->commands[$name])) { + if (is_string($this->commands[$name])) // class file path or alias + { + if (strpos($this->commands[$name], '/') !== false || strpos($this->commands[$name], '\\') !== false) { + $className = substr(basename($this->commands[$name]), 0, -4); + if (!class_exists($className, false)) + require_once($this->commands[$name]); + } + else // an alias + $className = Yii::import($this->commands[$name]); + return new $className($name, $this); + } + else // an array configuration + return Yii::createComponent($this->commands[$name], $name, $this); + } + else if ($name === 'help') + return new CHelpCommand('help', $this); + else + return null; } - /** - * Returns the command runner. - * @return CConsoleCommandRunner the command runner. - */ - public function getCommandRunner() + public function coreCommands() { - return $this->_runner; + return array( + 'message' => 'yii\console\commands\MessageController', + 'help' => 'yii\console\commands\HelpController', + 'migrate' => 'yii\console\commands\MigrateController', + 'shell' => 'yii\console\commands\ShellController', + 'app' => 'yii\console\commands\AppController', + ); } } diff --git a/framework/console/Command.php b/framework/console/Command.php index e2ca243..cd4f746 100644 --- a/framework/console/Command.php +++ b/framework/console/Command.php @@ -2,9 +2,8 @@ /** * Command class file. * - * @author Qiang Xue * @link http://www.yiiframework.com/ - * @copyright Copyright © 2008-2011 Yii Software LLC + * @copyright Copyright © 2008-2012 Yii Software LLC * @license http://www.yiiframework.com/license/ */ @@ -45,37 +44,9 @@ namespace yii\console; * @author Qiang Xue * @since 2.0 */ -abstract class Command extends \yii\base\Component +class Command extends \yii\base\Controller { /** - * @var string the name of the default action. Defaults to 'index'. - */ - public $defaultAction='index'; - - private $_name; - private $_runner; - - /** - * Constructor. - * @param string $name name of the command - * @param CConsoleCommandRunner $runner the command runner - */ - public function __construct($name,$runner) - { - $this->_name=$name; - $this->_runner=$runner; - } - - /** - * Initializes the command object. - * This method is invoked after a command object is created and initialized with configurations. - * You may override this method to further customize the command before it executes. - */ - public function init() - { - } - - /** * Executes the command. * The default implementation will parse the input parameters and * dispatch the command request to an appropriate action with the corresponding @@ -84,115 +55,86 @@ abstract class Command extends \yii\base\Component */ public function run($args) { - list($action, $options, $args)=$this->resolveRequest($args); - $methodName='action'.$action; - if(!preg_match('/^\w+$/',$action) || !method_exists($this,$methodName)) - $this->usageError("Unknown action: ".$action); + list($action, $options, $args) = $this->resolveRequest($args); + $methodName = 'action' . $action; + if (!preg_match('/^\w+$/', $action) || !method_exists($this, $methodName)) + { + $this->usageError("Unknown action: " . $action); + } - $method=new \ReflectionMethod($this,$methodName); - $params=array(); + $method = new \ReflectionMethod($this, $methodName); + $params = array(); // named and unnamed options - foreach($method->getParameters() as $param) - { - $name=$param->getName(); - if(isset($options[$name])) - { - if($param->isArray()) - $params[]=is_array($options[$name]) ? $options[$name] : array($options[$name]); - else if(!is_array($options[$name])) - $params[]=$options[$name]; + foreach ($method->getParameters() as $param) { + $name = $param->getName(); + if (isset($options[$name])) { + if ($param->isArray()) + $params[] = is_array($options[$name]) ? $options[$name] : array($options[$name]); + else if (!is_array($options[$name])) + $params[] = $options[$name]; else $this->usageError("Option --$name requires a scalar. Array is given."); - } else if($name==='args') - $params[]=$args; - else if($param->isDefaultValueAvailable()) - $params[]=$param->getDefaultValue(); + } else if ($name === 'args') + $params[] = $args; + else if ($param->isDefaultValueAvailable()) + $params[] = $param->getDefaultValue(); else $this->usageError("Missing required option --$name."); unset($options[$name]); } // try global options - if(!empty($options)) - { - $class=new \ReflectionClass(get_class($this)); - foreach($options as $name=>$value) - { - if($class->hasProperty($name)) - { - $property=$class->getProperty($name); - if($property->isPublic() && !$property->isStatic()) - { - $this->$name=$value; + if (!empty($options)) { + $class = new \ReflectionClass(get_class($this)); + foreach ($options as $name => $value) { + if ($class->hasProperty($name)) { + $property = $class->getProperty($name); + if ($property->isPublic() && !$property->isStatic()) { + $this->$name = $value; unset($options[$name]); } } } } - if(!empty($options)) - $this->usageError("Unknown options: ".implode(', ',array_keys($options))); + if (!empty($options)) + $this->usageError("Unknown options: " . implode(', ', array_keys($options))); - if($this->beforeAction($action,$params)) - { - $method->invokeArgs($this,$params); - $this->afterAction($action,$params); + if ($this->beforeAction($action, $params)) { + $method->invokeArgs($this, $params); + $this->afterAction($action, $params); } } /** - * This method is invoked right before an action is to be executed. - * You may override this method to do last-minute preparation for the action. - * @param string $action the action name - * @param array $params the parameters to be passed to the action method. - * @return boolean whether the action should be executed. - */ - protected function beforeAction($action,$params) - { - return true; - } - - /** - * This method is invoked right after an action finishes execution. - * You may override this method to do some postprocessing for the action. - * @param string $action the action name - * @param array $params the parameters to be passed to the action method. - */ - protected function afterAction($action,$params) - { - } - - /** * Parses the command line arguments and determines which action to perform. * @param array $args command line arguments * @return array the action name, named options (name=>value), and unnamed options */ protected function resolveRequest($args) { - $options=array(); // named parameters - $params=array(); // unnamed parameters - foreach($args as $arg) - { - if(preg_match('/^--(\w+)(=(.*))?$/',$arg,$matches)) // an option + $options = array(); // named parameters + $params = array(); // unnamed parameters + foreach ($args as $arg) { + if (preg_match('/^--(\w+)(=(.*))?$/', $arg, $matches)) // an option { - $name=$matches[1]; - $value=isset($matches[3]) ? $matches[3] : true; - if(isset($options[$name])) - { - if(!is_array($options[$name])) - $options[$name]=array($options[$name]); - $options[$name][]=$value; + $name = $matches[1]; + $value = isset($matches[3]) ? $matches[3] : true; + if (isset($options[$name])) { + if (!is_array($options[$name])) + $options[$name] = array($options[$name]); + $options[$name][] = $value; } else - $options[$name]=$value; - } else if(isset($action)) - $params[]=$arg; + $options[$name] = $value; + } else if (isset($action)) + $params[] = $arg; else - $action=$arg; + $action = $arg; } - if(!isset($action)) - $action=$this->defaultAction; + if (!isset($action)) + $action = $this->defaultAction; - return array($action,$options,$params); + return array($action, $options, $params); } /** @@ -204,29 +146,21 @@ abstract class Command extends \yii\base\Component } /** - * @return \yii\console\CommandRunner the command runner instance - */ - public function getCommandRunner() - { - return $this->_runner; - } - - /** * Provides the command description. * This method may be overridden to return the actual command description. * @return string the command description. Defaults to 'Usage: php entry-script.php command-name'. */ public function getHelp() { - $help='Usage: '.$this->getCommandRunner()->getScriptName().' '.$this->getName(); - $options=$this->getOptionHelp(); - if(empty($options)) + $help = 'Usage: ' . $this->getCommandRunner()->getScriptName() . ' ' . $this->getName(); + $options = $this->getOptionHelp(); + if (empty($options)) return $help; - if(count($options)===1) - return $help.' '.$options[0]; - $help.=" \nActions:\n"; - foreach($options as $option) - $help.=' '.$option."\n"; + if (count($options) === 1) + return $help . ' ' . $options[0]; + $help .= " \nActions:\n"; + foreach ($options as $option) + $help .= ' ' . $option . "\n"; return $help; } @@ -239,31 +173,28 @@ abstract class Command extends \yii\base\Component */ public function getOptionHelp() { - $options=array(); - $class=new \ReflectionClass(get_class($this)); - foreach($class->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) - { - $name=$method->getName(); - if(!strncasecmp($name,'action',6) && strlen($name)>6) - { - $name=substr($name,6); - $name[0]=strtolower($name[0]); - $help=$name; + $options = array(); + $class = new \ReflectionClass(get_class($this)); + foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) { + $name = $method->getName(); + if (!strncasecmp($name, 'action', 6) && strlen($name) > 6) { + $name = substr($name, 6); + $name[0] = strtolower($name[0]); + $help = $name; - foreach($method->getParameters() as $param) - { - $optional=$param->isDefaultValueAvailable(); - $defaultValue=$optional ? $param->getDefaultValue() : null; - $name=$param->getName(); - if($optional) - $help.=" [--$name=$defaultValue]"; + foreach ($method->getParameters() as $param) { + $optional = $param->isDefaultValueAvailable(); + $defaultValue = $optional ? $param->getDefaultValue() : null; + $name = $param->getName(); + if ($optional) + $help .= " [--$name=$defaultValue]"; else - $help.=" --$name=value"; + $help .= " --$name=value"; } - $options[]=$help; - } - } - return $options; + $options[] = $help; + } + } + return $options; } /** @@ -273,7 +204,7 @@ abstract class Command extends \yii\base\Component */ public function usageError($message) { - echo "Error: $message\n\n".$this->getHelp()."\n"; + echo "Error: $message\n\n" . $this->getHelp() . "\n"; exit(1); } @@ -284,14 +215,13 @@ abstract class Command extends \yii\base\Component * @param boolean $_return_ whether to return the rendering result instead of displaying it * @return mixed the rendering result if required. Null otherwise. */ - public function renderFile($_viewFile_,$_data_=null,$_return_=false) + public function renderFile($_viewFile_, $_data_ = null, $_return_ = false) { - if(is_array($_data_)) - extract($_data_,EXTR_PREFIX_SAME,'data'); + if (is_array($_data_)) + extract($_data_, EXTR_PREFIX_SAME, 'data'); else - $data=$_data_; - if($_return_) - { + $data = $_data_; + if ($_return_) { ob_start(); ob_implicit_flush(false); require($_viewFile_); @@ -310,29 +240,28 @@ abstract class Command extends \yii\base\Component */ public function prompt($message, $default = null) { - if($default !== null) { + if ($default !== null) { $message .= " [$default] "; } else { $message .= ' '; } - if(extension_loaded('readline')) - { + if (extension_loaded('readline')) { $input = readline($message); - if($input) { + if ($input) { readline_add_history($input); } } else { echo $message; $input = fgets(STDIN); } - if($input === false) { + if ($input === false) { return false; } else { $input = trim($input); - return ($input==='' && $default!==null) ? $default : $input; + return ($input === '' && $default !== null) ? $default : $input; } } @@ -344,7 +273,7 @@ abstract class Command extends \yii\base\Component */ public function confirm($message) { - echo $message.' [yes|no] '; - return !strncasecmp(trim(fgets(STDIN)),'y',1); + echo $message . ' [yes|no] '; + return !strncasecmp(trim(fgets(STDIN)), 'y', 1); } } \ No newline at end of file diff --git a/framework/console/CommandRunner.php b/framework/console/CommandRunner.php deleted file mode 100644 index ba2c041..0000000 --- a/framework/console/CommandRunner.php +++ /dev/null @@ -1,138 +0,0 @@ - - * @link http://www.yiiframework.com/ - * @copyright Copyright © 2008-2011 Yii Software LLC - * @license http://www.yiiframework.com/license/ - */ - -namespace yii\console; - -/** - * CConsoleCommandRunner manages commands and executes the requested command. - * - * @property string $scriptName The entry script name. - * - * @author Qiang Xue - * @since 2.0 - */ -class CommandRunner extends \yii\base\Component -{ - /** - * @var array list of all available commands (command name=>command configuration). - * Each command configuration can be either a string or an array. - * If the former, the string should be the class name or - * {@link YiiBase::getPathOfAlias class path alias} of the command. - * If the latter, the array must contain a 'class' element which specifies - * the command's class name or {@link YiiBase::getPathOfAlias class path alias}. - * The rest name-value pairs in the array are used to initialize - * the corresponding command properties. For example, - *
-	 * array(
-	 *   'email'=>array(
-	 *      'class'=>'path.to.Mailer',
-	 *      'interval'=>3600,
-	 *   ),
-	 *   'log'=>'path.to.LoggerCommand',
-	 * )
-	 * 
- */ - public $commands=array(); - - private $_scriptName; - - /** - * Executes the requested command. - * @param array $args list of user supplied parameters (including the entry script name and the command name). - */ - public function run($args) - { - $this->_scriptName=$args[0]; - array_shift($args); - if(isset($args[0])) - { - $name=$args[0]; - array_shift($args); - } else - $name='help'; - - if(($command=$this->createCommand($name))===null) - $command=$this->createCommand('help'); - $command->init(); - $command->run($args); - } - - /** - * @return string the entry script name - */ - public function getScriptName() - { - return $this->_scriptName; - } - - /** - * Searches for commands under the specified directory. - * @param string $path the directory containing the command class files. - * @return array list of commands (command name=>command class file) - */ - public function findCommands($path) - { - if(($dir=@opendir($path))===false) - return array(); - $commands=array(); - while(($name=readdir($dir))!==false) - { - $file=$path.DIRECTORY_SEPARATOR.$name; - if(!strcasecmp(substr($name,-11),'Command.php') && is_file($file)) - $commands[strtolower(substr($name,0,-11))]=$file; - } - closedir($dir); - return $commands; - } - - /** - * Adds commands from the specified command path. - * If a command already exists, the new one will be ignored. - * @param string $path the alias of the directory containing the command class files. - */ - public function addCommands($path) - { - if(($commands=$this->findCommands($path))!==array()) - { - foreach($commands as $name=>$file) - { - if(!isset($this->commands[$name])) - $this->commands[$name]=$file; - } - } - } - - /** - * @param string $name command name (case-insensitive) - * @return \yii\console\Command the command object. Null if the name is invalid. - */ - public function createCommand($name) - { - $name=strtolower($name); - if(isset($this->commands[$name])) - { - if(is_string($this->commands[$name])) // class file path or alias - { - if(strpos($this->commands[$name],'/')!==false || strpos($this->commands[$name],'\\')!==false) - { - $className=substr(basename($this->commands[$name]),0,-4); - if(!class_exists($className,false)) - require_once($this->commands[$name]); - } else // an alias - $className=\Yii::import($this->commands[$name]); - return new $className($name,$this); - } else // an array configuration - return \Yii::create($this->commands[$name],$name,$this); - } else if($name==='help') - return new HelpCommand('help',$this); - else - return null; - } -} \ No newline at end of file diff --git a/framework/console/commands/AppController.php b/framework/console/commands/AppController.php new file mode 100644 index 0000000..0f3f846 --- /dev/null +++ b/framework/console/commands/AppController.php @@ -0,0 +1,129 @@ + + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + * @version $Id$ + */ + +/** + * WebAppCommand creates an Yii Web application at the specified location. + * + * @author Qiang Xue + * @version $Id$ + * @package system.cli.commands + * @since 1.0 + */ +class WebAppCommand extends CConsoleCommand +{ + private $_rootPath; + + public function getHelp() + { + return << + +DESCRIPTION + This command generates an Yii Web Application at the specified location. + +PARAMETERS + * app-path: required, the directory where the new application will be created. + If the directory does not exist, it will be created. After the application + is created, please make sure the directory can be accessed by Web users. + +EOD; + } + + /** + * Execute the action. + * @param array command line parameters specific for this command + */ + public function run($args) + { + if(!isset($args[0])) + $this->usageError('the Web application location is not specified.'); + $path=strtr($args[0],'/\\',DIRECTORY_SEPARATOR); + if(strpos($path,DIRECTORY_SEPARATOR)===false) + $path='.'.DIRECTORY_SEPARATOR.$path; + $dir=rtrim(realpath(dirname($path)),'\\/'); + if($dir===false || !is_dir($dir)) + $this->usageError("The directory '$path' is not valid. Please make sure the parent directory exists."); + if(basename($path)==='.') + $this->_rootPath=$path=$dir; + else + $this->_rootPath=$path=$dir.DIRECTORY_SEPARATOR.basename($path); + if($this->confirm("Create a Web application under '$path'?")) + { + $sourceDir=realpath(dirname(__FILE__).'/../views/webapp'); + if($sourceDir===false) + die("\nUnable to locate the source directory.\n"); + $list=$this->buildFileList($sourceDir,$path); + $list['index.php']['callback']=array($this,'generateIndex'); + $list['index-test.php']['callback']=array($this,'generateIndex'); + $list['protected/tests/bootstrap.php']['callback']=array($this,'generateTestBoostrap'); + $list['protected/yiic.php']['callback']=array($this,'generateYiic'); + $this->copyFiles($list); + @chmod($path.'/assets',0777); + @chmod($path.'/protected/runtime',0777); + @chmod($path.'/protected/data',0777); + @chmod($path.'/protected/data/testdrive.db',0777); + @chmod($path.'/protected/yiic',0755); + echo "\nYour application has been created successfully under {$path}.\n"; + } + } + + public function generateIndex($source,$params) + { + $content=file_get_contents($source); + $yii=realpath(dirname(__FILE__).'/../../yii.php'); + $yii=$this->getRelativePath($yii,$this->_rootPath.DIRECTORY_SEPARATOR.'index.php'); + $yii=str_replace('\\','\\\\',$yii); + return preg_replace('/\$yii\s*=(.*?);/',"\$yii=$yii;",$content); + } + + public function generateTestBoostrap($source,$params) + { + $content=file_get_contents($source); + $yii=realpath(dirname(__FILE__).'/../../yiit.php'); + $yii=$this->getRelativePath($yii,$this->_rootPath.DIRECTORY_SEPARATOR.'protected'.DIRECTORY_SEPARATOR.'tests'.DIRECTORY_SEPARATOR.'bootstrap.php'); + $yii=str_replace('\\','\\\\',$yii); + return preg_replace('/\$yiit\s*=(.*?);/',"\$yiit=$yii;",$content); + } + + public function generateYiic($source,$params) + { + $content=file_get_contents($source); + $yiic=realpath(dirname(__FILE__).'/../../yiic.php'); + $yiic=$this->getRelativePath($yiic,$this->_rootPath.DIRECTORY_SEPARATOR.'protected'.DIRECTORY_SEPARATOR.'yiic.php'); + $yiic=str_replace('\\','\\\\',$yiic); + return preg_replace('/\$yiic\s*=(.*?);/',"\$yiic=$yiic;",$content); + } + + protected function getRelativePath($path1,$path2) + { + $segs1=explode(DIRECTORY_SEPARATOR,$path1); + $segs2=explode(DIRECTORY_SEPARATOR,$path2); + $n1=count($segs1); + $n2=count($segs2); + + for($i=0;$i<$n1 && $i<$n2;++$i) + { + if($segs1[$i]!==$segs2[$i]) + break; + } + + if($i===0) + return "'".$path1."'"; + $up=''; + for($j=$i;$j<$n2-1;++$j) + $up.='/..'; + for(;$i<$n1-1;++$i) + $up.='/'.$segs1[$i]; + + return 'dirname(__FILE__).\''.$up.'/'.basename($path1).'\''; + } +} \ No newline at end of file diff --git a/framework/console/HelpCommand.php b/framework/console/commands/HelpController.php similarity index 100% rename from framework/console/HelpCommand.php rename to framework/console/commands/HelpController.php diff --git a/framework/console/commands/MessageController.php b/framework/console/commands/MessageController.php new file mode 100644 index 0000000..8b8595b --- /dev/null +++ b/framework/console/commands/MessageController.php @@ -0,0 +1,223 @@ + + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * MessageCommand extracts messages to be translated from source files. + * The extracted messages are saved as PHP message source files + * under the specified directory. + * + * @author Qiang Xue + * @version $Id$ + * @package system.cli.commands + * @since 1.0 + */ +class MessageCommand extends CConsoleCommand +{ + public function getHelp() + { + return << + +DESCRIPTION + This command searches for messages to be translated in the specified + source files and compiles them into PHP arrays as message source. + +PARAMETERS + * config-file: required, the path of the configuration file. You can find + an example in framework/messages/config.php. + + The file can be placed anywhere and must be a valid PHP script which + returns an array of name-value pairs. Each name-value pair represents + a configuration option. + + The following options are available: + + - sourcePath: string, root directory of all source files. + - messagePath: string, root directory containing message translations. + - languages: array, list of language codes that the extracted messages + should be translated to. For example, array('zh_cn','en_au'). + - fileTypes: array, a list of file extensions (e.g. 'php', 'xml'). + Only the files whose extension name can be found in this list + will be processed. If empty, all files will be processed. + - exclude: array, a list of directory and file exclusions. Each + exclusion can be either a name or a path. If a file or directory name + or path matches the exclusion, it will not be copied. For example, + an exclusion of '.svn' will exclude all files and directories whose + name is '.svn'. And an exclusion of '/a/b' will exclude file or + directory 'sourcePath/a/b'. + - translator: the name of the function for translating messages. + Defaults to 'Yii::t'. This is used as a mark to find messages to be + translated. + - overwrite: if message file must be overwritten with the merged messages. + - removeOld: if message no longer needs translation it will be removed, + instead of being enclosed between a pair of '@@' marks. + - sort: sort messages by key when merging, regardless of their translation + state (new, obsolete, translated.) + +EOD; + } + + /** + * Execute the action. + * @param array command line parameters specific for this command + */ + public function run($args) + { + if(!isset($args[0])) + $this->usageError('the configuration file is not specified.'); + if(!is_file($args[0])) + $this->usageError("the configuration file {$args[0]} does not exist."); + + $config=require_once($args[0]); + $translator='Yii::t'; + extract($config); + + if(!isset($sourcePath,$messagePath,$languages)) + $this->usageError('The configuration file must specify "sourcePath", "messagePath" and "languages".'); + if(!is_dir($sourcePath)) + $this->usageError("The source path $sourcePath is not a valid directory."); + if(!is_dir($messagePath)) + $this->usageError("The message path $messagePath is not a valid directory."); + if(empty($languages)) + $this->usageError("Languages cannot be empty."); + + if(!isset($overwrite)) + $overwrite = false; + + if(!isset($removeOld)) + $removeOld = false; + + if(!isset($sort)) + $sort = false; + + $options=array(); + if(isset($fileTypes)) + $options['fileTypes']=$fileTypes; + if(isset($exclude)) + $options['exclude']=$exclude; + $files=CFileHelper::findFiles(realpath($sourcePath),$options); + + $messages=array(); + foreach($files as $file) + $messages=array_merge_recursive($messages,$this->extractMessages($file,$translator)); + + foreach($languages as $language) + { + $dir=$messagePath.DIRECTORY_SEPARATOR.$language; + if(!is_dir($dir)) + @mkdir($dir); + foreach($messages as $category=>$msgs) + { + $msgs=array_values(array_unique($msgs)); + $this->generateMessageFile($msgs,$dir.DIRECTORY_SEPARATOR.$category.'.php',$overwrite,$removeOld,$sort); + } + } + } + + protected function extractMessages($fileName,$translator) + { + echo "Extracting messages from $fileName...\n"; + $subject=file_get_contents($fileName); + $n=preg_match_all('/\b'.$translator.'\s*\(\s*(\'.*?(?$translation) + { + if(!isset($merged[$message]) && !isset($todo[$message]) && !$removeOld) + { + if(substr($translation,0,2)==='@@' && substr($translation,-2)==='@@') + $todo[$message]=$translation; + else + $todo[$message]='@@'.$translation.'@@'; + } + } + $merged=array_merge($todo,$merged); + if($sort) + ksort($merged); + if($overwrite === false) + $fileName.='.merged'; + echo "translation merged.\n"; + } + else + { + $merged=array(); + foreach($messages as $message) + $merged[$message]=''; + ksort($merged); + echo "saved.\n"; + } + $array=str_replace("\r",'',var_export($merged,true)); + $content=<< + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * MigrateCommand manages the database migrations. + * + * The implementation of this command and other supporting classes referenced + * the yii-dbmigrations extension ((https://github.com/pieterclaerhout/yii-dbmigrations), + * authored by Pieter Claerhout. + * + * @author Qiang Xue + * @version $Id$ + * @package system.cli.commands + * @since 1.1.6 + */ +class MigrateCommand extends CConsoleCommand +{ + const BASE_MIGRATION='m000000_000000_base'; + + /** + * @var string the directory that stores the migrations. This must be specified + * in terms of a path alias, and the corresponding directory must exist. + * Defaults to 'application.migrations' (meaning 'protected/migrations'). + */ + public $migrationPath='application.migrations'; + /** + * @var string the name of the table for keeping applied migration information. + * This table will be automatically created if not exists. Defaults to 'tbl_migration'. + * The table structure is: (version varchar(255) primary key, apply_time integer) + */ + public $migrationTable='tbl_migration'; + /** + * @var string the application component ID that specifies the database connection for + * storing migration information. Defaults to 'db'. + */ + public $connectionID='db'; + /** + * @var string the path of the template file for generating new migrations. This + * must be specified in terms of a path alias (e.g. application.migrations.template). + * If not set, an internal template will be used. + */ + public $templateFile; + /** + * @var string the default command action. It defaults to 'up'. + */ + public $defaultAction='up'; + /** + * @var boolean whether to execute the migration in an interactive mode. Defaults to true. + * Set this to false when performing migration in a cron job or background process. + */ + public $interactive=true; + + public function beforeAction($action,$params) + { + $path=Yii::getPathOfAlias($this->migrationPath); + if($path===false || !is_dir($path)) + die('Error: The migration directory does not exist: '.$this->migrationPath."\n"); + $this->migrationPath=$path; + + $yiiVersion=Yii::getVersion(); + echo "\nYii Migration Tool v1.0 (based on Yii v{$yiiVersion})\n\n"; + + return true; + } + + public function actionUp($args) + { + if(($migrations=$this->getNewMigrations())===array()) + { + echo "No new migration found. Your system is up-to-date.\n"; + return; + } + + $total=count($migrations); + $step=isset($args[0]) ? (int)$args[0] : 0; + if($step>0) + $migrations=array_slice($migrations,0,$step); + + $n=count($migrations); + if($n===$total) + echo "Total $n new ".($n===1 ? 'migration':'migrations')." to be applied:\n"; + else + echo "Total $n out of $total new ".($total===1 ? 'migration':'migrations')." to be applied:\n"; + + foreach($migrations as $migration) + echo " $migration\n"; + echo "\n"; + + if($this->confirm('Apply the above '.($n===1 ? 'migration':'migrations')."?")) + { + foreach($migrations as $migration) + { + if($this->migrateUp($migration)===false) + { + echo "\nMigration failed. All later migrations are canceled.\n"; + return; + } + } + echo "\nMigrated up successfully.\n"; + } + } + + public function actionDown($args) + { + $step=isset($args[0]) ? (int)$args[0] : 1; + if($step<1) + die("Error: The step parameter must be greater than 0.\n"); + + if(($migrations=$this->getMigrationHistory($step))===array()) + { + echo "No migration has been done before.\n"; + return; + } + $migrations=array_keys($migrations); + + $n=count($migrations); + echo "Total $n ".($n===1 ? 'migration':'migrations')." to be reverted:\n"; + foreach($migrations as $migration) + echo " $migration\n"; + echo "\n"; + + if($this->confirm('Revert the above '.($n===1 ? 'migration':'migrations')."?")) + { + foreach($migrations as $migration) + { + if($this->migrateDown($migration)===false) + { + echo "\nMigration failed. All later migrations are canceled.\n"; + return; + } + } + echo "\nMigrated down successfully.\n"; + } + } + + public function actionRedo($args) + { + $step=isset($args[0]) ? (int)$args[0] : 1; + if($step<1) + die("Error: The step parameter must be greater than 0.\n"); + + if(($migrations=$this->getMigrationHistory($step))===array()) + { + echo "No migration has been done before.\n"; + return; + } + $migrations=array_keys($migrations); + + $n=count($migrations); + echo "Total $n ".($n===1 ? 'migration':'migrations')." to be redone:\n"; + foreach($migrations as $migration) + echo " $migration\n"; + echo "\n"; + + if($this->confirm('Redo the above '.($n===1 ? 'migration':'migrations')."?")) + { + foreach($migrations as $migration) + { + if($this->migrateDown($migration)===false) + { + echo "\nMigration failed. All later migrations are canceled.\n"; + return; + } + } + foreach(array_reverse($migrations) as $migration) + { + if($this->migrateUp($migration)===false) + { + echo "\nMigration failed. All later migrations are canceled.\n"; + return; + } + } + echo "\nMigration redone successfully.\n"; + } + } + + public function actionTo($args) + { + if(isset($args[0])) + $version=$args[0]; + else + $this->usageError('Please specify which version to migrate to.'); + + $originalVersion=$version; + if(preg_match('/^m?(\d{6}_\d{6})(_.*?)?$/',$version,$matches)) + $version='m'.$matches[1]; + else + die("Error: The version option must be either a timestamp (e.g. 101129_185401)\nor the full name of a migration (e.g. m101129_185401_create_user_table).\n"); + + // try migrate up + $migrations=$this->getNewMigrations(); + foreach($migrations as $i=>$migration) + { + if(strpos($migration,$version.'_')===0) + { + $this->actionUp(array($i+1)); + return; + } + } + + // try migrate down + $migrations=array_keys($this->getMigrationHistory(-1)); + foreach($migrations as $i=>$migration) + { + if(strpos($migration,$version.'_')===0) + { + if($i===0) + echo "Already at '$originalVersion'. Nothing needs to be done.\n"; + else + $this->actionDown(array($i)); + return; + } + } + + die("Error: Unable to find the version '$originalVersion'.\n"); + } + + public function actionMark($args) + { + if(isset($args[0])) + $version=$args[0]; + else + $this->usageError('Please specify which version to mark to.'); + $originalVersion=$version; + if(preg_match('/^m?(\d{6}_\d{6})(_.*?)?$/',$version,$matches)) + $version='m'.$matches[1]; + else + die("Error: The version option must be either a timestamp (e.g. 101129_185401)\nor the full name of a migration (e.g. m101129_185401_create_user_table).\n"); + + $db=$this->getDbConnection(); + + // try mark up + $migrations=$this->getNewMigrations(); + foreach($migrations as $i=>$migration) + { + if(strpos($migration,$version.'_')===0) + { + if($this->confirm("Set migration history at $originalVersion?")) + { + $command=$db->createCommand(); + for($j=0;$j<=$i;++$j) + { + $command->insert($this->migrationTable, array( + 'version'=>$migrations[$j], + 'apply_time'=>time(), + )); + } + echo "The migration history is set at $originalVersion.\nNo actual migration was performed.\n"; + } + return; + } + } + + // try mark down + $migrations=array_keys($this->getMigrationHistory(-1)); + foreach($migrations as $i=>$migration) + { + if(strpos($migration,$version.'_')===0) + { + if($i===0) + echo "Already at '$originalVersion'. Nothing needs to be done.\n"; + else + { + if($this->confirm("Set migration history at $originalVersion?")) + { + $command=$db->createCommand(); + for($j=0;$j<$i;++$j) + $command->delete($this->migrationTable, $db->quoteColumnName('version').'=:version', array(':version'=>$migrations[$j])); + echo "The migration history is set at $originalVersion.\nNo actual migration was performed.\n"; + } + } + return; + } + } + + die("Error: Unable to find the version '$originalVersion'.\n"); + } + + public function actionHistory($args) + { + $limit=isset($args[0]) ? (int)$args[0] : -1; + $migrations=$this->getMigrationHistory($limit); + if($migrations===array()) + echo "No migration has been done before.\n"; + else + { + $n=count($migrations); + if($limit>0) + echo "Showing the last $n applied ".($n===1 ? 'migration' : 'migrations').":\n"; + else + echo "Total $n ".($n===1 ? 'migration has' : 'migrations have')." been applied before:\n"; + foreach($migrations as $version=>$time) + echo " (".date('Y-m-d H:i:s',$time).') '.$version."\n"; + } + } + + public function actionNew($args) + { + $limit=isset($args[0]) ? (int)$args[0] : -1; + $migrations=$this->getNewMigrations(); + if($migrations===array()) + echo "No new migrations found. Your system is up-to-date.\n"; + else + { + $n=count($migrations); + if($limit>0 && $n>$limit) + { + $migrations=array_slice($migrations,0,$limit); + echo "Showing $limit out of $n new ".($n===1 ? 'migration' : 'migrations').":\n"; + } + else + echo "Found $n new ".($n===1 ? 'migration' : 'migrations').":\n"; + + foreach($migrations as $migration) + echo " ".$migration."\n"; + } + } + + public function actionCreate($args) + { + if(isset($args[0])) + $name=$args[0]; + else + $this->usageError('Please provide the name of the new migration.'); + + if(!preg_match('/^\w+$/',$name)) + die("Error: The name of the migration must contain letters, digits and/or underscore characters only.\n"); + + $name='m'.gmdate('ymd_His').'_'.$name; + $content=strtr($this->getTemplate(), array('{ClassName}'=>$name)); + $file=$this->migrationPath.DIRECTORY_SEPARATOR.$name.'.php'; + + if($this->confirm("Create new migration '$file'?")) + { + file_put_contents($file, $content); + echo "New migration created successfully.\n"; + } + } + + public function confirm($message) + { + if(!$this->interactive) + return true; + return parent::confirm($message); + } + + protected function migrateUp($class) + { + if($class===self::BASE_MIGRATION) + return; + + echo "*** applying $class\n"; + $start=microtime(true); + $migration=$this->instantiateMigration($class); + if($migration->up()!==false) + { + $this->getDbConnection()->createCommand()->insert($this->migrationTable, array( + 'version'=>$class, + 'apply_time'=>time(), + )); + $time=microtime(true)-$start; + echo "*** applied $class (time: ".sprintf("%.3f",$time)."s)\n\n"; + } + else + { + $time=microtime(true)-$start; + echo "*** failed to apply $class (time: ".sprintf("%.3f",$time)."s)\n\n"; + return false; + } + } + + protected function migrateDown($class) + { + if($class===self::BASE_MIGRATION) + return; + + echo "*** reverting $class\n"; + $start=microtime(true); + $migration=$this->instantiateMigration($class); + if($migration->down()!==false) + { + $db=$this->getDbConnection(); + $db->createCommand()->delete($this->migrationTable, $db->quoteColumnName('version').'=:version', array(':version'=>$class)); + $time=microtime(true)-$start; + echo "*** reverted $class (time: ".sprintf("%.3f",$time)."s)\n\n"; + } + else + { + $time=microtime(true)-$start; + echo "*** failed to revert $class (time: ".sprintf("%.3f",$time)."s)\n\n"; + return false; + } + } + + protected function instantiateMigration($class) + { + $file=$this->migrationPath.DIRECTORY_SEPARATOR.$class.'.php'; + require_once($file); + $migration=new $class; + $migration->setDbConnection($this->getDbConnection()); + return $migration; + } + + /** + * @var CDbConnection + */ + private $_db; + protected function getDbConnection() + { + if($this->_db!==null) + return $this->_db; + else if(($this->_db=Yii::app()->getComponent($this->connectionID)) instanceof CDbConnection) + return $this->_db; + else + die("Error: CMigrationCommand.connectionID '{$this->connectionID}' is invalid. Please make sure it refers to the ID of a CDbConnection application component.\n"); + } + + protected function getMigrationHistory($limit) + { + $db=$this->getDbConnection(); + if($db->schema->getTable($this->migrationTable)===null) + { + $this->createMigrationHistoryTable(); + } + return CHtml::listData($db->createCommand() + ->select('version, apply_time') + ->from($this->migrationTable) + ->order('version DESC') + ->limit($limit) + ->queryAll(), 'version', 'apply_time'); + } + + protected function createMigrationHistoryTable() + { + $db=$this->getDbConnection(); + echo 'Creating migration history table "'.$this->migrationTable.'"...'; + $db->createCommand()->createTable($this->migrationTable,array( + 'version'=>'string NOT NULL PRIMARY KEY', + 'apply_time'=>'integer', + )); + $db->createCommand()->insert($this->migrationTable,array( + 'version'=>self::BASE_MIGRATION, + 'apply_time'=>time(), + )); + echo "done.\n"; + } + + protected function getNewMigrations() + { + $applied=array(); + foreach($this->getMigrationHistory(-1) as $version=>$time) + $applied[substr($version,1,13)]=true; + + $migrations=array(); + $handle=opendir($this->migrationPath); + while(($file=readdir($handle))!==false) + { + if($file==='.' || $file==='..') + continue; + $path=$this->migrationPath.DIRECTORY_SEPARATOR.$file; + if(preg_match('/^(m(\d{6}_\d{6})_.*?)\.php$/',$file,$matches) && is_file($path) && !isset($applied[$matches[2]])) + $migrations[]=$matches[1]; + } + closedir($handle); + sort($migrations); + return $migrations; + } + + public function getHelp() + { + return <<templateFile!==null) + return file_get_contents(Yii::getPathOfAlias($this->templateFile).'.php'); + else + return << + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + * @version $Id$ + */ + +/** + * ShellCommand executes the specified Web application and provides a shell for interaction. + * + * @property string $help The help information for the shell command. + * + * @author Qiang Xue + * @version $Id$ + * @package system.cli.commands + * @since 1.0 + */ +class ShellCommand extends CConsoleCommand +{ + /** + * @return string the help information for the shell command + */ + public function getHelp() + { + return <<usageError("{$args[0]} does not exist or is not an entry script file."); + + // fake the web server setting + $cwd=getcwd(); + chdir(dirname($entryScript)); + $_SERVER['SCRIPT_NAME']='/'.basename($entryScript); + $_SERVER['REQUEST_URI']=$_SERVER['SCRIPT_NAME']; + $_SERVER['SCRIPT_FILENAME']=$entryScript; + $_SERVER['HTTP_HOST']='localhost'; + $_SERVER['SERVER_NAME']='localhost'; + $_SERVER['SERVER_PORT']=80; + + // reset context to run the web application + restore_error_handler(); + restore_exception_handler(); + Yii::setApplication(null); + Yii::setPathOfAlias('application',null); + + ob_start(); + $config=require($entryScript); + ob_end_clean(); + + // oops, the entry script turns out to be a config file + if(is_array($config)) + { + chdir($cwd); + $_SERVER['SCRIPT_NAME']='/index.php'; + $_SERVER['REQUEST_URI']=$_SERVER['SCRIPT_NAME']; + $_SERVER['SCRIPT_FILENAME']=$cwd.DIRECTORY_SEPARATOR.'index.php'; + Yii::createWebApplication($config); + } + + restore_error_handler(); + restore_exception_handler(); + + $yiiVersion=Yii::getVersion(); + echo <<runShell(); + } + + protected function runShell() + { + // disable E_NOTICE so that the shell is more friendly + error_reporting(E_ALL ^ E_NOTICE); + + $_runner_=new CConsoleCommandRunner; + $_runner_->addCommands(dirname(__FILE__).'/shell'); + $_runner_->addCommands(Yii::getPathOfAlias('application.commands.shell')); + if(($_path_=@getenv('YIIC_SHELL_COMMAND_PATH'))!==false) + $_runner_->addCommands($_path_); + $_commands_=$_runner_->commands; + $log=Yii::app()->log; + + while(($_line_=$this->prompt("\n>>"))!==false) + { + $_line_=trim($_line_); + if($_line_==='exit') + return; + try + { + $_args_=preg_split('/[\s,]+/',rtrim($_line_,';'),-1,PREG_SPLIT_NO_EMPTY); + if(isset($_args_[0]) && isset($_commands_[$_args_[0]])) + { + $_command_=$_runner_->createCommand($_args_[0]); + array_shift($_args_); + $_command_->init(); + $_command_->run($_args_); + } + else + echo eval($_line_.';'); + } + catch(Exception $e) + { + if($e instanceof ShellException) + echo $e->getMessage(); + else + echo $e; + } + } + } +} + +class ShellException extends CException +{ +} \ No newline at end of file diff --git a/framework/yiic b/framework/yiic index cba5566..c19e529 100644 --- a/framework/yiic +++ b/framework/yiic @@ -5,10 +5,9 @@ * * This is the bootstrap script for running yiic on Unix/Linux. * - * @author Qiang Xue * @link http://www.yiiframework.com/ - * @copyright Copyright © 2012 Yii Software LLC + * @copyright Copyright © 2008-2012 Yii Software LLC * @license http://www.yiiframework.com/license/ */ -require_once(__DIR__.'/yiic.php'); +require_once(__DIR__ . '/yiic.php'); diff --git a/framework/yiic.php b/framework/yiic.php index 9592526..cd5c746 100644 --- a/framework/yiic.php +++ b/framework/yiic.php @@ -7,23 +7,12 @@ * @license http://www.yiiframework.com/license/ */ -// fcgi doesn't have STDIN defined by default -defined('STDIN') or define('STDIN', fopen('php://stdin', 'r')); +require(__DIR__ . '/yii.php'); -require(__DIR__.'/yii.php'); +$config = array( + 'controllerPath' => '@yii/console/commands', +); +$id = 'yiic'; +$basePath = __DIR__ . '/console'; -if(isset($config)) -{ - $app=new \yii\console\Application($config); - $app->commandRunner->addCommands(YII_PATH.'/cli/commands'); - $env=@getenv('YII_CONSOLE_COMMANDS'); - if(!empty($env)) - $app->commandRunner->addCommands($env); -} else -{ - $app=new \yii\console\Application(array( - 'basePath'=>__DIR__.'/cli', - )); -} - -$app->run(); \ No newline at end of file +yii\console\Application::newInstance($config, $id, $basePath)->run(); \ No newline at end of file