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