diff options
Diffstat (limited to 'framework/web/CController.php')
| -rw-r--r-- | framework/web/CController.php | 1232 |
1 files changed, 1232 insertions, 0 deletions
diff --git a/framework/web/CController.php b/framework/web/CController.php new file mode 100644 index 0000000..0ac65bc --- /dev/null +++ b/framework/web/CController.php @@ -0,0 +1,1232 @@ +<?php +/** + * CController class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + + +/** + * CController manages a set of actions which deal with the corresponding user requests. + * + * Through the actions, CController coordinates the data flow between models and views. + * + * When a user requests an action 'XYZ', CController will do one of the following: + * 1. Method-based action: call method 'actionXYZ' if it exists; + * 2. Class-based action: create an instance of class 'XYZ' if the class is found in the action class map + * (specified via {@link actions()}, and execute the action; + * 3. Call {@link missingAction()}, which by default will raise a 404 HTTP exception. + * + * If the user does not specify an action, CController will run the action specified by + * {@link defaultAction}, instead. + * + * CController may be configured to execute filters before and after running actions. + * Filters preprocess/postprocess the user request/response and may quit executing actions + * if needed. They are executed in the order they are specified. If during the execution, + * any of the filters returns true, the rest filters and the action will no longer get executed. + * + * Filters can be individual objects, or methods defined in the controller class. + * They are specified by overriding {@link filters()} method. The following is an example + * of the filter specification: + * <pre> + * array( + * 'accessControl - login', + * 'ajaxOnly + search', + * array( + * 'COutputCache + list', + * 'duration'=>300, + * ), + * ) + * </pre> + * The above example declares three filters: accessControl, ajaxOnly, COutputCache. The first two + * are method-based filters (defined in CController), which refer to filtering methods in the controller class; + * while the last refers to a object-based filter whose class is 'system.web.widgets.COutputCache' and + * the 'duration' property is initialized as 300 (s). + * + * For method-based filters, a method named 'filterXYZ($filterChain)' in the controller class + * will be executed, where 'XYZ' stands for the filter name as specified in {@link filters()}. + * Note, inside the filter method, you must call <code>$filterChain->run()</code> if the action should + * be executed. Otherwise, the filtering process would stop at this filter. + * + * Filters can be specified so that they are executed only when running certain actions. + * For method-based filters, this is done by using '+' and '-' operators in the filter specification. + * The '+' operator means the filter runs only when the specified actions are requested; + * while the '-' operator means the filter runs only when the requested action is not among those actions. + * For object-based filters, the '+' and '-' operators are following the class name. + * + * @property array $actionParams The request parameters to be used for action parameter binding. + * @property CAction $action The action currently being executed, null if no active action. + * @property string $id ID of the controller. + * @property string $uniqueId The controller ID that is prefixed with the module ID (if any). + * @property string $route The route (module ID, controller ID and action ID) of the current request. + * @property CWebModule $module The module that this controller belongs to. It returns null + * if the controller does not belong to any module. + * @property string $viewPath The directory containing the view files for this controller. Defaults to 'protected/views/ControllerID'. + * @property CMap $clips The list of clips. + * @property string $pageTitle The page title. Defaults to the controller name and the action name. + * @property CStack $cachingStack Stack of {@link COutputCache} objects. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CController.php 3515 2011-12-28 12:29:24Z mdomba $ + * @package system.web + * @since 1.0 + */ +class CController extends CBaseController +{ + /** + * Name of the hidden field storing persistent page states. + */ + const STATE_INPUT_NAME='YII_PAGE_STATE'; + + /** + * @var mixed the name of the layout to be applied to this controller's views. + * Defaults to null, meaning the {@link CWebApplication::layout application layout} + * is used. If it is false, no layout will be applied. + * The {@link CWebModule::layout module layout} will be used + * if the controller belongs to a module and this layout property is null. + */ + public $layout; + /** + * @var string the name of the default action. Defaults to 'index'. + */ + public $defaultAction='index'; + + private $_id; + private $_action; + private $_pageTitle; + private $_cachingStack; + private $_clips; + private $_dynamicOutput; + private $_pageStates; + private $_module; + + + /** + * @param string $id id of this controller + * @param CWebModule $module the module that this controller belongs to. + */ + public function __construct($id,$module=null) + { + $this->_id=$id; + $this->_module=$module; + $this->attachBehaviors($this->behaviors()); + } + + /** + * Initializes the controller. + * This method is called by the application before the controller starts to execute. + * You may override this method to perform the needed initialization for the controller. + */ + public function init() + { + } + + /** + * Returns the filter configurations. + * + * By overriding this method, child classes can specify filters to be applied to actions. + * + * This method returns an array of filter specifications. Each array element specify a single filter. + * + * For a method-based filter (called inline filter), it is specified as 'FilterName[ +|- Action1, Action2, ...]', + * where the '+' ('-') operators describe which actions should be (should not be) applied with the filter. + * + * For a class-based filter, it is specified as an array like the following: + * <pre> + * array( + * 'FilterClass[ +|- Action1, Action2, ...]', + * 'name1'=>'value1', + * 'name2'=>'value2', + * ... + * ) + * </pre> + * where the name-value pairs will be used to initialize the properties of the filter. + * + * Note, in order to inherit filters defined in the parent class, a child class needs to + * merge the parent filters with child filters using functions like array_merge(). + * + * @return array a list of filter configurations. + * @see CFilter + */ + public function filters() + { + return array(); + } + + /** + * Returns a list of external action classes. + * Array keys are action IDs, and array values are the corresponding + * action class in dot syntax (e.g. 'edit'=>'application.controllers.article.EditArticle') + * or arrays representing the configuration of the actions, such as the following, + * <pre> + * return array( + * 'action1'=>'path.to.Action1Class', + * 'action2'=>array( + * 'class'=>'path.to.Action2Class', + * 'property1'=>'value1', + * 'property2'=>'value2', + * ), + * ); + * </pre> + * Derived classes may override this method to declare external actions. + * + * Note, in order to inherit actions defined in the parent class, a child class needs to + * merge the parent actions with child actions using functions like array_merge(). + * + * You may import actions from an action provider + * (such as a widget, see {@link CWidget::actions}), like the following: + * <pre> + * return array( + * ...other actions... + * // import actions declared in ProviderClass::actions() + * // the action IDs will be prefixed with 'pro.' + * 'pro.'=>'path.to.ProviderClass', + * // similar as above except that the imported actions are + * // configured with the specified initial property values + * 'pro2.'=>array( + * 'class'=>'path.to.ProviderClass', + * 'action1'=>array( + * 'property1'=>'value1', + * ), + * 'action2'=>array( + * 'property2'=>'value2', + * ), + * ), + * ) + * </pre> + * + * In the above, we differentiate action providers from other action + * declarations by the array keys. For action providers, the array keys + * must contain a dot. As a result, an action ID 'pro2.action1' will + * be resolved as the 'action1' action declared in the 'ProviderClass'. + * + * @return array list of external action classes + * @see createAction + */ + public function actions() + { + return array(); + } + + /** + * Returns a list of behaviors that this controller should behave as. + * The return value should be an array of behavior configurations indexed by + * behavior names. Each behavior configuration can be either a string specifying + * the behavior class or an array of the following structure: + * <pre> + * 'behaviorName'=>array( + * 'class'=>'path.to.BehaviorClass', + * 'property1'=>'value1', + * 'property2'=>'value2', + * ) + * </pre> + * + * Note, the behavior classes must implement {@link IBehavior} or extend from + * {@link CBehavior}. Behaviors declared in this method will be attached + * to the controller when it is instantiated. + * + * For more details about behaviors, see {@link CComponent}. + * @return array the behavior configurations (behavior name=>behavior configuration) + */ + public function behaviors() + { + return array(); + } + + /** + * Returns the access rules for this controller. + * Override this method if you use the {@link filterAccessControl accessControl} filter. + * @return array list of access rules. See {@link CAccessControlFilter} for details about rule specification. + */ + public function accessRules() + { + return array(); + } + + /** + * Runs the named action. + * Filters specified via {@link filters()} will be applied. + * @param string $actionID action ID + * @throws CHttpException if the action does not exist or the action name is not proper. + * @see filters + * @see createAction + * @see runAction + */ + public function run($actionID) + { + if(($action=$this->createAction($actionID))!==null) + { + if(($parent=$this->getModule())===null) + $parent=Yii::app(); + if($parent->beforeControllerAction($this,$action)) + { + $this->runActionWithFilters($action,$this->filters()); + $parent->afterControllerAction($this,$action); + } + } + else + $this->missingAction($actionID); + } + + /** + * Runs an action with the specified filters. + * A filter chain will be created based on the specified filters + * and the action will be executed then. + * @param CAction $action the action to be executed. + * @param array $filters list of filters to be applied to the action. + * @see filters + * @see createAction + * @see runAction + */ + public function runActionWithFilters($action,$filters) + { + if(empty($filters)) + $this->runAction($action); + else + { + $priorAction=$this->_action; + $this->_action=$action; + CFilterChain::create($this,$action,$filters)->run(); + $this->_action=$priorAction; + } + } + + /** + * Runs the action after passing through all filters. + * This method is invoked by {@link runActionWithFilters} after all possible filters have been executed + * and the action starts to run. + * @param CAction $action action to run + */ + public function runAction($action) + { + $priorAction=$this->_action; + $this->_action=$action; + if($this->beforeAction($action)) + { + if($action->runWithParams($this->getActionParams())===false) + $this->invalidActionParams($action); + else + $this->afterAction($action); + } + $this->_action=$priorAction; + } + + /** + * Returns the request parameters that will be used for action parameter binding. + * By default, this method will return $_GET. You may override this method if you + * want to use other request parameters (e.g. $_GET+$_POST). + * @return array the request parameters to be used for action parameter binding + * @since 1.1.7 + */ + public function getActionParams() + { + return $_GET; + } + + /** + * This method is invoked when the request parameters do not satisfy the requirement of the specified action. + * The default implementation will throw a 400 HTTP exception. + * @param CAction $action the action being executed + * @since 1.1.7 + */ + public function invalidActionParams($action) + { + throw new CHttpException(400,Yii::t('yii','Your request is invalid.')); + } + + /** + * Postprocesses the output generated by {@link render()}. + * This method is invoked at the end of {@link render()} and {@link renderText()}. + * If there are registered client scripts, this method will insert them into the output + * at appropriate places. If there are dynamic contents, they will also be inserted. + * This method may also save the persistent page states in hidden fields of + * stateful forms in the page. + * @param string $output the output generated by the current action + * @return string the output that has been processed. + */ + public function processOutput($output) + { + Yii::app()->getClientScript()->render($output); + + // if using page caching, we should delay dynamic output replacement + if($this->_dynamicOutput!==null && $this->isCachingStackEmpty()) + { + $output=$this->processDynamicOutput($output); + $this->_dynamicOutput=null; + } + + if($this->_pageStates===null) + $this->_pageStates=$this->loadPageStates(); + if(!empty($this->_pageStates)) + $this->savePageStates($this->_pageStates,$output); + + return $output; + } + + /** + * Postprocesses the dynamic output. + * This method is internally used. Do not call this method directly. + * @param string $output output to be processed + * @return string the processed output + */ + public function processDynamicOutput($output) + { + if($this->_dynamicOutput) + { + $output=preg_replace_callback('/<###dynamic-(\d+)###>/',array($this,'replaceDynamicOutput'),$output); + } + return $output; + } + + /** + * Replaces the dynamic content placeholders with actual content. + * This is a callback function used internally. + * @param array $matches matches + * @return string the replacement + * @see processOutput + */ + protected function replaceDynamicOutput($matches) + { + $content=$matches[0]; + if(isset($this->_dynamicOutput[$matches[1]])) + { + $content=$this->_dynamicOutput[$matches[1]]; + $this->_dynamicOutput[$matches[1]]=null; + } + return $content; + } + + /** + * Creates the action instance based on the action name. + * The action can be either an inline action or an object. + * The latter is created by looking up the action map specified in {@link actions}. + * @param string $actionID ID of the action. If empty, the {@link defaultAction default action} will be used. + * @return CAction the action instance, null if the action does not exist. + * @see actions + */ + public function createAction($actionID) + { + if($actionID==='') + $actionID=$this->defaultAction; + if(method_exists($this,'action'.$actionID) && strcasecmp($actionID,'s')) // we have actions method + return new CInlineAction($this,$actionID); + else + { + $action=$this->createActionFromMap($this->actions(),$actionID,$actionID); + if($action!==null && !method_exists($action,'run')) + throw new CException(Yii::t('yii', 'Action class {class} must implement the "run" method.', array('{class}'=>get_class($action)))); + return $action; + } + } + + /** + * Creates the action instance based on the action map. + * This method will check to see if the action ID appears in the given + * action map. If so, the corresponding configuration will be used to + * create the action instance. + * @param array $actionMap the action map + * @param string $actionID the action ID that has its prefix stripped off + * @param string $requestActionID the originally requested action ID + * @param array $config the action configuration that should be applied on top of the configuration specified in the map + * @return CAction the action instance, null if the action does not exist. + */ + protected function createActionFromMap($actionMap,$actionID,$requestActionID,$config=array()) + { + if(($pos=strpos($actionID,'.'))===false && isset($actionMap[$actionID])) + { + $baseConfig=is_array($actionMap[$actionID]) ? $actionMap[$actionID] : array('class'=>$actionMap[$actionID]); + return Yii::createComponent(empty($config)?$baseConfig:array_merge($baseConfig,$config),$this,$requestActionID); + } + else if($pos===false) + return null; + + // the action is defined in a provider + $prefix=substr($actionID,0,$pos+1); + if(!isset($actionMap[$prefix])) + return null; + $actionID=(string)substr($actionID,$pos+1); + + $provider=$actionMap[$prefix]; + if(is_string($provider)) + $providerType=$provider; + else if(is_array($provider) && isset($provider['class'])) + { + $providerType=$provider['class']; + if(isset($provider[$actionID])) + { + if(is_string($provider[$actionID])) + $config=array_merge(array('class'=>$provider[$actionID]),$config); + else + $config=array_merge($provider[$actionID],$config); + } + } + else + throw new CException(Yii::t('yii','Object configuration must be an array containing a "class" element.')); + + $class=Yii::import($providerType,true); + $map=call_user_func(array($class,'actions')); + + return $this->createActionFromMap($map,$actionID,$requestActionID,$config); + } + + /** + * Handles the request whose action is not recognized. + * This method is invoked when the controller cannot find the requested action. + * The default implementation simply throws an exception. + * @param string $actionID the missing action name + * @throws CHttpException whenever this method is invoked + */ + public function missingAction($actionID) + { + throw new CHttpException(404,Yii::t('yii','The system is unable to find the requested action "{action}".', + array('{action}'=>$actionID==''?$this->defaultAction:$actionID))); + } + + /** + * @return CAction the action currently being executed, null if no active action. + */ + public function getAction() + { + return $this->_action; + } + + /** + * @param CAction $value the action currently being executed. + */ + public function setAction($value) + { + $this->_action=$value; + } + + /** + * @return string ID of the controller + */ + public function getId() + { + return $this->_id; + } + + /** + * @return string the controller ID that is prefixed with the module ID (if any). + */ + public function getUniqueId() + { + return $this->_module ? $this->_module->getId().'/'.$this->_id : $this->_id; + } + + /** + * @return string the route (module ID, controller ID and action ID) of the current request. + * @since 1.1.0 + */ + public function getRoute() + { + if(($action=$this->getAction())!==null) + return $this->getUniqueId().'/'.$action->getId(); + else + return $this->getUniqueId(); + } + + /** + * @return CWebModule the module that this controller belongs to. It returns null + * if the controller does not belong to any module + */ + public function getModule() + { + return $this->_module; + } + + /** + * Returns the directory containing view files for this controller. + * The default implementation returns 'protected/views/ControllerID'. + * Child classes may override this method to use customized view path. + * If the controller belongs to a module, the default view path + * is the {@link CWebModule::getViewPath module view path} appended with the controller ID. + * @return string the directory containing the view files for this controller. Defaults to 'protected/views/ControllerID'. + */ + public function getViewPath() + { + if(($module=$this->getModule())===null) + $module=Yii::app(); + return $module->getViewPath().DIRECTORY_SEPARATOR.$this->getId(); + } + + /** + * Looks for the view file according to the given view name. + * + * When a theme is currently active, this method will call {@link CTheme::getViewFile} to determine + * which view file should be returned. + * + * Otherwise, this method will return the corresponding view file based on the following criteria: + * <ul> + * <li>absolute view within a module: the view name starts with a single slash '/'. + * In this case, the view will be searched for under the currently active module's view path. + * If there is no active module, the view will be searched for under the application's view path.</li> + * <li>absolute view within the application: the view name starts with double slashes '//'. + * In this case, the view will be searched for under the application's view path. + * This syntax has been available since version 1.1.3.</li> + * <li>aliased view: the view name contains dots and refers to a path alias. + * The view file is determined by calling {@link YiiBase::getPathOfAlias()}. Note that aliased views + * cannot be themed because they can refer to a view file located at arbitrary places.</li> + * <li>relative view: otherwise. Relative views will be searched for under the currently active + * controller's view path.</li> + * </ul> + * + * After the view file is identified, this method may further call {@link CApplication::findLocalizedFile} + * to find its localized version if internationalization is needed. + * + * @param string $viewName view name + * @return string the view file path, false if the view file does not exist + * @see resolveViewFile + * @see CApplication::findLocalizedFile + */ + public function getViewFile($viewName) + { + if(($theme=Yii::app()->getTheme())!==null && ($viewFile=$theme->getViewFile($this,$viewName))!==false) + return $viewFile; + $moduleViewPath=$basePath=Yii::app()->getViewPath(); + if(($module=$this->getModule())!==null) + $moduleViewPath=$module->getViewPath(); + return $this->resolveViewFile($viewName,$this->getViewPath(),$basePath,$moduleViewPath); + } + + /** + * Looks for the layout view script based on the layout name. + * + * The layout name can be specified in one of the following ways: + * + * <ul> + * <li>layout is false: returns false, meaning no layout.</li> + * <li>layout is null: the currently active module's layout will be used. If there is no active module, + * the application's layout will be used.</li> + * <li>a regular view name.</li> + * </ul> + * + * The resolution of the view file based on the layout view is similar to that in {@link getViewFile}. + * In particular, the following rules are followed: + * + * Otherwise, this method will return the corresponding view file based on the following criteria: + * <ul> + * <li>When a theme is currently active, this method will call {@link CTheme::getLayoutFile} to determine + * which view file should be returned.</li> + * <li>absolute view within a module: the view name starts with a single slash '/'. + * In this case, the view will be searched for under the currently active module's view path. + * If there is no active module, the view will be searched for under the application's view path.</li> + * <li>absolute view within the application: the view name starts with double slashes '//'. + * In this case, the view will be searched for under the application's view path. + * This syntax has been available since version 1.1.3.</li> + * <li>aliased view: the view name contains dots and refers to a path alias. + * The view file is determined by calling {@link YiiBase::getPathOfAlias()}. Note that aliased views + * cannot be themed because they can refer to a view file located at arbitrary places.</li> + * <li>relative view: otherwise. Relative views will be searched for under the currently active + * module's layout path. In case when there is no active module, the view will be searched for + * under the application's layout path.</li> + * </ul> + * + * After the view file is identified, this method may further call {@link CApplication::findLocalizedFile} + * to find its localized version if internationalization is needed. + * + * @param mixed $layoutName layout name + * @return string the view file for the layout. False if the view file cannot be found + */ + public function getLayoutFile($layoutName) + { + if($layoutName===false) + return false; + if(($theme=Yii::app()->getTheme())!==null && ($layoutFile=$theme->getLayoutFile($this,$layoutName))!==false) + return $layoutFile; + + if(empty($layoutName)) + { + $module=$this->getModule(); + while($module!==null) + { + if($module->layout===false) + return false; + if(!empty($module->layout)) + break; + $module=$module->getParentModule(); + } + if($module===null) + $module=Yii::app(); + $layoutName=$module->layout; + } + else if(($module=$this->getModule())===null) + $module=Yii::app(); + + return $this->resolveViewFile($layoutName,$module->getLayoutPath(),Yii::app()->getViewPath(),$module->getViewPath()); + } + + /** + * Finds a view file based on its name. + * The view name can be in one of the following formats: + * <ul> + * <li>absolute view within a module: the view name starts with a single slash '/'. + * In this case, the view will be searched for under the currently active module's view path. + * If there is no active module, the view will be searched for under the application's view path.</li> + * <li>absolute view within the application: the view name starts with double slashes '//'. + * In this case, the view will be searched for under the application's view path. + * This syntax has been available since version 1.1.3.</li> + * <li>aliased view: the view name contains dots and refers to a path alias. + * The view file is determined by calling {@link YiiBase::getPathOfAlias()}. Note that aliased views + * cannot be themed because they can refer to a view file located at arbitrary places.</li> + * <li>relative view: otherwise. Relative views will be searched for under the currently active + * controller's view path.</li> + * </ul> + * For absolute view and relative view, the corresponding view file is a PHP file + * whose name is the same as the view name. The file is located under a specified directory. + * This method will call {@link CApplication::findLocalizedFile} to search for a localized file, if any. + * @param string $viewName the view name + * @param string $viewPath the directory that is used to search for a relative view name + * @param string $basePath the directory that is used to search for an absolute view name under the application + * @param string $moduleViewPath the directory that is used to search for an absolute view name under the current module. + * If this is not set, the application base view path will be used. + * @return mixed the view file path. False if the view file does not exist. + */ + public function resolveViewFile($viewName,$viewPath,$basePath,$moduleViewPath=null) + { + if(empty($viewName)) + return false; + + if($moduleViewPath===null) + $moduleViewPath=$basePath; + + if(($renderer=Yii::app()->getViewRenderer())!==null) + $extension=$renderer->fileExtension; + else + $extension='.php'; + if($viewName[0]==='/') + { + if(strncmp($viewName,'//',2)===0) + $viewFile=$basePath.$viewName; + else + $viewFile=$moduleViewPath.$viewName; + } + else if(strpos($viewName,'.')) + $viewFile=Yii::getPathOfAlias($viewName); + else + $viewFile=$viewPath.DIRECTORY_SEPARATOR.$viewName; + + if(is_file($viewFile.$extension)) + return Yii::app()->findLocalizedFile($viewFile.$extension); + else if($extension!=='.php' && is_file($viewFile.'.php')) + return Yii::app()->findLocalizedFile($viewFile.'.php'); + else + return false; + } + + /** + * Returns the list of clips. + * A clip is a named piece of rendering result that can be + * inserted at different places. + * @return CMap the list of clips + * @see CClipWidget + */ + public function getClips() + { + if($this->_clips!==null) + return $this->_clips; + else + return $this->_clips=new CMap; + } + + /** + * Processes the request using another controller action. + * This is like {@link redirect}, but the user browser's URL remains unchanged. + * In most cases, you should call {@link redirect} instead of this method. + * @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 boolean $exit whether to end the application after this call. Defaults to true. + * @since 1.1.0 + */ + public function forward($route,$exit=true) + { + if(strpos($route,'/')===false) + $this->run($route); + else + { + if($route[0]!=='/' && ($module=$this->getModule())!==null) + $route=$module->getId().'/'.$route; + Yii::app()->runController($route); + } + if($exit) + Yii::app()->end(); + } + + /** + * Renders a view with a layout. + * + * This method first calls {@link renderPartial} to render the view (called content view). + * It then renders the layout view which may embed the content view at appropriate place. + * In the layout view, the content view rendering result can be accessed via variable + * <code>$content</code>. At the end, it calls {@link processOutput} to insert scripts + * and dynamic contents if they are available. + * + * By default, the layout view script is "protected/views/layouts/main.php". + * This may be customized by changing {@link layout}. + * + * @param string $view name of the view to be rendered. See {@link getViewFile} for details + * about how the view script is resolved. + * @param array $data data to be extracted into PHP variables and made available to the view script + * @param boolean $return whether the rendering result should be returned instead of being displayed to end users. + * @return string the rendering result. Null if the rendering result is not required. + * @see renderPartial + * @see getLayoutFile + */ + public function render($view,$data=null,$return=false) + { + if($this->beforeRender($view)) + { + $output=$this->renderPartial($view,$data,true); + if(($layoutFile=$this->getLayoutFile($this->layout))!==false) + $output=$this->renderFile($layoutFile,array('content'=>$output),true); + + $this->afterRender($view,$output); + + $output=$this->processOutput($output); + + if($return) + return $output; + else + echo $output; + } + } + + /** + * This method is invoked at the beginning of {@link render()}. + * You may override this method to do some preprocessing when rendering a view. + * @param string $view the view to be rendered + * @return boolean whether the view should be rendered. + * @since 1.1.5 + */ + protected function beforeRender($view) + { + return true; + } + + /** + * This method is invoked after the specified is rendered by calling {@link render()}. + * Note that this method is invoked BEFORE {@link processOutput()}. + * You may override this method to do some postprocessing for the view rendering. + * @param string $view the view that has been rendered + * @param string $output the rendering result of the view. Note that this parameter is passed + * as a reference. That means you can modify it within this method. + * @since 1.1.5 + */ + protected function afterRender($view, &$output) + { + } + + /** + * Renders a static text string. + * The string will be inserted in the current controller layout and returned back. + * @param string $text the static text string + * @param boolean $return whether the rendering result should be returned instead of being displayed to end users. + * @return string the rendering result. Null if the rendering result is not required. + * @see getLayoutFile + */ + public function renderText($text,$return=false) + { + if(($layoutFile=$this->getLayoutFile($this->layout))!==false) + $text=$this->renderFile($layoutFile,array('content'=>$text),true); + + $text=$this->processOutput($text); + + if($return) + return $text; + else + echo $text; + } + + /** + * Renders a view. + * + * The named view refers to a PHP script (resolved via {@link getViewFile}) + * that is included by this method. If $data is an associative array, + * it will be extracted as PHP variables and made available to the script. + * + * This method differs from {@link render()} in that it does not + * apply a layout to the rendered result. It is thus mostly used + * in rendering a partial view, or an AJAX response. + * + * @param string $view name of the view to be rendered. See {@link getViewFile} for details + * about how the view script is resolved. + * @param array $data data to be extracted into PHP variables and made available to the view script + * @param boolean $return whether the rendering result should be returned instead of being displayed to end users + * @param boolean $processOutput whether the rendering result should be postprocessed using {@link processOutput}. + * @return string the rendering result. Null if the rendering result is not required. + * @throws CException if the view does not exist + * @see getViewFile + * @see processOutput + * @see render + */ + public function renderPartial($view,$data=null,$return=false,$processOutput=false) + { + if(($viewFile=$this->getViewFile($view))!==false) + { + $output=$this->renderFile($viewFile,$data,true); + if($processOutput) + $output=$this->processOutput($output); + if($return) + return $output; + else + echo $output; + } + else + throw new CException(Yii::t('yii','{controller} cannot find the requested view "{view}".', + array('{controller}'=>get_class($this), '{view}'=>$view))); + } + + /** + * Renders a named clip with the supplied parameters. + * This is similar to directly accessing the {@link clips} property. + * The main difference is that it can take an array of named parameters + * which will replace the corresponding placeholders in the clip. + * @param string $name the name of the clip + * @param array $params an array of named parameters (name=>value) that should replace + * their corresponding placeholders in the clip + * @param boolean $return whether to return the clip content or echo it. + * @return mixed either the clip content or null + * @since 1.1.8 + */ + public function renderClip($name,$params=array(),$return=false) + { + $text=isset($this->clips[$name]) ? strtr($this->clips[$name], $params) : ''; + + if($return) + return $text; + else + echo $text; + } + + /** + * Renders dynamic content returned by the specified callback. + * This method is used together with {@link COutputCache}. Dynamic contents + * will always show as their latest state even if the content surrounding them is being cached. + * This is especially useful when caching pages that are mostly static but contain some small + * dynamic regions, such as username or current time. + * We can use this method to render these dynamic regions to ensure they are always up-to-date. + * + * The first parameter to this method should be a valid PHP callback, while the rest parameters + * will be passed to the callback. + * + * Note, the callback and its parameter values will be serialized and saved in cache. + * Make sure they are serializable. + * + * @param callback $callback a PHP callback which returns the needed dynamic content. + * When the callback is specified as a string, it will be first assumed to be a method of the current + * controller class. If the method does not exist, it is assumed to be a global PHP function. + * Note, the callback should return the dynamic content instead of echoing it. + */ + public function renderDynamic($callback) + { + $n=count($this->_dynamicOutput); + echo "<###dynamic-$n###>"; + $params=func_get_args(); + array_shift($params); + $this->renderDynamicInternal($callback,$params); + } + + /** + * This method is internally used. + * @param callback $callback a PHP callback which returns the needed dynamic content. + * @param array $params parameters passed to the PHP callback + * @see renderDynamic + */ + public function renderDynamicInternal($callback,$params) + { + $this->recordCachingAction('','renderDynamicInternal',array($callback,$params)); + if(is_string($callback) && method_exists($this,$callback)) + $callback=array($this,$callback); + $this->_dynamicOutput[]=call_user_func_array($callback,$params); + } + + /** + * Creates a relative URL for the specified action defined in this controller. + * @param string $route the URL route. This should be in the format of 'ControllerID/ActionID'. + * If the ControllerID is not present, the current controller ID will be prefixed to the route. + * If the route is empty, it is assumed to be the current action. + * If the controller belongs to a module, the {@link CWebModule::getId module ID} + * will be prefixed to the route. (If you do not want the module ID prefix, the route should start with a slash '/'.) + * @param array $params additional GET parameters (name=>value). Both the name and value will be URL-encoded. + * If the name is '#', the corresponding value will be treated as an anchor + * and will be appended at the end of the URL. + * @param string $ampersand the token separating name-value pairs in the URL. + * @return string the constructed URL + */ + public function createUrl($route,$params=array(),$ampersand='&') + { + if($route==='') + $route=$this->getId().'/'.$this->getAction()->getId(); + else if(strpos($route,'/')===false) + $route=$this->getId().'/'.$route; + if($route[0]!=='/' && ($module=$this->getModule())!==null) + $route=$module->getId().'/'.$route; + return Yii::app()->createUrl(trim($route,'/'),$params,$ampersand); + } + + /** + * Creates an absolute URL for the specified action defined in this controller. + * @param string $route the URL route. This should be in the format of 'ControllerID/ActionID'. + * If the ControllerPath is not present, the current controller ID will be prefixed to the route. + * If the route is empty, it is assumed to be the current action. + * @param array $params additional GET parameters (name=>value). Both the name and value will be URL-encoded. + * @param string $schema schema to use (e.g. http, https). If empty, the schema used for the current request will be used. + * @param string $ampersand the token separating name-value pairs in the URL. + * @return string the constructed URL + */ + public function createAbsoluteUrl($route,$params=array(),$schema='',$ampersand='&') + { + $url=$this->createUrl($route,$params,$ampersand); + if(strpos($url,'http')===0) + return $url; + else + return Yii::app()->getRequest()->getHostInfo($schema).$url; + } + + /** + * @return string the page title. Defaults to the controller name and the action name. + */ + public function getPageTitle() + { + if($this->_pageTitle!==null) + return $this->_pageTitle; + else + { + $name=ucfirst(basename($this->getId())); + if($this->getAction()!==null && strcasecmp($this->getAction()->getId(),$this->defaultAction)) + return $this->_pageTitle=Yii::app()->name.' - '.ucfirst($this->getAction()->getId()).' '.$name; + else + return $this->_pageTitle=Yii::app()->name.' - '.$name; + } + } + + /** + * @param string $value the page title. + */ + public function setPageTitle($value) + { + $this->_pageTitle=$value; + } + + /** + * Redirects the browser to the specified URL or route (controller/action). + * @param mixed $url the URL to be redirected to. If the parameter is an array, + * the first element must be a route to a controller action and the rest + * are GET parameters in name-value pairs. + * @param boolean $terminate whether to terminate the current application after calling this method + * @param integer $statusCode the HTTP status code. Defaults to 302. See {@link http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html} + * for details about HTTP status code. + */ + public function redirect($url,$terminate=true,$statusCode=302) + { + if(is_array($url)) + { + $route=isset($url[0]) ? $url[0] : ''; + $url=$this->createUrl($route,array_splice($url,1)); + } + Yii::app()->getRequest()->redirect($url,$terminate,$statusCode); + } + + /** + * Refreshes the current page. + * The effect of this method call is the same as user pressing the + * refresh button on the browser (without post data). + * @param boolean $terminate whether to terminate the current application after calling this method + * @param string $anchor the anchor that should be appended to the redirection URL. + * Defaults to empty. Make sure the anchor starts with '#' if you want to specify it. + */ + public function refresh($terminate=true,$anchor='') + { + $this->redirect(Yii::app()->getRequest()->getUrl().$anchor,$terminate); + } + + /** + * Records a method call when an output cache is in effect. + * When the content is served from the output cache, the recorded + * method will be re-invoked. + * @param string $context a property name of the controller. It refers to an object + * whose method is being called. If empty it means the controller itself. + * @param string $method the method name + * @param array $params parameters passed to the method + * @see COutputCache + */ + public function recordCachingAction($context,$method,$params) + { + if($this->_cachingStack) // record only when there is an active output cache + { + foreach($this->_cachingStack as $cache) + $cache->recordAction($context,$method,$params); + } + } + + /** + * @param boolean $createIfNull whether to create a stack if it does not exist yet. Defaults to true. + * @return CStack stack of {@link COutputCache} objects + */ + public function getCachingStack($createIfNull=true) + { + if(!$this->_cachingStack) + $this->_cachingStack=new CStack; + return $this->_cachingStack; + } + + /** + * Returns whether the caching stack is empty. + * @return boolean whether the caching stack is empty. If not empty, it means currently there are + * some output cache in effect. Note, the return result of this method may change when it is + * called in different output regions, depending on the partition of output caches. + */ + public function isCachingStackEmpty() + { + return $this->_cachingStack===null || !$this->_cachingStack->getCount(); + } + + /** + * This method is invoked right before an action is to be executed (after all possible filters.) + * You may override this method to do last-minute preparation for the action. + * @param CAction $action the action to be executed. + * @return boolean whether the action should be executed. + */ + protected function beforeAction($action) + { + return true; + } + + /** + * This method is invoked right after an action is executed. + * You may override this method to do some postprocessing for the action. + * @param CAction $action the action just executed. + */ + protected function afterAction($action) + { + } + + /** + * The filter method for 'postOnly' filter. + * This filter reports an error if the applied action is receiving a non-POST request. + * @param CFilterChain $filterChain the filter chain that the filter is on. + * @throws CHttpException if the current request is not a POST request + */ + public function filterPostOnly($filterChain) + { + if(Yii::app()->getRequest()->getIsPostRequest()) + $filterChain->run(); + else + throw new CHttpException(400,Yii::t('yii','Your request is invalid.')); + } + + /** + * The filter method for 'ajaxOnly' filter. + * This filter reports an error if the applied action is receiving a non-AJAX request. + * @param CFilterChain $filterChain the filter chain that the filter is on. + * @throws CHttpException if the current request is not an AJAX request. + */ + public function filterAjaxOnly($filterChain) + { + if(Yii::app()->getRequest()->getIsAjaxRequest()) + $filterChain->run(); + else + throw new CHttpException(400,Yii::t('yii','Your request is invalid.')); + } + + /** + * The filter method for 'accessControl' filter. + * This filter is a wrapper of {@link CAccessControlFilter}. + * To use this filter, you must override {@link accessRules} method. + * @param CFilterChain $filterChain the filter chain that the filter is on. + */ + public function filterAccessControl($filterChain) + { + $filter=new CAccessControlFilter; + $filter->setRules($this->accessRules()); + $filter->filter($filterChain); + } + + /** + * Returns a persistent page state value. + * A page state is a variable that is persistent across POST requests of the same page. + * In order to use persistent page states, the form(s) must be stateful + * which are generated using {@link CHtml::statefulForm}. + * @param string $name the state name + * @param mixed $defaultValue the value to be returned if the named state is not found + * @return mixed the page state value + * @see setPageState + * @see CHtml::statefulForm + */ + public function getPageState($name,$defaultValue=null) + { + if($this->_pageStates===null) + $this->_pageStates=$this->loadPageStates(); + return isset($this->_pageStates[$name])?$this->_pageStates[$name]:$defaultValue; + } + + /** + * Saves a persistent page state value. + * A page state is a variable that is persistent across POST requests of the same page. + * In order to use persistent page states, the form(s) must be stateful + * which are generated using {@link CHtml::statefulForm}. + * @param string $name the state name + * @param mixed $value the page state value + * @param mixed $defaultValue the default page state value. If this is the same as + * the given value, the state will be removed from persistent storage. + * @see getPageState + * @see CHtml::statefulForm + */ + public function setPageState($name,$value,$defaultValue=null) + { + if($this->_pageStates===null) + $this->_pageStates=$this->loadPageStates(); + if($value===$defaultValue) + unset($this->_pageStates[$name]); + else + $this->_pageStates[$name]=$value; + + $params=func_get_args(); + $this->recordCachingAction('','setPageState',$params); + } + + /** + * Removes all page states. + */ + public function clearPageStates() + { + $this->_pageStates=array(); + } + + /** + * Loads page states from a hidden input. + * @return array the loaded page states + */ + protected function loadPageStates() + { + if(!empty($_POST[self::STATE_INPUT_NAME])) + { + if(($data=base64_decode($_POST[self::STATE_INPUT_NAME]))!==false) + { + if(extension_loaded('zlib')) + $data=@gzuncompress($data); + if(($data=Yii::app()->getSecurityManager()->validateData($data))!==false) + return unserialize($data); + } + } + return array(); + } + + /** + * Saves page states as a base64 string. + * @param array $states the states to be saved. + * @param string $output the output to be modified. Note, this is passed by reference. + */ + protected function savePageStates($states,&$output) + { + $data=Yii::app()->getSecurityManager()->hashData(serialize($states)); + if(extension_loaded('zlib')) + $data=gzcompress($data); + $value=base64_encode($data); + $output=str_replace(CHtml::pageStateField(''),CHtml::pageStateField($value),$output); + } +} |
