PageRenderTime 211ms CodeModel.GetById 90ms app.highlight 55ms RepoModel.GetById 36ms app.codeStats 0ms

/demo/yii/web/CController.php

https://bitbucket.org/intel352/yii-bootstrap
PHP | 1232 lines | 494 code | 80 blank | 658 comment | 92 complexity | 2ad3dd05fe737ebb898695191a2672a7 MD5 | raw file
   1<?php
   2/**
   3 * CController class file.
   4 *
   5 * @author Qiang Xue <qiang.xue@gmail.com>
   6 * @link http://www.yiiframework.com/
   7 * @copyright Copyright &copy; 2008-2011 Yii Software LLC
   8 * @license http://www.yiiframework.com/license/
   9 */
  10
  11
  12/**
  13 * CController manages a set of actions which deal with the corresponding user requests.
  14 *
  15 * Through the actions, CController coordinates the data flow between models and views.
  16 *
  17 * When a user requests an action 'XYZ', CController will do one of the following:
  18 * 1. Method-based action: call method 'actionXYZ' if it exists;
  19 * 2. Class-based action: create an instance of class 'XYZ' if the class is found in the action class map
  20 *    (specified via {@link actions()}, and execute the action;
  21 * 3. Call {@link missingAction()}, which by default will raise a 404 HTTP exception.
  22 *
  23 * If the user does not specify an action, CController will run the action specified by
  24 * {@link defaultAction}, instead.
  25 *
  26 * CController may be configured to execute filters before and after running actions.
  27 * Filters preprocess/postprocess the user request/response and may quit executing actions
  28 * if needed. They are executed in the order they are specified. If during the execution,
  29 * any of the filters returns true, the rest filters and the action will no longer get executed.
  30 *
  31 * Filters can be individual objects, or methods defined in the controller class.
  32 * They are specified by overriding {@link filters()} method. The following is an example
  33 * of the filter specification:
  34 * <pre>
  35 * array(
  36 *     'accessControl - login',
  37 *     'ajaxOnly + search',
  38 *     array(
  39 *         'COutputCache + list',
  40 *         'duration'=>300,
  41 *     ),
  42 * )
  43 * </pre>
  44 * The above example declares three filters: accessControl, ajaxOnly, COutputCache. The first two
  45 * are method-based filters (defined in CController), which refer to filtering methods in the controller class;
  46 * while the last refers to a object-based filter whose class is 'system.web.widgets.COutputCache' and
  47 * the 'duration' property is initialized as 300 (s).
  48 *
  49 * For method-based filters, a method named 'filterXYZ($filterChain)' in the controller class
  50 * will be executed, where 'XYZ' stands for the filter name as specified in {@link filters()}.
  51 * Note, inside the filter method, you must call <code>$filterChain->run()</code> if the action should
  52 * be executed. Otherwise, the filtering process would stop at this filter.
  53 *
  54 * Filters can be specified so that they are executed only when running certain actions.
  55 * For method-based filters, this is done by using '+' and '-' operators in the filter specification.
  56 * The '+' operator means the filter runs only when the specified actions are requested;
  57 * while the '-' operator means the filter runs only when the requested action is not among those actions.
  58 * For object-based filters, the '+' and '-' operators are following the class name.
  59 *
  60 * @property array $actionParams The request parameters to be used for action parameter binding.
  61 * @property CAction $action The action currently being executed, null if no active action.
  62 * @property string $id ID of the controller.
  63 * @property string $uniqueId The controller ID that is prefixed with the module ID (if any).
  64 * @property string $route The route (module ID, controller ID and action ID) of the current request.
  65 * @property CWebModule $module The module that this controller belongs to. It returns null
  66 * if the controller does not belong to any module.
  67 * @property string $viewPath The directory containing the view files for this controller. Defaults to 'protected/views/ControllerID'.
  68 * @property CMap $clips The list of clips.
  69 * @property string $pageTitle The page title. Defaults to the controller name and the action name.
  70 * @property CStack $cachingStack Stack of {@link COutputCache} objects.
  71 *
  72 * @author Qiang Xue <qiang.xue@gmail.com>
  73 * @version $Id: CController.php 3515 2011-12-28 12:29:24Z mdomba $
  74 * @package system.web
  75 * @since 1.0
  76 */
  77class CController extends CBaseController
  78{
  79	/**
  80	 * Name of the hidden field storing persistent page states.
  81	 */
  82	const STATE_INPUT_NAME='YII_PAGE_STATE';
  83
  84	/**
  85	 * @var mixed the name of the layout to be applied to this controller's views.
  86	 * Defaults to null, meaning the {@link CWebApplication::layout application layout}
  87	 * is used. If it is false, no layout will be applied.
  88	 * The {@link CWebModule::layout module layout} will be used
  89	 * if the controller belongs to a module and this layout property is null.
  90	 */
  91	public $layout;
  92	/**
  93	 * @var string the name of the default action. Defaults to 'index'.
  94	 */
  95	public $defaultAction='index';
  96
  97	private $_id;
  98	private $_action;
  99	private $_pageTitle;
 100	private $_cachingStack;
 101	private $_clips;
 102	private $_dynamicOutput;
 103	private $_pageStates;
 104	private $_module;
 105
 106
 107	/**
 108	 * @param string $id id of this controller
 109	 * @param CWebModule $module the module that this controller belongs to.
 110	 */
 111	public function __construct($id,$module=null)
 112	{
 113		$this->_id=$id;
 114		$this->_module=$module;
 115		$this->attachBehaviors($this->behaviors());
 116	}
 117
 118	/**
 119	 * Initializes the controller.
 120	 * This method is called by the application before the controller starts to execute.
 121	 * You may override this method to perform the needed initialization for the controller.
 122	 */
 123	public function init()
 124	{
 125	}
 126
 127	/**
 128	 * Returns the filter configurations.
 129	 *
 130	 * By overriding this method, child classes can specify filters to be applied to actions.
 131	 *
 132	 * This method returns an array of filter specifications. Each array element specify a single filter.
 133	 *
 134	 * For a method-based filter (called inline filter), it is specified as 'FilterName[ +|- Action1, Action2, ...]',
 135	 * where the '+' ('-') operators describe which actions should be (should not be) applied with the filter.
 136	 *
 137	 * For a class-based filter, it is specified as an array like the following:
 138	 * <pre>
 139	 * array(
 140	 *     'FilterClass[ +|- Action1, Action2, ...]',
 141	 *     'name1'=>'value1',
 142	 *     'name2'=>'value2',
 143	 *     ...
 144	 * )
 145	 * </pre>
 146	 * where the name-value pairs will be used to initialize the properties of the filter.
 147	 *
 148	 * Note, in order to inherit filters defined in the parent class, a child class needs to
 149	 * merge the parent filters with child filters using functions like array_merge().
 150	 *
 151	 * @return array a list of filter configurations.
 152	 * @see CFilter
 153	 */
 154	public function filters()
 155	{
 156		return array();
 157	}
 158
 159	/**
 160	 * Returns a list of external action classes.
 161	 * Array keys are action IDs, and array values are the corresponding
 162	 * action class in dot syntax (e.g. 'edit'=>'application.controllers.article.EditArticle')
 163	 * or arrays representing the configuration of the actions, such as the following,
 164	 * <pre>
 165	 * return array(
 166	 *     'action1'=>'path.to.Action1Class',
 167	 *     'action2'=>array(
 168	 *         'class'=>'path.to.Action2Class',
 169	 *         'property1'=>'value1',
 170	 *         'property2'=>'value2',
 171	 *     ),
 172	 * );
 173	 * </pre>
 174	 * Derived classes may override this method to declare external actions.
 175	 *
 176	 * Note, in order to inherit actions defined in the parent class, a child class needs to
 177	 * merge the parent actions with child actions using functions like array_merge().
 178	 *
 179	 * You may import actions from an action provider
 180	 * (such as a widget, see {@link CWidget::actions}), like the following:
 181	 * <pre>
 182	 * return array(
 183	 *     ...other actions...
 184	 *     // import actions declared in ProviderClass::actions()
 185	 *     // the action IDs will be prefixed with 'pro.'
 186	 *     'pro.'=>'path.to.ProviderClass',
 187	 *     // similar as above except that the imported actions are
 188	 *     // configured with the specified initial property values
 189	 *     'pro2.'=>array(
 190	 *         'class'=>'path.to.ProviderClass',
 191	 *         'action1'=>array(
 192	 *             'property1'=>'value1',
 193	 *         ),
 194	 *         'action2'=>array(
 195	 *             'property2'=>'value2',
 196	 *         ),
 197	 *     ),
 198	 * )
 199	 * </pre>
 200	 *
 201	 * In the above, we differentiate action providers from other action
 202	 * declarations by the array keys. For action providers, the array keys
 203	 * must contain a dot. As a result, an action ID 'pro2.action1' will
 204	 * be resolved as the 'action1' action declared in the 'ProviderClass'.
 205	 *
 206	 * @return array list of external action classes
 207	 * @see createAction
 208	 */
 209	public function actions()
 210	{
 211		return array();
 212	}
 213
 214	/**
 215	 * Returns a list of behaviors that this controller should behave as.
 216	 * The return value should be an array of behavior configurations indexed by
 217	 * behavior names. Each behavior configuration can be either a string specifying
 218	 * the behavior class or an array of the following structure:
 219	 * <pre>
 220	 * 'behaviorName'=>array(
 221	 *     'class'=>'path.to.BehaviorClass',
 222	 *     'property1'=>'value1',
 223	 *     'property2'=>'value2',
 224	 * )
 225	 * </pre>
 226	 *
 227	 * Note, the behavior classes must implement {@link IBehavior} or extend from
 228	 * {@link CBehavior}. Behaviors declared in this method will be attached
 229	 * to the controller when it is instantiated.
 230	 *
 231	 * For more details about behaviors, see {@link CComponent}.
 232	 * @return array the behavior configurations (behavior name=>behavior configuration)
 233	 */
 234	public function behaviors()
 235	{
 236		return array();
 237	}
 238
 239	/**
 240	 * Returns the access rules for this controller.
 241	 * Override this method if you use the {@link filterAccessControl accessControl} filter.
 242	 * @return array list of access rules. See {@link CAccessControlFilter} for details about rule specification.
 243	 */
 244	public function accessRules()
 245	{
 246		return array();
 247	}
 248
 249	/**
 250	 * Runs the named action.
 251	 * Filters specified via {@link filters()} will be applied.
 252	 * @param string $actionID action ID
 253	 * @throws CHttpException if the action does not exist or the action name is not proper.
 254	 * @see filters
 255	 * @see createAction
 256	 * @see runAction
 257	 */
 258	public function run($actionID)
 259	{
 260		if(($action=$this->createAction($actionID))!==null)
 261		{
 262			if(($parent=$this->getModule())===null)
 263				$parent=Yii::app();
 264			if($parent->beforeControllerAction($this,$action))
 265			{
 266				$this->runActionWithFilters($action,$this->filters());
 267				$parent->afterControllerAction($this,$action);
 268			}
 269		}
 270		else
 271			$this->missingAction($actionID);
 272	}
 273
 274	/**
 275	 * Runs an action with the specified filters.
 276	 * A filter chain will be created based on the specified filters
 277	 * and the action will be executed then.
 278	 * @param CAction $action the action to be executed.
 279	 * @param array $filters list of filters to be applied to the action.
 280	 * @see filters
 281	 * @see createAction
 282	 * @see runAction
 283	 */
 284	public function runActionWithFilters($action,$filters)
 285	{
 286		if(empty($filters))
 287			$this->runAction($action);
 288		else
 289		{
 290			$priorAction=$this->_action;
 291			$this->_action=$action;
 292			CFilterChain::create($this,$action,$filters)->run();
 293			$this->_action=$priorAction;
 294		}
 295	}
 296
 297	/**
 298	 * Runs the action after passing through all filters.
 299	 * This method is invoked by {@link runActionWithFilters} after all possible filters have been executed
 300	 * and the action starts to run.
 301	 * @param CAction $action action to run
 302	 */
 303	public function runAction($action)
 304	{
 305		$priorAction=$this->_action;
 306		$this->_action=$action;
 307		if($this->beforeAction($action))
 308		{
 309			if($action->runWithParams($this->getActionParams())===false)
 310				$this->invalidActionParams($action);
 311			else
 312				$this->afterAction($action);
 313		}
 314		$this->_action=$priorAction;
 315	}
 316
 317	/**
 318	 * Returns the request parameters that will be used for action parameter binding.
 319	 * By default, this method will return $_GET. You may override this method if you
 320	 * want to use other request parameters (e.g. $_GET+$_POST).
 321	 * @return array the request parameters to be used for action parameter binding
 322	 * @since 1.1.7
 323	 */
 324	public function getActionParams()
 325	{
 326		return $_GET;
 327	}
 328
 329	/**
 330	 * This method is invoked when the request parameters do not satisfy the requirement of the specified action.
 331	 * The default implementation will throw a 400 HTTP exception.
 332	 * @param CAction $action the action being executed
 333	 * @since 1.1.7
 334	 */
 335	public function invalidActionParams($action)
 336	{
 337		throw new CHttpException(400,Yii::t('yii','Your request is invalid.'));
 338	}
 339
 340	/**
 341	 * Postprocesses the output generated by {@link render()}.
 342	 * This method is invoked at the end of {@link render()} and {@link renderText()}.
 343	 * If there are registered client scripts, this method will insert them into the output
 344	 * at appropriate places. If there are dynamic contents, they will also be inserted.
 345	 * This method may also save the persistent page states in hidden fields of
 346	 * stateful forms in the page.
 347	 * @param string $output the output generated by the current action
 348	 * @return string the output that has been processed.
 349	 */
 350	public function processOutput($output)
 351	{
 352		Yii::app()->getClientScript()->render($output);
 353
 354		// if using page caching, we should delay dynamic output replacement
 355		if($this->_dynamicOutput!==null && $this->isCachingStackEmpty())
 356		{
 357			$output=$this->processDynamicOutput($output);
 358			$this->_dynamicOutput=null;
 359		}
 360
 361		if($this->_pageStates===null)
 362			$this->_pageStates=$this->loadPageStates();
 363		if(!empty($this->_pageStates))
 364			$this->savePageStates($this->_pageStates,$output);
 365
 366		return $output;
 367	}
 368
 369	/**
 370	 * Postprocesses the dynamic output.
 371	 * This method is internally used. Do not call this method directly.
 372	 * @param string $output output to be processed
 373	 * @return string the processed output
 374	 */
 375	public function processDynamicOutput($output)
 376	{
 377		if($this->_dynamicOutput)
 378		{
 379			$output=preg_replace_callback('/<###dynamic-(\d+)###>/',array($this,'replaceDynamicOutput'),$output);
 380		}
 381		return $output;
 382	}
 383
 384	/**
 385	 * Replaces the dynamic content placeholders with actual content.
 386	 * This is a callback function used internally.
 387	 * @param array $matches matches
 388	 * @return string the replacement
 389	 * @see processOutput
 390	 */
 391	protected function replaceDynamicOutput($matches)
 392	{
 393		$content=$matches[0];
 394		if(isset($this->_dynamicOutput[$matches[1]]))
 395		{
 396			$content=$this->_dynamicOutput[$matches[1]];
 397			$this->_dynamicOutput[$matches[1]]=null;
 398		}
 399		return $content;
 400	}
 401
 402	/**
 403	 * Creates the action instance based on the action name.
 404	 * The action can be either an inline action or an object.
 405	 * The latter is created by looking up the action map specified in {@link actions}.
 406	 * @param string $actionID ID of the action. If empty, the {@link defaultAction default action} will be used.
 407	 * @return CAction the action instance, null if the action does not exist.
 408	 * @see actions
 409	 */
 410	public function createAction($actionID)
 411	{
 412		if($actionID==='')
 413			$actionID=$this->defaultAction;
 414		if(method_exists($this,'action'.$actionID) && strcasecmp($actionID,'s')) // we have actions method
 415			return new CInlineAction($this,$actionID);
 416		else
 417		{
 418			$action=$this->createActionFromMap($this->actions(),$actionID,$actionID);
 419			if($action!==null && !method_exists($action,'run'))
 420				throw new CException(Yii::t('yii', 'Action class {class} must implement the "run" method.', array('{class}'=>get_class($action))));
 421			return $action;
 422		}
 423	}
 424
 425	/**
 426	 * Creates the action instance based on the action map.
 427	 * This method will check to see if the action ID appears in the given
 428	 * action map. If so, the corresponding configuration will be used to
 429	 * create the action instance.
 430	 * @param array $actionMap the action map
 431	 * @param string $actionID the action ID that has its prefix stripped off
 432	 * @param string $requestActionID the originally requested action ID
 433	 * @param array $config the action configuration that should be applied on top of the configuration specified in the map
 434	 * @return CAction the action instance, null if the action does not exist.
 435	 */
 436	protected function createActionFromMap($actionMap,$actionID,$requestActionID,$config=array())
 437	{
 438		if(($pos=strpos($actionID,'.'))===false && isset($actionMap[$actionID]))
 439		{
 440			$baseConfig=is_array($actionMap[$actionID]) ? $actionMap[$actionID] : array('class'=>$actionMap[$actionID]);
 441			return Yii::createComponent(empty($config)?$baseConfig:array_merge($baseConfig,$config),$this,$requestActionID);
 442		}
 443		else if($pos===false)
 444			return null;
 445
 446		// the action is defined in a provider
 447		$prefix=substr($actionID,0,$pos+1);
 448		if(!isset($actionMap[$prefix]))
 449			return null;
 450		$actionID=(string)substr($actionID,$pos+1);
 451
 452		$provider=$actionMap[$prefix];
 453		if(is_string($provider))
 454			$providerType=$provider;
 455		else if(is_array($provider) && isset($provider['class']))
 456		{
 457			$providerType=$provider['class'];
 458			if(isset($provider[$actionID]))
 459			{
 460				if(is_string($provider[$actionID]))
 461					$config=array_merge(array('class'=>$provider[$actionID]),$config);
 462				else
 463					$config=array_merge($provider[$actionID],$config);
 464			}
 465		}
 466		else
 467			throw new CException(Yii::t('yii','Object configuration must be an array containing a "class" element.'));
 468
 469		$class=Yii::import($providerType,true);
 470		$map=call_user_func(array($class,'actions'));
 471
 472		return $this->createActionFromMap($map,$actionID,$requestActionID,$config);
 473	}
 474
 475	/**
 476	 * Handles the request whose action is not recognized.
 477	 * This method is invoked when the controller cannot find the requested action.
 478	 * The default implementation simply throws an exception.
 479	 * @param string $actionID the missing action name
 480	 * @throws CHttpException whenever this method is invoked
 481	 */
 482	public function missingAction($actionID)
 483	{
 484		throw new CHttpException(404,Yii::t('yii','The system is unable to find the requested action "{action}".',
 485			array('{action}'=>$actionID==''?$this->defaultAction:$actionID)));
 486	}
 487
 488	/**
 489	 * @return CAction the action currently being executed, null if no active action.
 490	 */
 491	public function getAction()
 492	{
 493		return $this->_action;
 494	}
 495
 496	/**
 497	 * @param CAction $value the action currently being executed.
 498	 */
 499	public function setAction($value)
 500	{
 501		$this->_action=$value;
 502	}
 503
 504	/**
 505	 * @return string ID of the controller
 506	 */
 507	public function getId()
 508	{
 509		return $this->_id;
 510	}
 511
 512	/**
 513	 * @return string the controller ID that is prefixed with the module ID (if any).
 514	 */
 515	public function getUniqueId()
 516	{
 517		return $this->_module ? $this->_module->getId().'/'.$this->_id : $this->_id;
 518	}
 519
 520	/**
 521	 * @return string the route (module ID, controller ID and action ID) of the current request.
 522	 * @since 1.1.0
 523	 */
 524	public function getRoute()
 525	{
 526		if(($action=$this->getAction())!==null)
 527			return $this->getUniqueId().'/'.$action->getId();
 528		else
 529			return $this->getUniqueId();
 530	}
 531
 532	/**
 533	 * @return CWebModule the module that this controller belongs to. It returns null
 534	 * if the controller does not belong to any module
 535	 */
 536	public function getModule()
 537	{
 538		return $this->_module;
 539	}
 540
 541	/**
 542	 * Returns the directory containing view files for this controller.
 543	 * The default implementation returns 'protected/views/ControllerID'.
 544	 * Child classes may override this method to use customized view path.
 545	 * If the controller belongs to a module, the default view path
 546	 * is the {@link CWebModule::getViewPath module view path} appended with the controller ID.
 547	 * @return string the directory containing the view files for this controller. Defaults to 'protected/views/ControllerID'.
 548	 */
 549	public function getViewPath()
 550	{
 551		if(($module=$this->getModule())===null)
 552			$module=Yii::app();
 553		return $module->getViewPath().DIRECTORY_SEPARATOR.$this->getId();
 554	}
 555
 556	/**
 557	 * Looks for the view file according to the given view name.
 558	 *
 559	 * When a theme is currently active, this method will call {@link CTheme::getViewFile} to determine
 560	 * which view file should be returned.
 561	 *
 562	 * Otherwise, this method will return the corresponding view file based on the following criteria:
 563	 * <ul>
 564	 * <li>absolute view within a module: the view name starts with a single slash '/'.
 565	 * In this case, the view will be searched for under the currently active module's view path.
 566	 * If there is no active module, the view will be searched for under the application's view path.</li>
 567	 * <li>absolute view within the application: the view name starts with double slashes '//'.
 568	 * In this case, the view will be searched for under the application's view path.
 569	 * This syntax has been available since version 1.1.3.</li>
 570	 * <li>aliased view: the view name contains dots and refers to a path alias.
 571	 * The view file is determined by calling {@link YiiBase::getPathOfAlias()}. Note that aliased views
 572	 * cannot be themed because they can refer to a view file located at arbitrary places.</li>
 573	 * <li>relative view: otherwise. Relative views will be searched for under the currently active
 574	 * controller's view path.</li>
 575	 * </ul>
 576	 *
 577	 * After the view file is identified, this method may further call {@link CApplication::findLocalizedFile}
 578	 * to find its localized version if internationalization is needed.
 579	 *
 580	 * @param string $viewName view name
 581	 * @return string the view file path, false if the view file does not exist
 582	 * @see resolveViewFile
 583	 * @see CApplication::findLocalizedFile
 584	 */
 585	public function getViewFile($viewName)
 586	{
 587		if(($theme=Yii::app()->getTheme())!==null && ($viewFile=$theme->getViewFile($this,$viewName))!==false)
 588			return $viewFile;
 589		$moduleViewPath=$basePath=Yii::app()->getViewPath();
 590		if(($module=$this->getModule())!==null)
 591			$moduleViewPath=$module->getViewPath();
 592		return $this->resolveViewFile($viewName,$this->getViewPath(),$basePath,$moduleViewPath);
 593	}
 594
 595	/**
 596	 * Looks for the layout view script based on the layout name.
 597	 *
 598	 * The layout name can be specified in one of the following ways:
 599	 *
 600	 * <ul>
 601	 * <li>layout is false: returns false, meaning no layout.</li>
 602	 * <li>layout is null: the currently active module's layout will be used. If there is no active module,
 603	 * the application's layout will be used.</li>
 604	 * <li>a regular view name.</li>
 605	 * </ul>
 606	 *
 607	 * The resolution of the view file based on the layout view is similar to that in {@link getViewFile}.
 608	 * In particular, the following rules are followed:
 609	 *
 610	 * Otherwise, this method will return the corresponding view file based on the following criteria:
 611	 * <ul>
 612	 * <li>When a theme is currently active, this method will call {@link CTheme::getLayoutFile} to determine
 613	 * which view file should be returned.</li>
 614	 * <li>absolute view within a module: the view name starts with a single slash '/'.
 615	 * In this case, the view will be searched for under the currently active module's view path.
 616	 * If there is no active module, the view will be searched for under the application's view path.</li>
 617	 * <li>absolute view within the application: the view name starts with double slashes '//'.
 618	 * In this case, the view will be searched for under the application's view path.
 619	 * This syntax has been available since version 1.1.3.</li>
 620	 * <li>aliased view: the view name contains dots and refers to a path alias.
 621	 * The view file is determined by calling {@link YiiBase::getPathOfAlias()}. Note that aliased views
 622	 * cannot be themed because they can refer to a view file located at arbitrary places.</li>
 623	 * <li>relative view: otherwise. Relative views will be searched for under the currently active
 624	 * module's layout path. In case when there is no active module, the view will be searched for
 625	 * under the application's layout path.</li>
 626	 * </ul>
 627	 *
 628	 * After the view file is identified, this method may further call {@link CApplication::findLocalizedFile}
 629	 * to find its localized version if internationalization is needed.
 630	 *
 631	 * @param mixed $layoutName layout name
 632	 * @return string the view file for the layout. False if the view file cannot be found
 633	 */
 634	public function getLayoutFile($layoutName)
 635	{
 636		if($layoutName===false)
 637			return false;
 638		if(($theme=Yii::app()->getTheme())!==null && ($layoutFile=$theme->getLayoutFile($this,$layoutName))!==false)
 639			return $layoutFile;
 640
 641		if(empty($layoutName))
 642		{
 643			$module=$this->getModule();
 644			while($module!==null)
 645			{
 646				if($module->layout===false)
 647					return false;
 648				if(!empty($module->layout))
 649					break;
 650				$module=$module->getParentModule();
 651			}
 652			if($module===null)
 653				$module=Yii::app();
 654			$layoutName=$module->layout;
 655		}
 656		else if(($module=$this->getModule())===null)
 657			$module=Yii::app();
 658
 659		return $this->resolveViewFile($layoutName,$module->getLayoutPath(),Yii::app()->getViewPath(),$module->getViewPath());
 660	}
 661
 662	/**
 663	 * Finds a view file based on its name.
 664	 * The view name can be in one of the following formats:
 665	 * <ul>
 666	 * <li>absolute view within a module: the view name starts with a single slash '/'.
 667	 * In this case, the view will be searched for under the currently active module's view path.
 668	 * If there is no active module, the view will be searched for under the application's view path.</li>
 669	 * <li>absolute view within the application: the view name starts with double slashes '//'.
 670	 * In this case, the view will be searched for under the application's view path.
 671	 * This syntax has been available since version 1.1.3.</li>
 672	 * <li>aliased view: the view name contains dots and refers to a path alias.
 673	 * The view file is determined by calling {@link YiiBase::getPathOfAlias()}. Note that aliased views
 674	 * cannot be themed because they can refer to a view file located at arbitrary places.</li>
 675	 * <li>relative view: otherwise. Relative views will be searched for under the currently active
 676	 * controller's view path.</li>
 677	 * </ul>
 678	 * For absolute view and relative view, the corresponding view file is a PHP file
 679	 * whose name is the same as the view name. The file is located under a specified directory.
 680	 * This method will call {@link CApplication::findLocalizedFile} to search for a localized file, if any.
 681	 * @param string $viewName the view name
 682	 * @param string $viewPath the directory that is used to search for a relative view name
 683	 * @param string $basePath the directory that is used to search for an absolute view name under the application
 684	 * @param string $moduleViewPath the directory that is used to search for an absolute view name under the current module.
 685	 * If this is not set, the application base view path will be used.
 686	 * @return mixed the view file path. False if the view file does not exist.
 687	 */
 688	public function resolveViewFile($viewName,$viewPath,$basePath,$moduleViewPath=null)
 689	{
 690		if(empty($viewName))
 691			return false;
 692
 693		if($moduleViewPath===null)
 694			$moduleViewPath=$basePath;
 695
 696		if(($renderer=Yii::app()->getViewRenderer())!==null)
 697			$extension=$renderer->fileExtension;
 698		else
 699			$extension='.php';
 700		if($viewName[0]==='/')
 701		{
 702			if(strncmp($viewName,'//',2)===0)
 703				$viewFile=$basePath.$viewName;
 704			else
 705				$viewFile=$moduleViewPath.$viewName;
 706		}
 707		else if(strpos($viewName,'.'))
 708			$viewFile=Yii::getPathOfAlias($viewName);
 709		else
 710			$viewFile=$viewPath.DIRECTORY_SEPARATOR.$viewName;
 711
 712		if(is_file($viewFile.$extension))
 713			return Yii::app()->findLocalizedFile($viewFile.$extension);
 714		else if($extension!=='.php' && is_file($viewFile.'.php'))
 715			return Yii::app()->findLocalizedFile($viewFile.'.php');
 716		else
 717			return false;
 718	}
 719
 720	/**
 721	 * Returns the list of clips.
 722	 * A clip is a named piece of rendering result that can be
 723	 * inserted at different places.
 724	 * @return CMap the list of clips
 725	 * @see CClipWidget
 726	 */
 727	public function getClips()
 728	{
 729		if($this->_clips!==null)
 730			return $this->_clips;
 731		else
 732			return $this->_clips=new CMap;
 733	}
 734
 735	/**
 736	 * Processes the request using another controller action.
 737	 * This is like {@link redirect}, but the user browser's URL remains unchanged.
 738	 * In most cases, you should call {@link redirect} instead of this method.
 739	 * @param string $route the route of the new controller action. This can be an action ID, or a complete route
 740	 * with module ID (optional in the current module), controller ID and action ID. If the former, the action is assumed
 741	 * to be located within the current controller.
 742	 * @param boolean $exit whether to end the application after this call. Defaults to true.
 743	 * @since 1.1.0
 744	 */
 745	public function forward($route,$exit=true)
 746	{
 747		if(strpos($route,'/')===false)
 748			$this->run($route);
 749		else
 750		{
 751			if($route[0]!=='/' && ($module=$this->getModule())!==null)
 752				$route=$module->getId().'/'.$route;
 753			Yii::app()->runController($route);
 754		}
 755		if($exit)
 756			Yii::app()->end();
 757	}
 758
 759	/**
 760	 * Renders a view with a layout.
 761	 *
 762	 * This method first calls {@link renderPartial} to render the view (called content view).
 763	 * It then renders the layout view which may embed the content view at appropriate place.
 764	 * In the layout view, the content view rendering result can be accessed via variable
 765	 * <code>$content</code>. At the end, it calls {@link processOutput} to insert scripts
 766	 * and dynamic contents if they are available.
 767	 *
 768	 * By default, the layout view script is "protected/views/layouts/main.php".
 769	 * This may be customized by changing {@link layout}.
 770	 *
 771	 * @param string $view name of the view to be rendered. See {@link getViewFile} for details
 772	 * about how the view script is resolved.
 773	 * @param array $data data to be extracted into PHP variables and made available to the view script
 774	 * @param boolean $return whether the rendering result should be returned instead of being displayed to end users.
 775	 * @return string the rendering result. Null if the rendering result is not required.
 776	 * @see renderPartial
 777	 * @see getLayoutFile
 778	 */
 779	public function render($view,$data=null,$return=false)
 780	{
 781		if($this->beforeRender($view))
 782		{
 783			$output=$this->renderPartial($view,$data,true);
 784			if(($layoutFile=$this->getLayoutFile($this->layout))!==false)
 785				$output=$this->renderFile($layoutFile,array('content'=>$output),true);
 786
 787			$this->afterRender($view,$output);
 788
 789			$output=$this->processOutput($output);
 790
 791			if($return)
 792				return $output;
 793			else
 794				echo $output;
 795		}
 796	}
 797
 798	/**
 799	 * This method is invoked at the beginning of {@link render()}.
 800	 * You may override this method to do some preprocessing when rendering a view.
 801	 * @param string $view the view to be rendered
 802	 * @return boolean whether the view should be rendered.
 803	 * @since 1.1.5
 804	 */
 805	protected function beforeRender($view)
 806	{
 807		return true;
 808	}
 809
 810	/**
 811	 * This method is invoked after the specified is rendered by calling {@link render()}.
 812	 * Note that this method is invoked BEFORE {@link processOutput()}.
 813	 * You may override this method to do some postprocessing for the view rendering.
 814	 * @param string $view the view that has been rendered
 815	 * @param string $output the rendering result of the view. Note that this parameter is passed
 816	 * as a reference. That means you can modify it within this method.
 817	 * @since 1.1.5
 818	 */
 819	protected function afterRender($view, &$output)
 820	{
 821	}
 822
 823	/**
 824	 * Renders a static text string.
 825	 * The string will be inserted in the current controller layout and returned back.
 826	 * @param string $text the static text string
 827	 * @param boolean $return whether the rendering result should be returned instead of being displayed to end users.
 828	 * @return string the rendering result. Null if the rendering result is not required.
 829	 * @see getLayoutFile
 830	 */
 831	public function renderText($text,$return=false)
 832	{
 833		if(($layoutFile=$this->getLayoutFile($this->layout))!==false)
 834			$text=$this->renderFile($layoutFile,array('content'=>$text),true);
 835
 836		$text=$this->processOutput($text);
 837
 838		if($return)
 839			return $text;
 840		else
 841			echo $text;
 842	}
 843
 844	/**
 845	 * Renders a view.
 846	 *
 847	 * The named view refers to a PHP script (resolved via {@link getViewFile})
 848	 * that is included by this method. If $data is an associative array,
 849	 * it will be extracted as PHP variables and made available to the script.
 850	 *
 851	 * This method differs from {@link render()} in that it does not
 852	 * apply a layout to the rendered result. It is thus mostly used
 853	 * in rendering a partial view, or an AJAX response.
 854	 *
 855	 * @param string $view name of the view to be rendered. See {@link getViewFile} for details
 856	 * about how the view script is resolved.
 857	 * @param array $data data to be extracted into PHP variables and made available to the view script
 858	 * @param boolean $return whether the rendering result should be returned instead of being displayed to end users
 859	 * @param boolean $processOutput whether the rendering result should be postprocessed using {@link processOutput}.
 860	 * @return string the rendering result. Null if the rendering result is not required.
 861	 * @throws CException if the view does not exist
 862	 * @see getViewFile
 863	 * @see processOutput
 864	 * @see render
 865	 */
 866	public function renderPartial($view,$data=null,$return=false,$processOutput=false)
 867	{
 868		if(($viewFile=$this->getViewFile($view))!==false)
 869		{
 870			$output=$this->renderFile($viewFile,$data,true);
 871			if($processOutput)
 872				$output=$this->processOutput($output);
 873			if($return)
 874				return $output;
 875			else
 876				echo $output;
 877		}
 878		else
 879			throw new CException(Yii::t('yii','{controller} cannot find the requested view "{view}".',
 880				array('{controller}'=>get_class($this), '{view}'=>$view)));
 881	}
 882
 883	/**
 884	 * Renders a named clip with the supplied parameters.
 885	 * This is similar to directly accessing the {@link clips} property.
 886	 * The main difference is that it can take an array of named parameters
 887	 * which will replace the corresponding placeholders in the clip.
 888	 * @param string $name the name of the clip
 889	 * @param array $params an array of named parameters (name=>value) that should replace
 890	 * their corresponding placeholders in the clip
 891	 * @param boolean $return whether to return the clip content or echo it.
 892	 * @return mixed either the clip content or null
 893	 * @since 1.1.8
 894	 */
 895	public function renderClip($name,$params=array(),$return=false)
 896	{
 897		$text=isset($this->clips[$name]) ? strtr($this->clips[$name], $params) : '';
 898
 899		if($return)
 900			return $text;
 901		else
 902			echo $text;
 903	}
 904
 905	/**
 906	 * Renders dynamic content returned by the specified callback.
 907	 * This method is used together with {@link COutputCache}. Dynamic contents
 908	 * will always show as their latest state even if the content surrounding them is being cached.
 909	 * This is especially useful when caching pages that are mostly static but contain some small
 910	 * dynamic regions, such as username or current time.
 911	 * We can use this method to render these dynamic regions to ensure they are always up-to-date.
 912	 *
 913	 * The first parameter to this method should be a valid PHP callback, while the rest parameters
 914	 * will be passed to the callback.
 915	 *
 916	 * Note, the callback and its parameter values will be serialized and saved in cache.
 917	 * Make sure they are serializable.
 918	 *
 919	 * @param callback $callback a PHP callback which returns the needed dynamic content.
 920	 * When the callback is specified as a string, it will be first assumed to be a method of the current
 921	 * controller class. If the method does not exist, it is assumed to be a global PHP function.
 922	 * Note, the callback should return the dynamic content instead of echoing it.
 923	 */
 924	public function renderDynamic($callback)
 925	{
 926		$n=count($this->_dynamicOutput);
 927		echo "<###dynamic-$n###>";
 928		$params=func_get_args();
 929		array_shift($params);
 930		$this->renderDynamicInternal($callback,$params);
 931	}
 932
 933	/**
 934	 * This method is internally used.
 935	 * @param callback $callback a PHP callback which returns the needed dynamic content.
 936	 * @param array $params parameters passed to the PHP callback
 937	 * @see renderDynamic
 938	 */
 939	public function renderDynamicInternal($callback,$params)
 940	{
 941		$this->recordCachingAction('','renderDynamicInternal',array($callback,$params));
 942		if(is_string($callback) && method_exists($this,$callback))
 943			$callback=array($this,$callback);
 944		$this->_dynamicOutput[]=call_user_func_array($callback,$params);
 945	}
 946
 947	/**
 948	 * Creates a relative URL for the specified action defined in this controller.
 949	 * @param string $route the URL route. This should be in the format of 'ControllerID/ActionID'.
 950	 * If the ControllerID is not present, the current controller ID will be prefixed to the route.
 951	 * If the route is empty, it is assumed to be the current action.
 952	 * If the controller belongs to a module, the {@link CWebModule::getId module ID}
 953	 * will be prefixed to the route. (If you do not want the module ID prefix, the route should start with a slash '/'.)
 954	 * @param array $params additional GET parameters (name=>value). Both the name and value will be URL-encoded.
 955	 * If the name is '#', the corresponding value will be treated as an anchor
 956	 * and will be appended at the end of the URL.
 957	 * @param string $ampersand the token separating name-value pairs in the URL.
 958	 * @return string the constructed URL
 959	 */
 960	public function createUrl($route,$params=array(),$ampersand='&')
 961	{
 962		if($route==='')
 963			$route=$this->getId().'/'.$this->getAction()->getId();
 964		else if(strpos($route,'/')===false)
 965			$route=$this->getId().'/'.$route;
 966		if($route[0]!=='/' && ($module=$this->getModule())!==null)
 967			$route=$module->getId().'/'.$route;
 968		return Yii::app()->createUrl(trim($route,'/'),$params,$ampersand);
 969	}
 970
 971	/**
 972	 * Creates an absolute URL for the specified action defined in this controller.
 973	 * @param string $route the URL route. This should be in the format of 'ControllerID/ActionID'.
 974	 * If the ControllerPath is not present, the current controller ID will be prefixed to the route.
 975	 * If the route is empty, it is assumed to be the current action.
 976	 * @param array $params additional GET parameters (name=>value). Both the name and value will be URL-encoded.
 977	 * @param string $schema schema to use (e.g. http, https). If empty, the schema used for the current request will be used.
 978	 * @param string $ampersand the token separating name-value pairs in the URL.
 979	 * @return string the constructed URL
 980	 */
 981	public function createAbsoluteUrl($route,$params=array(),$schema='',$ampersand='&')
 982	{
 983		$url=$this->createUrl($route,$params,$ampersand);
 984		if(strpos($url,'http')===0)
 985			return $url;
 986		else
 987			return Yii::app()->getRequest()->getHostInfo($schema).$url;
 988	}
 989
 990	/**
 991	 * @return string the page title. Defaults to the controller name and the action name.
 992	 */
 993	public function getPageTitle()
 994	{
 995		if($this->_pageTitle!==null)
 996			return $this->_pageTitle;
 997		else
 998		{
 999			$name=ucfirst(basename($this->getId()));
1000			if($this->getAction()!==null && strcasecmp($this->getAction()->getId(),$this->defaultAction))
1001				return $this->_pageTitle=Yii::app()->name.' - '.ucfirst($this->getAction()->getId()).' '.$name;
1002			else
1003				return $this->_pageTitle=Yii::app()->name.' - '.$name;
1004		}
1005	}
1006
1007	/**
1008	 * @param string $value the page title.
1009	 */
1010	public function setPageTitle($value)
1011	{
1012		$this->_pageTitle=$value;
1013	}
1014
1015	/**
1016	 * Redirects the browser to the specified URL or route (controller/action).
1017	 * @param mixed $url the URL to be redirected to. If the parameter is an array,
1018	 * the first element must be a route to a controller action and the rest
1019	 * are GET parameters in name-value pairs.
1020	 * @param boolean $terminate whether to terminate the current application after calling this method
1021	 * @param integer $statusCode the HTTP status code. Defaults to 302. See {@link http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html}
1022	 * for details about HTTP status code.
1023	 */
1024	public function redirect($url,$terminate=true,$statusCode=302)
1025	{
1026		if(is_array($url))
1027		{
1028			$route=isset($url[0]) ? $url[0] : '';
1029			$url=$this->createUrl($route,array_splice($url,1));
1030		}
1031		Yii::app()->getRequest()->redirect($url,$terminate,$statusCode);
1032	}
1033
1034	/**
1035	 * Refreshes the current page.
1036	 * The effect of this method call is the same as user pressing the
1037	 * refresh button on the browser (without post data).
1038	 * @param boolean $terminate whether to terminate the current application after calling this method
1039	 * @param string $anchor the anchor that should be appended to the redirection URL.
1040	 * Defaults to empty. Make sure the anchor starts with '#' if you want to specify it.
1041	 */
1042	public function refresh($terminate=true,$anchor='')
1043	{
1044		$this->redirect(Yii::app()->getRequest()->getUrl().$anchor,$terminate);
1045	}
1046
1047	/**
1048	 * Records a method call when an output cache is in effect.
1049	 * When the content is served from the output cache, the recorded
1050	 * method will be re-invoked.
1051	 * @param string $context a property name of the controller. It refers to an object
1052	 * whose method is being called. If empty it means the controller itself.
1053	 * @param string $method the method name
1054	 * @param array $params parameters passed to the method
1055	 * @see COutputCache
1056	 */
1057	public function recordCachingAction($context,$method,$params)
1058	{
1059		if($this->_cachingStack) // record only when there is an active output cache
1060		{
1061			foreach($this->_cachingStack as $cache)
1062				$cache->recordAction($context,$method,$params);
1063		}
1064	}
1065
1066	/**
1067	 * @param boolean $createIfNull whether to create a stack if it does not exist yet. Defaults to true.
1068	 * @return CStack stack of {@link COutputCache} objects
1069	 */
1070	public function getCachingStack($createIfNull=true)
1071	{
1072		if(!$this->_cachingStack)
1073			$this->_cachingStack=new CStack;
1074		return $this->_cachingStack;
1075	}
1076
1077	/**
1078	 * Returns whether the caching stack is empty.
1079	 * @return boolean whether the caching stack is empty. If not empty, it means currently there are
1080	 * some output cache in effect. Note, the return result of this method may change when it is
1081	 * called in different output regions, depending on the partition of output caches.
1082	 */
1083	public function isCachingStackEmpty()
1084	{
1085		return $this->_cachingStack===null || !$this->_cachingStack->getCount();
1086	}
1087
1088	/**
1089	 * This method is invoked right before an action is to be executed (after all possible filters.)
1090	 * You may override this method to do last-minute preparation for the action.
1091	 * @param CAction $action the action to be executed.
1092	 * @return boolean whether the action should be executed.
1093	 */
1094	protected function beforeAction($action)
1095	{
1096		return true;
1097	}
1098
1099	/**
1100	 * This method is invoked right after an action is executed.
1101	 * You may override this method to do some postprocessing for the action.
1102	 * @param CAction $action the action just executed.
1103	 */
1104	protected function afterAction($action)
1105	{
1106	}
1107
1108	/**
1109	 * The filter method for 'postOnly' filter.
1110	 * This filter reports an error if the applied action is receiving a non-POST request.
1111	 * @param CFilterChain $filterChain the filter chain that the filter is on.
1112	 * @throws CHttpException if the current request is not a POST request
1113	 */
1114	public function filterPostOnly($filterChain)
1115	{
1116		if(Yii::app()->getRequest()->getIsPostRequest())
1117			$filterChain->run();
1118		else
1119			throw new CHttpException(400,Yii::t('yii','Your request is invalid.'));
1120	}
1121
1122	/**
1123	 * The filter method for 'ajaxOnly' filter.
1124	 * This filter reports an error if the applied action is receiving a non-AJAX request.
1125	 * @param CFilterChain $filterChain the filter chain that the filter is on.
1126	 * @throws CHttpException if the current request is not an AJAX request.
1127	 */
1128	public function filterAjaxOnly($filterChain)
1129	{
1130		if(Yii::app()->getRequest()->getIsAjaxRequest())
1131			$filterChain->run();
1132		else
1133			throw new CHttpException(400,Yii::t('yii','Your request is invalid.'));
1134	}
1135
1136	/**
1137	 * The filter method for 'accessControl' filter.
1138	 * This filter is a wrapper of {@link CAccessControlFilter}.
1139	 * To use this filter, you must override {@link accessRules} method.
1140	 * @param CFilterChain $filterChain the filter chain that the filter is on.
1141	 */
1142	public function filterAccessControl($filterChain)
1143	{
1144		$filter=new CAccessControlFilter;
1145		$filter->setRules($this->accessRules());
1146		$filter->filter($filterChain);
1147	}
1148
1149	/**
1150	 * Returns a persistent page state value.
1151	 * A page state is a variable that is persistent across POST requests of the same page.
1152	 * In order to use persistent page states, the form(s) must be stateful
1153	 * which are generated using {@link CHtml::statefulForm}.
1154	 * @param string $name the state name
1155	 * @param mixed $defaultValue the value to be returned if the named state is not found
1156	 * @return mixed the page state value
1157	 * @see setPageState
1158	 * @see CHtml::statefulForm
1159	 */
1160	public function getPageState($name,$defaultValue=null)
1161	{
1162		if($this->_pageStates===null)
1163			$this->_pageStates=$this->loadPageStates();
1164		return isset($this->_pageStates[$name])?$this->_pageStates[$name]:$defaultValue;
1165	}
1166
1167	/**
1168	 * Saves a persistent page state value.
1169	 * A page state is a variable that is persistent across POST requests of the same page.
1170	 * In order to use persistent page states, the form(s) must be stateful
1171	 * which are generated using {@link CHtml::statefulForm}.
1172	 * @param string $name the state name
1173	 * @param mixed $value the page state value
1174	 * @param mixed $defaultValue the default page state value. If this is the same as
1175	 * the given value, the state will be removed from persistent storage.
1176	 * @see getPageState
1177	 * @see CHtml::statefulForm
1178	 */
1179	public function setPageState($name,$value,$defaultValue=null)
1180	{
1181		if($this->_pageStates===null)
1182			$this->_pageStates=$this->loadPageStates();
1183		if($value===$defaultValue)
1184			unset($this->_pageStates[$name]);
1185		else
1186			$this->_pageStates[$name]=$value;
1187
1188		$params=func_get_args();
1189		$this->recordCachingAction('','setPageState',$params);
1190	}
1191
1192	/**
1193	 * Removes all page states.
1194	 */
1195	public function clearPageStates()
1196	{
1197		$this->_pageStates=array();
1198	}
1199
1200	/**
1201	 * Loads page states from a hidden input.
1202	 * @return array the loaded page states
1203	 */
1204	protected function loadPageStates()
1205	{
1206		if(!empty($_POST[self::STATE_INPUT_NAME]))
1207		{
1208			if(($data=base64_decode($_POST[self::STATE_INPUT_NAME]))!==false)
1209			{
1210				if(extension_loaded('zlib'))
1211					$data=@gzuncompress($data);
1212				if(($data=Yii::app()->getSecurityManager()->validateData($data))!==false)
1213					return unserialize($data);
1214			}
1215		}
1216		return array();
1217	}
1218
1219	/**
1220	 * Saves page states as a base64 string.
1221	 * @param array $states the states to be saved.
1222	 * @param string $output the output to be modified. Note, this is passed by reference.
1223	 */
1224	protected function savePageStates($states,&$output)
1225	{
1226		$data=Yii::app()->getSecurityManager()->hashData(serialize($states));
1227		if(extension_loaded('zlib'))
1228			$data=gzcompress($data);
1229		$value=base64_encode($data);
1230		$output=str_replace(CHtml::pageStateField(''),CHtml::pageStateField($value),$output);
1231	}
1232}