|
Server IP : 10.2.73.233 / Your IP : 216.73.216.223 Web Server : Apache/2.4.59 (Debian) System : Linux polon 4.19.0-27-amd64 #1 SMP Debian 4.19.316-1 (2024-06-25) x86_64 User : www-data ( 33) PHP Version : 5.6.40-64+0~20230107.71+debian10~1.gbp673146 Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority, MySQL : ON | cURL : ON | WGET : ON | Perl : ON | Python : ON Directory (0755) : /home/ilpnowa/../ifk/web.back/framework/Web/UI/../UI/ |
| [ Home ] | [ C0mmand ] | [ Upload File ] |
|---|
<?php
/**
* TTemplateManager and TTemplate class file
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.pradosoft.com/
* @copyright Copyright © 2005-2014 PradoSoft
* @license http://www.pradosoft.com/license/
* @package System.Web.UI
*/
/**
* Includes TOutputCache class file
*/
Prado::using('System.Web.UI.WebControls.TOutputCache');
/**
* TTemplateManager class
*
* TTemplateManager manages the loading and parsing of control templates.
*
* There are two ways of loading a template, either by the associated template
* control class name, or the template file name.
* The former is via calling {@link getTemplateByClassName}, which 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'. To load a template from a template file path,
* call {@link getTemplateByFileName}.
*
* 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>
* @package System.Web.UI
* @since 3.0
*/
class TTemplateManager extends TModule
{
/**
* Template file extension
*/
const TEMPLATE_FILE_EXT='.tpl';
/**
* Prefix of the cache variable name for storing parsed templates
*/
const TEMPLATE_CACHE_PREFIX='prado:template:';
/**
* Initializes the module.
* This method is required by IModule and is invoked by application.
* It starts output buffer if it is enabled.
* @param TXmlElement module configuration
*/
public function init($config)
{
$this->getService()->setTemplateManager($this);
}
/**
* 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()).DIRECTORY_SEPARATOR.$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=$this->getLocalizedTemplate($fileName))!==null)
{
Prado::trace("Loading template $fileName",'System.Web.UI.TTemplateManager');
if(($cache=$this->getApplication()->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,$timestamps)=$array;
if($this->getApplication()->getMode()===TApplicationMode::Performance)
return $template;
$cacheValid=true;
foreach($timestamps as $tplFile=>$timestamp)
{
if(!is_file($tplFile) || filemtime($tplFile)>$timestamp)
{
$cacheValid=false;
break;
}
}
if($cacheValid)
return $template;
}
$template=new TTemplate(file_get_contents($fileName),dirname($fileName),$fileName);
$includedFiles=$template->getIncludedFiles();
$timestamps=array();
$timestamps[$fileName]=filemtime($fileName);
foreach($includedFiles as $includedFile)
$timestamps[$includedFile]=filemtime($includedFile);
$cache->set(self::TEMPLATE_CACHE_PREFIX.$fileName,array($template,$timestamps));
return $template;
}
}
else
return null;
}
/**
* Finds a localized template file.
* @param string template file.
* @return string|null a localized template file if found, null otherwise.
*/
protected function getLocalizedTemplate($filename)
{
if(($app=$this->getApplication()->getGlobalization(false))===null)
return is_file($filename)?$filename:null;
foreach($app->getLocalizedResource($filename) as $file)
{
if(($file=realpath($file))!==false && is_file($file))
return $file;
}
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.
* - group subproperty tags: subproperties of a common property can be configured using
* <prop:MainProperty SubProperty1="Value1" SubProperty2="Value2" .../>
* - directive: directive specifies the property values for the template owner.
* It is in the format of <%@ property name-value pairs %>;
* - expressions: They are in the format 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 $control.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package System.Web.UI
* @since 3.0
*/
class TTemplate extends TApplicationComponent implements ITemplate
{
/**
* '<!--.*?--!>' - template comments
* '<!--.*?-->' - HTML comments
* '<\/?com:([\w\.]+)((?:\s*[\w\.]+\s*=\s*\'.*?\'|\s*[\w\.]+\s*=\s*".*?"|\s*[\w\.]+\s*=\s*<%.*?%>)*)\s*\/?>' - component tags
* '<\/?prop:([\w\.]+)\s*>' - property tags
* '<%@\s*((?:\s*[\w\.]+\s*=\s*\'.*?\'|\s*[\w\.]+\s*=\s*".*?")*)\s*%>' - directives
* '<%[%#~\/\\$=\\[](.*?)%>' - expressions
* '<prop:([\w\.]+)((?:\s*[\w\.]+=\'.*?\'|\s*[\w\.]+=".*?"|\s*[\w\.]+=<%.*?%>)*)\s*\/>' - group subproperty tags
*/
const REGEX_RULES='/<!--.*?--!>|<!---.*?--->|<\/?com:([\w\.]+)((?:\s*[\w\.]+\s*=\s*\'.*?\'|\s*[\w\.]+\s*=\s*".*?"|\s*[\w\.]+\s*=\s*<%.*?%>)*)\s*\/?>|<\/?prop:([\w\.]+)\s*>|<%@\s*((?:\s*[\w\.]+\s*=\s*\'.*?\'|\s*[\w\.]+\s*=\s*".*?")*)\s*%>|<%[%#~\/\\$=\\[](.*?)%>|<prop:([\w\.]+)((?:\s*[\w\.]+\s*=\s*\'.*?\'|\s*[\w\.]+\s*=\s*".*?"|\s*[\w\.]+\s*=\s*<%.*?%>)*)\s*\/>/msS';
/**
* Different configurations of component property/event/attribute
*/
const CONFIG_DATABIND=0;
const CONFIG_EXPRESSION=1;
const CONFIG_ASSET=2;
const CONFIG_PARAMETER=3;
const CONFIG_LOCALIZATION=4;
const CONFIG_TEMPLATE=5;
/**
* @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;
/**
* @var integer the line number that parsing starts from (internal use)
*/
private $_startingLine=0;
/**
* @var string template content to be parsed
*/
private $_content;
/**
* @var boolean whether this template is a source template
*/
private $_sourceTemplate=true;
/**
* @var string hash code of the template
*/
private $_hashCode='';
private $_tplControl=null;
private $_includedFiles=array();
private $_includeAtLine=array();
private $_includeLines=array();
/**
* 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
* @param integer the line number that parsing starts from (internal use)
* @param boolean whether this template is a source template, i.e., this template is loaded from
* some external storage rather than from within another template.
*/
public function __construct($template,$contextPath,$tplFile=null,$startingLine=0,$sourceTemplate=true)
{
$this->_sourceTemplate=$sourceTemplate;
$this->_contextPath=$contextPath;
$this->_tplFile=$tplFile;
$this->_startingLine=$startingLine;
$this->_content=$template;
$this->_hashCode=md5($template);
$this->parse($template);
$this->_content=null; // reset to save memory
}
/**
* @return string template file path if available, null otherwise.
*/
public function getTemplateFile()
{
return $this->_tplFile;
}
/**
* @return boolean whether this template is a source template, i.e., this template is loaded from
* some external storage rather than from within another template.
*/
public function getIsSourceTemplate()
{
return $this->_sourceTemplate;
}
/**
* @return string context directory path
*/
public function getContextPath()
{
return $this->_contextPath;
}
/**
* @return array name-value pairs declared in the directive
*/
public function getDirective()
{
return $this->_directive;
}
/**
* @return string hash code that can be used to identify the template
*/
public function getHashCode()
{
return $this->_hashCode;
}
/**
* @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 control who owns the template
* @param TControl the control who will become the root parent of the controls on the template. If null, it uses the template control.
*/
public function instantiateIn($tplControl,$parentControl=null)
{
$this->_tplControl=$tplControl;
if($parentControl===null)
$parentControl=$tplControl;
if(($page=$tplControl->getPage())===null)
$page=$this->getService()->getRequestedPage();
$controls=array();
$directChildren=array();
foreach($this->_tpl as $key=>$object)
{
if($object[0]===-1)
$parent=$parentControl;
else if(isset($controls[$object[0]]))
$parent=$controls[$object[0]];
else
continue;
if(isset($object[2])) // component
{
$component=Prado::createComponent($object[1]);
$properties=&$object[2];
if($component instanceof TControl)
{
if($component instanceof TOutputCache)
$component->setCacheKeyPrefix($this->_hashCode.$key);
$component->setTemplateControl($tplControl);
if(isset($properties['id']))
{
if(is_array($properties['id']))
$properties['id']=$component->evaluateExpression($properties['id'][1]);
$tplControl->registerObject($properties['id'],$component);
}
if(isset($properties['skinid']))
{
if(is_array($properties['skinid']))
$component->setSkinID($component->evaluateExpression($properties['skinid'][1]));
else
$component->setSkinID($properties['skinid']);
unset($properties['skinid']);
}
$component->trackViewState(false);
$component->applyStyleSheetSkin($page);
foreach($properties as $name=>$value)
$this->configureControl($component,$name,$value);
$component->trackViewState(true);
if($parent===$parentControl)
$directChildren[]=$component;
else
$component->createdOnTemplate($parent);
if($component->getAllowChildControls())
$controls[$key]=$component;
}
else if($component instanceof TComponent)
{
$controls[$key]=$component;
if(isset($properties['id']))
{
if(is_array($properties['id']))
$properties['id']=$component->evaluateExpression($properties['id'][1]);
$tplControl->registerObject($properties['id'],$component);
if(!$component->hasProperty('id'))
unset($properties['id']);
}
foreach($properties as $name=>$value)
$this->configureComponent($component,$name,$value);
if($parent===$parentControl)
$directChildren[]=$component;
else
$component->createdOnTemplate($parent);
}
}
else
{
if($object[1] instanceof TCompositeLiteral)
{
// need to clone a new object because the one in template is reused
$o=clone $object[1];
$o->setContainer($tplControl);
if($parent===$parentControl)
$directChildren[]=$o;
else
$parent->addParsedObject($o);
}
else
{
if($parent===$parentControl)
$directChildren[]=$object[1];
else
$parent->addParsedObject($object[1]);
}
}
}
// delay setting parent till now because the parent may cause
// the child to do lifecycle catchup which may cause problem
// if the child needs its own child controls.
foreach($directChildren as $control)
{
if($control instanceof TComponent)
$control->createdOnTemplate($parentControl);
else
$parentControl->addParsedObject($control);
}
}
/**
* Configures a property/event of a control.
* @param TControl control to be configured
* @param string property name
* @param mixed property initial value
*/
protected function configureControl($control,$name,$value)
{
if(strncasecmp($name,'on',2)===0) // is an event
$this->configureEvent($control,$name,$value,$control);
else if(($pos=strrpos($name,'.'))===false) // is a simple property or custom attribute
$this->configureProperty($control,$name,$value);
else // is a subproperty
$this->configureSubProperty($control,$name,$value);
}
/**
* Configures a property of a non-control component.
* @param TComponent component to be configured
* @param string property name
* @param mixed property initial value
*/
protected function configureComponent($component,$name,$value)
{
if(strpos($name,'.')===false) // is a simple property or custom attribute
$this->configureProperty($component,$name,$value);
else // is a subproperty
$this->configureSubProperty($component,$name,$value);
}
/**
* Configures an event for a control.
* @param TControl control to be configured
* @param string event name
* @param string event handler
* @param TControl context control
*/
protected function configureEvent($control,$name,$value,$contextControl)
{
if(strpos($value,'.')===false)
$control->attachEventHandler($name,array($contextControl,'TemplateControl.'.$value));
else
$control->attachEventHandler($name,array($contextControl,$value));
}
/**
* Configures a simple property for a component.
* @param TComponent component to be configured
* @param string property name
* @param mixed property initial value
*/
protected function configureProperty($component,$name,$value)
{
if(is_array($value))
{
switch($value[0])
{
case self::CONFIG_DATABIND:
$component->bindProperty($name,$value[1]);
break;
case self::CONFIG_EXPRESSION:
if($component instanceof TControl)
$component->autoBindProperty($name,$value[1]);
else
{
$setter='set'.$name;
$component->$setter($this->_tplControl->evaluateExpression($value[1]));
}
break;
case self::CONFIG_TEMPLATE:
$setter='set'.$name;
$component->$setter($value[1]);
break;
case self::CONFIG_ASSET: // asset URL
$setter='set'.$name;
$url=$this->publishFilePath($this->_contextPath.DIRECTORY_SEPARATOR.$value[1]);
$component->$setter($url);
break;
case self::CONFIG_PARAMETER: // application parameter
$setter='set'.$name;
$component->$setter($this->getApplication()->getParameters()->itemAt($value[1]));
break;
case self::CONFIG_LOCALIZATION:
$setter='set'.$name;
$component->$setter(Prado::localize($value[1]));
break;
default: // an error if reaching here
throw new TConfigurationException('template_tag_unexpected',$name,$value[1]);
break;
}
}
else
{
if (substr($name,0,2)=='js')
if ($value and !($value instanceof TJavaScriptLiteral))
$value = new TJavaScriptLiteral($value);
$setter='set'.$name;
$component->$setter($value);
}
}
/**
* Configures a subproperty for a component.
* @param TComponent component to be configured
* @param string subproperty name
* @param mixed subproperty initial value
*/
protected function configureSubProperty($component,$name,$value)
{
if(is_array($value))
{
switch($value[0])
{
case self::CONFIG_DATABIND: // databinding
$component->bindProperty($name,$value[1]);
break;
case self::CONFIG_EXPRESSION: // expression
if($component instanceof TControl)
$component->autoBindProperty($name,$value[1]);
else
$component->setSubProperty($name,$this->_tplControl->evaluateExpression($value[1]));
break;
case self::CONFIG_TEMPLATE:
$component->setSubProperty($name,$value[1]);
break;
case self::CONFIG_ASSET: // asset URL
$url=$this->publishFilePath($this->_contextPath.DIRECTORY_SEPARATOR.$value[1]);
$component->setSubProperty($name,$url);
break;
case self::CONFIG_PARAMETER: // application parameter
$component->setSubProperty($name,$this->getApplication()->getParameters()->itemAt($value[1]));
break;
case self::CONFIG_LOCALIZATION:
$component->setSubProperty($name,Prado::localize($value[1]));
break;
default: // an error if reaching here
throw new TConfigurationException('template_tag_unexpected',$name,$value[1]);
break;
}
}
else
$component->setSubProperty($name,$value);
}
/**
* 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 TConfigurationException if a parsing error is encountered
*/
protected function parse($input)
{
$input=$this->preprocess($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;
$matchEnd=0;
$c=0;
$this->_directive=null;
try
{
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],$match[2][1]);
$this->validateAttributes($type,$attributes);
$tpl[$c++]=array($container,$type,$attributes);
if($str[strlen($str)-2]!=='/') // open tag
{
$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))
throw new TConfigurationException('template_closingtag_unexpected',"</com:$type>");
$name=array_pop($stack);
if($name!==$type)
{
$tag=$name[0]==='@' ? '</prop:'.substr($name,1).'>' : "</com:$name>";
throw new TConfigurationException('template_closingtag_expected',$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]) || $this->_directive!==null)
throw new TConfigurationException('template_directive_nonunique');
$this->_directive=$this->parseAttributes($match[4][0],$match[4][1]);
}
else if(strpos($str,'<%')===0) // expression
{
if($expectPropEnd)
continue;
if($matchStart>$textStart)
$tpl[$c++]=array($container,substr($input,$textStart,$matchStart-$textStart));
$textStart=$matchEnd+1;
$literal=trim($match[5][0]);
if($str[2]==='=') // expression
$tpl[$c++]=array($container,array(TCompositeLiteral::TYPE_EXPRESSION,$literal));
else if($str[2]==='%') // statements
$tpl[$c++]=array($container,array(TCompositeLiteral::TYPE_STATEMENTS,$literal));
else if($str[2]==='#')
$tpl[$c++]=array($container,array(TCompositeLiteral::TYPE_DATABINDING,$literal));
else if($str[2]==='$')
$tpl[$c++]=array($container,array(TCompositeLiteral::TYPE_EXPRESSION,"\$this->getApplication()->getParameters()->itemAt('$literal')"));
else if($str[2]==='~')
$tpl[$c++]=array($container,array(TCompositeLiteral::TYPE_EXPRESSION,"\$this->publishFilePath('$this->_contextPath/$literal')"));
else if($str[2]==='/')
$tpl[$c++]=array($container,array(TCompositeLiteral::TYPE_EXPRESSION,"rtrim(dirname(\$this->getApplication()->getRequest()->getApplicationUrl()), '/').'/$literal'"));
else if($str[2]==='[')
{
$literal=strtr(trim(substr($literal,0,strlen($literal)-1)),array("'"=>"\'","\\"=>"\\\\"));
$tpl[$c++]=array($container,array(TCompositeLiteral::TYPE_EXPRESSION,"Prado::localize('$literal')"));
}
}
else if(strpos($str,'<prop:')===0) // opening property
{
if(strrpos($str,'/>')===strlen($str)-2) //subproperties
{
if($expectPropEnd)
continue;
if($matchStart>$textStart)
$tpl[$c++]=array($container,substr($input,$textStart,$matchStart-$textStart));
$textStart=$matchEnd+1;
$prop=strtolower($match[6][0]);
$attrs=$this->parseAttributes($match[7][0],$match[7][1]);
$attributes=array();
foreach($attrs as $name=>$value)
$attributes[$prop.'.'.$name]=$value;
$type=$tpl[$container][1];
$this->validateAttributes($type,$attributes);
foreach($attributes as $name=>$value)
{
if(isset($tpl[$container][2][$name]))
throw new TConfigurationException('template_property_duplicated',$name);
$tpl[$container][2][$name]=$value;
}
}
else // regular property
{
$prop=strtolower($match[3][0]);
$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))
throw new TConfigurationException('template_closingtag_unexpected',"</prop:$prop>");
$name=array_pop($stack);
if($name!=='@'.$prop)
{
$tag=$name[0]==='@' ? '</prop:'.substr($name,1).'>' : "</com:$name>";
throw new TConfigurationException('template_closingtag_expected',$tag);
}
if(($last=count($stack))<1 || $stack[$last-1][0]!=='@')
{
if($matchStart>$textStart)
{
$value=substr($input,$textStart,$matchStart-$textStart);
if(substr($prop,-8,8)==='template')
$value=$this->parseTemplateProperty($value,$textStart);
else
$value=$this->parseAttribute($value);
if($container>=0)
{
$type=$tpl[$container][1];
$this->validateAttributes($type,array($prop=>$value));
if(isset($tpl[$container][2][$prop]))
throw new TConfigurationException('template_property_duplicated',$prop);
$tpl[$container][2][$prop]=$value;
}
else // a property for the template control
$this->_directive[$prop]=$value;
$textStart=$matchEnd+1;
}
$expectPropEnd=false;
}
}
else if(strpos($str,'<!--')===0) // comments
{
if($expectPropEnd)
throw new TConfigurationException('template_comments_forbidden');
if($matchStart>$textStart)
$tpl[$c++]=array($container,substr($input,$textStart,$matchStart-$textStart));
$textStart=$matchEnd+1;
}
else
throw new TConfigurationException('template_matching_unexpected',$match);
}
if(!empty($stack))
{
$name=array_pop($stack);
$tag=$name[0]==='@' ? '</prop:'.substr($name,1).'>' : "</com:$name>";
throw new TConfigurationException('template_closingtag_expected',$tag);
}
if($textStart<strlen($input))
$tpl[$c++]=array($container,substr($input,$textStart));
}
catch(Exception $e)
{
if(($e instanceof TException) && ($e instanceof TTemplateException))
throw $e;
if($matchEnd===0)
$line=$this->_startingLine+1;
else
$line=$this->_startingLine+count(explode("\n",substr($input,0,$matchEnd+1)));
$this->handleException($e,$line,$input);
}
if($this->_directive===null)
$this->_directive=array();
// optimization by merging consecutive strings, expressions, statements and bindings
$objects=array();
$parent=null;
$merged=array();
foreach($tpl as $id=>$object)
{
if(isset($object[2]) || $object[0]!==$parent)
{
if($parent!==null)
{
if(count($merged[1])===1 && is_string($merged[1][0]))
$objects[$id-1]=array($merged[0],$merged[1][0]);
else
$objects[$id-1]=array($merged[0],new TCompositeLiteral($merged[1]));
}
if(isset($object[2]))
{
$parent=null;
$objects[$id]=$object;
}
else
{
$parent=$object[0];
$merged=array($parent,array($object[1]));
}
}
else
$merged[1][]=$object[1];
}
if($parent!==null)
{
if(count($merged[1])===1 && is_string($merged[1][0]))
$objects[$id]=array($merged[0],$merged[1][0]);
else
$objects[$id]=array($merged[0],new TCompositeLiteral($merged[1]));
}
$tpl=$objects;
return $objects;
}
/**
* 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,$offset)
{
if($str==='')
return array();
$pattern='/([\w\.\-]+)\s*=\s*(\'.*?\'|".*?"|<%.*?%>)/msS';
$attributes=array();
$n=preg_match_all($pattern,$str,$matches,PREG_SET_ORDER|PREG_OFFSET_CAPTURE);
for($i=0;$i<$n;++$i)
{
$match=&$matches[$i];
$name=strtolower($match[1][0]);
if(isset($attributes[$name]))
throw new TConfigurationException('template_property_duplicated',$name);
$value=$match[2][0];
if(substr($name,-8,8)==='template')
{
if($value[0]==='\'' || $value[0]==='"')
$attributes[$name]=$this->parseTemplateProperty(substr($value,1,strlen($value)-2),$match[2][1]+1);
else
$attributes[$name]=$this->parseTemplateProperty($value,$match[2][1]);
}
else
{
if($value[0]==='\'' || $value[0]==='"')
$attributes[$name]=$this->parseAttribute(substr($value,1,strlen($value)-2));
else
$attributes[$name]=$this->parseAttribute($value);
}
}
return $attributes;
}
protected function parseTemplateProperty($content,$offset)
{
$line=$this->_startingLine+count(explode("\n",substr($this->_content,0,$offset)))-1;
return array(self::CONFIG_TEMPLATE,new TTemplate($content,$this->_contextPath,$this->_tplFile,$line,false));
}
/**
* Parses a single attribute.
* @param string the string to be parsed.
* @return array attribute initialization
*/
protected function parseAttribute($value)
{
if(($n=preg_match_all('/<%[#=].*?%>/msS',$value,$matches,PREG_OFFSET_CAPTURE))>0)
{
$isDataBind=false;
$textStart=0;
$expr='';
for($i=0;$i<$n;++$i)
{
$match=$matches[0][$i];
$token=$match[0];
$offset=$match[1];
$length=strlen($token);
if($token[2]==='#')
$isDataBind=true;
if($offset>$textStart)
$expr.=".'".strtr(substr($value,$textStart,$offset-$textStart),array("'"=>"\\'","\\"=>"\\\\"))."'";
$expr.='.('.substr($token,3,$length-5).')';
$textStart=$offset+$length;
}
$length=strlen($value);
if($length>$textStart)
$expr.=".'".strtr(substr($value,$textStart,$length-$textStart),array("'"=>"\\'","\\"=>"\\\\"))."'";
if($isDataBind)
return array(self::CONFIG_DATABIND,ltrim($expr,'.'));
else
return array(self::CONFIG_EXPRESSION,ltrim($expr,'.'));
}
else if(preg_match('/\\s*(<%~.*?%>|<%\\$.*?%>|<%\\[.*?\\]%>|<%\/.*?%>)\\s*/msS',$value,$matches) && $matches[0]===$value)
{
$value=$matches[1];
if($value[2]==='~')
return array(self::CONFIG_ASSET,trim(substr($value,3,strlen($value)-5)));
elseif($value[2]==='[')
return array(self::CONFIG_LOCALIZATION,trim(substr($value,3,strlen($value)-6)));
elseif($value[2]==='$')
return array(self::CONFIG_PARAMETER,trim(substr($value,3,strlen($value)-5)));
elseif($value[2]==='/') {
$literal = trim(substr($value,3,strlen($value)-5));
return array(self::CONFIG_EXPRESSION,"rtrim(dirname(\$this->getApplication()->getRequest()->getApplicationUrl()), '/').'/$literal'");
}
}
else
return $value;
}
protected function validateAttributes($type,$attributes)
{
Prado::using($type);
if(($pos=strrpos($type,'.'))!==false)
$className=substr($type,$pos+1);
else
$className=$type;
$class=new ReflectionClass($className);
if(is_subclass_of($className,'TControl') || $className==='TControl')
{
foreach($attributes as $name=>$att)
{
if(($pos=strpos($name,'.'))!==false)
{
// a subproperty, so the first segment must be readable
$subname=substr($name,0,$pos);
if(!$class->hasMethod('get'.$subname))
throw new TConfigurationException('template_property_unknown',$type,$subname);
}
else if(strncasecmp($name,'on',2)===0)
{
// an event
if(!$class->hasMethod($name))
throw new TConfigurationException('template_event_unknown',$type,$name);
else if(!is_string($att))
throw new TConfigurationException('template_eventhandler_invalid',$type,$name);
}
else
{
// a simple property
if (! ($class->hasMethod('set'.$name) || $class->hasMethod('setjs'.$name)) )
{
if ($class->hasMethod('get'.$name) || $class->hasMethod('getjs'.$name))
throw new TConfigurationException('template_property_readonly',$type,$name);
else
throw new TConfigurationException('template_property_unknown',$type,$name);
}
else if(is_array($att) && $att[0]!==self::CONFIG_EXPRESSION)
{
if(strcasecmp($name,'id')===0)
throw new TConfigurationException('template_controlid_invalid',$type);
else if(strcasecmp($name,'skinid')===0)
throw new TConfigurationException('template_controlskinid_invalid',$type);
}
}
}
}
else if(is_subclass_of($className,'TComponent') || $className==='TComponent')
{
foreach($attributes as $name=>$att)
{
if(is_array($att) && ($att[0]===self::CONFIG_DATABIND))
throw new TConfigurationException('template_databind_forbidden',$type,$name);
if(($pos=strpos($name,'.'))!==false)
{
// a subproperty, so the first segment must be readable
$subname=substr($name,0,$pos);
if(!$class->hasMethod('get'.$subname))
throw new TConfigurationException('template_property_unknown',$type,$subname);
}
else if(strncasecmp($name,'on',2)===0)
throw new TConfigurationException('template_event_forbidden',$type,$name);
else
{
// id is still alowed for TComponent, even if id property doesn't exist
if(strcasecmp($name,'id')!==0 && !$class->hasMethod('set'.$name))
{
if($class->hasMethod('get'.$name))
throw new TConfigurationException('template_property_readonly',$type,$name);
else
throw new TConfigurationException('template_property_unknown',$type,$name);
}
}
}
}
else
throw new TConfigurationException('template_component_required',$type);
}
/**
* @return array list of included external template files
*/
public function getIncludedFiles()
{
return $this->_includedFiles;
}
/**
* Handles template parsing exception.
* This method rethrows the exception caught during template parsing.
* It adjusts the error location by giving out correct error line number and source file.
* @param Exception template exception
* @param int line number
* @param string template string if no source file is used
*/
protected function handleException($e,$line,$input=null)
{
$srcFile=$this->_tplFile;
if(($n=count($this->_includedFiles))>0) // need to adjust error row number and file name
{
for($i=$n-1;$i>=0;--$i)
{
if($this->_includeAtLine[$i]<=$line)
{
if($line<$this->_includeAtLine[$i]+$this->_includeLines[$i])
{
$line=$line-$this->_includeAtLine[$i]+1;
$srcFile=$this->_includedFiles[$i];
break;
}
else
$line=$line-$this->_includeLines[$i]+1;
}
}
}
$exception=new TTemplateException('template_format_invalid',$e->getMessage());
$exception->setLineNumber($line);
if(!empty($srcFile))
$exception->setTemplateFile($srcFile);
else
$exception->setTemplateSource($input);
throw $exception;
}
/**
* Preprocesses the template string by including external templates
* @param string template string
* @return string expanded template string
*/
protected function preprocess($input)
{
if($n=preg_match_all('/<%include(.*?)%>/',$input,$matches,PREG_SET_ORDER|PREG_OFFSET_CAPTURE))
{
for($i=0;$i<$n;++$i)
{
$filePath=Prado::getPathOfNamespace(trim($matches[$i][1][0]),TTemplateManager::TEMPLATE_FILE_EXT);
if($filePath!==null && is_file($filePath))
$this->_includedFiles[]=$filePath;
else
{
$errorLine=count(explode("\n",substr($input,0,$matches[$i][0][1]+1)));
$this->handleException(new TConfigurationException('template_include_invalid',trim($matches[$i][1][0])),$errorLine,$input);
}
}
$base=0;
for($i=0;$i<$n;++$i)
{
$ext=file_get_contents($this->_includedFiles[$i]);
$length=strlen($matches[$i][0][0]);
$offset=$base+$matches[$i][0][1];
$this->_includeAtLine[$i]=count(explode("\n",substr($input,0,$offset)));
$this->_includeLines[$i]=count(explode("\n",$ext));
$input=substr_replace($input,$ext,$offset,$length);
$base+=strlen($ext)-$length;
}
}
return $input;
}
}