setCacheDir($cache_dir); $this->self_name = $self_name; $this->addValue('include', [$this, 'includeFile']); $this->addBlock('Block', [$this, 'blockSection']); } public function includeFile($attr) { if (!isset($attr['src'])) {return;} $src = path::clean($attr['src']); $tpl_file = $this->getFilePath($src); if (!$tpl_file) {return;} if (in_array($tpl_file, $this->compile_stack)) {return;} return 'self_name . "->getData('" . str_replace("'", "\'", $src) . "'); " . '} catch (Exception $e) {} ?>' . "\n"; } public function blockSection($attr, $content) { return $content; } public function setPath() { $path = []; foreach (func_get_args() as $v) { if (is_array($v)) { $path = array_merge($path, array_values($v)); } else { $path[] = $v; } } foreach ($path as $k => $v) { if (($v = path::real($v)) === false) { unset($path[$k]); } } $this->tpl_path = array_unique($path); } public function getPath() { return $this->tpl_path; } public function setCacheDir($dir) { if (!is_dir($dir)) { throw new Exception($dir . ' is not a valid directory.'); } if (!is_writable($dir)) { throw new Exception($dir . ' is not writable.'); } $this->cache_dir = path::real($dir) . '/'; } public function addBlock($name, $callback) { if (!is_callable($callback)) { throw new Exception('No valid callback for ' . $name); } $this->blocks[$name] = $callback; } public function addValue($name, $callback) { if (!is_callable($callback)) { throw new Exception('No valid callback for ' . $name); } $this->values[$name] = $callback; } public function blockExists($name) { return isset($this->blocks[$name]); } public function valueExists($name) { return isset($this->values[$name]); } public function tagExists($name) { return $this->blockExists($name) || $this->valueExists($name); } public function getValueCallback($name) { if ($this->valueExists($name)) { return $this->values[$name]; } return false; } public function getBlockCallback($name) { if ($this->blockExists($name)) { return $this->blocks[$name]; } return false; } public function getBlocksList() { return array_keys($this->blocks); } public function getValuesList() { return array_keys($this->values); } public function getFile($file) { $tpl_file = $this->getFilePath($file); if (!$tpl_file) { throw new Exception('No template found for ' . $file); return false; } $file_md5 = md5($tpl_file); $dest_file = sprintf('%s/%s/%s/%s/%s.php', $this->cache_dir, 'cbtpl', substr($file_md5, 0, 2), substr($file_md5, 2, 2), $file_md5 ); clearstatcache(); $stat_f = $stat_d = false; if (file_exists($dest_file)) { $stat_f = stat($tpl_file); $stat_d = stat($dest_file); } # We create template if: # - dest_file doest not exists # - we don't want cache # - dest_file size == 0 # - tpl_file is more recent thant dest_file if (!$stat_d || !$this->use_cache || $stat_d['size'] == 0 || $stat_f['mtime'] > $stat_d['mtime']) { files::makeDir(dirname($dest_file), true); if (($fp = @fopen($dest_file, 'wb')) === false) { throw new Exception('Unable to create cache file'); } $fc = $this->compileFile($tpl_file); fwrite($fp, $fc); fclose($fp); files::inheritChmod($dest_file); } return $dest_file; } public function getFilePath($file) { foreach ($this->tpl_path as $p) { if (file_exists($p . '/' . $file)) { return $p . '/' . $file; } } return false; } public function getParentFilePath($previous_path, $file) { $check_file = false; foreach ($this->tpl_path as $p) { if ($check_file && file_exists($p . '/' . $file)) { return $p . "/" . $file; } if ($p == $previous_path) { $check_file = true; } } return false; } public function getData($________) { self::$_k = array_keys($GLOBALS); foreach (self::$_k as self::$_n) { if (!in_array(self::$_n, self::$superglobals)) { global ${self::$_n}; } } $dest_file = $this->getFile($________); ob_start(); if (ini_get('display_errors') == true) { include $dest_file; } else { @include $dest_file; } self::$_r = ob_get_contents(); ob_end_clean(); return self::$_r; } protected function getCompiledTree($file, &$err) { $fc = file_get_contents($file); $this->compile_stack[] = $file; # Remove every PHP tags if ($this->remove_php) { $fc = preg_replace('/<\?(?=php|=|\s).*?\?>/ms', '', $fc); } # Transform what could be considered as PHP short tags $fc = preg_replace('/(<\?(?!php|=|\s))(.*?)(\?>)/ms', '$2', $fc); # Remove template comments $fc = preg_replace('/(^\s*)?/ms', '', $fc); # Lexer part : split file into small pieces # each array entry will be either a tag or plain text $blocks = preg_split( '#(]*>)|()|({{tpl:\w+[^}]*}})#msu', $fc, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); # Next : build semantic tree from tokens. $rootNode = new tplNode(); $node = $rootNode; $errors = []; $this->parent_file = ''; foreach ($blocks as $id => $block) { $isblock = preg_match('#|>)||{{tpl:(\w+)(\s(.*?))?}}#ms', $block, $match); if ($isblock == 1) { if (substr($match[0], 1, 1) == '/') { // Closing tag, check if it matches current opened node $tag = $match[3]; if (($node instanceof tplNodeBlock) && $node->getTag() == $tag) { $node->setClosing(); $node = $node->getParent(); } else { // Closing tag does not match opening tag // Search if it closes a parent tag $search = $node; while ($search->getTag() != 'ROOT' && $search->getTag() != $tag) { $search = $search->getParent(); } if ($search->getTag() == $tag) { $errors[] = sprintf( __('Did not find closing tag for block . Content has been ignored.'), html::escapeHTML($node->getTag())); $search->setClosing(); $node = $search->getParent(); } else { $errors[] = sprintf( __('Unexpected closing tag found.'), $tag); } } } elseif (substr($match[0], 0, 1) == '{') { // Value tag $tag = $match[4]; $str_attr = ''; $attr = []; if (isset($match[6])) { $str_attr = $match[6]; $attr = $this->getAttrs($match[6]); } if (strtolower($tag) == "extends") { if (isset($attr['parent']) && $this->parent_file == "") { $this->parent_file = $attr['parent']; } } elseif (strtolower($tag) == "parent") { $node->addChild(new tplNodeValueParent($tag, $attr, $str_attr)); } else { $node->addChild(new tplNodeValue($tag, $attr, $str_attr)); } } else { // Opening tag, create new node and dive into it $tag = $match[1]; if ($tag == "Block") { $newnode = new tplNodeBlockDefinition($tag, isset($match[2]) ? $this->getAttrs($match[2]) : []); } else { $newnode = new tplNodeBlock($tag, isset($match[2]) ? $this->getAttrs($match[2]) : []); } $node->addChild($newnode); $node = $newnode; } } else { // Simple text $node->addChild(new tplNodeText($block)); } } if (($node instanceof tplNodeBlock) && !$node->isClosed()) { $errors[] = sprintf( __('Did not find closing tag for block . Content has been ignored.'), html::escapeHTML($node->getTag())); } $err = ""; if (count($errors) > 0) { $err = "\n\n\n"; } return $rootNode; } protected function compileFile($file) { $tree = null; while (true) { if ($file && !in_array($file, $this->parent_stack)) { $tree = $this->getCompiledTree($file, $err); if ($this->parent_file == "__parent__") { $this->parent_stack[] = $file; $newfile = $this->getParentFilePath(dirname($file), basename($file)); if (!$newfile) { throw new Exception('No template found for ' . basename($file)); return false; } $file = $newfile; } elseif ($this->parent_file != "") { $this->parent_stack[] = $file; $file = $this->getFilePath($this->parent_file); if (!$file) { throw new Exception('No template found for ' . $this->parent_file); return false; } } else { return $tree->compile($this) . $err; } } else { if ($tree != null) { return $tree->compile($this) . $err; } else { return ''; } } } } public function compileBlockNode($tag, $attr, $content) { $res = ''; if (isset($this->blocks[$tag])) { $res .= call_user_func($this->blocks[$tag], $attr, $content); } elseif ($this->unknown_block_handler != null) { $res .= call_user_func($this->unknown_block_handler, $tag, $attr, $content); } return $res; } public function compileValueNode($tag, $attr, $str_attr) { $res = ''; if (isset($this->values[$tag])) { $res .= call_user_func($this->values[$tag], $attr, ltrim($str_attr)); } elseif ($this->unknown_value_handler != null) { $res .= call_user_func($this->unknown_value_handler, $tag, $attr, $str_attr); } return $res; } protected function compileValue($match) { $v = $match[1]; $attr = isset($match[2]) ? $this->getAttrs($match[2]) : []; $str_attr = isset($match[2]) ? $match[2] : null; return call_user_func($this->values[$v], $attr, ltrim($str_attr)); } public function setUnknownValueHandler($callback) { if (is_callable($callback)) { $this->unknown_value_handler = $callback; } } public function setUnknownBlockHandler($callback) { if (is_callable($callback)) { $this->unknown_block_handler = $callback; } } protected function getAttrs($str) { $res = []; if (preg_match_all('|([a-zA-Z0-9_:-]+)="([^"]*)"|ms', $str, $m) > 0) { foreach ($m[1] as $i => $v) { $res[$v] = $m[2][$i]; } } return $res; } }