root/trunk/framework/Web/UI/TTemplateManager.php

Revision 2482, 35.2 kB (checked in by knut, 4 months ago)

updated copyright

  • Property svn:keywords set to Id
<
Line 
1<?php
2/**
3 * TTemplateManager and TTemplate class file
4 *
5 * @author Qiang Xue <qiang.xue@gmail.com>
6 * @link http://www.pradosoft.com/
7 * @copyright Copyright &copy; 2005-2008 PradoSoft
8 * @license http://www.pradosoft.com/license/
9 * @version $Id$
10 * @package System.Web.UI
11 */
12
13/**
14 * Includes TOutputCache class file
15 */
16Prado::using('System.Web.UI.WebControls.TOutputCache');
17
18/**
19 * TTemplateManager class
20 *
21 * TTemplateManager manages the loading and parsing of control templates.
22 *
23 * There are two ways of loading a template, either by the associated template
24 * control class name, or the template file name.
25 * The former is via calling {@link getTemplateByClassName}, which tries to
26 * locate the corresponding template file under the directory containing
27 * the class file. The name of the template file is the class name with
28 * the extension '.tpl'. To load a template from a template file path,
29 * call {@link getTemplateByFileName}.
30 *
31 * By default, TTemplateManager is registered with {@link TPageService} as the
32 * template manager module that can be accessed via {@link TPageService::getTemplateManager()}.
33 *
34 * @author Qiang Xue <qiang.xue@gmail.com>
35 * @version $Id$
36 * @package System.Web.UI
37 * @since 3.0
38 */
39class TTemplateManager extends TModule
40{
41    /**
42     * Template file extension
43     */
44    const TEMPLATE_FILE_EXT='.tpl';
45    /**
46     * Prefix of the cache variable name for storing parsed templates
47     */
48    const TEMPLATE_CACHE_PREFIX='prado:template:';
49
50    /**
51     * Initializes the module.
52     * This method is required by IModule and is invoked by application.
53     * It starts output buffer if it is enabled.
54     * @param TXmlElement module configuration
55     */
56    public function init($config)
57    {
58        $this->getService()->setTemplateManager($this);
59    }
60
61    /**
62     * Loads the template corresponding to the specified class name.
63     * @return ITemplate template for the class name, null if template doesn't exist.
64     */
65    public function getTemplateByClassName($className)
66    {
67        $class=new ReflectionClass($className);
68        $tplFile=dirname($class->getFileName()).DIRECTORY_SEPARATOR.$className.self::TEMPLATE_FILE_EXT;
69        return $this->getTemplateByFileName($tplFile);
70    }
71
72    /**
73     * Loads the template from the specified file.
74     * @return ITemplate template parsed from the specified file, null if the file doesn't exist.
75     */
76    public function getTemplateByFileName($fileName)
77    {
78        if(($fileName=$this->getLocalizedTemplate($fileName))!==null)
79        {
80            Prado::trace("Loading template $fileName",'System.Web.UI.TTemplateManager');
81            if(($cache=$this->getApplication()->getCache())===null)
82                return new TTemplate(file_get_contents($fileName),dirname($fileName),$fileName);
83            else
84            {
85                $array=$cache->get(self::TEMPLATE_CACHE_PREFIX.$fileName);
86                if(is_array($array))
87                {
88                    list($template,$timestamps)=$array;
89                    if($this->getApplication()->getMode()===TApplicationMode::Performance)
90                        return $template;
91                    $cacheValid=true;
92                    foreach($timestamps as $tplFile=>$timestamp)
93                    {
94                        if(!is_file($tplFile) || filemtime($tplFile)>$timestamp)
95                        {
96                            $cacheValid=false;
97                            break;
98                        }
99                    }
100                    if($cacheValid)
101                        return $template;
102                }
103                $template=new TTemplate(file_get_contents($fileName),dirname($fileName),$fileName);
104                $includedFiles=$template->getIncludedFiles();
105                $timestamps=array();
106                $timestamps[$fileName]=filemtime($fileName);
107                foreach($includedFiles as $includedFile)
108                    $timestamps[$includedFile]=filemtime($includedFile);
109                $cache->set(self::TEMPLATE_CACHE_PREFIX.$fileName,array($template,$timestamps));
110                return $template;
111            }
112        }
113        else
114            return null;
115    }
116
117    /**
118     * Finds a localized template file.
119     * @param string template file.
120     * @return string|null a localized template file if found, null otherwise.
121     */
122    protected function getLocalizedTemplate($filename)
123    {
124        if(($app=$this->getApplication()->getGlobalization(false))===null)
125            return is_file($filename)?$filename:null;
126        foreach($app->getLocalizedResource($filename) as $file)
127        {
128            if(($file=realpath($file))!==false && is_file($file))
129                return $file;
130        }
131        return null;
132    }
133}
134
135/**
136 * TTemplate implements PRADO template parsing logic.
137 * A TTemplate object represents a parsed PRADO control template.
138 * It can instantiate the template as child controls of a specified control.
139 * The template format is like HTML, with the following special tags introduced,
140 * - component tags: a component tag represents the configuration of a component.
141 * The tag name is in the format of com:ComponentType, where ComponentType is the component
142 * class name. Component tags must be well-formed. Attributes of the component tag
143 * are treated as either property initial values, event handler attachment, or regular
144 * tag attributes.
145 * - property tags: property tags are used to set large block of attribute values.
146 * The property tag name is in the format of <prop:AttributeName> where AttributeName
147 * can be a property name, an event name or a regular tag attribute name.
148 * - group subproperty tags: subproperties of a common property can be configured using
149 * <prop:MainProperty SubProperty1="Value1" SubProperty2="Value2" .../>
150 * - directive: directive specifies the property values for the template owner.
151 * It is in the format of <%@ property name-value pairs %>;
152 * - expressions: They are in the formate of <%= PHP expression %> and <%% PHP statements %>
153 * - comments: There are two kinds of comments, regular HTML comments and special template comments.
154 * The former is in the format of <!-- comments -->, which will be treated as text strings.
155 * The latter is in the format of <!-- comments --!>, which will be stripped out.
156 *
157 * Tags other than the above are not required to be well-formed.
158 *
159 * A TTemplate object represents a parsed PRADO template. To instantiate the template
160 * for a particular control, call {@link instantiateIn($control)}, which
161 * will create and intialize all components specified in the template and
162 * set their parent as $control.
163 *
164 * @author Qiang Xue <qiang.xue@gmail.com>
165 * @version $Id$
166 * @package System.Web.UI
167 * @since 3.0
168 */
169class TTemplate extends TApplicationComponent implements ITemplate
170{
171    /**
172     *  '<!--.*?--!>' - template comments
173     *  '<!--.*?-->'  - HTML comments
174     *    '<\/?com:([\w\.]+)((?:\s*[\w\.]+\s*=\s*\'.*?\'|\s*[\w\.]+\s*=\s*".*?"|\s*[\w\.]+\s*=\s*<%.*?%>)*)\s*\/?>' - component tags
175     *    '<\/?prop:([\w\.]+)\s*>'  - property tags
176     *    '<%@\s*((?:\s*[\w\.]+\s*=\s*\'.*?\'|\s*[\w\.]+\s*=\s*".*?")*)\s*%>'  - directives
177     *    '<%[%#~\\$=\\[](.*?)%>'  - expressions
178     *  '<prop:([\w\.]+)((?:\s*[\w\.]+=\'.*?\'|\s*[\w\.]+=".*?"|\s*[\w\.]+=<%.*?%>)*)\s*\/>' - group subproperty tags
179     */
180    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';
181
182    /**
183     * Different configurations of component property/event/attribute
184     */
185    const CONFIG_DATABIND=0;
186    const CONFIG_EXPRESSION=1;
187    const CONFIG_ASSET=2;
188    const CONFIG_PARAMETER=3;
189    const CONFIG_LOCALIZATION=4;
190    const CONFIG_TEMPLATE=5;
191
192    /**
193     * @var array list of component tags and strings
194     */
195    private $_tpl=array();
196    /**
197     * @var array list of directive settings
198     */
199    private $_directive=array();
200    /**
201     * @var string context path
202     */
203    private $_contextPath;
204    /**
205     * @var string template file path (if available)
206     */
207    private $_tplFile=null;
208    /**
209     * @var integer the line number that parsing starts from (internal use)
210     */
211    private $_startingLine=0;
212    /**
213     * @var string template content to be parsed
214     */
215    private $_content;
216    /**
217     * @var boolean whether this template is a source template
218     */
219    private $_sourceTemplate=true;
220    /**
221     * @var string hash code of the template
222     */
223    private $_hashCode='';
224    private $_tplControl=null;
225    private $_includedFiles=array();
226    private $_includeAtLine=array();
227    private $_includeLines=array();
228
229
230    /**
231     * Constructor.
232     * The template will be parsed after construction.
233     * @param string the template string
234     * @param string the template context directory
235     * @param string the template file, null if no file
236     * @param integer the line number that parsing starts from (internal use)
237     * @param boolean whether this template is a source template, i.e., this template is loaded from
238     * some external storage rather than from within another template.
239     */
240    public function __construct($template,$contextPath,$tplFile=null,$startingLine=0,$sourceTemplate=true)
241    {
242        $this->_sourceTemplate=$sourceTemplate;
243        $this->_contextPath=$contextPath;
244        $this->_tplFile=$tplFile;
245        $this->_startingLine=$startingLine;
246        $this->_content=$template;
247        $this->_hashCode=md5($template);
248        $this->parse($template);
249        $this->_content=null; // reset to save memory
250    }
251
252    /**
253     * @return string  template file path if available, null otherwise.
254     */
255    public function getTemplateFile()
256    {
257        return $this->_tplFile;
258    }
259
260    /**
261     * @return boolean whether this template is a source template, i.e., this template is loaded from
262     * some external storage rather than from within another template.
263     */
264    public function getIsSourceTemplate()
265    {
266        return $this->_sourceTemplate;
267    }
268
269    /**
270     * @return string context directory path
271     */
272    public function getContextPath()
273    {
274        return $this->_contextPath;
275    }
276
277    /**
278     * @return array name-value pairs declared in the directive
279     */
280    public function getDirective()
281    {
282        return $this->_directive;
283    }
284
285    /**
286     * @return string hash code that can be used to identify the template
287     */
288    public function getHashCode()
289    {
290        return $this->_hashCode;
291    }
292
293    /**
294     * @return array the parsed template
295     */
296    public function &getItems()
297    {
298        return $this->_tpl;
299    }
300
301    /**
302     * Instantiates the template.
303     * Content in the template will be instantiated as components and text strings
304     * and passed to the specified parent control.
305     * @param TControl the control who owns the template
306     * @param TControl the control who will become the root parent of the controls on the template. If null, it uses the template control.
307     */
308    public function instantiateIn($tplControl,$parentControl=null)
309    {
310        $this->_tplControl=$tplControl;
311        if($parentControl===null)
312            $parentControl=$tplControl;
313        if(($page=$tplControl->getPage())===null)
314            $page=$this->getService()->getRequestedPage();
315        $controls=array();
316        $directChildren=array();
317        foreach($this->_tpl as $key=>$object)
318        {
319            if($object[0]===-1)
320                $parent=$parentControl;
321            else if(isset($controls[$object[0]]))
322                $parent=$controls[$object[0]];
323            else
324                continue;
325            if(isset($object[2]))    // component
326            {
327                $component=Prado::createComponent($object[1]);
328                $properties=&$object[2];
329                if($component instanceof TControl)
330                {
331                    if($component instanceof TOutputCache)
332                        $component->setCacheKeyPrefix($this->_hashCode.$key);
333                    $component->setTemplateControl($tplControl);
334                    if(isset($properties['id']))
335                    {
336                        if(is_array($properties['id']))
337                            $properties['id']=$component->evaluateExpression($properties['id'][1]);
338                        $tplControl->registerObject($properties['id'],$component);
339                    }
340                    if(isset($properties['skinid']))
341                    {
342                        if(is_array($properties['skinid']))
343                            $component->setSkinID($component->evaluateExpression($properties['skinid'][1]));
344                        else
345                            $component->setSkinID($properties['skinid']);
346                        unset($properties['skinid']);
347                    }
348
349                    $component->trackViewState(false);
350
351                    $component->applyStyleSheetSkin($page);
352                    foreach($properties as $name=>$value)
353                        $this->configureControl($component,$name,$value);
354
355                    $component->trackViewState(true);
356
357                    if($parent===$parentControl)
358                        $directChildren[]=$component;
359                    else
360                        $component->createdOnTemplate($parent);
361                    if($component->getAllowChildControls())
362                        $controls[$key]=$component;
363                }
364                else if($component instanceof TComponent)
365                {
366                    $controls[$key]=$component;
367                    if(isset($properties['id']))
368                    {
369                        if(is_array($properties['id']))
370                            $properties['id']=$component->evaluateExpression($properties['id'][1]);
371                        $tplControl->registerObject($properties['id'],$component);
372                        if(!$component->hasProperty('id'))
373                            unset($properties['id']);
374                    }
375                    foreach($properties as $name=>$value)
376                        $this->configureComponent($component,$name,$value);
377                    if($parent===$parentControl)
378                        $directChildren[]=$component;
379                    else
380                        $component->createdOnTemplate($parent);
381                }
382            }
383            else
384            {
385                if($object[1] instanceof TCompositeLiteral)
386                {
387                    // need to clone a new object because the one in template is reused
388                    $o=clone $object[1];
389                    $o->setContainer($tplControl);
390                    if($parent===$parentControl)
391                        $directChildren[]=$o;
392                    else
393                        $parent->addParsedObject($o);
394                }
395                else
396                {
397                    if($parent===$parentControl)
398                        $directChildren[]=$object[1];
399                    else
400                        $parent->addParsedObject($object[1]);
401                }
402            }
403        }
404        // delay setting parent till now because the parent may cause
405        // the child to do lifecycle catchup which may cause problem
406        // if the child needs its own child controls.
407        foreach($directChildren as $control)
408        {
409            if($control instanceof TComponent)
410                $control->createdOnTemplate($parentControl);
411            else
412                $parentControl->addParsedObject($control);
413        }
414    }
415
416    /**
417     * Configures a property/event of a control.
418     * @param TControl control to be configured
419     * @param string property name
420     * @param mixed property initial value
421     */
422    protected function configureControl($control,$name,$value)
423    {
424        if(strncasecmp($name,'on',2)===0)        // is an event
425            $this->configureEvent($control,$name,$value,$control);
426        else if(($pos=strrpos($name,'.'))===false)    // is a simple property or custom attribute
427            $this->configureProperty($control,$name,$value);
428        else    // is a subproperty
429            $this->configureSubProperty($control,$name,$value);
430    }
431
432    /**
433     * Configures a property of a non-control component.
434     * @param TComponent component to be configured
435     * @param string property name
436     * @param mixed property initial value
437     */
438    protected function configureComponent($component,$name,$value)
439    {
440        if(strpos($name,'.')===false)    // is a simple property or custom attribute
441            $this->configureProperty($component,$name,$value);
442        else    // is a subproperty
443            $this->configureSubProperty($component,$name,$value);
444    }
445
446    /**
447     * Configures an event for a control.
448     * @param TControl control to be configured
449     * @param string event name
450     * @param string event handler
451     * @param TControl context control
452     */
453    protected function configureEvent($control,$name,$value,$contextControl)
454    {
455        if(strpos($value,'.')===false)
456            $control->attachEventHandler($name,array($contextControl,'TemplateControl.'.$value));
457        else
458            $control->attachEventHandler($name,array($contextControl,$value));
459    }
460
461    /**
462     * Configures a simple property for a component.
463     * @param TComponent component to be configured
464     * @param string property name
465     * @param mixed property initial value
466     */
467    protected function configureProperty($component,$name,$value)
468    {
469        if(is_array($value))
470        {
471            switch($value[0])
472            {
473                case self::CONFIG_DATABIND:
474                    $component->bindProperty($name,$value[1]);
475                    break;
476                case self::CONFIG_EXPRESSION:
477                    if($component instanceof TControl)
478                        $component->autoBindProperty($name,$value[1]);
479                    else
480                    {
481                        $setter='set'.$name;
482                        $component->$setter($this->_tplControl->evaluateExpression($value[1]));
483                    }
484                    break;
485                case self::CONFIG_TEMPLATE:
486                    $setter='set'.$name;
487                    $component->$setter($value[1]);
488                    break;
489                case self::CONFIG_ASSET:        // asset URL
490                    $setter='set'.$name;
491                    $url=$this->publishFilePath($this->_contextPath.DIRECTORY_SEPARATOR.$value[1]);
492                    $component->$setter($url);
493                    break;
494                case self::CONFIG_PARAMETER:        // application parameter
495                    $setter='set'.$name;
496                    $component->$setter($this->getApplication()->getParameters()->itemAt($value[1]));
497                    break;
498                case self::CONFIG_LOCALIZATION:
499                    $setter='set'.$name;
500                    $component->$setter(Prado::localize($value[1]));
501                    break;
502                default:    // an error if reaching here
503                    throw new TConfigurationException('template_tag_unexpected',$name,$value[1]);
504                    break;
505            }
506        }
507        else
508        {
509            $setter='set'.$name;
510            $component->$setter($value);
511        }
512    }
513
514    /**
515     * Configures a subproperty for a component.
516     * @param TComponent component to be configured
517     * @param string subproperty name
518     * @param mixed subproperty initial value
519     */
520    protected function configureSubProperty($component,$name,$value)
521    {
522        if(is_array($value))
523        {
524            switch($value[0])
525            {
526                case self::CONFIG_DATABIND:        // databinding
527                    $component->bindProperty($name,$value[1]);
528                    break;
529                case self::CONFIG_EXPRESSION:        // expression
530                    if($component instanceof TControl)
531                        $component->autoBindProperty($name,$value[1]);
532                    else
533                        $component->setSubProperty($name,$this->_tplControl->evaluateExpression($value[1]));
534                    break;
535                case self::CONFIG_TEMPLATE:
536                    $component->setSubProperty($name,$value[1]);
537                    break;
538                case self::CONFIG_ASSET:        // asset URL
539                    $url=$this->publishFilePath($this->_contextPath.DIRECTORY_SEPARATOR.$value[1]);
540                    $component->setSubProperty($name,$url);
541                    break;
542                case self::CONFIG_PARAMETER:        // application parameter
543                    $component->setSubProperty($name,$this->getApplication()->getParameters()->itemAt($value[1]));
544                    break;
545                case self::CONFIG_LOCALIZATION:
546                    $component->setSubProperty($name,Prado::localize($value[1]));
547                    break;
548                default:    // an error if reaching here
549                    throw new TConfigurationException('template_tag_unexpected',$name,$value[1]);
550                    break;
551            }
552        }
553        else
554            $component->setSubProperty($name,$value);
555    }
556
557    /**
558     * Parses a template string.
559     *