<?php
/**
* CBaseController class file.
*
* @author Qiang Xue <[email protected]>
* @link http://www.yiiframework.com/
* @copyright Copyright © 2008-2011 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* CBaseController is the base class for {@link CController} and {@link CWidget}.
*
* It provides the common functionalities shared by controllers who need to render views.
*
* CBaseController also implements the support for the following features:
* <ul>
* <li>{@link CClipWidget Clips} : a clip is a piece of captured output that can be inserted elsewhere.</li>
* <li>{@link CWidget Widgets} : a widget is a self-contained sub-controller with its own view and model.</li>
* <li>{@link COutputCache Fragment cache} : fragment cache selectively caches a portion of the output.</li>
* </ul>
*
* To use a widget in a view, use the following in the view:
* <pre>
* $this->widget('path.to.widgetClass',array('property1'=>'value1',...));
* </pre>
* or
* <pre>
* $this->beginWidget('path.to.widgetClass',array('property1'=>'value1',...));
* // ... display other contents here
* $this->endWidget();
* </pre>
*
* To create a clip, use the following:
* <pre>
* $this->beginClip('clipID');
* // ... display the clip contents
* $this->endClip();
* </pre>
* Then, in a different view or place, the captured clip can be inserted as:
* <pre>
* echo $this->clips['clipID'];
* </pre>
*
* Note that $this in the code above refers to current controller so, for example,
* if you need to access clip from a widget where $this refers to widget itself
* you need to do it the following way:
*
* <pre>
* echo $this->getController()->clips['clipID'];
* </pre>
*
* To use fragment cache, do as follows,
* <pre>
* if($this->beginCache('cacheID',array('property1'=>'value1',...))
* {
* // ... display the content to be cached here
* $this->endCache();
* }
* </pre>
*
* @author Qiang Xue <[email protected]>
* @package system.web
* @since 1.0
*/
abstract class CBaseController extends CComponent
{
private $_widgetStack=array();
/**
* Returns the view script file according to the specified view name.
* This method must be implemented by child classes.
* @param string $viewName view name
* @return string the file path for the named view. False if the view cannot be found.
*/
abstract public function getViewFile($viewName);
/**
* Renders a view file.
*
* @param string $viewFile view file path
* @param array $data data to be extracted and made available to the view
* @param boolean $return whether the rendering result should be returned instead of being echoed
* @return string the rendering result. Null if the rendering result is not required.
* @throws CException if the view file does not exist
*/
public function renderFile($viewFile,$data=null,$return=false)
{
$widgetCount=count($this->_widgetStack);
if(($renderer=Yii::app()->getViewRenderer())!==null && $renderer->fileExtension==='.'.CFileHelper::getExtension($viewFile))
$content=$renderer->renderFile($this,$viewFile,$data,$return);
else
$content=$this->renderInternal($viewFile,$data,$return);
if(count($this->_widgetStack)===$widgetCount)
return $content;
else
{
$widget=end($this->_widgetStack);
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.',
array('{controller}'=>get_class($this), '{view}'=>$viewFile, '{widget}'=>get_class($widget))));
}
}
/**
* Renders a view file.
* This method includes the view file as a PHP script
* and captures the display result if required.
* @param string $_viewFile_ view file
* @param array $_data_ data to be extracted and made available to the view file
* @param boolean $_return_ whether the rendering result should be returned as a string
* @return string the rendering result. Null if the rendering result is not required.
*/
public function renderInternal($_viewFile_,$_data_=null,$_return_=false)
{
// we use special variable names here to avoid conflict when extracting data
if(is_array($_data_))
extract($_data_,EXTR_PREFIX_SAME,'data');
else
$data=$_data_;
if($_return_)
{
ob_start();
ob_implicit_flush(false);
require($_viewFile_);
return ob_get_clean();
}
else
require($_viewFile_);
}
/**
* Creates a widget and initializes it.
* This method first creates the specified widget instance.
* It then configures the widget's properties with the given initial values.
* At the end it calls {@link CWidget::init} to initialize the widget.
* Starting from version 1.1, if a {@link CWidgetFactory widget factory} is enabled,
* this method will use the factory to create the widget, instead.
* @param string $className class name (can be in path alias format)
* @param array $properties initial property values
* @return CWidget the fully initialized widget instance.
*/
public function createWidget($className,$properties=array())
{
$widget=Yii::app()->getWidgetFactory()->createWidget($this,$className,$properties);
$widget->init();
return $widget;
}
/**
* Creates a widget and executes it.
* @param string $className the widget class name or class in dot syntax (e.g. application.widgets.MyWidget)
* @param array $properties list of initial property values for the widget (Property Name => Property Value)
* @param boolean $captureOutput whether to capture the output of the widget. If true, the method will capture
* and return the output generated by the widget. If false, the output will be directly sent for display
* and the widget object will be returned. This parameter is available since version 1.1.2.
* @return mixed the widget instance when $captureOutput is false, or the widget output when $captureOutput is true.
*/
public function widget($className,$properties=array(),$captureOutput=false)
{
if($captureOutput)
{
ob_start();
ob_implicit_flush(false);
$widget=$this->createWidget($className,$properties);
$widget->run();
return ob_get_clean();
}
else
{
$widget=$this->createWidget($className,$properties);
$widget->run();
return $widget;
}
}
/**
* Creates a widget and executes it.
* This method is similar to {@link widget()} except that it is expecting
* a {@link endWidget()} call to end the execution.
* @param string $className the widget class name or class in dot syntax (e.g. application.widgets.MyWidget)
* @param array $properties list of initial property values for the widget (Property Name => Property Value)
* @return CWidget the widget created to run
* @see endWidget
*/
public function beginWidget($className,$properties=array())
{
$widget=$this->createWidget($className,$properties);
$this->_widgetStack[]=$widget;
return $widget;
}
/**
* Ends the execution of the named widget.
* This method is used together with {@link beginWidget()}.
* @param string $id optional tag identifying the method call for debugging purpose.
* @return CWidget the widget just ended running
* @throws CException if an extra endWidget call is made
* @see beginWidget
*/
public function endWidget($id='')
{
if(($widget=array_pop($this->_widgetStack))!==null)
{
$widget->run();
return $widget;
}
else
throw new CException(Yii::t('yii','{controller} has an extra endWidget({id}) call in its view.',
array('{controller}'=>get_class($this),'{id}'=>$id)));
}
/**
* Begins recording a clip.
* This method is a shortcut to beginning {@link CClipWidget}.
* @param string $id the clip ID.
* @param array $properties initial property values for {@link CClipWidget}.
*/
public function beginClip($id,$properties=array())
{
$properties['id']=$id;
$this->beginWidget('CClipWidget',$properties);
}
/**
* Ends recording a clip.
* This method is an alias to {@link endWidget}.
*/
public function endClip()
{
$this->endWidget('CClipWidget');
}
/**
* Begins fragment caching.
* This method will display cached content if it is availabe.
* If not, it will start caching and would expect a {@link endCache()}
* call to end the cache and save the content into cache.
* A typical usage of fragment caching is as follows,
* <pre>
* if($this->beginCache($id))
* {
* // ...generate content here
* $this->endCache();
* }
* </pre>
* @param string $id a unique ID identifying the fragment to be cached.
* @param array $properties initial property values for {@link COutputCache}.
* @return boolean whether we need to generate content for caching. False if cached version is available.
* @see endCache
*/
public function beginCache($id,$properties=array())
{
$properties['id']=$id;
$cache=$this->beginWidget('COutputCache',$properties);
if($cache->getIsContentCached())
{
$this->endCache();
return false;
}
else
return true;
}
/**
* Ends fragment caching.
* This is an alias to {@link endWidget}.
* @see beginCache
*/
public function endCache()
{
$this->endWidget('COutputCache');
}
/**
* Begins the rendering of content that is to be decorated by the specified view.
* @param mixed $view the name of the view that will be used to decorate the content. The actual view script
* is resolved via {@link getViewFile}. If this parameter is null (default),
* the default layout will be used as the decorative view.
* Note that if the current controller does not belong to
* any module, the default layout refers to the application's {@link CWebApplication::layout default layout};
* If the controller belongs to a module, the default layout refers to the module's
* {@link CWebModule::layout default layout}.
* @param array $data the variables (name=>value) to be extracted and made available in the decorative view.
* @see endContent
* @see CContentDecorator
*/
public function beginContent($view=null,$data=array())
{
$this->beginWidget('CContentDecorator',array('view'=>$view, 'data'=>$data));
}
/**
* Ends the rendering of content.
* @see beginContent
*/
public function endContent()
{
$this->endWidget('CContentDecorator');
}
}