1: <?php
2: /**
3: * CBaseController 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: * CBaseController is the base class for {@link CController} and {@link CWidget}.
14: *
15: * It provides the common functionalities shared by controllers who need to render views.
16: *
17: * CBaseController also implements the support for the following features:
18: * <ul>
19: * <li>{@link CClipWidget Clips} : a clip is a piece of captured output that can be inserted elsewhere.</li>
20: * <li>{@link CWidget Widgets} : a widget is a self-contained sub-controller with its own view and model.</li>
21: * <li>{@link COutputCache Fragment cache} : fragment cache selectively caches a portion of the output.</li>
22: * </ul>
23: *
24: * To use a widget in a view, use the following in the view:
25: * <pre>
26: * $this->widget('path.to.widgetClass',array('property1'=>'value1',...));
27: * </pre>
28: * or
29: * <pre>
30: * $this->beginWidget('path.to.widgetClass',array('property1'=>'value1',...));
31: * // ... display other contents here
32: * $this->endWidget();
33: * </pre>
34: *
35: * To create a clip, use the following:
36: * <pre>
37: * $this->beginClip('clipID');
38: * // ... display the clip contents
39: * $this->endClip();
40: * </pre>
41: * Then, in a different view or place, the captured clip can be inserted as:
42: * <pre>
43: * echo $this->clips['clipID'];
44: * </pre>
45: *
46: * Note that $this in the code above refers to current controller so, for example,
47: * if you need to access clip from a widget where $this refers to widget itself
48: * you need to do it the following way:
49: *
50: * <pre>
51: * echo $this->getController()->clips['clipID'];
52: * </pre>
53: *
54: * To use fragment cache, do as follows,
55: * <pre>
56: * if($this->beginCache('cacheID',array('property1'=>'value1',...))
57: * {
58: * // ... display the content to be cached here
59: * $this->endCache();
60: * }
61: * </pre>
62: *
63: * @author Qiang Xue <qiang.xue@gmail.com>
64: * @package system.web
65: * @since 1.0
66: */
67: abstract class CBaseController extends CComponent
68: {
69: private $_widgetStack=array();
70:
71: /**
72: * Returns the view script file according to the specified view name.
73: * This method must be implemented by child classes.
74: * @param string $viewName view name
75: * @return string the file path for the named view. False if the view cannot be found.
76: */
77: abstract public function getViewFile($viewName);
78:
79:
80: /**
81: * Renders a view file.
82: *
83: * @param string $viewFile view file path
84: * @param array $data data to be extracted and made available to the view
85: * @param boolean $return whether the rendering result should be returned instead of being echoed
86: * @return string the rendering result. Null if the rendering result is not required.
87: * @throws CException if the view file does not exist
88: */
89: public function renderFile($viewFile,$data=null,$return=false)
90: {
91: $widgetCount=count($this->_widgetStack);
92: if(($renderer=Yii::app()->getViewRenderer())!==null && $renderer->fileExtension==='.'.CFileHelper::getExtension($viewFile))
93: $content=$renderer->renderFile($this,$viewFile,$data,$return);
94: else
95: $content=$this->renderInternal($viewFile,$data,$return);
96: if(count($this->_widgetStack)===$widgetCount)
97: return $content;
98: else
99: {
100: $widget=end($this->_widgetStack);
101: throw new CException(Yii::t('yii','{controller} contains improperly nested widget tags in its view "{view}". A {widget} widget does not have an endWidget() call.',
102: array('{controller}'=>get_class($this), '{view}'=>$viewFile, '{widget}'=>get_class($widget))));
103: }
104: }
105:
106: /**
107: * Renders a view file.
108: * This method includes the view file as a PHP script
109: * and captures the display result if required.
110: * @param string $_viewFile_ view file
111: * @param array $_data_ data to be extracted and made available to the view file
112: * @param boolean $_return_ whether the rendering result should be returned as a string
113: * @return string the rendering result. Null if the rendering result is not required.
114: */
115: public function renderInternal($_viewFile_,$_data_=null,$_return_=false)
116: {
117: // we use special variable names here to avoid conflict when extracting data
118: if(is_array($_data_))
119: extract($_data_,EXTR_PREFIX_SAME,'data');
120: else
121: $data=$_data_;
122: if($_return_)
123: {
124: ob_start();
125: ob_implicit_flush(false);
126: require($_viewFile_);
127: return ob_get_clean();
128: }
129: else
130: require($_viewFile_);
131: }
132:
133: /**
134: * Creates a widget and initializes it.
135: * This method first creates the specified widget instance.
136: * It then configures the widget's properties with the given initial values.
137: * At the end it calls {@link CWidget::init} to initialize the widget.
138: * Starting from version 1.1, if a {@link CWidgetFactory widget factory} is enabled,
139: * this method will use the factory to create the widget, instead.
140: * @param string $className class name (can be in path alias format)
141: * @param array $properties initial property values
142: * @return CWidget the fully initialized widget instance.
143: */
144: public function createWidget($className,$properties=array())
145: {
146: $widget=Yii::app()->getWidgetFactory()->createWidget($this,$className,$properties);
147: $widget->init();
148: return $widget;
149: }
150:
151: /**
152: * Creates a widget and executes it.
153: * @param string $className the widget class name or class in dot syntax (e.g. application.widgets.MyWidget)
154: * @param array $properties list of initial property values for the widget (Property Name => Property Value)
155: * @param boolean $captureOutput whether to capture the output of the widget. If true, the method will capture
156: * and return the output generated by the widget. If false, the output will be directly sent for display
157: * and the widget object will be returned. This parameter is available since version 1.1.2.
158: * @return mixed the widget instance when $captureOutput is false, or the widget output when $captureOutput is true.
159: */
160: public function widget($className,$properties=array(),$captureOutput=false)
161: {
162: if($captureOutput)
163: {
164: ob_start();
165: ob_implicit_flush(false);
166: try
167: {
168: $widget=$this->createWidget($className,$properties);
169: $widget->run();
170: }
171: catch(Exception $e)
172: {
173: ob_end_clean();
174: throw $e;
175: }
176: return ob_get_clean();
177: }
178: else
179: {
180: $widget=$this->createWidget($className,$properties);
181: $widget->run();
182: return $widget;
183: }
184: }
185:
186: /**
187: * Creates a widget and executes it.
188: * This method is similar to {@link widget()} except that it is expecting
189: * a {@link endWidget()} call to end the execution.
190: * @param string $className the widget class name or class in dot syntax (e.g. application.widgets.MyWidget)
191: * @param array $properties list of initial property values for the widget (Property Name => Property Value)
192: * @return CWidget the widget created to run
193: * @see endWidget
194: */
195: public function beginWidget($className,$properties=array())
196: {
197: $widget=$this->createWidget($className,$properties);
198: $this->_widgetStack[]=$widget;
199: return $widget;
200: }
201:
202: /**
203: * Ends the execution of the named widget.
204: * This method is used together with {@link beginWidget()}.
205: * @param string $id optional tag identifying the method call for debugging purpose.
206: * @return CWidget the widget just ended running
207: * @throws CException if an extra endWidget call is made
208: * @see beginWidget
209: */
210: public function endWidget($id='')
211: {
212: if(($widget=array_pop($this->_widgetStack))!==null)
213: {
214: $widget->run();
215: return $widget;
216: }
217: else
218: throw new CException(Yii::t('yii','{controller} has an extra endWidget({id}) call in its view.',
219: array('{controller}'=>get_class($this),'{id}'=>$id)));
220: }
221:
222: /**
223: * Begins recording a clip.
224: * This method is a shortcut to beginning {@link CClipWidget}.
225: * @param string $id the clip ID.
226: * @param array $properties initial property values for {@link CClipWidget}.
227: */
228: public function beginClip($id,$properties=array())
229: {
230: $properties['id']=$id;
231: $this->beginWidget('CClipWidget',$properties);
232: }
233:
234: /**
235: * Ends recording a clip.
236: * This method is an alias to {@link endWidget}.
237: */
238: public function endClip()
239: {
240: $this->endWidget('CClipWidget');
241: }
242:
243: /**
244: * Begins fragment caching.
245: * This method will display cached content if it is availabe.
246: * If not, it will start caching and would expect a {@link endCache()}
247: * call to end the cache and save the content into cache.
248: * A typical usage of fragment caching is as follows,
249: * <pre>
250: * if($this->beginCache($id))
251: * {
252: * // ...generate content here
253: * $this->endCache();
254: * }
255: * </pre>
256: * @param string $id a unique ID identifying the fragment to be cached.
257: * @param array $properties initial property values for {@link COutputCache}.
258: * @return boolean whether we need to generate content for caching. False if cached version is available.
259: * @see endCache
260: */
261: public function beginCache($id,$properties=array())
262: {
263: $properties['id']=$id;
264: $cache=$this->beginWidget('COutputCache',$properties);
265: if($cache->getIsContentCached())
266: {
267: $this->endCache();
268: return false;
269: }
270: else
271: return true;
272: }
273:
274: /**
275: * Ends fragment caching.
276: * This is an alias to {@link endWidget}.
277: * @see beginCache
278: */
279: public function endCache()
280: {
281: $this->endWidget('COutputCache');
282: }
283:
284: /**
285: * Begins the rendering of content that is to be decorated by the specified view.
286: * @param mixed $view the name of the view that will be used to decorate the content. The actual view script
287: * is resolved via {@link getViewFile}. If this parameter is null (default),
288: * the default layout will be used as the decorative view.
289: * Note that if the current controller does not belong to
290: * any module, the default layout refers to the application's {@link CWebApplication::layout default layout};
291: * If the controller belongs to a module, the default layout refers to the module's
292: * {@link CWebModule::layout default layout}.
293: * @param array $data the variables (name=>value) to be extracted and made available in the decorative view.
294: * @see endContent
295: * @see CContentDecorator
296: */
297: public function beginContent($view=null,$data=array())
298: {
299: $this->beginWidget('CContentDecorator',array('view'=>$view, 'data'=>$data));
300: }
301:
302: /**
303: * Ends the rendering of content.
304: * @see beginContent
305: */
306: public function endContent()
307: {
308: $this->endWidget('CContentDecorator');
309: }
310: }
311: