/** * The compilation launcher. It executes the proper compilation steps * according to the inheritance rules etc. * * @param String $code The source code to be compiled. * @param String $filename The source template filename. * @param String $compiledFilename The output template filename. * @param Int $mode The compilation mode. */ public function compile($code, $filename, $compiledFilename, $mode) { $manager = $this->getCdfManager(); // Initialize the context. $this->_contextStack->push(new Opt_Compiler_Context($this, Opt_Compiler_Context::TEMPLATE_CTX, $filename)); try { // First, we select a parser. if (!isset($this->_parsers[$mode])) { $this->_parsers[$mode] = new $mode(); if (!$this->_parsers[$mode] instanceof Opt_Parser_Interface) { throw new Opt_InvalidParser_Exception($mode); } } $parser = $this->_parsers[$mode]; $parser->setCompiler($this); // We cannot compile two templates at the same time if (!is_null($this->_template)) { throw new Opt_CompilerLocked_Exception($filename, $this->_template); } // Detecting recursive inclusion if (is_null(self::$_recursionDetector)) { self::$_recursionDetector = array(0 => $filename); $weFree = true; } else { if (in_array($filename, self::$_recursionDetector)) { $exception = new Opt_CompilerRecursion_Exception($filename); $exception->setData(self::$_recursionDetector); throw $exception; } self::$_recursionDetector[] = $filename; $weFree = false; } // Cleaning up the processors foreach ($this->_processors as $proc) { $proc->reset(); } // Initializing the template launcher $this->set('template', $this->_template = $filename); $this->set('mode', $mode); $this->set('currentTemplate', $this->_template); array_push(self::$_templates, $filename); $this->_stack = new SplStack(); $i = 0; $extend = $filename; $memory = 0; // The inheritance loop do { // Stage 1 - code compilation if ($this->_tpl->debugConsole) { $initial = memory_get_usage(); $tree = $parser->parse($extend, $code); // Migration stage - only if backwards compatibility is on if ($this->_tpl->backwardCompatibility) { $tree = $this->_migrate($tree); } // Stage 2 - PHP tree processing $this->_stack = null; $this->_stage2($tree); $this->set('escape', NULL); unset($this->_stack); $memory += memory_get_usage() - $initial; unset($code); } else { $tree = $parser->parse($extend, $code); unset($code); // Migration stage - only if backward compatibility is on if ($this->_tpl->backwardCompatibility) { $this->_migrate($tree); } // Stage 2 - PHP tree processing $this->_stack = array(); $this->_stage2($tree); $this->set('escape', NULL); unset($this->_stack); } // if the template extends something, load it and also process if (isset($extend) && $extend != $filename) { $this->addDependantTemplate($extend); } if (!is_null($snippet = $tree->get('snippet'))) { $tree->dispose(); unset($tree); // Change the specified snippet into a root node. $tree = new Opt_Xml_Root(); $attribute = new Opt_Xml_Attribute('opt:use', $snippet); $this->processor('snippet')->processAttribute($tree, $attribute); $this->processor('snippet')->postprocessAttribute($tree, $attribute); $this->_stage2($tree, true); break; } if (!is_null($extend = $tree->get('extend'))) { $tree->dispose(); unset($tree); $this->set('currentTemplate', $extend); array_pop(self::$_templates); array_push(self::$_templates, $extend); $code = $this->_tpl->_getSource($extend); } $i++; } while (!is_null($extend)); // There are some dependant templates. We must add a suitable PHP code // to the output. if (sizeof($this->_dependencies) > 0) { $this->_addDependencies($tree); } if ($this->_tpl->debugConsole) { Opt_Support::addCompiledTemplate($this->_template, $memory); } // Stage 3 - linking the last tree if (!is_null($compiledFilename)) { $this->_output = ''; $this->_newQueue = null; $this->_dynamicBlocks = array(); $this->_stage3($output, $tree); $tree->dispose(); unset($tree); $this->_output = str_replace('?><' . '?php', '', $this->_output); // Build the directories, if needed. if (($pos = strrpos($compiledFilename, '/')) !== false) { $path = $this->_tpl->compileDir . substr($compiledFilename, 0, $pos); if (!is_dir($path)) { mkdir($path, 0750, true); } } // Save the file if (sizeof($this->_dynamicBlocks) > 0) { file_put_contents($this->_tpl->compileDir . $compiledFilename . '.dyn', serialize($this->_dynamicBlocks)); } file_put_contents($this->_tpl->compileDir . $compiledFilename, $this->_output); $this->_output = ''; $this->_dynamicBlocks = null; } else { $tree->dispose(); } array_pop(self::$_templates); $this->_inheritance = array(); if ($weFree) { // Do the cleanup. $this->_dependencies = array(); self::$_recursionDetector = NULL; foreach ($this->_processors as $processor) { $processor->reset(); } } $this->_template = NULL; $manager->clearLocals(); // Free the context. while ($this->_contextStack->count() > 0) { $ctx = $this->_contextStack->pop(); $ctx->dispose(); } // Run the new garbage collector, if it is available. /* if(version_compare(PHP_VERSION, '5.3.0', '>=')) { gc_collect_cycles(); }*/ } catch (Exception $e) { // Free the context while ($this->_contextStack->count() > 0) { $ctx = $this->_contextStack->pop(); $ctx->dispose(); } // Free the memory if (isset($tree)) { $tree->dispose(); } // Clean the compiler state in case of exception $this->_template = NULL; $this->_dependencies = array(); self::$_recursionDetector = NULL; foreach ($this->_processors as $processor) { $processor->reset(); } $manager->clearLocals(); // And throw it forward. throw $e; } }
/** * Uses the specified variable within the context. * * @param string $variable The variable name. * @param boolean $isGlobal Is the variable global or local? * @param string $contextFormat The format enforced by the occurence context. * @return array */ public function useVariable($variable, $type, $isGlobal, $contextFormat = null) { if (!isset($this->_variables[$type . $variable])) { // In this case the variable has not been used yet. We must check // if the user have not selected any format for it, calculate the // default format and modify the context format. $manager = $this->_compiler->getCdfManager(); try { // In case of template variables, we should not check the data formats // because we would run into several problems. We immediately jump to the // format resolution algorithm. if ($type == '@') { throw new Exception(); } // OK, look for a variable $this->_variables[$type . $variable] = $manager->getFormat('variable', $variable, $this); return array('format' => $this->_variables[$type . $variable], 'replacement' => null, 'cast' => null); } catch (Exception $exception) { if ($contextFormat === null) { // This is very strange. It seems that someone has used // an uninitialized variable, so we can set it to null and // report it as an unused variable. Opt_Support::warning('Uninitialized variable ' . $variable . ' - casting to NULL'); return array('format' => null, 'replacement' => 'null'); } else { $manager->addFormat('variable', $variable, $contextFormat, $this->getElementLocation('variable', $variable)); $this->_variables[$type . $variable] = $manager->getFormat('variable', $variable, $this); return array('format' => $this->_variables[$type . $variable], 'replacement' => null, 'cast' => null); } } } else { // The variable has already been used. We must match the // previously selected format. if ($contextFormat !== null) { return array('format' => $this->_variables[$type . $variable], 'replacement' => null, 'cast' => $this->_variables[$type . $variable]->getName()); } else { return array('format' => $this->_variables[$type . $variable], 'replacement' => null); } } }
/** * Parses the XML prolog and returns its attributes as an array. The parsing * algorith is the same, as in _compileAttributes(). * * @internal * @param String $prolog The prolog string. * @return Array */ protected function _compileProlog($prolog) { // Tokenize the list preg_match_all($this->_rPrologTokens, $prolog, $match, PREG_SET_ORDER); $size = sizeof($match); $result = array(); for ($i = 0; $i < $size; $i++) { if (!ctype_space($match[$i][0])) { // Traverse through a single attribute if (!preg_match($this->_rNameExpression, $match[$i][0])) { throw new Opt_XmlInvalidProlog_Exception('invalid attribute format'); } $vret = false; $name = $match[$i][0]; $value = null; for ($i++; $i < $size && ctype_space($match[$i][0]); $i++) { } if ($i >= $size || $match[$i][0] != '=') { throw new Opt_XmlInvalidProlog_Exception('invalid attribute format'); } for ($i++; ctype_space($match[$i][0]) && $i < $size; $i++) { } if ($match[$i][0] != '"' && $match[$i][0] != '\'') { throw new Opt_XmlInvalidProlog_Exception('invalid attribute format'); } $opening = $match[$i][0]; $value = ''; for ($i++; $i < $size; $i++) { if ($match[$i][0] == $opening) { break; } $value .= $match[$i][0]; } if (!isset($match[$i][0]) || $match[$i][0] != $opening) { throw new Opt_XmlInvalidProlog_Exception('invalid attribute format'); } // If we are here, the attribute is correct. No shit on the way detected. $result[$name] = $value; } } $returnedResult = $result; // Check, whether the arguments are correct. if (isset($result['version'])) { // There is no other version so far, so report a warning. For 99,9% this is a mistake. if ($result['version'] != '1.0') { $this->_tpl->debugConsole and Opt_Support::warning('OPT', 'XML prolog warning: strange XML version: ' . $result['version']); } unset($result['version']); } if (isset($result['encoding'])) { if (!preg_match($this->_rEncodingName, $result['encoding'])) { throw new Opt_XmlInvalidProlog_Exception('invalid encoding name format'); } // The encoding should match the value we mentioned in the OPT configuration and sent to the browser. $result['encoding'] = strtolower($result['encoding']); $charset = is_null($this->_tpl->charset) ? null : strtolower($this->_tpl->charset); if ($result['encoding'] != $charset && !is_null($charset)) { $this->_tpl->debugConsole and Opt_Support::warning('OPT', 'XML prolog warning: the declared encoding: "' . $result['encoding'] . '" differs from setContentType() setting: "' . $charset . '"'); } unset($result['encoding']); } else { $this->_tpl->debugConsole and Opt_Support::warning('XML prolog warning: no encoding information. Remember your content must be pure UTF-8 or UTF-16 then.'); } if (isset($result['standalone'])) { if ($result['standalone'] != 'yes' && $result['standalone'] != 'no') { throw new Opt_XmlInvalidProlog_Exception('invalid value for "standalone" attribute: "' . $result['standalone'] . '"; expected: "yes", "no".'); } unset($result['standalone']); } if (sizeof($result) > 0) { throw new Opt_XmlInvalidProlog_Exception('invalid attributes in prolog.'); } return $returnedResult; }
/** * Starts the section by putting its record on a section stack. * * @param Array $section The section record. */ protected function _sectionStart(array &$section) { self::_addSection($section); if (!is_null($section['node']->get('call:use'))) { $this->_compiler->setConversion('##simplevar_' . $section['node']->get('call:use'), $section['name']); } // Populate the debug console. if ($this->_tpl->debugConsole) { if (isset($section['datasource'])) { $parent = '<em>Datasource</em>'; } elseif (!is_null($section['parent'])) { $parent = $section['parent']; } else { $parent = '-'; } Opt_Support::addSection($section['name'], $parent, (string) $section['format'], $section['node']->getXmlName()); } }
/** * Executes, and optionally compiles the template represented by the view. * Returns true, if the template was found and successfully executed. * * @param Opt_Output_Interface $output The output interface. * @param Boolean $exception Should the exceptions be thrown if the template does not exist? * @return Boolean */ public function _parse(Opt_Output_Interface $output, $exception = true) { if ($this->_tpl->debugConsole) { $time = microtime(true); } $cached = false; if ($this->_cache !== null) { $result = $this->_cache->templateCacheStart($this); if ($result !== false) { // For dynamic cache... if (is_string($result)) { include $result; } return true; } $cached = true; } $this->_tf = $this->_tpl->getTranslationInterface(); if ($this->_tpl->compileMode != Opt_Class::CM_PERFORMANCE) { list($compileName, $compileTime) = $this->_preprocess($exception); if ($compileName === null) { return false; } } else { $compileName = $this->_convert($this->_template); $compileTime = null; if (!$exception && !file_exists($compileName)) { return false; } } $old = error_reporting($this->_tpl->errorReporting); require $this->_tpl->compileDir . $compileName; error_reporting($old); // The counter stops, if the time counting has been enabled for the debug console purposes if ($this->_cache !== null) { $this->_cache->templateCacheStop($this); } if (isset($time)) { Opt_Support::addView($this->_template, $output->getName(), $this->_processingTime = microtime(true) - $time, $cached); } return true; }