From bdd139266bbd71b828394a1500551254fc42a819 Mon Sep 17 00:00:00 2001 From: xue <> Date: Sat, 3 Dec 2005 04:45:44 +0000 Subject: --- .gitattributes | 8 +- framework/Web/Services/TPageService.php | 8 +- framework/Web/TAssetManager.php | 221 -------- framework/Web/THiddenFieldPageStatePersister.php | 80 --- framework/Web/TTemplateManager.php | 630 --------------------- framework/Web/TThemeManager.php | 351 ------------ framework/Web/UI/TAssetManager.php | 221 ++++++++ .../Web/UI/THiddenFieldPageStatePersister.php | 2 +- framework/Web/UI/TPageStatePersister.php | 22 - framework/Web/UI/TTemplateManager.php | 630 +++++++++++++++++++++ framework/Web/UI/TThemeManager.php | 351 ++++++++++++ 11 files changed, 1210 insertions(+), 1314 deletions(-) delete mode 100644 framework/Web/TAssetManager.php delete mode 100644 framework/Web/THiddenFieldPageStatePersister.php delete mode 100644 framework/Web/TTemplateManager.php delete mode 100644 framework/Web/TThemeManager.php create mode 100644 framework/Web/UI/TAssetManager.php delete mode 100644 framework/Web/UI/TPageStatePersister.php create mode 100644 framework/Web/UI/TTemplateManager.php create mode 100644 framework/Web/UI/TThemeManager.php diff --git a/.gitattributes b/.gitattributes index 90c81e47..1ff1ed0f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -70,23 +70,21 @@ framework/TComponent.php -text framework/TODO.txt -text framework/Web/Javascripts/WebForms.js -text framework/Web/Services/TPageService.php -text -framework/Web/TAssetManager.php -text -framework/Web/THiddenFieldPageStatePersister.php -text framework/Web/THttpRequest.php -text framework/Web/THttpResponse.php -text framework/Web/THttpSession.php -text framework/Web/THttpUtility.php -text -framework/Web/TTemplateManager.php -text -framework/Web/TThemeManager.php -text +framework/Web/UI/TAssetManager.php -text framework/Web/UI/TClientScriptManager.php -text framework/Web/UI/TControl.php -text framework/Web/UI/TForm.php -text framework/Web/UI/THiddenFieldPageStatePersister.php -text framework/Web/UI/THtmlWriter.php -text framework/Web/UI/TPage.php -text -framework/Web/UI/TPageStatePersister.php -text framework/Web/UI/TPostBackOptions.php -text framework/Web/UI/TTemplateControl.php -text +framework/Web/UI/TTemplateManager.php -text +framework/Web/UI/TThemeManager.php -text framework/Web/UI/WebControls/TButton.php -text framework/Web/UI/WebControls/TCheckBox.php -text framework/Web/UI/WebControls/TContent.php -text diff --git a/framework/Web/Services/TPageService.php b/framework/Web/Services/TPageService.php index 078f8706..a95235c2 100644 --- a/framework/Web/Services/TPageService.php +++ b/framework/Web/Services/TPageService.php @@ -14,10 +14,10 @@ * Include classes to be used by page service */ Prado::using('System.Web.UI.TPage'); -Prado::using('System.Web.TTemplateManager'); -Prado::using('System.Web.TThemeManager'); -Prado::using('System.Web.TAssetManager'); -Prado::using('System.Web.THiddenFieldPageStatePersister'); +Prado::using('System.Web.UI.TTemplateManager'); +Prado::using('System.Web.UI.TThemeManager'); +Prado::using('System.Web.UI.TAssetManager'); +Prado::using('System.Web.UI.THiddenFieldPageStatePersister'); /** * TPageService class. diff --git a/framework/Web/TAssetManager.php b/framework/Web/TAssetManager.php deleted file mode 100644 index e2b9f9a8..00000000 --- a/framework/Web/TAssetManager.php +++ /dev/null @@ -1,221 +0,0 @@ - - * @link http://www.pradosoft.com/ - * @copyright Copyright © 2005 PradoSoft - * @license http://www.pradosoft.com/license/ - * @version $Revision: $ $Date: $ - * @package System.Web - */ - -/** - * 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, - * - * 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 - * @version $Revision: $ $Date: $ - * @package System.Web - * @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/THiddenFieldPageStatePersister.php b/framework/Web/THiddenFieldPageStatePersister.php deleted file mode 100644 index 3d305668..00000000 --- a/framework/Web/THiddenFieldPageStatePersister.php +++ /dev/null @@ -1,80 +0,0 @@ -_application=$application; - } - - /** - * @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; - } - - public function save($state) - { - $data=Prado::serialize($state); - $hmac=$this->computeHMAC($data,$this->getKey()); - if(function_exists('gzuncompress') && function_exists('gzcompress')) - $data=gzcompress($hmac.$data); - else - $data=$hmac.$data; - $this->_application->getService()->getRequestedPage()->saveStateField($data); - } - - public function load() - { - $str=$this->_application->getService()->getRequestedPage()->loadStateField(); - if($str==='') - return null; - if(function_exists('gzuncompress') && function_exists('gzcompress')) - $data=gzuncompress($str); - else - $data=$str; - if($data!==false && strlen($data)>32) - { - $hmac=substr($data,0,32); - $state=substr($data,32); - if($hmac===$this->computeHMAC($state,$this->getKey())) - return Prado::unserialize($state); - } - throw new Exception('viewstate data is corrupted.'); - } - - private function getKey() - { - return 'abcdefe'; - } - - private function computeHMAC($data,$key) - { - if (strlen($key) > 64) - $key = pack('H32', md5($key)); - else if (strlen($key) < 64) - $key = str_pad($key, 64, "\0"); - return md5((str_repeat("\x5c", 64) ^ substr($key, 0, 64)) . pack('H32', md5((str_repeat("\x36", 64) ^ substr($key, 0, 64)) . $data))); - } -} - -?> \ No newline at end of file diff --git a/framework/Web/TTemplateManager.php b/framework/Web/TTemplateManager.php deleted file mode 100644 index 1f3044a2..00000000 --- a/framework/Web/TTemplateManager.php +++ /dev/null @@ -1,630 +0,0 @@ - - * @link http://www.pradosoft.com/ - * @copyright Copyright © 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 - * @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 <% property name-value pairs %> - * - expressions: expressions are shorthand of {@link TExpression} and {@link TStatements} - * controls. They are in the formate of <= PHP expression > and < PHP statements > - * - comments: There are two kinds of comments, regular HTML comments and special template comments. - * The former is in the format of <!-- comments -->, which will be treated as text strings. - * The latter is in the format of <%* comments %>, 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 - * @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,'$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,'$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",""); - else - throw new TTemplateParsingException('template_closingtag_unexpected',"{$this->_tplFile} (Line $line)",""); - } - - $name=array_pop($stack); - if($name!==$type) - { - if($name[0]==='@') - $tag=''; - else - $tag=''; - $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,'$textStart) - $tpl[$c++]=array($container,substr($input,$textStart,$matchStart-$textStart)); - $textStart=$matchEnd+1; - $expectPropEnd=true; - } - } - else if(strpos($str,'_tplFile===null) - throw new TTemplateParsingException('template_closingtag_unexpected',"Line $line",""); - else - throw new TTemplateParsingException('template_closingtag_unexpected',"{$this->_tplFile} (Line $line)",""); - } - $name=array_pop($stack); - if($name!=='@'.$prop) - { - if($name[0]==='@') - $tag=''; - else - $tag=''; - $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,'' - 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,'$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,'$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",""); + else + throw new TTemplateParsingException('template_closingtag_unexpected',"{$this->_tplFile} (Line $line)",""); + } + + $name=array_pop($stack); + if($name!==$type) + { + if($name[0]==='@') + $tag=''; + else + $tag=''; + $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,'$textStart) + $tpl[$c++]=array($container,substr($input,$textStart,$matchStart-$textStart)); + $textStart=$matchEnd+1; + $expectPropEnd=true; + } + } + else if(strpos($str,'_tplFile===null) + throw new TTemplateParsingException('template_closingtag_unexpected',"Line $line",""); + else + throw new TTemplateParsingException('template_closingtag_unexpected',"{$this->_tplFile} (Line $line)",""); + } + $name=array_pop($stack); + if($name!=='@'.$prop) + { + if($name[0]==='@') + $tag=''; + else + $tag=''; + $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,'