// define('PHPTAL_VERSION', '1_1_14'); //{{{PHPTAL_DIR if (!defined('PHPTAL_DIR')) define('PHPTAL_DIR',dirname(__FILE__).DIRECTORY_SEPARATOR); else assert('substr(PHPTAL_DIR,-1) == DIRECTORY_SEPARATOR'); //}}} /* Please don't use the following constants. They have been replaced by methods in the PHPTAL class and are kept for backwards compatibility only. */ //{{{ if (!defined('PHPTAL_PHP_CODE_DESTINATION')) { if (function_exists('sys_get_temp_dir')) define('PHPTAL_PHP_CODE_DESTINATION',rtrim(sys_get_temp_dir(), DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR); else if (substr(PHP_OS,0,3) == 'WIN') { if (file_exists('c:\\WINNT\\Temp\\')) define('PHPTAL_PHP_CODE_DESTINATION', 'c:\\WINNT\\Temp\\'); else define('PHPTAL_PHP_CODE_DESTINATION', 'c:\\WINDOWS\\Temp\\'); } else define('PHPTAL_PHP_CODE_DESTINATION', '/tmp/'); } if (!defined('PHPTAL_DEFAULT_ENCODING')) define('PHPTAL_DEFAULT_ENCODING', 'UTF-8'); if (!defined('PHPTAL_PHP_CODE_EXTENSION')) define('PHPTAL_PHP_CODE_EXTENSION', 'php'); //}}} define('PHPTAL_XHTML', 1); define('PHPTAL_XML', 2); require_once PHPTAL_DIR.'PHPTAL/FileSource.php'; require_once PHPTAL_DIR.'PHPTAL/RepeatController.php'; require_once PHPTAL_DIR.'PHPTAL/Context.php'; require_once PHPTAL_DIR.'PHPTAL/Exception.php'; require_once PHPTAL_DIR.'PHPTAL/TalesRegistry.php'; /** * PHPTAL template entry point. * * * title = 'Welcome here'; * $tpl->result = range(1, 100); * ... * echo $tpl->execute(); * } * catch (Exception $e) { * echo $e; * } * ?> * * * @author Laurent Bedubourg */ class PHPTAL { const XHTML = 1; const XML = 2; /** * PHPTAL Constructor. * * @param string $path Template file path. */ public function __construct($path=false) { $this->_path = $path; $this->_repositories = array(); if (defined('PHPTAL_TEMPLATE_REPOSITORY')){ $this->_repositories[] = PHPTAL_TEMPLATE_REPOSITORY; } $this->_resolvers = array(); $this->_globalContext = new StdClass(); $this->_context = new PHPTAL_Context(); $this->_context->setGlobal($this->_globalContext); } /** * create * returns a new PHPTAL object * * @param string $path Template file path. * @return PHPTAL */ public static function create($path=false) { return new PHPTAL($path); } /** * Clone template state and context. */ public function __clone() { $context = $this->_context; $this->_context = clone $this->_context; $this->_context->setParent($context); $this->_context->setGlobal($this->_globalContext); } /** * Set template from file path. * @param $path string */ public function setTemplate($path) { $this->_prepared = false; $this->_functionName = null; $this->_path = $path; $this->_source = null; return $this; } /** * Set template from source. * * Should be used only with temporary template sources. Use setTemplate() whenever possible. * * @param $src string The phptal template source. * @param path string Fake and 'unique' template path. */ public function setSource($src, $path=false) { if ($path == false) $path = ' '.md5($src); require_once PHPTAL_DIR.'PHPTAL/StringSource.php'; $this->_source = new PHPTAL_StringSource($src, $path); $this->_path = $path; return $this; } /** * Specify where to look for templates. * * @param $rep mixed string or Array of repositories */ public function setTemplateRepository($rep) { if (is_array($rep)){ $this->_repositories = $rep; } else { $this->_repositories[] = $rep; } return $this; } /** * Get template repositories. */ public function getTemplateRepositories() { return $this->_repositories; } /** * Clears the template repositories. */ public function clearTemplateRepositories() { $this->_repositories = array(); return $this; } /** * Ignore XML/XHTML comments on parsing. * @param $bool bool */ public function stripComments($bool) { $this->_stripComments = $bool; return $this; } /** * Set output mode * XHTML output mode will force elements like , and , etc. to be empty * and threats attributes like selected, checked to be boolean attributes. * * XML output mode outputs XML without such modifications and is neccessary to generate RSS feeds properly. * @param $mode int (PHPTAL::XML or PHPTAL::XHTML). */ public function setOutputMode($mode=PHPTAL_XHTML) { if ($mode != PHPTAL::XHTML && $mode != PHPTAL::XML){ throw new PHPTAL_Exception('Unsupported output mode '.$mode); } $this->_outputMode = $mode; return $this; } /** * Get output mode */ public function getOutputMode() { return $this->_outputMode; } /** * Set input and ouput encoding. * @param $enc string example: 'UTF-8' */ public function setEncoding($enc) { $this->_encoding = $enc; if ($this->_translator) $this->_translator->setEncoding($enc); return $this; } /** * Get input and ouput encoding. * @param $enc string example: 'UTF-8' */ public function getEncoding() { return $this->_encoding; } /** * Set the storage location for intermediate PHP files. The path cannot contain characters that would be interpreted by glob() (e.g. * or ?) * @param string $path Intermediate file path. */ public function setPhpCodeDestination($path) { $this->_phpCodeDestination = rtrim($path, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR; return $this; } /** * Get the storage location for intermediate PHP files. */ public function getPhpCodeDestination() { return $this->_phpCodeDestination; } /** * Set the file extension for intermediate PHP files. * @param string $extension The file extension. */ public function setPhpCodeExtension($extension) { $this->_phpCodeExtension = $extension; return $this; } /** * Get the file extension for intermediate PHP files. */ public function getPhpCodeExtension() { return $this->_phpCodeExtension; } /** * Flags whether to ignore intermediate php files and to * reparse templates every time (if set to true). * Don't use in production - this makes PHPTAL significantly slower. * @param bool bool Forced reparse state. */ public function setForceReparse($bool) { $this->_forceReparse = (bool) $bool; return $this; } /** * Get the value of the force reparse state. */ public function getForceReparse() { return $this->_forceReparse !== NULL ? $this->_forceReparse : (defined('PHPTAL_FORCE_REPARSE') && PHPTAL_FORCE_REPARSE); } /** * Set I18N translator. * This sets encoding used by the translator, so be sure to use encoding-dependent features of the translator (e.g. addDomain) _after_ calling setTranslator. */ public function setTranslator(PHPTAL_TranslationService $t) { $this->_translator = $t; $this->_translator->setEncoding($this->getEncoding()); return $this; } /** * Set template pre filter. It will be called once before template is compiled. */ public function setPreFilter(PHPTAL_Filter $filter) { $this->_prefilter = $filter; return $this; } /** * Set template post filter. It will be called every time after template generates output. */ public function setPostFilter(PHPTAL_Filter $filter) { $this->_postfilter = $filter; return $this; } /** * Register a trigger for specified phptal:id. * @param $id string phptal:id to look for */ public function addTrigger($id, PHPTAL_Trigger $trigger) { $this->_triggers[$id] = $trigger; return $this; } /** * Returns trigger for specified phptal:id. * @param $id string phptal:id */ public function getTrigger($id) { if (array_key_exists($id, $this->_triggers)){ return $this->_triggers[$id]; } return null; } /** * Set a context variable. * @param $varname string * @param $value mixed */ public function __set($varname, $value) { $this->_context->__set($varname, $value); } /** * Set a context variable. * @param $varname string * @param $value mixed */ public function set($varname, $value) { $this->_context->__set($varname, $value); return $this; } /** * Execute the template code. * * @return string */ public function execute() { if (!$this->_prepared) { $this->prepare(); } // includes generated template PHP code $this->_context->__file = $this->__file; require_once $this->getCodePath(); $templateFunction = $this->getFunctionName(); try { ob_start(); $templateFunction($this, $this->_context); $res = ob_get_clean(); } catch (Exception $e){ ob_end_clean(); throw $e; } // unshift doctype $docType = $this->_context->__docType; if ($docType){ $res = $docType . "\n" . $res; } // unshift xml declaration $xmlDec = $this->_context->__xmlDeclaration; if ($xmlDec){ $res = $xmlDec . "\n" . $res; } if ($this->_postfilter != null){ return $this->_postfilter->filter($res); } return $res; } protected function setConfigurationFrom(PHPTAL $from) { // use references - this way config of both objects will be more-or-less in sync $this->_encoding = &$from->_encoding; $this->_outputMode = &$from->_outputMode; $this->_stripComments = &$from->_stripComments; $this->_forceReparse = &$from->_forceReparse; $this->_phpCodeDestination = &$from->_phpCodeDestination; $this->_phpCodeExtension = &$from->_phpCodeExtension; $this->_cacheLifetime = &$from->_cacheLifetime; $this->_cachePurgeFrequency = &$from->_cachePurgeFrequency; $this->setTemplateRepository($from->_repositories); array_unshift($this->_repositories, dirname($from->_source->getRealPath())); $this->_resolvers = &$from->_resolvers; $this->_prefilter = &$from->_prefilter; $this->_postfilter = &$from->_postfilter; } private $externalMacroTempaltesCache = array(); /** * Execute a template macro. * Should be used only from within generated template code! * * @param $path string Template macro path */ public function executeMacro($path) { // extract macro source file from macro name, if not source file // found in $path, then the macro is assumed to be local if (preg_match('/^(.*?)\/([a-z0-9_]*)$/i', $path, $m)){ list(,$file,$macroName) = $m; if (isset($this->externalMacroTempaltesCache[$file])) { $tpl = $this->externalMacroTempaltesCache[$file]; } else { $tpl = new PHPTAL($file); $tpl->setConfigurationFrom($this); $tpl->prepare(); // require PHP generated code require_once $tpl->getCodePath(); $this->externalMacroTempaltesCache[$file] = $tpl; if (count($this->externalMacroTempaltesCache) > 10) $this->externalMacroTempaltesCache = array(); // keep it small (typically only 1 or 2 external files are used) } // save current file $currentFile = $this->_context->__file; $this->_context->__file = $tpl->__file; $fun = $tpl->getFunctionName() . '_' . $macroName; if (!function_exists($fun)) throw new PHPTAL_Exception("Macro '$macroName' is not defined in $file",$this->_source->getRealPath()); $fun($this, $this->_context); // restore current file $this->_context->__file = $currentFile; } else { // call local macro $fun = $this->getFunctionName() . '_' . trim($path); if (!function_exists($fun)) throw new PHPTAL_Exception("Macro '$path' is not defined",$this->_source->getRealPath()); $fun( $this, $this->_context ); } } private function setCodeFile() { $this->_codeFile = $this->getPhpCodeDestination() . $this->getFunctionName() . '.' . $this->getPhpCodeExtension(); } /** * Prepare template without executing it. */ public function prepare() { // clear just in case settings changed and cache is out of date $this->externalMacroTempaltesCache = array(); // find the template source file $this->findTemplate(); $this->__file = $this->_source->getRealPath(); $this->setCodeFile(); // parse template if php generated code does not exists or template // source file modified since last generation of PHPTAL_FORCE_REPARSE // is defined. if ($this->getForceReparse() || !file_exists($this->_codeFile)) { if ($this->getCachePurgeFrequency() && mt_rand()%$this->getCachePurgeFrequency() == 0) { $this->cleanUpGarbage(); } $this->parse(); } $this->_prepared = true; return $this; } public function getCacheLifetime() { return $this->_cacheLifetime; } /** * how long compiled templates and phptal:cache files are kept, in days */ public function setCacheLifetime($days) { $this->_cacheLifetime = max(0.5,$days); return $this; } /** * PHPTAL will scan cache and remove old files on every nth compile * Set to 0 to disable cleanups */ public function setCachePurgeFrequency($n) { $this->_cachePurgeFrequency = (int)$n; return $this; } public function getCachePurgeFrequency() { return $this->_cachePurgeFrequency; } /** * Removes all compiled templates from cache after PHPTAL_CACHE_LIFETIME days */ public function cleanUpGarbage() { $phptalCacheFilesExpire = time() - $this->getCacheLifetime() * 3600 * 24; $upperLimit = $this->getPhpCodeDestination() . 'tpl_' . $phptalCacheFilesExpire . '_'; $lowerLimit = $this->getPhpCodeDestination() . 'tpl_0_'; $phptalCacheFiles = glob($this->getPhpCodeDestination() . 'tpl_*.' . $this->getPhpCodeExtension() . '*'); if ($phptalCacheFiles) { foreach($phptalCacheFiles as $index => $file) { if ($file > $upperLimit && substr($file,0,strlen($lowerLimit)) !== $lowerLimit) { unset($phptalCacheFiles[$index]); } } foreach($phptalCacheFiles as $file) { $time = filemtime($file); if ($time && $time < $phptalCacheFilesExpire) @unlink($file); } } } /** * Removes single compiled template from cache and all its fragments cached by phptal:cache. * Must be called after setSource/setTemplate. */ public function cleanUpCache() { if (!$this->getCodePath()) { $this->findTemplate(); $this->setCodeFile(); if (!$this->getCodePath()) throw new PHPTAL_Exception("No codefile"); } $filename = $this->getCodePath(); $phptalCacheFiles = glob($filename . '*'); if ($phptalCacheFiles) foreach($phptalCacheFiles as $file) { if (substr($file, 0, strlen($filename)) !== $filename) continue; // safety net @unlink($file); } $this->_prepared = false; } /** * Returns the path of the intermediate PHP code file. * * The returned file may be used to cleanup (unlink) temporary files * generated by temporary templates or more simply for debug. * * @return string */ public function getCodePath() { return $this->_codeFile; } /** * Returns the generated template function name. * @return string */ public function getFunctionName() { if (!$this->_functionName) { $this->_functionName = 'tpl_' . $this->_source->getLastModifiedTime() . '_' . PHPTAL_VERSION . substr(preg_replace('/[^a-zA-Z]/','',basename($this->_source->getRealPath())),0,10) . md5($this->_source->getRealPath()); } return $this->_functionName; } /** * Returns template translator. * @return PHPTAL_TranslationService */ public function getTranslator() { return $this->_translator; } /** * Returns array of exceptions caught by tal:on-error attribute. * @return array */ public function getErrors() { return $this->_errors; } /** * Public for phptal templates, private for user. * @access private */ public function addError(Exception $error) { array_push($this->_errors, $error); return $this; } /** * Returns current context object. * Use only in Triggers. * * @return PHPTAL_Context */ public function getContext() { return $this->_context; } /** * only for use in generated template code * @access private */ public function getGlobalContext() { return $this->_globalContext; } /** * only for use in generated template code * @access private */ public function pushContext() { $this->_context = $this->_context->pushContext(); return $this->_context; } /** * only for use in generated template code * @access private */ public function popContext() { $this->_context = $this->_context->popContext(); return $this->_context; } protected function parse() { require_once PHPTAL_DIR.'PHPTAL/Dom/Parser.php'; // instantiate the PHPTAL source parser $parser = new PHPTAL_Dom_Parser($this->_encoding); $parser->stripComments($this->_stripComments); $data = $this->_source->getData(); $realpath = $this->_source->getRealPath(); if ($this->_prefilter) $data = $this->_prefilter->filter($data); $tree = $parser->parseString($data, $realpath); require_once PHPTAL_DIR.'PHPTAL/Php/CodeGenerator.php'; $generator = new PHPTAL_Php_CodeGenerator($this->getFunctionName(), $this->_source->getRealPath()); $generator->setEncoding($this->_encoding); $generator->setOutputMode($this->_outputMode); $generator->generate($tree); if (!@file_put_contents($this->_codeFile, $generator->getResult())) { throw new PHPTAL_Exception('Unable to open '.$this->_codeFile.' for writing'); } return $this; } /** * Search template source location. */ protected function findTemplate() { if ($this->_path == false){ throw new PHPTAL_Exception('No template file specified'); } // template source already defined if ($this->_source != null){ return; } array_push($this->_resolvers, new PHPTAL_FileSourceResolver($this->_repositories)); foreach ($this->_resolvers as $resolver){ $source = $resolver->resolve($this->_path); if ($source != null){ $this->_source = $source; break; } } array_pop($this->_resolvers); if ($this->_source == null){ throw new PHPTAL_Exception('Unable to locate template file '.$this->_path); } } protected $_prefilter = null; protected $_postfilter = null; // list of template source repositories protected $_repositories = array(); // template path protected $_path = null; // template source resolvers protected $_resolvers = array(); // template source (only set when not working with file) protected $_source = null; // destination of PHP intermediate file protected $_codeFile = null; // php function generated for the template protected $_functionName = null; // set to true when template is ready for execution protected $_prepared = false; // associative array of phptal:id => PHPTAL_Trigger protected $_triggers = array(); // i18n translator protected $_translator = null; // global execution context protected $_globalContext = null; // current execution context protected $_context = null; // current template file (changes within macros) public $__file = false; // list of on-error caught exceptions protected $_errors = array(); protected $_encoding = PHPTAL_DEFAULT_ENCODING; protected $_outputMode = PHPTAL::XHTML; protected $_stripComments = false; // configuration properties protected $_forceReparse = NULL; protected $_phpCodeDestination = PHPTAL_PHP_CODE_DESTINATION; protected $_phpCodeExtension = PHPTAL_PHP_CODE_EXTENSION; protected $_cacheLifetime = 30; protected $_cachePurgeFrequency = 50; } ?>