summaryrefslogtreecommitdiff
path: root/framework/Web/UI
diff options
context:
space:
mode:
authorxue <>2005-12-03 04:45:44 +0000
committerxue <>2005-12-03 04:45:44 +0000
commitbdd139266bbd71b828394a1500551254fc42a819 (patch)
tree118933cdbec0a7f710666f35fa1027451d148c99 /framework/Web/UI
parent0d42be79b9dfeaa7686fb7da11920af4eb0561d7 (diff)
Diffstat (limited to 'framework/Web/UI')
-rw-r--r--framework/Web/UI/TAssetManager.php221
-rw-r--r--framework/Web/UI/THiddenFieldPageStatePersister.php2
-rw-r--r--framework/Web/UI/TPageStatePersister.php22
-rw-r--r--framework/Web/UI/TTemplateManager.php630
-rw-r--r--framework/Web/UI/TThemeManager.php351
5 files changed, 1203 insertions, 23 deletions
diff --git a/framework/Web/UI/TAssetManager.php b/framework/Web/UI/TAssetManager.php
new file mode 100644
index 00000000..0f96ae90
--- /dev/null
+++ b/framework/Web/UI/TAssetManager.php
@@ -0,0 +1,221 @@
+<?php
+/**
+ * TAssetManager class
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 2005 PradoSoft
+ * @license http://www.pradosoft.com/license/
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI
+ */
+
+/**
+ * TAssetManager class
+ *
+ * TAssetManager provides a scheme to allow web clients visiting
+ * private files that are normally web-inaccessible.
+ *
+ * TAssetManager will copy the file to be published into a web-accessible
+ * directory. The default base directory for storing the file is "assets", which
+ * should be under the application directory. This can be changed by setting
+ * the {@link setBasePath BasePath} property together with the
+ * {@link setBaseUrl BaseUrl} property that refers to the URL for accessing the base path.
+ *
+ * By default, TAssetManager will not publish a file or directory if it already
+ * exists in the publishing directory and has an older modification time.
+ * If the application mode is set as 'Performance', the modification time check
+ * will be skipped. You can explicitly require a modification time check
+ * with the function {@link publishFilePath}. This is usually
+ * very useful during development.
+ *
+ * TAssetManager may be configured in application configuration file within
+ * page service element as follows,
+ * <module id="asset" BasePath="Application.assets" BaseUrl="/assets" />
+ * where {@link getBasePath BasePath} and {@link getBaseUrl BaseUrl} are
+ * configurable properties of TAssetManager. Make sure that BasePath is a namespace
+ * pointing to a valid directory writable by the Web server process.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI
+ * @since 3.0
+ */
+class TAssetManager extends TComponent implements IModule
+{
+ /**
+ * Default web accessible base path for storing private files
+ */
+ const DEFAULT_BASEPATH='assets';
+ /**
+ * @var string base web accessible path for storing private files
+ */
+ private $_basePath=null;
+ /**
+ * @var string base URL for accessing the publishing directory.
+ */
+ private $_baseUrl=null;
+ /**
+ * @var string module ID
+ */
+ private $_id='asset';
+ /**
+ * @var boolean whether to use timestamp checking to ensure files are published with up-to-date versions.
+ */
+ private $_checkTimestamp=false;
+ /**
+ * @var TApplication application instance
+ */
+ private $_application;
+ /**
+ * @var array published assets
+ */
+ private $_published=array();
+
+ /**
+ * Initializes the module.
+ * This method is required by IModule and is invoked by application.
+ * @param TApplication application
+ * @param TXmlElement module configuration
+ */
+ public function init($application,$config)
+ {
+ $this->_application=$application;
+ if($this->_basePath===null)
+ $this->_basePath=dirname($application->getRequest()->getPhysicalApplicationPath()).'/'.self::DEFAULT_BASEPATH;
+ if(!is_writable($this->_basePath) || !is_dir($this->_basePath))
+ throw new TConfigurationException('assetmanager_basepath_invalid',$this->_basePath);
+ if($this->_baseUrl===null)
+ $this->_baseUrl=dirname($application->getRequest()->getApplicationPath()).'/'.self::DEFAULT_BASEPATH;
+ $application->getService()->setAssetManager($this);
+ }
+
+ /**
+ * @return string id of this module
+ */
+ public function getID()
+ {
+ return $this->_id;
+ }
+
+ /**
+ * @param string id of this module
+ */
+ public function setID($value)
+ {
+ $this->_id=$value;
+ }
+
+ /**
+ * @return string the root directory storing published asset files
+ */
+ public function getBasePath()
+ {
+ return $this->_basePath;
+ }
+
+ /**
+ * Sets the root directory storing published asset files.
+ * The directory must be in namespace format.
+ * @param string the root directory storing published asset files
+ * @throws TInvalidOperationException if the service is initialized already
+ */
+ public function setBasePath($value)
+ {
+ if($this->_initialized)
+ throw new TInvalidOperationException('assetmanager_basepath_unchangeable');
+ else
+ {
+ $this->_basePath=Prado::getPathOfAlias($value);
+ if($this->_basePath===null || !is_dir($this->_basePath) || !is_writable($this->_basePath))
+ throw new TInvalidDataValueException('assetmanage_basepath_invalid',$value);
+ }
+ }
+
+ /**
+ * @return string the base url that the published asset files can be accessed
+ */
+ public function getBaseUrl()
+ {
+ return $this->_baseUrl;
+ }
+
+ /**
+ * @param string the base url that the published asset files can be accessed
+ * @throws TInvalidOperationException if the service is initialized already
+ */
+ public function setBaseUrl($value)
+ {
+ if($this->_initialized)
+ throw new TInvalidOperationException('assetmanager_baseurl_unchangeable');
+ else
+ $this->_baseUrl=$value;
+ }
+
+ /**
+ * Publishes a file or a directory (recursively).
+ * This method will copy the content in a directory (recursively) to
+ * a web accessible directory and returns the URL for the directory.
+ * @param string the path to be published
+ * @param boolean whether to use file modify time to ensure every published file is latest
+ * @return string an absolute URL to the published directory
+ */
+ public function publishFilePath($path,$checkTimestamp=false)
+ {
+ if(isset($this->_published[$path]))
+ return $this->_published[$path];
+ else if(($fullpath=realpath($path))===false)
+ return '';
+ else if(is_file($fullpath))
+ {
+ $dir=md5(dirname($fullpath));
+ $file=$this->_basePath.'/'.$dir.'/'.basename($fullpath);
+ if(!is_file($file) || $checkTimestamp || $this->_application->getMode()!=='Performance')
+ {
+ if(!is_dir($this->_basePath.'/'.$dir))
+ @mkdir($this->_basePath.'/'.$dir);
+ if(!is_file($file) || @filemtime($file)<@filemtime($fullpath))
+ @copy($fullpath,$file);
+ }
+ $this->_published[$path]=$this->_baseUrl.'/'.$dir.'/'.basename($fullpath);
+ return $this->_published[$path];
+ }
+ else
+ {
+ $dir=md5($fullpath);
+ if(!is_dir($this->_basePath.'/'.$dir) || $checkTimestamp || $this->_application->getMode()!=='Performance')
+ $this->copyDirectory($fullpath,$this->_basePath.'/'.$dir);
+ $this->_published[$path]=$this->_baseUrl.'/'.$dir;
+ return $this->_published[$path];
+ }
+ }
+
+ /**
+ * Copies a directory recursively as another.
+ * If the destination directory does not exist, it will be created.
+ * File modification time is used to ensure the copied files are latest.
+ * @param string the source directory
+ * @param string the destination directory
+ */
+ protected function copyDirectory($src,$dst)
+ {
+ if(!is_dir($dst))
+ @mkdir($dst);
+ $folder=@opendir($src);
+ while($file=@readdir($folder))
+ {
+ if($file==='.' || $file==='..')
+ continue;
+ else if(is_file($src.'/'.$file))
+ {
+ if(@filemtime($dst.'/'.$file)<@filemtime($src.'/'.$file))
+ @copy($src.'/'.$file,$dst.'/'.$file);
+ }
+ else
+ $this->copyDirectory($src.'/'.$file,$dst.'/'.$file);
+ }
+ closedir($folder);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/framework/Web/UI/THiddenFieldPageStatePersister.php b/framework/Web/UI/THiddenFieldPageStatePersister.php
index 3d305668..bd32f686 100644
--- a/framework/Web/UI/THiddenFieldPageStatePersister.php
+++ b/framework/Web/UI/THiddenFieldPageStatePersister.php
@@ -11,7 +11,7 @@ class THiddenFieldPageStatePersister extends TComponent implements IPageStatePer
* @param TApplication application
* @param TXmlElement module configuration
*/
- public function init($application,$config)
+ public function init($application, $config)
{
$this->_application=$application;
}
diff --git a/framework/Web/UI/TPageStatePersister.php b/framework/Web/UI/TPageStatePersister.php
deleted file mode 100644
index 6ec9527b..00000000
--- a/framework/Web/UI/TPageStatePersister.php
+++ /dev/null
@@ -1,22 +0,0 @@
-<?php
-
-abstract class TPageStatePersister extends TComponent
-{
- private $_page;
-
- public function __construct($page)
- {
- $this->_page=$page;
- }
-
- public function getPage()
- {
- return $this->_page;
- }
-
- abstract public function load();
-
- abstract public function save($state);
-}
-
-?> \ No newline at end of file
diff --git a/framework/Web/UI/TTemplateManager.php b/framework/Web/UI/TTemplateManager.php
new file mode 100644
index 00000000..1f3044a2
--- /dev/null
+++ b/framework/Web/UI/TTemplateManager.php
@@ -0,0 +1,630 @@
+<?php
+/**
+ * TTemplateManager and TTemplate class file
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 2005 PradoSoft
+ * @license http://www.pradosoft.com/license/
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI
+ */
+
+/**
+ * TTemplateManager class
+ *
+ * TTemplateManager manages the loading and parsing of control templates.
+ *
+ * Given a class name, TTemplateManager tries to locate the corresponding template
+ * file under the directory containing the class file. The name of the template file
+ * is the class name with the extension '.tpl'.
+ *
+ * By default, TTemplateManager is registered with {@link TPageService} as the
+ * template manager module that can be accessed via {@link TPageService::getTemplateManager()}.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI
+ * @since 3.0
+ */
+class TTemplateManager extends TComponent implements IModule
+{
+ /**
+ * Template file extension
+ */
+ const TEMPLATE_FILE_EXT='.tpl';
+ /**
+ * Prefix of the cache variable name for storing parsed templates
+ */
+ const TEMPLATE_CACHE_PREFIX='prado:template:';
+ /**
+ * @var TApplication application instance
+ */
+ private $_application;
+ /**
+ * @var string module ID
+ */
+ private $_id;
+
+ /**
+ * Initializes the module.
+ * This method is required by IModule and is invoked by application.
+ * It starts output buffer if it is enabled.
+ * @param TApplication application
+ * @param TXmlElement module configuration
+ */
+ public function init($application,$config)
+ {
+ $this->_application=$application;
+ $application->getService()->setTemplateManager($this);
+ }
+
+ /**
+ * @return string id of this module
+ */
+ public function getID()
+ {
+ return $this->_id;
+ }
+
+ /**
+ * @param string id of this module
+ */
+ public function setID($value)
+ {
+ $this->_id=$value;
+ }
+
+ /**
+ * Loads the template corresponding to the specified class name.
+ * @return ITemplate template for the class name, null if template doesn't exist.
+ */
+ public function getTemplateByClassName($className)
+ {
+ $class=new ReflectionClass($className);
+ $tplFile=dirname($class->getFileName()).'/'.$className.self::TEMPLATE_FILE_EXT;
+ return $this->getTemplateByFileName($tplFile);
+ }
+
+ /**
+ * Loads the template from the specified file.
+ * @return ITemplate template parsed from the specified file, null if the file doesn't exist.
+ */
+ public function getTemplateByFileName($fileName)
+ {
+ if(($fileName=realpath($fileName))!==false && is_file($fileName))
+ {
+ if(($cache=$this->_application->getCache())===null)
+ return new TTemplate(file_get_contents($fileName),dirname($fileName),$fileName);
+ else
+ {
+ $array=$cache->get(self::TEMPLATE_CACHE_PREFIX.$fileName);
+ if(is_array($array))
+ {
+ list($template,$timestamp)=$array;
+ if(filemtime($fileName)<$timestamp)
+ return $template;
+ }
+ $template=new TTemplate(file_get_contents($fileName),dirname($fileName),$fileName);
+ $cache->set(self::TEMPLATE_CACHE_PREFIX.$fileName,array($template,time()));
+ return $template;
+ }
+ }
+ else
+ return null;
+ }
+}
+
+/**
+ * TTemplate implements PRADO template parsing logic.
+ * A TTemplate object represents a parsed PRADO control template.
+ * It can instantiate the template as child controls of a specified control.
+ * The template format is like HTML, with the following special tags introduced,
+ * - component tags: a component tag represents the configuration of a component.
+ * The tag name is in the format of com:ComponentType, where ComponentType is the component
+ * class name. Component tags must be well-formed. Attributes of the component tag
+ * are treated as either property initial values, event handler attachment, or regular
+ * tag attributes.
+ * - property tags: property tags are used to set large block of attribute values.
+ * The property tag name is in the format of prop:AttributeName, where AttributeName
+ * can be a property name, an event name or a regular tag attribute name.
+ * - directive: directive specifies the property values for the template owner.
+ * It is in the format of &lt;% property name-value pairs %&gt;
+ * - expressions: expressions are shorthand of {@link TExpression} and {@link TStatements}
+ * controls. They are in the formate of &lt;= PHP expression &gt; and &lt; PHP statements &gt;
+ * - comments: There are two kinds of comments, regular HTML comments and special template comments.
+ * The former is in the format of &lt;!-- comments --&gt;, which will be treated as text strings.
+ * The latter is in the format of &lt;%* comments %&gt;, which will be stripped out.
+ *
+ * Tags other than the above are not required to be well-formed.
+ *
+ * A TTemplate object represents a parsed PRADO template. To instantiate the template
+ * for a particular control, call {@link instantiateIn($control)}, which
+ * will create and intialize all components specified in the template and
+ * set their parent as the control.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI
+ * @since 3.0
+ */
+class TTemplate extends TComponent implements ITemplate
+{
+ /**
+ * '<!.*?!>' - template comments
+ * '<!--.*?-->' - HTML comments
+ * '<\/?com:([\w\.]+)((?:\s*[\w\.]+=\'.*?\'|\s*[\w\.]+=".*?"|\s*[\w\.]+=<%.*?%>)*)\s*\/?>' - component tags
+ * '<\/?prop:([\w\.]+)\s*>' - property tags
+ * '<%@\s*(\w+)((?:\s*[\w\.]+=\'.*?\'|\s*[\w\.]+=".*?")*)\s*%>' - directives
+ * '<%=?(.*?)%>' - expressions
+ */
+ const REGEX_RULES='/<!.*?!>|<!--.*?-->|<\/?com:([\w\.]+)((?:\s*[\w\.]+=\'.*?\'|\s*[\w\.]+=".*?"|\s*[\w\.]+=<%.*?%>)*)\s*\/?>|<\/?prop:([\w\.]+)\s*>|<%@\s*((?:\s*[\w\.]+=\'.*?\'|\s*[\w\.]+=".*?")*)\s*%>|<%=?(.*?)%>/msS';
+
+ /**
+ * @var array list of component tags and strings
+ */
+ private $_tpl=array();
+ /**
+ * @var array list of directive settings
+ */
+ private $_directive=array();
+ /**
+ * @var string context path
+ */
+ private $_contextPath;
+ /**
+ * @var string template file path (if available)
+ */
+ private $_tplFile=null;
+
+ /**
+ * Constructor.
+ * The template will be parsed after construction.
+ * @param string the template string
+ * @param string the template context directory
+ * @param string the template file, null if no file
+ */
+ public function __construct($template,$contextPath,$tplFile=null)
+ {
+ $this->_contextPath=$contextPath;
+ $this->_tplFile=$tplFile;
+ $this->parse($template);
+ }
+
+ /**
+ * @return array name-value pairs declared in the directive
+ */
+ public function getDirective()
+ {
+ return $this->_directive;
+ }
+
+ /**
+ * @return array the parsed template
+ */
+ public function &getItems()
+ {
+ return $this->_tpl;
+ }
+
+ /**
+ * Instantiates the template.
+ * Content in the template will be instantiated as components and text strings
+ * and passed to the specified parent control.
+ * @param TControl the parent control
+ * @throws TTemplateRuntimeException if an error is encountered during the instantiation.
+ */
+ public function instantiateIn($tplControl)
+ {
+ $page=$tplControl->getPage();
+ $assetManager=$page->getService()->getAssetManager();
+ $controls=array();
+ foreach($this->_tpl as $key=>$object)
+ {
+ if(isset($object[2])) // component
+ {
+ if(strpos($object[1],'.')===false)
+ {
+ if(class_exists($object[1],false))
+ $component=new $object[1];
+ else
+ {
+ include_once($object[1].Prado::CLASS_FILE_EXT);
+ if(class_exists($object[1],false))
+ $component=new $object[1];
+ else
+ throw new TTemplateRuntimeException('template_component_unknown',$object[1]);
+ }
+ }
+ else
+ $component=Prado::createComponent($object[1]);
+ if($component instanceof TControl)
+ {
+ $controls[$key]=$component;
+ $component->setTemplateControl($tplControl);
+ if(isset($object[2]['id']))
+ $tplControl->registerObject($object[2]['id'],$component);
+ if(isset($object[2]['skinid']))
+ {
+ $component->setSkinID($object[2]['skinid']);
+ unset($object[2]['skinid']);
+ }
+ $component->applyStyleSheetSkin($page);
+ // apply attributes
+ foreach($object[2] as $name=>$value)
+ {
+ if($component->hasEvent($name)) // is an event
+ {
+ if(is_string($value))
+ {
+ if(strpos($value,'.')===false)
+ $component->attachEventHandler($name,array($component,'TemplateControl.'.$value));
+ else
+ $component->attachEventHandler($name,array($component,$value));
+ }
+ else
+ throw new TTemplateRuntimeException('template_event_invalid',get_class($component),$name);
+ }
+ else if(strpos($name,'.')===false) // is simple property or custom attribute
+ {
+ if($component->hasProperty($name))
+ {
+ if($component->canSetProperty($name))
+ {
+ $setter='set'.$name;
+ if(is_string($value))
+ $component->$setter($value);
+ else if($value[0]===0)
+ $component->bindProperty($name,$value[1]);
+ else if($value[0]===1)
+ $component->$setter($component->evaluateExpression($value[1]));
+ else // url
+ {
+ $url=$assetManager->publishFilePath($this->_contextPath.'/'.$value[1]);
+ $component->$setter($url);
+ }
+ }
+ else
+ throw new TTemplateRuntimeException('template_property_readonly',get_class($component),$name);
+ }
+ else if($component->getAllowCustomAttributes())
+ {
+ if(is_array($value))
+ {
+ if($value[0]===1)
+ $value=$component->evaluateExpression($value[1]);
+ else if($value[0]===2)
+ $value=$assetManager->publishFilePath($this->_contextPath.'/'.ltrim($value[1],'/'));
+ else
+ throw new TTemplateRuntimeException('template_attribute_unbindable',get_class($component),$name);
+ }
+ $component->getAttributes()->add($name,$value);
+ }
+ else
+ throw new TTemplateRuntimeException('template_property_undefined',get_class($component),$name);
+ }
+ else // complex property
+ {
+ if(is_string($value))
+ $component->setSubProperty($name,$value);
+ else if($value[0]===0)
+ $component->bindProperty($name,$value[1]);
+ else if($value[0]===1)
+ $component->setSubProperty($component->evaluateExpression($value[1]));
+ else
+ {
+ $url=$assetManager->publishFilePath($this->_contextPath.'/'.$value[1]);
+ $component->$setter($url);
+ }
+ }
+ }
+ $parent=isset($controls[$object[0]])?$controls[$object[0]]:$tplControl;
+ $component->createdOnTemplate($parent);
+ }
+ else if($component instanceof TComponent)
+ {
+ if(isset($object[2]['id']))
+ {
+ $tplControl->registerObject($object[2]['id'],$component);
+ if(!$component->hasProperty('id'))
+ unset($object[2]['id']);
+ }
+ foreach($object[2] as $name=>$value)
+ {
+ if($component->hasProperty($name))
+ {
+ if($component->canSetProperty($name))
+ {
+ $setter='set'.$name;
+ if(is_string($value))
+ $component->$setter($value);
+ else if($value[0]===1)
+ $component->$setter($component->evaluateExpression($value[1]));
+ else if($value[0]===2)
+ {
+ $url=$assetManager->publishFilePath($this->_contextPath.'/'.ltrim($value[1],'/'));
+ $component->$setter($url);
+ }
+ else
+ throw new TTemplateRuntimeException('template_property_unbindable',get_class($component),$name);
+ }
+ else
+ throw new TTemplateRuntimeException('template_property_readonly',get_class($component),$name);
+ }
+ else
+ throw new TTemplateRuntimeException('template_property_undefined',get_class($component),$name);
+ }
+ $parent=isset($controls[$object[0]])?$controls[$object[0]]:$tplControl;
+ $parent->addParsedObject($component);
+ }
+ else
+ throw new TTemplateRuntimeException('template_component_required',$object[1]);
+ }
+ else // string
+ {
+ if(isset($controls[$object[0]]))
+ $controls[$object[0]]->addParsedObject($object[1]);
+ else
+ $tplControl->addParsedObject($object[1]);
+ }
+ }
+ }
+
+ /**
+ * Parses a template string.
+ *
+ * This template parser recognizes five types of data:
+ * regular string, well-formed component tags, well-formed property tags, directives, and expressions.
+ *
+ * The parsing result is returned as an array. Each array element can be of three types:
+ * - a string, 0: container index; 1: string content;
+ * - a component tag, 0: container index; 1: component type; 2: attributes (name=>value pairs)
+ * If a directive is found in the template, it will be parsed and can be
+ * retrieved via {@link getDirective}, which returns an array consisting of
+ * name-value pairs in the directive.
+ *
+ * Note, attribute names are treated as case-insensitive and will be turned into lower cases.
+ * Component and directive types are case-sensitive.
+ * Container index is the index to the array element that stores the container object.
+ * If an object has no container, its container index is -1.
+ *
+ * @param string the template string
+ * @throws TTemplateParsingException if a parsing error is encountered
+ */
+ protected function parse($input)
+ {
+ $tpl=&$this->_tpl;
+ $n=preg_match_all(self::REGEX_RULES,$input,$matches,PREG_SET_ORDER|PREG_OFFSET_CAPTURE);
+ $expectPropEnd=false;
+ $textStart=0;
+ $stack=array();
+ $container=-1;
+ $c=0;
+ for($i=0;$i<$n;++$i)
+ {
+ $match=&$matches[$i];
+ $str=$match[0][0];
+ $matchStart=$match[0][1];
+ $matchEnd=$matchStart+strlen($str)-1;
+ if(strpos($str,'<com:')===0) // opening component tag
+ {
+ if($expectPropEnd)
+ continue;
+ if($matchStart>$textStart)
+ $tpl[$c++]=array($container,substr($input,$textStart,$matchStart-$textStart));
+ $textStart=$matchEnd+1;
+ $type=$match[1][0];
+ $attributes=$this->parseAttributes($match[2][0]);
+ $tpl[$c++]=array($container,$type,$attributes);
+ if($str[strlen($str)-2]!=='/') // open tag
+ {
+ array_push($stack,$type);
+ $container=$c-1;
+ }
+ }
+ else if(strpos($str,'</com:')===0) // closing component tag
+ {
+ if($expectPropEnd)
+ continue;
+ if($matchStart>$textStart)
+ $tpl[$c++]=array($container,substr($input,$textStart,$matchStart-$textStart));
+ $textStart=$matchEnd+1;
+ $type=$match[1][0];
+
+ if(empty($stack))
+ {
+ $line=count(explode("\n",substr($input,0,$matchEnd+1)));
+ if($this->_tplFile===null)
+ throw new TTemplateParsingException('template_closingtag_unexpected',"Line $line","</com:$type>");
+ else
+ throw new TTemplateParsingException('template_closingtag_unexpected',"{$this->_tplFile} (Line $line)","</com:$type>");
+ }
+
+ $name=array_pop($stack);
+ if($name!==$type)
+ {
+ if($name[0]==='@')
+ $tag='</prop:'.substr($name,1).'>';
+ else
+ $tag='</com:'.$name.'>';
+ $line=count(explode("\n",substr($input,0,$matchEnd+1)));
+ if($this->_tplFile===null)
+ throw new TTemplateParsingException('template_closingtag_expected',"Line $line",$tag);
+ else
+ throw new TTemplateParsingException('template_closingtag_expected',"{$this->_tplFile} (Line $line)",$tag);
+ }
+ $container=$tpl[$container][0];
+ }
+ else if(strpos($str,'<%@')===0) // directive
+ {
+ if($expectPropEnd)
+ continue;
+ if($matchStart>$textStart)
+ $tpl[$c++]=array($container,substr($input,$textStart,$matchStart-$textStart));
+ $textStart=$matchEnd+1;
+ if(isset($tpl[0]))
+ {
+ $line=count(explode("\n",substr($input,0,$matchEnd+1)));
+ if($this->_tplFile===null)
+ throw new TTemplateParsingException('template_directive_nonunique',"Line $line");
+ else
+ throw new TTemplateParsingException('template_directive_nonunique',"{$this->_tplFile} (Line $line)");
+ }
+ $this->_directive=$this->parseAttributes($match[4][0]);
+ }
+ else if(strpos($str,'<%')===0) // expression
+ {
+ if($expectPropEnd)
+ continue;
+ if($matchStart>$textStart)
+ $tpl[$c++]=array($container,substr($input,$textStart,$matchStart-$textStart));
+ $textStart=$matchEnd+1;
+ if($str[2]==='=')
+ $tpl[$c++]=array($container,'TExpression',array('Expression'=>$match[5][0]));
+ else
+ $tpl[$c++]=array($container,'TStatements',array('Statements'=>$match[5][0]));
+ }
+ else if(strpos($str,'<prop:')===0) // opening property
+ {
+ $prop=strtolower($match[3][0]);
+ array_push($stack,'@'.$prop);
+ if(!$expectPropEnd)
+ {
+ if($matchStart>$textStart)
+ $tpl[$c++]=array($container,substr($input,$textStart,$matchStart-$textStart));
+ $textStart=$matchEnd+1;
+ $expectPropEnd=true;
+ }
+ }
+ else if(strpos($str,'</prop:')===0) // closing property
+ {
+ $prop=strtolower($match[3][0]);
+ if(empty($stack))
+ {
+ $line=count(explode("\n",substr($input,0,$matchEnd+1)));
+ if($this->_tplFile===null)
+ throw new TTemplateParsingException('template_closingtag_unexpected',"Line $line","</prop:$prop>");
+ else
+ throw new TTemplateParsingException('template_closingtag_unexpected',"{$this->_tplFile} (Line $line)","</prop:$prop>");
+ }
+ $name=array_pop($stack);
+ if($name!=='@'.$prop)
+ {
+ if($name[0]==='@')
+ $tag='</prop:'.substr($name,1).'>';
+ else
+ $tag='</com:'.$name.'>';
+ $line=count(explode("\n",substr($input,0,$matchEnd+1)));
+ if($this->_tplFile===null)
+ throw new TTemplateParsingException('template_closingtag_expected',"Line $line",$tag);
+ else
+ throw new TTemplateParsingException('template_closingtag_expected',"{$this->_tplFile} (Line $line)",$tag);
+ }
+ if(($last=count($stack))<1 || $stack[$last-1][0]!=='@')
+ {
+ if($matchStart>$textStart && $container>=0)
+ {
+ $value=substr($input,$textStart,$matchStart-$textStart);
+ if(preg_match('/^<%.*?%>$/msS',$value))
+ {
+ if($value[2]==='#') // databind
+ $tpl[$container][2][$prop]=array(0,substr($value,3,strlen($value)-5));
+ else if($value[2]==='=') // a dynamic initialization
+ $tpl[$container][2][$prop]=array(1,substr($value,3,strlen($value)-5));
+ else
+ $tpl[$container][2][$prop]=$value;
+ }
+ else
+ $tpl[$container][2][$prop]=$value;
+ $textStart=$matchEnd+1;
+ }
+ $expectPropEnd=false;
+ }
+ }
+ else if(strpos($str,'<!--')===0) // HTML comments
+ {
+ $state=0;
+ }
+ else if(strpos($str,'<!')===0) // template comments
+ {
+ if($expectPropEnd)
+ {
+ $line=count(explode("\n",substr($input,0,$matchEnd+1)));
+ if($this->_tplFile===null)
+ throw new TTemplateParsingException('template_comments_forbidden',"Line $line");
+ else
+ throw new TTemplateParsingException('template_comments_forbidden',"{$this->_tplFile} (Line $line)");
+ }
+ if($matchStart>$textStart)
+ $tpl[$c++]=array($container,substr($input,$textStart,$matchStart-$textStart));
+ $textStart=$matchEnd+1;
+ }
+ else
+ {
+ $line=count(explode("\n",substr($input,0,$matchEnd+1)));
+ if($this->_tplFile===null)
+ throw new TTemplateParsingException('template_matching_unexpected',"Line $line",$match);
+ else
+ throw new TTemplateParsingException('template_matching_unexpected',"{$this->_tplFile} (Line $line)",$match);
+ }
+ }
+ if(!empty($stack))
+ {
+ $name=array_pop($stack);
+ if($name[0]==='@')
+ $tag='</prop:'.substr($name,1).'>';
+ else
+ $tag='</com:'.$name.'>';
+ $line=count(explode("\n",substr($input,0,$matchEnd+1)));
+ if($this->_tplFile===null)
+ throw new TTemplateParsingException('template_closingtag_expected',"Line $line",$tag);
+ else
+ throw new TTemplateParsingException('template_closingtag_expected',"{$this->_tplFile} (Line $line)",$tag);
+ }
+ if($textStart<strlen($input))
+ $tpl[$c++]=array($container,substr($input,$textStart));
+ return $tpl;
+ }
+
+ /**
+ * Parses the attributes of a tag from a string.
+ * @param string the string to be parsed.
+ * @return array attribute values indexed by names.
+ */
+ protected function parseAttributes($str)
+ {
+ if($str==='')
+ return array();
+ $pattern='/([\w\.]+)=(\'.*?\'|".*?"|<%.*?%>)/msS';
+ $attributes=array();
+ $n=preg_match_all($pattern,$str,$matches,PREG_SET_ORDER);
+ for($i=0;$i<$n;++$i)
+ {
+ $name=strtolower($matches[$i][1]);
+ $value=$matches[$i][2];
+ if($value[0]==='\'' || $value[0]==='"')
+ {
+ $value=substr($value,1,strlen($value)-2);
+ if(!preg_match('/(<%#.*?%>|<%=.*?%>|<%~.*?%>)/msS',$value))
+ {
+ $attributes[$name]=$value;
+ continue;
+ }
+ }
+ if($value[0]==='<')
+ {
+ if($value[2]==='#') // databind
+ $attributes[$name]=array(0,substr($value,3,strlen($value)-5));
+ else if($value[2]==='=') // a dynamic initialization
+ $attributes[$name]=array(1,substr($value,3,strlen($value)-5));
+ else if($value[2]==='~') // a URL
+ $attributes[$name]=array(2,trim(substr($value,3,strlen($value)-5)));
+ else
+ $attributes[$name]=substr($value,2,strlen($value)-4);
+ }
+ }
+ return $attributes;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/framework/Web/UI/TThemeManager.php b/framework/Web/UI/TThemeManager.php
new file mode 100644
index 00000000..504e8ecb
--- /dev/null
+++ b/framework/Web/UI/TThemeManager.php
@@ -0,0 +1,351 @@
+<?php
+/**
+ * TThemeManager class
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 2005 PradoSoft
+ * @license http://www.pradosoft.com/license/
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI
+ */
+
+/**
+ * TThemeManager class
+ *
+ * TThemeManager manages the themes used in a Prado application.
+ *
+ * Themes are stored under the directory specified by the
+ * {@link setBasePath BasePath} property. The themes can be accessed
+ * via URL {@link setBaseUrl BaseUrl}. Each theme is represented by a subdirectory
+ * and all the files under that directory. The name of a theme is the name
+ * of the corresponding subdirectory.
+ * By default, the base path of all themes is a directory named "themes"
+ * under the directory containing the application entry script.
+ * To get a theme (normally you do not need to), call {@link getTheme}.
+ *
+ * TThemeManager may be configured within page service tag in application
+ * configuration file as follows,
+ * <module id="themes" type="System.Web.UI.TThemeManager"
+ * BasePath="Application.themes" BaseUrl="/themes" />
+ * where {@link getCacheExpire CacheExpire}, {@link getCacheControl CacheControl}
+ * and {@link getBufferOutput BufferOutput} are configurable properties of THttpResponse.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI
+ * @since 3.0
+ */
+class TThemeManager extends TComponent implements IModule
+{
+ /**
+ * default themes base path
+ */
+ const DEFAULT_BASEPATH='themes';
+ /**
+ * @var string module ID
+ */
+ private $_id='theme';
+ /**
+ * @var boolean whether this module has been initialized
+ */
+ private $_initialized=false;
+ /**
+ * @var string the directory containing all themes
+ */
+ private $_basePath=null;
+ /**
+ * @var string the base URL for all themes
+ */
+ private $_baseUrl=null;
+ /**
+ * @var TApplication application
+ */
+ private $_application;
+
+ /**
+ * Initializes the module.
+ * This method is required by IModule and is invoked by application.
+ * @param TApplication application
+ * @param TXmlElement module configuration
+ */
+ public function init($application,$config)
+ {
+ $this->_application=$application;
+ $this->_initialized=true;
+ $application->getService()->setThemeManager($this);
+ }
+
+ /**
+ * @return string id of this module
+ */
+ public function getID()
+ {
+ return $this->_id;
+ }
+
+ /**
+ * @param string id of this module
+ */
+ public function setID($value)
+ {
+ $this->_id=$value;
+ }
+
+ /**
+ * @param string name of the theme to be retrieved
+ * @return TTheme the theme retrieved
+ */
+ public function getTheme($name)
+ {
+ $themePath=$this->getBasePath().'/'.$name;
+ $themeUrl=$this->getBaseUrl().'/'.$name;
+ return new TTheme($this->_application,$themePath,$themeUrl);
+
+ }
+
+ /**
+ * @return string the base path for all themes. It is returned as an absolute path.
+ * @throws TConfigurationException if base path is not set and "themes" directory does not exist.
+ */
+ public function getBasePath()
+ {
+ if($this->_basePath===null)
+ {
+ $this->_basePath=dirname($this->_application->getRequest()->getPhysicalApplicationPath()).'/'.self::DEFAULT_BASEPATH;
+ if(($basePath=realpath($this->_basePath))===false || !is_dir($basePath))
+ throw new TConfigurationException('thememanager_basepath_invalid',$this->_basePath);
+ $this->_basePath=$basePath;
+ }
+ return $this->_basePath;
+ }
+
+ /**
+ * @param string the base path for all themes. It must be in the format of a namespace.
+ * @throws TInvalidDataValueException if the base path is not a proper namespace.
+ */
+ public function setBasePath($value)
+ {
+ if($this->_initialized)
+ throw new TInvalidOperationException('thememanager_basepath_unchangeable');
+ else
+ {
+ $this->_basePath=Prado::getPathOfAlias($value);
+ if($this->_basePath===null || !is_dir($this->_basePath))
+ throw new TInvalidDataValueException('thememanager_basepath_invalid',$value);
+ $this->_basePath=$value;
+ }
+ }
+
+ /**
+ * @return string the base URL for all themes.
+ * @throws TConfigurationException If base URL is not set and a correct one cannot be determined by Prado.
+ */
+ public function getBaseUrl()
+ {
+ if($this->_baseUrl===null)
+ {
+ $appPath=dirname($this->_application->getRequest()->getPhysicalApplicationPath());
+ $basePath=$this->getBasePath();
+ if(strpos($basePath,$appPath)===false)
+ throw new TConfigurationException('thememanager_baseurl_required');
+ $appUrl=dirname($this->_application->getRequest()->getApplicationPath());
+ $this->_baseUrl=$appUrl.strtr(substr($basePath,strlen($appPath)),'\\','/');
+ }
+ return $this->_baseUrl;
+ }
+
+ /**
+ * @param string the base URL for all themes.
+ */
+ public function setBaseUrl($value)
+ {
+ $this->_baseUrl=rtrim($value,'/');
+ }
+}
+
+/**
+ * TTheme class
+ *
+ * TTheme represents a particular theme. It is merely a collection of skins
+ * that are applicable to the corresponding controls.
+ *
+ * Each theme is stored as a directory and files under that directory.
+ * The theme name is the directory name. When TTheme is created, the files
+ * whose name has the extension ".skin" are parsed and saved as controls skins.
+ *
+ * A skin is essentially a list of initial property values that are to be applied
+ * to a control when the skin is applied.
+ * Each type of control can have multiple skins identified by the SkinID.
+ * If a skin does not have SkinID, it is the default skin that will be applied
+ * to controls that do not specify particular SkinID.
+ *
+ * Whenever possible, TTheme will try to make use of available cache to save
+ * the parsing time.
+ *
+ * To apply a theme to a particular control, call {@link applySkin}.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI
+ * @since 3.0
+ */
+class TTheme extends TComponent
+{
+ /**
+ * prefix for cache variable name used to store parsed themes
+ */
+ const THEME_CACHE_PREFIX='prado:theme:';
+ /**
+ * Extension name of skin files
+ */
+ const SKIN_FILE_EXT='.skin';
+ /**
+ * @var string theme path
+ */
+ private $_themePath;
+ /**
+ * @var string theme url
+ */
+ private $_themeUrl;
+ /**
+ * @var array list of skins for the theme
+ */
+ private $_skins=null;
+ /**
+ * @var string theme name
+ */
+ private $_name='';
+
+ /**
+ * Constructor.
+ * @param TApplication application instance
+ * @param string theme path
+ * @param string theme URL
+ * @throws TConfigurationException if theme path does not exist or any parsing error of the skin files
+ */
+ public function __construct($application,$themePath,$themeUrl)
+ {
+ $this->_themeUrl=$themeUrl;
+ $this->_name=basename($themePath);
+ if(($cache=$application->getCache())!==null)
+ {
+ $array=$cache->get(self::THEME_CACHE_PREFIX.$themePath);
+ if(is_array($array))
+ {
+ list($skins,$timestamp)=$array;
+ $cacheValid=true;
+ if($application->getMode()!=='Performance')
+ {
+ if(($dir=opendir($themePath))===false)
+ throw new TIOException('theme_path_inexistent',$themePath);
+ while(($file=readdir($dir))!==false)
+ {
+ if(basename($file,self::SKIN_FILE_EXT)!==$file && filemtime($themePath.'/'.$file)>$timestamp)
+ {
+ $cacheValid=false;
+ break;
+ }
+ }
+ closedir($dir);
+ }
+ if($cacheValid)
+ $this->_skins=$skins;
+ }
+ }
+ if($this->_skins===null)
+ {
+ if(($dir=opendir($themePath))===false)
+ throw new TIOException('theme_path_inexistent',$themePath);
+ while(($file=readdir($dir))!==false)
+ {
+ if(basename($file,self::SKIN_FILE_EXT)!==$file)
+ {
+ $template=new TTemplate(file_get_contents($themePath.'/'.$file),$themePath,$themePath.'/'.$file);
+ foreach($template->getItems() as $skin)
+ {
+ if($skin[0]!==-1)
+ throw new TConfigurationException('theme_control_nested',$skin[1],dirname($themePath));
+ else if(!isset($skin[2])) // a text string, ignored
+ continue;
+ $type=$skin[1];
+ $id=isset($skin[2]['skinid'])?$skin[2]['skinid']:0;
+ unset($skin[2]['skinid']);
+ if(isset($this->_skins[$type][$id]))
+ throw new TConfigurationException('theme_skinid_duplicated',$type,$id,dirname($themePath));
+ foreach($skin[2] as $name=>$value)
+ {
+ if(is_array($value) && $value[0]===0)
+ throw new TConfigurationException('theme_databind_forbidden',dirname($themePath),$type,$id);
+ }
+ $this->_skins[$type][$id]=$skin[2];
+ }
+ }
+ }
+ closedir($dir);
+ if($cache!==null)
+ $cache->set(self::THEME_CACHE_PREFIX.$themePath,array($this->_skins,time()));
+ }
+ }
+
+ /**
+ * @return string theme name
+ */
+ public function getName()
+ {
+ return $this->_name;
+ }
+
+ /**
+ * Applies the theme to a particular control.
+ * The control's class name and SkinID value will be used to
+ * identify which skin to be applied. If the control's SkinID is empty,
+ * the default skin will be applied.
+ * @param TControl the control to be applied with a skin
+ * @return boolean if a skin is successfully applied
+ * @throws TConfigurationException if any error happened during the skin application
+ */
+ public function applySkin($control)
+ {
+ $type=get_class($control);
+ if(($id=$control->getSkinID())==='')
+ $id=0;
+ if(isset($this->_skins[$type][$id]))
+ {
+ foreach($this->_skins[$type][$id] as $name=>$value)
+ {
+ if(is_array($value))
+ {
+ if($value[0]===1)
+ $value=$this->evaluateExpression($value[1]);
+ else if($value[0]===2)
+ $value=$this->_themeUrl.'/'.ltrim($value[1],'/');
+ }
+ if(strpos($name,'.')===false) // is simple property or custom attribute
+ {
+ if($control->hasProperty($name))
+ {
+ if($control->canSetProperty($name))
+ {
+ $setter='set'.$name;
+ $control->$setter($value);
+ }
+ else
+ throw new TConfigurationException('theme_property_readonly',$type,$name);
+ }
+ else if($control->getAllowCustomAttributes())
+ $control->getAttributes()->add($name,$value);
+ else
+ throw new TConfigurationException('theme_property_undefined',$type,$name);
+ }
+ else // complex property
+ $control->setSubProperty($name,$value);
+ }
+ return true;
+ }
+ else
+ return false;
+ }
+}
+
+?> \ No newline at end of file