From d8122a0f98137822b0ea20f7602d105fcb2fe962 Mon Sep 17 00:00:00 2001
From: xue <>
Date: Sun, 9 Apr 2006 01:50:08 +0000
Subject: Fixed a few bugs in TOutputCache. Documentation for TOutputCache is
 completed.

---
 framework/Web/UI/TClientScriptManager.php     |  24 +-
 framework/Web/UI/TControl.php                 |  16 +-
 framework/Web/UI/TPage.php                    |   4 +-
 framework/Web/UI/TTemplateManager.php         |  10 +-
 framework/Web/UI/WebControls/TOutputCache.php | 308 +++++++++++++++++++++-----
 5 files changed, 285 insertions(+), 77 deletions(-)

(limited to 'framework/Web')

diff --git a/framework/Web/UI/TClientScriptManager.php b/framework/Web/UI/TClientScriptManager.php
index 34f17bf3..cb945fbf 100644
--- a/framework/Web/UI/TClientScriptManager.php
+++ b/framework/Web/UI/TClientScriptManager.php
@@ -120,7 +120,7 @@ class TClientScriptManager extends TApplicationComponent
 
 		$params=func_get_args();
 		foreach($this->_page->getCachingStack() as $item)
-			$item->registerAction('registerPradoScript',$params);
+			$item->registerAction('Page.ClientScript','registerPradoScript',$params);
 	}
 
 	private function registerPradoScriptInternal($name)
@@ -183,7 +183,7 @@ class TClientScriptManager extends TApplicationComponent
 
 		$params=func_get_args();
 		foreach($this->_page->getCachingStack() as $item)
-			$item->registerAction('registerPostBackControl',$params);
+			$item->registerAction('Page.ClientScript','registerPostBackControl',$params);
 	}
 
 	/**
@@ -202,7 +202,7 @@ class TClientScriptManager extends TApplicationComponent
 
 		$params=func_get_args();
 		foreach($this->_page->getCachingStack() as $item)
-			$item->registerAction('registerDefaultButton',$params);
+			$item->registerAction('Page.ClientScript','registerDefaultButton',$params);
 	}
 
 	/**
@@ -216,7 +216,7 @@ class TClientScriptManager extends TApplicationComponent
 
 		$params=func_get_args();
 		foreach($this->_page->getCachingStack() as $item)
-			$item->registerAction('registerFocusControl',$params);
+			$item->registerAction('Page.ClientScript','registerFocusControl',$params);
 	}
 
 	/**
@@ -243,7 +243,7 @@ class TClientScriptManager extends TApplicationComponent
 
 		$params=func_get_args();
 		foreach($this->_page->getCachingStack() as $item)
-			$item->registerAction('registerStyleSheetFile',$params);
+			$item->registerAction('Page.ClientScript','registerStyleSheetFile',$params);
 	}
 
 	/**
@@ -257,7 +257,7 @@ class TClientScriptManager extends TApplicationComponent
 
 		$params=func_get_args();
 		foreach($this->_page->getCachingStack() as $item)
-			$item->registerAction('registerStyleSheet',$params);
+			$item->registerAction('Page.ClientScript','registerStyleSheet',$params);
 	}
 
 	/**
@@ -271,7 +271,7 @@ class TClientScriptManager extends TApplicationComponent
 
 		$params=func_get_args();
 		foreach($this->_page->getCachingStack() as $item)
-			$item->registerAction('registerHeadScriptFile',$params);
+			$item->registerAction('Page.ClientScript','registerHeadScriptFile',$params);
 	}
 
 	/**
@@ -285,7 +285,7 @@ class TClientScriptManager extends TApplicationComponent
 
 		$params=func_get_args();
 		foreach($this->_page->getCachingStack() as $item)
-			$item->registerAction('registerHeadScript',$params);
+			$item->registerAction('Page.ClientScript','registerHeadScript',$params);
 	}
 
 	/**
@@ -299,7 +299,7 @@ class TClientScriptManager extends TApplicationComponent
 
 		$params=func_get_args();
 		foreach($this->_page->getCachingStack() as $item)
-			$item->registerAction('registerScriptFile',$params);
+			$item->registerAction('Page.ClientScript','registerScriptFile',$params);
 	}
 
 	/**
@@ -313,7 +313,7 @@ class TClientScriptManager extends TApplicationComponent
 
 		$params=func_get_args();
 		foreach($this->_page->getCachingStack() as $item)
-			$item->registerAction('registerBeginScript',$params);
+			$item->registerAction('Page.ClientScript','registerBeginScript',$params);
 	}
 
 	/**
@@ -327,7 +327,7 @@ class TClientScriptManager extends TApplicationComponent
 
 		$params=func_get_args();
 		foreach($this->_page->getCachingStack() as $item)
-			$item->registerAction('registerEndScript',$params);
+			$item->registerAction('Page.ClientScript','registerEndScript',$params);
 	}
 
 	/**
@@ -342,7 +342,7 @@ class TClientScriptManager extends TApplicationComponent
 
 		$params=func_get_args();
 		foreach($this->_page->getCachingStack() as $item)
-			$item->registerAction('registerHiddenField',$params);
+			$item->registerAction('Page.ClientScript','registerHiddenField',$params);
 	}
 
 	/**
diff --git a/framework/Web/UI/TControl.php b/framework/Web/UI/TControl.php
index 3c3c7060..f5f77513 100644
--- a/framework/Web/UI/TControl.php
+++ b/framework/Web/UI/TControl.php
@@ -1446,7 +1446,8 @@ class TControl extends TApplicationComponent implements IRenderable, IBindable
 
 	/**
 	 * Loads state (viewstate and controlstate) into a control and its children.
-	 * @param TMap the collection of the state
+	 * This method should only be used by framework developers.
+	 * @param array the collection of the state
 	 * @param boolean whether the viewstate should be loaded
 	 */
 	protected function loadStateRecursive(&$state,$needViewState=true)
@@ -1502,6 +1503,7 @@ class TControl extends TApplicationComponent implements IRenderable, IBindable
 
 	/**
 	 * Saves all control state (viewstate and controlstate) as a collection.
+	 * This method should only be used by framework developers.
 	 * @param boolean whether the viewstate should be saved
 	 * @return array the collection of the control state (including its children's state).
 	 */
@@ -1718,6 +1720,18 @@ class TEmptyControlCollection extends TControlCollection
 	{
 		parent::__construct($owner,true);
 	}
+
+	/**
+	 * Inserts an item at the specified position.
+	 * This overrides the parent implementation by ignoring new addition.
+	 * @param integer the speicified position.
+	 * @param mixed new item
+	 */
+	public function insertAt($index,$item)
+	{
+		if(!is_string($item))  // string is possible if property tag is used. we simply ignore it in this case
+			parent::insertAt($index,$item);  // this will generate an exception in parent implementation
+	}
 }
 
 /**
diff --git a/framework/Web/UI/TPage.php b/framework/Web/UI/TPage.php
index 7f4c490c..6a4083b0 100644
--- a/framework/Web/UI/TPage.php
+++ b/framework/Web/UI/TPage.php
@@ -609,7 +609,7 @@ class TPage extends TTemplateControl
 		$this->_controlsRegisteredForPostData[$id]=true;
 		$params=func_get_args();
 		foreach($this->getCachingStack() as $item)
-			$item->registerAction('registerRequiresPostData',$id);
+			$item->registerAction('Page','registerRequiresPostData',$id);
 	}
 
 	/**
@@ -727,7 +727,7 @@ class TPage extends TTemplateControl
 	public function ensureRenderInForm($control)
 	{
 		if(!$this->_inFormRender)
-			throw new TConfigurationException('page_control_outofform',$control->getUniqueID());
+			throw new TConfigurationException('page_control_outofform',get_class($control),$control->getUniqueID());
 	}
 
 	/**
diff --git a/framework/Web/UI/TTemplateManager.php b/framework/Web/UI/TTemplateManager.php
index fa4cbcbf..73a336c8 100644
--- a/framework/Web/UI/TTemplateManager.php
+++ b/framework/Web/UI/TTemplateManager.php
@@ -262,8 +262,11 @@ class TTemplate extends TApplicationComponent implements ITemplate
 		$controls=array();
 		foreach($this->_tpl as $key=>$object)
 		{
-			$parent=isset($controls[$object[0]])?$controls[$object[0]]:$tplControl;
-			if(!$parent->getAllowChildControls())
+			if($object[0]===-1)
+				$parent=$tplControl;
+			else if(isset($controls[$object[0]]))
+				$parent=$controls[$object[0]];
+			else
 				continue;
 			if(isset($object[2]))	// component
 			{
@@ -271,7 +274,6 @@ class TTemplate extends TApplicationComponent implements ITemplate
 				$properties=&$object[2];
 				if($component instanceof TControl)
 				{
-					$controls[$key]=$component;
 					$component->setTemplateControl($tplControl);
 					if(isset($properties['id']))
 					{
@@ -292,6 +294,8 @@ class TTemplate extends TApplicationComponent implements ITemplate
 					foreach($properties as $name=>$value)
 						$this->configureControl($component,$name,$value);
 					$component->createdOnTemplate($parent);
+					if($component->getAllowChildControls())
+						$controls[$key]=$component;
 				}
 				else if($component instanceof TComponent)
 				{
diff --git a/framework/Web/UI/WebControls/TOutputCache.php b/framework/Web/UI/WebControls/TOutputCache.php
index 9708c033..08f60f90 100644
--- a/framework/Web/UI/WebControls/TOutputCache.php
+++ b/framework/Web/UI/WebControls/TOutputCache.php
@@ -1,21 +1,109 @@
 <?php
+/**
+ * TOutputCache class file
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 2006 PradoSoft
+ * @license http://www.pradosoft.com/license/
+ * @version $Revision: $  $Date: $
+ * @package System.Web.UI.WebControls
+ */
 
+/**
+ * TOutputCache class.
+ *
+ * TOutputCache enables caching a portion of a Web page, also known as
+ * partial caching. The content being cached can be either static or
+ * dynamic.
+ *
+ * To use TOutputCache, simply enclose the content to be cached
+ * within the TOutputCache component tag on a template, e.g.,
+ * <code>
+ * <com:TOutputCache>
+ *   content to be cached
+ * </com:TOutputCache>
+ * </code>
+ * where content to be cached can be static text and/or component tags.
+ *
+ * The validity of the cached content is determined based on two factors:
+ * the {@link setDuration Duration} and the {@link getCacheDependency CacheDependency}.
+ * The former specifies the number of seconds that the data can remain
+ * valid in cache (defaults to 60s), while the latter specifies a dependency
+ * that the data depends on. If the dependency changes, the cached content
+ * is invalidated. By default, TOutputCache doesn't specify a dependency.
+ * Derived classes may override {@link getCacheDependency()} method to
+ * enforce a dependency (such as system state change, etc.)
+ *
+ * The content fetched from cache may be variated with respect to
+ * some parameters. It supports variation with respect to request parameters,
+ * which is specified by {@link setVaryByParam VaryByParam} property.
+ * If a specified request parameter is different, a different version of
+ * cached content is used. This is extremely useful if a page's content
+ * may be variated according to some GET parameters. To variate the cached
+ * content by other factors, override {@link calculateCacheKey()} method.
+ *
+ * Output caches can be nested. An outer cache takes precedence over an
+ * inner cache. This means, if the content cached by the inner cache expires
+ * or is invalidated, while that by the outer cache not, the outer cached
+ * content will be used.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $  $Date: $
+ * @package System.Web.UI.WebControls
+ * @since 3.0
+ */
 class TOutputCache extends TControl implements INamingContainer
 {
 	const CACHE_ID_PREFIX='prado:outputcache';
-	private $_useCache=false;
-	private $_cacheReady=false;
+	private $_dataCached=false;
+	private $_cacheAvailable=false;
 	private $_cacheChecked=false;
-	private $_expiry=60;
+	private $_cacheKey=null;
+	private $_duration=60;
 	private $_cache=null;
 	private $_contents;
 	private $_state;
-	private $_enableCaching=true;
 	private $_actions=array();
+	private $_varyByParam='';
 
+	/**
+	 * Returns a value indicating whether body contents are allowed for this control.
+	 * This method overrides the parent implementation by checking if cached
+	 * content is available or not. If yes, it returns false, otherwise true.
+	 * @param boolean whether body contents are allowed for this control.
+	 */
+	public function getAllowChildControls()
+	{
+		$this->determineCacheability();
+		return !$this->_dataCached;
+	}
+
+	private function determineCacheability()
+	{
+		if(!$this->_cacheChecked)
+		{
+			$this->_cacheChecked=true;
+			if(!$this->getPage()->getIsPostBack() && ($this->_cache=$this->getApplication()->getCache())!==null && $this->_duration>0)
+			{
+				$this->_cacheAvailable=true;
+				$data=$this->_cache->get($this->getCacheKey());
+				if(($this->_dataCached=($data!==false)))
+					list($this->_contents,$this->_state,$this->_actions)=$data;
+			}
+		}
+	}
+
+	/**
+	 * Performs the Init step for the control and all its child controls.
+	 * This method overrides the parent implementation by setting up
+	 * the stack of the output cache in the page.
+	 * Only framework developers should use this method.
+	 * @param TControl the naming container control
+	 */
 	protected function initRecursive($namingContainer=null)
 	{
-		if($this->_cacheReady && !$this->_useCache)
+		if($this->_cacheAvailable && !$this->_dataCached)
 		{
 			$stack=$this->getPage()->getCachingStack();
 			$stack->push($this);
@@ -26,9 +114,17 @@ class TOutputCache extends TControl implements INamingContainer
 			parent::initRecursive($namingContainer);
 	}
 
+	/**
+	 * Performs the Load step for the control and all its child controls.
+	 * This method overrides the parent implementation by setting up
+	 * the stack of the output cache in the page. If the data is restored
+	 * from cache, it also recovers the actions associated with the cached data.
+	 * Only framework developers should use this method.
+	 * @param TControl the naming container control
+	 */
 	protected function loadRecursive()
 	{
-		if($this->_cacheReady && !$this->_useCache)
+		if($this->_cacheAvailable && !$this->_dataCached)
 		{
 			$stack=$this->getPage()->getCachingStack();
 			$stack->push($this);
@@ -37,24 +133,37 @@ class TOutputCache extends TControl implements INamingContainer
 		}
 		else
 		{
-			if($this->_useCache)
-			{
-				$cs=$this->getPage()->getClientScript();
-				foreach($this->_actions as $action)
-				{
-					if($action[0]==='registerRequiresPostData')
-						$this->getPage()->registerRequiresPostData($action[1]);
-					else
-						call_user_func_array(array($cs,$action[0]),$action[1]);
-				}
-			}
+			if($this->_dataCached)
+				$this->performActions();
 			parent::loadRecursive();
 		}
 	}
 
+	private function performActions()
+	{
+		$page=$this->getPage();
+		$cs=$page->getClientScript();
+		foreach($this->_actions as $action)
+		{
+			if($action[0]==='Page.ClientScript')
+				call_user_func_array(array($cs,$action[1]),$action[2]);
+			else if($action[0]==='Page')
+				call_user_func_array(array($page,$action[1]),$action[2]);
+			else
+				call_user_func_array(array($this->getSubProperty($action[0]),$action[1]),$action[2]);
+		}
+	}
+
+	/**
+	 * Performs the PreRender step for the control and all its child controls.
+	 * This method overrides the parent implementation by setting up
+	 * the stack of the output cache in the page.
+	 * Only framework developers should use this method.
+	 * @param TControl the naming container control
+	 */
 	protected function preRenderRecursive()
 	{
-		if($this->_cacheReady && !$this->_useCache)
+		if($this->_cacheAvailable && !$this->_dataCached)
 		{
 			$stack=$this->getPage()->getCachingStack();
 			$stack->push($this);
@@ -65,80 +174,161 @@ class TOutputCache extends TControl implements INamingContainer
 			parent::preRenderRecursive();
 	}
 
-	public function registerAction($funcName,$funcParams)
+	/**
+	 * Loads state (viewstate and controlstate) into a control and its children.
+	 * This method overrides the parent implementation by loading
+	 * cached state if available.
+	 * This method should only be used by framework developers.
+	 * @param array the collection of the state
+	 * @param boolean whether the viewstate should be loaded
+	 */
+	protected function loadStateRecursive(&$state,$needViewState=true)
 	{
-		$this->_actions[]=array($funcName,$funcParams);
+		if($this->_dataCached)
+			parent::loadStateRecursive($this->_state,$needViewState);
+		else
+			parent::loadStateRecursive($state,$needViewState);
 	}
 
-	public function getAllowChildControls()
+	/**
+	 * Saves all control state (viewstate and controlstate) as a collection.
+	 * This method overrides the parent implementation by saving state
+	 * into cache if needed.
+	 * This method should only be used by framework developers.
+	 * @param boolean whether the viewstate should be saved
+	 * @return array the collection of the control state (including its children's state).
+	 */
+	protected function &saveStateRecursive($needViewState=true)
 	{
-		if(!$this->_cacheChecked)
+		if($this->_dataCached)
+			return $this->_state;
+		else if($this->_cacheAvailable)
 		{
-			$this->_cacheChecked=true;
-			if(!$this->getPage()->getIsPostBack() && ($this->_cache=$this->getApplication()->getCache())!==null && $this->getEnableCaching())
-			{
-				$this->_cacheReady=true;
-				$data=$this->_cache->get($this->getCacheKey());
-				if(($this->_useCache=($data!==false)))
-					list($this->_contents,$this->_state,$this->_actions)=$data;
-			}
+			$this->_state=parent::saveStateRecursive($needViewState);
+			return $this->_state;
 		}
-		return !$this->_useCache;
+		else
+			return parent::saveStateRecursive($needViewState);
 	}
 
-	public function getEnableCaching()
+	/**
+	 * Registers an action associated with the content being cached.
+	 * The registered action will be replayed if the content stored
+	 * in the cache is served to end-users.
+	 * @param string context of the action method. This is a property-path
+	 * referring to the context object (e.g. Page, Page.ClientScript)
+	 * @param string method name of the context object
+	 * @param array list of parameters to be passed to the action method
+	 */
+	public function registerAction($context,$funcName,$funcParams)
 	{
-		return $this->_enableCaching;
+		$this->_actions[]=array($context,$funcName,$funcParams);
 	}
 
-	public function setEnableCaching($value)
+	private function getCacheKey()
 	{
-		$this->_enableCaching=TPropertyValue::ensureBoolean($value);
+		if($this->_cacheKey===null)
+			$this->_cacheKey=$this->calculateCacheKey();
+		return $this->_cacheKey;
 	}
 
-	protected function loadStateRecursive(&$state,$needViewState=true)
+	/**
+	 * Calculates the cache key.
+	 * The key is calculated based on the unique ID of this control
+	 * and the request parameters specified via {@link setVaryByParam VaryByParam}.
+	 * This method may be overriden to support other variations in
+	 * the calculated cache key.
+	 * @return string cache key
+	 */
+	protected function calculateCacheKey()
 	{
-		if($this->_useCache)
-			parent::loadStateRecursive($this->_state,$needViewState);
+		if($this->_varyByParam!=='')
+		{
+			$params=array();
+			$request=$this->getRequest();
+			foreach(explode(',',$this->_varyByParam) as $name)
+			{
+				$name=trim($name);
+				$params[$name]=$request->itemAt($name);
+			}
+			return self::CACHE_ID_PREFIX.$this->getUniqueID().serialize($params);
+		}
 		else
-			parent::loadStateRecursive($state,$needViewState);
+			return self::CACHE_ID_PREFIX.$this->getUniqueID();
 	}
 
-	protected function &saveStateRecursive($needViewState=true)
+	/**
+	 * Returns the dependency of the data to be cached.
+	 * The default implementation simply returns null, meaning no specific dependency.
+	 * This method may be overriden to associate the data to be cached
+	 * with additional dependencies.
+	 * @return ICacheDependency
+	 */
+	protected function getCacheDependency()
 	{
-		if($this->_useCache)
-			return $this->_state;
-		else if($this->_cacheReady)
-		{
-			$this->_state=parent::saveStateRecursive($needViewState);
-			return $this->_state;
-		}
-		else
-			return parent::saveStateRecursive($needViewState);
+		return null;
 	}
 
-	protected function getCacheKey()
+	/**
+	 * @return boolean whether content enclosed is cached or not
+	 */
+	public function getContentCached()
 	{
-		return self::CACHE_ID_PREFIX.$this->getUniqueID();
+		return $this->_dataCached;
 	}
 
-	public function getExpiry()
+	/**
+	 * @return integer number of seconds that the data can remain in cache. Defaults to 60 seconds.
+	 * Note, if cache dependency changes or cache space is limited,
+	 * the data may be purged out of cache earlier.
+	 */
+	public function getDuration()
 	{
-		return $this->_expiry;
+		return $this->_duration;
 	}
 
-	public function setExpiry($value)
+	/**
+	 * @param integer number of seconds that the data can remain in cache. If 0, it means data is not cached.
+	 * @throws TInvalidDataValueException if the value is smaller than 0.
+	 */
+	public function setDuration($value)
 	{
 		if(($value=TPropertyValue::ensureInteger($value))<0)
-			throw new TInvalidDataValueException('outputcache_expiry_invalid');
-		$this->_expiry=$value;
+			throw new TInvalidDataValueException('outputcache_duration_invalid',get_class($this));
+		$this->_duration=$value;
+	}
+
+	/**
+	 * @return string a semicolon-separated list of strings used to vary the output cache. Defaults to ''.
+	 */
+	public function getVaryByParam()
+	{
+		return $this->_varyByParam;
+	}
+
+	/**
+	 * Sets the names of the request parameters that should be used in calculating the cache key.
+	 * The names should be concatenated by semicolons.
+	 * By setting this value, the output cache will use different cached data
+	 * for each different set of request parameter values.
+	 * @return string a semicolon-separated list of strings used to vary the output cache.
+	 */
+	public function setVaryByParam($value)
+	{
+		$this->_varyByParam=trim($value);
 	}
 
+	/**
+	 * Renders the output cache control.
+	 * This method overrides the parent implementation by capturing the output
+	 * from its child controls and saving it into cache, if output cache is needed.
+	 * @param THtmlWriter
+	 */
 	public function render($writer)
 	{
-		if($this->_useCache)
+		if($this->_dataCached)
 			$writer->write($this->_contents);
-		else if($this->_cacheReady)
+		else if($this->_cacheAvailable)
 		{
 			$textWriter=new TTextWriter;
 
@@ -149,7 +339,7 @@ class TOutputCache extends TControl implements INamingContainer
 
 			$content=$textWriter->flush();
 			$data=array($content,$this->_state,$this->_actions);
-			$this->_cache->set($this->getCacheKey(),$data,$this->getExpiry());
+			$this->_cache->set($this->getCacheKey(),$data,$this->getDuration(),$this->getCacheDependency());
 			$writer->write($content);
 		}
 		else
-- 
cgit v1.2.3