Added support for static includes
authorAlexander Ebert <ebert@woltlab.com>
Wed, 8 Feb 2012 19:54:14 +0000 (20:54 +0100)
committerAlexander Ebert <ebert@woltlab.com>
Wed, 8 Feb 2012 19:54:14 +0000 (20:54 +0100)
wcfsetup/install/files/lib/system/template/TemplateCompiler.class.php
wcfsetup/install/files/lib/system/template/TemplateEngine.class.php
wcfsetup/install/files/lib/system/template/TemplateScriptingCompiler.class.php

index 568e9ec31d67c457289bf999618f02b9a8fe72fd..6c4ecd20e972295159900f19ade06897b84d0f25 100644 (file)
@@ -34,9 +34,20 @@ class TemplateCompiler extends TemplateScriptingCompiler {
                $file->close();
                
                // write meta data to file
-               $file = new File($metaData['filename']);
+               $this->saveMetaData($templateName, $metaData['filename'], $compiledContent['meta']);
+       }
+       
+       /**
+        * Saves meta data for given template.
+        * 
+        * @param       string          $templateName
+        * @param       string          $filename
+        * @param       string          $content
+        */
+       public function saveMetaData($templateName, $filename, $content) {
+               $file = new File($filename);
                $file->write("<?php exit; /* meta data for template: ".$templateName." (generated at ".gmdate('r').") DO NOT EDIT THIS FILE */ ?>\n");
-               $file->write(serialize($compiledContent['meta']));
+               $file->write(serialize($content));
                $file->close();
        }
        
index 01436f577429399bf3643c048c266163d41c6f71..2165c7aa32f51495e87294fd1370f2fe5f310fb6 100644 (file)
@@ -304,12 +304,12 @@ class TemplateEngine extends SingletonFactory {
                $metaData = $this->getMetaData($templateName, $metaDataFilename);
                
                // check if compilation is necessary
-               if (!$this->isCompiled($templateName, $sourceFilename, $compiledFilename, $metaData)) {
+               if (($metaData === null) || !$this->isCompiled($templateName, $sourceFilename, $compiledFilename, $packageID, $metaData)) {
                        // compile
                        $this->compileTemplate($templateName, $sourceFilename, $compiledFilename, array(
                                'data' => $metaData,
                                'filename' => $metaDataFilename,
-                               'packageID' => $packageID
+                               'packageID' => $tplPackageID
                        ));
                }
                
@@ -417,11 +417,12 @@ class TemplateEngine extends SingletonFactory {
         * @param       string          $templateName
         * @param       string          $sourceFilename
         * @param       string          $compiledFilename
+        * @param       integer         $packageID
         * @param       array           $metaData
         * @return      boolean         $isCompiled
         */
-       protected function isCompiled($templateName, $sourceFilename, $compiledFilename, array $metaData) {
-               if ($this->forceCompile || !file_exists($compiledFilename) || !file_exists($metaDataFilename)) {
+       protected function isCompiled($templateName, $sourceFilename, $compiledFilename, $packageID, array $metaData) {
+               if ($this->forceCompile || !file_exists($compiledFilename)) {
                        return false;
                }
                else {
@@ -434,8 +435,9 @@ class TemplateEngine extends SingletonFactory {
                        else {
                                // check for meta data
                                if (!empty($metaData['include'])) {
-                                       foreach ($metaData['include'] as $template) {
-                                               $includedTemplateFilename = $this->getSourceFilename($template['templateName'], $packageID);
+                                       foreach ($metaData['include'] as $includedTemplate) {
+                                               $tplPackageID = $this->getPackageID($includedTemplate, $packageID);
+                                               $includedTemplateFilename = $this->getSourceFilename($includedTemplate, $tplPackageID);
                                                $includedMTime = @filemtime($includedTemplateFilename);
                                                
                                                if ($includedMTime >= $compileMTime) {
@@ -468,16 +470,11 @@ class TemplateEngine extends SingletonFactory {
         * @param       array           $metaData
         */
        protected function compileTemplate($templateName, $sourceFilename, $compiledFilename, array $metaData) {
-               // get compiler
-               if (!($this->compilerObj instanceof TemplateCompiler)) {
-                       $this->compilerObj = $this->getCompiler();
-               }
-               
                // get source
                $sourceContent = $this->getSourceContent($sourceFilename);
                
                // compile template
-               $this->compilerObj->compile($templateName, $sourceContent, $compiledFilename, $metaData);
+               $this->getCompiler()->compile($templateName, $sourceContent, $compiledFilename, $metaData);
        }
        
        /**
@@ -485,8 +482,12 @@ class TemplateEngine extends SingletonFactory {
         * 
         * @return      wcf\system\template\TemplateCompiler
         */
-       protected function getCompiler() {
-               return new TemplateCompiler($this);
+       public function getCompiler() {
+               if ($this->compilerObj === null) {
+                       $this->compilerObj = new TemplateCompiler($this);
+               }
+               
+               return $this->compilerObj;
        }
        
        /**
@@ -809,7 +810,9 @@ class TemplateEngine extends SingletonFactory {
                
                // find first newline
                $position = strpos($contents, "\n");
-               if ($position === false) throw new SystemException("Unable to load meta data for template '".$templateName."'");
+               if ($position === false) {
+                       return null;
+               }
                
                // cut contents
                $contents = substr($contents, $position + 1);
@@ -817,7 +820,7 @@ class TemplateEngine extends SingletonFactory {
                // read serializes data
                $data = @unserialize($contents);
                if ($data === false || !is_array($data)) {
-                       throw new SystemException("Invalid meta data for template '".$templateName."'");
+                       return null;
                }
                
                return $data;
index d71a8aa7e093c3e0067392d56440c3b6533651d5..47bee243ecba401701cc5fe8fb9e942b8baabdb9 100644 (file)
@@ -167,6 +167,18 @@ class TemplateScriptingCompiler {
         */
        protected $rdq;
        
+       /**
+        * list of static includes per template
+        * @var array<string>
+        */
+       protected $staticIncludes = array();
+       
+       /**
+        * list of previously included namespaces
+        * @var array<string>
+        */
+       protected $includedNamespaces = array();
+       
        /**
         * Creates a new TemplateScriptingCompiler object.
         * 
@@ -189,9 +201,24 @@ class TemplateScriptingCompiler {
         * @param       string          $identifier
         * @param       string          $sourceContent
         * @param       array           $metaData
+        * @param       boolean         $isolated
         * @return      string
         */
-       public function compileString($identifier, $sourceContent, array $metaData = array()) {
+       public function compileString($identifier, $sourceContent, array $metaData = array(), $isolated = false) {
+               if ($isolated) {
+                       $previousData = array(
+                               'autoloadPlugins' => $this->autoloadPlugins,
+                               'currentIdentifier' => $this->currentIdentifier,
+                               'currentLineNo' => $this->currentLineNo,
+                               'literalStack' => $this->literalStack,
+                               'stringStack' => $this->stringStack,
+                               'tagStack' => $this->tagStack
+                       );
+               }
+               else {
+                       $this->includedNamespaces = $this->staticIncludes = array();
+               }
+               
                // reset vars
                $this->autoloadPlugins = $this->tagStack = $this->stringStack = $this->literalStack = array();
                $this->currentIdentifier = $identifier;
@@ -221,7 +248,7 @@ class TemplateScriptingCompiler {
                $compiledTags = array();
                for ($i = 0, $j = count($templateTags); $i < $j; $i++) {
                        $this->currentLineNo += StringUtil::countSubstring($textBlocks[$i], "\n");
-                       $compiledTags[] = $this->compileTag($templateTags[$i], $metaData);
+                       $compiledTags[] = $this->compileTag($templateTags[$i], $identifier, $metaData);
                        $this->currentLineNo += StringUtil::countSubstring($templateTags[$i], "\n");
                }
                
@@ -255,7 +282,14 @@ class TemplateScriptingCompiler {
                if (count($this->autoloadPlugins) > 0) {
                        $compiledAutoloadPlugins = "<?php\n";
                        foreach ($this->autoloadPlugins as $className) {
-                               $compiledAutoloadPlugins .= "use ".$className.";\n";
+                               // prevent multiple use on the same namespace
+                               if (!in_array($className, $this->includedNamespaces)) {
+                                       // TODO: We're using the classes with prepended namespace, why should we first
+                                       //       import them with "use" for no reason?
+                                       //$compiledAutoloadPlugins .= "use ".$className.";\n";
+                                       $this->includedNamespaces[] = $className;
+                               }
+                               
                                $compiledAutoloadPlugins .= "if (!isset(\$this->pluginObjects['$className'])) {\n";
                                $compiledAutoloadPlugins .= "\$this->pluginObjects['$className'] = new $className;\n";
                                $compiledAutoloadPlugins .= "}\n";
@@ -263,16 +297,32 @@ class TemplateScriptingCompiler {
                        $compiledAutoloadPlugins .= "?>";
                }
                
-               return $compiledAutoloadPlugins.$compiledContent;
+               // restore data
+               if ($isolated) {
+                       $this->autoloadPlugins = $previousData['autoloadPlugins'];
+                       $this->currentIdentifier = $previousData['currentIdentifier'];
+                       $this->currentLineNo = $previousData['currentLineNo'];
+                       $this->literalStack = $previousData['literalStack'];
+                       $this->stringStack = $previousData['stringStack'];
+                       $this->tagStack = $previousData['tagStack'];
+               }
+               
+               return array(
+                       'meta' => array(
+                               'include' => $this->staticIncludes
+                       ),
+                       'template' => $compiledAutoloadPlugins.$compiledContent
+               );
        }
        
        /**
         * Compiles a template tag.
         * 
         * @param       string          $tag
+        * @param       string          $identifier
         * @param       array           $metaData
         */
-       protected function compileTag($tag, array &$metaData) {
+       protected function compileTag($tag, $identifier, array &$metaData) {
                if (preg_match('~^'.$this->outputPattern.'~s', $tag)) {
                        // variable output
                        return $this->compileOutputTag($tag);
@@ -319,7 +369,7 @@ class TemplateScriptingCompiler {
                                        return '<?php } ?>';
                                
                                case 'include':
-                                       return $this->compileIncludeTag($tagArgs, $metaData);
+                                       return $this->compileIncludeTag($tagArgs, $identifier, $metaData);
                                        
                                case 'foreach':
                                        $this->pushTag('foreach');
@@ -676,10 +726,11 @@ class TemplateScriptingCompiler {
         * Compiles an include tag.
         *
         * @param       string          $includeTag
+        * @param       string          $identifier
         * @param       array           $metaData
         * @return      string          phpCode
         */
-       protected function compileIncludeTag($includeTag, array &$metaData) {
+       protected function compileIncludeTag($includeTag, $identifier, array &$metaData) {
                $args = $this->parseTagArgs($includeTag, 'include');
                $append = false;
                
@@ -717,10 +768,44 @@ class TemplateScriptingCompiler {
                        unset($args['once']);
                }
                
+               $templateName = substr($file, 1, -1);
                // check for static includes
-               if ($sandbox === false && $assignVar === false && $once === false) {
-                       $content = WCF::getTPL()->fetch($file, array(), false, $metaData['packageID']);
-                       return $metaData;
+               if ($sandbox === 'false' && $assignVar === false && $once === false) {
+                       $phpCode = '';
+                       if (!in_array($templateName, $this->staticIncludes)) {
+                               $this->staticIncludes[] = $templateName;
+                       }
+                       
+                       // pass remaining tag args as variables
+                       $variables = array();
+                       if (!empty($args)) {
+                               foreach ($args as $variable => $value) {
+                                       if (substr($value, 0, 1) == "'") {
+                                               $phpCode .= "\$this->v['".$variable."'] = ".$value.";\n";
+                                       }
+                                       else {
+                                               if (preg_match('~^\$this->v\[\'(.*)\'\]$~U', $value, $matches)) {
+                                                       $phpCode .= "\$this->v['".$matches[1]."'] = ".$value.";\n";
+                                               }
+                                               else {
+                                                       throw new SystemException("Could not resolve variable type for value '".$value."'");
+                                               }
+                                       }
+                               }
+                       }
+                       if (!empty($phpCode)) $phpCode = "<?php\n".$phpCode."\n?>";
+                       
+                       $tplPackageID = WCF::getTPL()->getPackageID($templateName, $metaData['packageID']);
+                       $sourceFilename = WCF::getTPL()->getSourceFilename($templateName, $tplPackageID);
+                       $metaDataFilename = WCF::getTPL()->getMetaDataFilename($templateName, $tplPackageID);
+                       
+                       $data = $this->compileString($identifier, file_get_contents($sourceFilename), array(
+                               'data' => null,
+                               'filename' => '',
+                               'packageID' => $tplPackageID
+                       ), true);
+                       
+                       return $phpCode . $data['template'];
                }
                
                // make argument string