You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
471 lines
14 KiB
471 lines
14 KiB
<?php |
|
/** |
|
* Controller class file. |
|
* |
|
* @link http://www.yiiframework.com/ |
|
* @copyright Copyright © 2008-2012 Yii Software LLC |
|
* @license http://www.yiiframework.com/license/ |
|
*/ |
|
|
|
namespace yii\base; |
|
|
|
/** |
|
* Controller is the base class for {@link CController} and {@link CWidget}. |
|
* |
|
* It provides the common functionalities shared by controllers who need to render views. |
|
* |
|
* Controller also implements the support for the following features: |
|
* <ul> |
|
* <li>{@link CClipWidget Clips} : a clip is a piece of captured output that can be inserted elsewhere.</li> |
|
* <li>{@link CWidget Widgets} : a widget is a self-contained sub-controller with its own view and model.</li> |
|
* <li>{@link COutputCache Fragment cache} : fragment cache selectively caches a portion of the output.</li> |
|
* </ul> |
|
* |
|
* To use a widget in a view, use the following in the view: |
|
* <pre> |
|
* $this->widget('path.to.widgetClass',array('property1'=>'value1',...)); |
|
* </pre> |
|
* or |
|
* <pre> |
|
* $this->beginWidget('path.to.widgetClass',array('property1'=>'value1',...)); |
|
* // ... display other contents here |
|
* $this->endWidget(); |
|
* </pre> |
|
* |
|
* To create a clip, use the following: |
|
* <pre> |
|
* $this->beginClip('clipID'); |
|
* // ... display the clip contents |
|
* $this->endClip(); |
|
* </pre> |
|
* Then, in a different view or place, the captured clip can be inserted as: |
|
* <pre> |
|
* echo $this->clips['clipID']; |
|
* </pre> |
|
* |
|
* Note that $this in the code above refers to current controller so, for example, |
|
* if you need to access clip from a widget where $this refers to widget itself |
|
* you need to do it the following way: |
|
* |
|
* <pre> |
|
* echo $this->getController()->clips['clipID']; |
|
* </pre> |
|
* |
|
* To use fragment cache, do as follows, |
|
* <pre> |
|
* if($this->beginCache('cacheID',array('property1'=>'value1',...)) |
|
* { |
|
* // ... display the content to be cached here |
|
* $this->endCache(); |
|
* } |
|
* </pre> |
|
* |
|
* @author Qiang Xue <qiang.xue@gmail.com> |
|
* @since 2.0 |
|
*/ |
|
abstract class Controller extends Component implements Initable |
|
{ |
|
/** |
|
* @var string the name of the default action. Defaults to 'index'. |
|
*/ |
|
public $defaultAction = 'index'; |
|
|
|
private $_id; |
|
/** |
|
* @var Action the action that is currently being executed |
|
*/ |
|
public $action; |
|
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; |
|
} |
|
|
|
/** |
|
* 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 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(); |
|
} |
|
|
|
/** |
|
* 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 Action $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 Action $action action to run |
|
*/ |
|
public function runAction($action) |
|
{ |
|
$priorAction = $this->action; |
|
$this->action = $action; |
|
if ($this->beforeAction($action)) { |
|
if ($action->runWithParams($this->getActionParams())) { |
|
$this->afterAction($action); |
|
} else { |
|
$this->invalidActionParams($action); |
|
} |
|
} |
|
$this->action = $priorAction; |
|
} |
|
|
|
/** |
|
* Returns the request parameters that will be used for action parameter binding. |
|
* Default implementation simply returns an empty array. |
|
* Child classes may override this method to customize the parameters to be provided |
|
* for action parameter binding (e.g. `$_GET`). |
|
* @return array the request parameters (name-value pairs) to be used for action parameter binding |
|
*/ |
|
public function getActionParams() |
|
{ |
|
return array(); |
|
} |
|
|
|
/** |
|
* 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 Action $action the action being executed |
|
* @throws HttpException a 400 HTTP exception |
|
*/ |
|
public function invalidActionParams($action) |
|
{ |
|
throw new HttpException(400, \Yii::t('yii', 'Your request is invalid.')); |
|
} |
|
|
|
/** |
|
* 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 Action 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 Action 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 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; |
|
} |
|
|
|
/** |
|
* 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(); |
|
} |
|
} |
|
|
|
/** |
|
* This method is invoked when checking the access for the action to be executed. |
|
* @param Action $action the action to be executed. |
|
* @return boolean whether the action is allowed to be executed. |
|
*/ |
|
public function authorize(Action $action) |
|
{ |
|
$event = new ActionEvent($action); |
|
$this->trigger(__METHOD__, $event); |
|
return $event->isValid; |
|
} |
|
|
|
/** |
|
* This method is invoked right before an action is to be executed (after all possible filters.) |
|
* You may override this method to do last-minute preparation for the action. |
|
* @param Action $action the action to be executed. |
|
* @return boolean whether the action should continue to be executed. |
|
*/ |
|
public function beforeAction(Action $action) |
|
{ |
|
$event = new ActionEvent($action); |
|
$this->trigger(__METHOD__, $event); |
|
return $event->isValid; |
|
} |
|
|
|
/** |
|
* This method is invoked right after an action is executed. |
|
* You may override this method to do some postprocessing for the action. |
|
* @param Action $action the action just executed. |
|
*/ |
|
public function afterAction(Action $action) |
|
{ |
|
$event = new ActionEvent($action); |
|
$this->trigger(__METHOD__, $event); |
|
} |
|
|
|
/** |
|
* This method is invoked right before an action renders its result using [[render()]]. |
|
* @param Action $action the action to be executed. |
|
* @return boolean whether the action should continue to render. |
|
*/ |
|
public function beforeRender(Action $action) |
|
{ |
|
$event = new ActionEvent($action); |
|
$this->trigger(__METHOD__, $event); |
|
return $event->isValid; |
|
} |
|
|
|
/** |
|
* This method is invoked right after an action renders its result using [[render()]]. |
|
* @param Action $action the action just executed. |
|
*/ |
|
public function afterRender(Action $action) |
|
{ |
|
$event = new ActionEvent($action); |
|
$this->trigger(__METHOD__, $event); |
|
} |
|
}
|
|
|