xe_path = rtrim(preg_replace('/([^\.^\/]+)\.php$/i','',$_SERVER['SCRIPT_NAME']),'/'); } /** * returns TemplateHandler's singleton object * @return TemplateHandler instance **/ function &getInstance() { static $oTemplate = null; if(__DEBUG__==3 ) { if(!isset($GLOBALS['__TemplateHandlerCalled__'])) $GLOBALS['__TemplateHandlerCalled__']=1; else $GLOBALS['__TemplateHandlerCalled__']++; } if(!$oTemplate) $oTemplate = new TemplateHandler(); return $oTemplate; } /** * set variables for template compile * @param string $tpl_path * @param string $tpl_filename * @param string $tpl_file * @return void **/ function init($tpl_path, $tpl_filename, $tpl_file='') { // verify arguments if(substr($tpl_path,-1)!='/') $tpl_path .= '/'; if(!file_exists($tpl_path.$tpl_filename)&&file_exists($tpl_path.$tpl_filename.'.html')) $tpl_filename .= '.html'; // create tpl_file variable if(!$tpl_file) $tpl_file = $tpl_path.$tpl_filename; // set template file infos. $this->path = $tpl_path; $this->filename = $tpl_filename; $this->file = $tpl_file; $this->web_path = $this->xe_path.'/'.ltrim(preg_replace('@^'.preg_quote(_XE_PATH_,'@').'|\./@','',$this->path),'/'); // get compiled file name $hash = md5($this->file . __ZBXE_VERSION__); $this->compiled_file = "{$this->compiled_path}{$hash}.compiled.php"; // compare various file's modified time for check changed $this->handler_mtime = filemtime(__FILE__); $skip = array(''); } /** * compiles specified tpl file and execution result in Context into resultant content * @param string $tpl_path path of the directory containing target template file * @param string $tpl_filename target template file's name * @param string $tpl_file if specified use it as template file's full path * @return string Returns compiled result in case of success, NULL otherwise */ function compile($tpl_path, $tpl_filename, $tpl_file='') { global $__templatehandler_root_tpl; $buff = ''; // store the starting time for debug information if(__DEBUG__==3 ) $start = getMicroTime(); // initiation $this->init($tpl_path, $tpl_filename, $tpl_file); // if target file does not exist exit if(!$this->file || !file_exists($this->file)) return "Err : '{$this->file}' template file does not exists."; // for backward compatibility if(is_null($__templatehandler_root_tpl)) { $__templatehandler_root_tpl = $this->file; } $source_template_mtime = filemtime($this->file); $latest_mtime = $source_template_mtime>$this->handler_mtime?$source_template_mtime:$this->handler_mtime; // cache control $oCacheHandler = &CacheHandler::getInstance('template'); // get cached buff if($oCacheHandler->isSupport()){ $cache_key = 'template:'.$this->file; $buff = $oCacheHandler->get($cache_key, $latest_mtime); } else { if(is_readable($this->compiled_file) && filemtime($this->compiled_file)>$latest_mtime && filesize($this->compiled_file)) { $buff = 'file://'.$this->compiled_file; } } if(!$buff) { $buff = $this->parse(); if($oCacheHandler->isSupport()) $oCacheHandler->put($cache_key, $buff); else FileHandler::writeFile($this->compiled_file, $buff); } $output = $this->_fetch($buff); if($__templatehandler_root_tpl == $this->file) { $__templatehandler_root_tpl = null; } // store the ending time for debug information if(__DEBUG__==3 ) $GLOBALS['__template_elapsed__'] += getMicroTime() - $start; return $output; } /** * compile specified file and immediately return * @param string $tpl_path path of the directory containing target template file * @param string $tpl_filename target template file's name * @return string Returns compiled content in case of success or NULL in case of failure **/ function compileDirect($tpl_path, $tpl_filename) { $this->init($tpl_path, $tpl_filename, null); // if target file does not exist exit if(!$this->file || !file_exists($this->file)) { Context::close(); exit("Cannot find the template file: '{$this->file}'"); } return $this->parse(); } /** * parse syntax. * @param string $buff template file * @return string compiled result in case of success or NULL in case of error **/ function parse($buff=null) { if(is_null($buff)) { if(!is_readable($this->file)) return; // read tpl file $buff = FileHandler::readFile($this->file); } // HTML tags to skip if(is_null($this->skipTags)) { $this->skipTags = array('marquee'); } // replace comments $buff = preg_replace('@@s', '', $buff); // replace value of src in img/input/script tag $buff = preg_replace_callback('/<(?:img|input|script)(?:(?!["\'\/]\s*>).)* src="(?!https?:\/\/|[\/\{])([^"]+)"/is', array($this, '_replacePath'), $buff); // replace loop and cond template syntax $buff = $this->_parseInline($buff); // include, unload/load, import $buff = preg_replace_callback('/{(@[\s\S]+?|(?=\$\w+|_{1,2}[A-Z]+|[!\(+-]|\w+(?:\(|::)|\d+|[\'"].*?[\'"]).+?)}|<(!--[#%])?(include|import|(un)?load(?(4)|(?:_js_plugin)?))(?(2)\(["\']([^"\']+)["\'])(.*?)(?(2)\)--|\/)>|(\s*)/', array($this, '_parseResource'), $buff); // remove block which is a virtual tag $buff = preg_replace('@@is','',$buff); // form auto generation $buff = preg_replace_callback('/(|[^<>]+)*?>)(.*?)(<\/form>)/is', array($this, '_compileFormAuthGeneration'), $buff); // prevent from calling directly before writing into file $buff = ''.$buff; // remove php script reopening $buff = preg_replace(array('/(\n|\r\n)+/','/(;)?( )*\?\>([\n\t ]+)?\<\?php([\n\t ]+)?/'),array("\n",";\n"),$buff); return $buff; } /** * preg_replace_callback handler * 1. remove ruleset from form tag * 2. add hidden tag with ruleset value * 3. if empty default hidden tag, generate hidden tag (ex:mid, vid, act...) * 4. generate return url, return url use in server side validator * @param array $matches * @return string **/ function _compileFormAuthGeneration($matches) { // form ruleset attribute move to hidden tag if($matches[1]) { preg_match('/ruleset="([^"]*?)"/is', $matches[1], $m); if($m[0]) { $matches[1] = preg_replace('/'.addcslashes($m[0], '?$').'/i', '', $matches[1]); if (strpos($m[1],'@') !== false){ $path = str_replace('@', '', $m[1]); $path = './files/ruleset/'.$path.'.xml'; }else if(strpos($m[1],'#') !== false){ $fileName = str_replace('#', '', $m[1]); $fileName = str_replace('', '', $fileName); $path = '#./files/ruleset/'.$fileName.'.xml'; preg_match('@(?:^|\.?/)(modules/[\w-]+)@', $this->path, $mm); $module_path = $mm[1]; list($rulsetFile) = explode('.', $fileName); $autoPath = $module_path.'/ruleset/'.$rulsetFile.'.xml'; $m[1] = $rulsetFile; }else if(preg_match('@(?:^|\.?/)(modules/[\w-]+)@', $this->path, $mm)) { $module_path = $mm[1]; $path = $module_path.'/ruleset/'.$m[1].'.xml'; } $matches[2] = ''.$matches[2]; //assign to addJsFile method for js dynamic recache $matches[1] = ''.$matches[1]; } } // if not exists default hidden tag, generate hidden tag preg_match_all('/]* name="(act|mid|vid)"/is', $matches[2], $m2); $checkVar = array('act', 'mid', 'vid'); $resultArray = array_diff($checkVar, $m2[1]); if(is_array($resultArray)) { $generatedHidden = ''; foreach($resultArray AS $key=>$value) { $generatedHidden .= ''; } $matches[2] = $generatedHidden.$matches[2]; } // return url generate if(!preg_match('/no-error-return-url="true"/i', $matches[1])) { preg_match('/]*name="error_return_url"[^>]*>/is', $matches[2], $m3); if(!$m3[0]) $matches[2] = ''.$matches[2]; } else { $matches[1] = preg_replace('/no-error-return-url="true"/i', '', $matches[1]); } $matches[0] = ''; return implode($matches); } /** * fetch using ob_* function * @param string $buff if buff is not null, eval it instead of including compiled template file * @return string **/ function _fetch($buff) { if(!$buff) return; $__Context = &$GLOBALS['__Context__']; $__Context->tpl_path = $this->path; if($_SESSION['is_logged']) { $__Context->logged_info = Context::get('logged_info'); } ob_start(); if(substr($buff, 0, 7) == 'file://') { include(substr($buff, 7)); } else { $eval_str = "?>".$buff; eval($eval_str); } return ob_get_clean(); } /** * preg_replace_callback hanlder * * replace image path * @param array $match * * @return string changed result **/ function _replacePath($match) { $src = preg_replace('@^(\./)+@', '', trim($match[1])); $src = $this->web_path.$src; $src = str_replace('/./', '/', $src); // for backward compatibility $src = preg_replace('@/((?:[\w-]+/)+)\1@', '/\1', $src); while(($tmp=preg_replace('@[^/]+/\.\./@', '', $src))!==$src) $src = $tmp; return substr($match[0],0,-strlen($match[1])-6)."src=\"{$src}\""; } /** * replace loop and cond template syntax * @param string $buff * @return string changed result **/ function _parseInline($buff) { if(preg_match_all('/<([a-zA-Z]+\d?)(?>(?!<[a-z]+\d?[\s>]).)*?(?:[ \|]cond| loop)="/s', $buff, $match) === false) return $buff; $tags = array_diff(array_unique($match[1]), $this->skipTags); if(!count($tags)) return $buff; $tags = '(?:'.implode('|',$tags).')'; $split_regex = "@(<(?>/?{$tags})(?>[^<>\{\}\"']+||{[^}]+}|\".*?\"|'.*?'|.)*?>)@s"; $nodes = preg_split($split_regex, $buff, -1, PREG_SPLIT_DELIM_CAPTURE); // list of self closing tags $self_closing = array('area'=>1,'base'=>1,'basefont'=>1,'br'=>1,'hr'=>1,'input'=>1,'img'=>1,'link'=>1,'meta'=>1,'param'=>1,'frame'=>1,'col'=>1); for($idx=1,$node_len=count($nodes); $idx < $node_len; $idx+=2) { if(!($node=$nodes[$idx])) continue; if(preg_match_all('@\s(loop|cond)="([^"]+)"@', $node, $matches)) { // this tag $tag = substr($node, 1, strpos($node, ' ')-1); // if the vale of $closing is 0, it means 'skipping' $closing = 0; // process opening tag foreach($matches[1] as $n=>$stmt) { $expr = $matches[2][$n]; $expr = $this->_replaceVar($expr); $closing++; switch($stmt) { case 'cond': $nodes[$idx-1] .= ""; break; case 'loop': if(!preg_match('@^(?:(.+?)=>(.+?)(?:,(.+?))?|(.*?;.*?;.*?)|(.+?)\s*=\s*(.+?))$@', $expr, $expr_m)) break; if($expr_m[1]) { $expr_m[1] = trim($expr_m[1]); $expr_m[2] = trim($expr_m[2]); if($expr_m[3]) $expr_m[2] .= '=>'.trim($expr_m[3]); $nodes[$idx-1] .= ""; }elseif($expr_m[4]) { $nodes[$idx-1] .= ""; }elseif($expr_m[5]) { $nodes[$idx-1] .= ""; } break; } } $node = preg_replace('@\s(loop|cond)="([^"]+)"@', '', $node); // find closing tag $close_php = ''; if($node{1} == '!' || substr($node,-2,1) == '/' || isset($self_closing[$tag])) { // self closing tag $nodes[$idx+1] = $close_php.$nodes[$idx+1]; } else { $depth = 1; for($i=$idx+2; $i < $node_len; $i+=2) { $nd = $nodes[$i]; if(strpos($nd, $tag) === 1) { $depth++; } elseif(strpos($nd, '/'.$tag) === 1) { $depth--; if(!$depth) { $nodes[$i-1] .= $nodes[$i].$close_php; $nodes[$i] = ''; break; } } } } } if(strpos($node, '|cond="') !== false) { $node = preg_replace('@(\s[-\w:]+="[^"]+?")\|cond="(.+?)"@s', '$1', $node); $node = $this->_replaceVar($node); } if($nodes[$idx] != $node) $nodes[$idx] = $node; } $buff = implode('', $nodes); return $buff; } /** * preg_replace_callback hanlder * replace php code. * @param array $m * @return string changed result **/ function _parseResource($m) { // {@ ... } or {$var} or {func(...)} if($m[1]) { if(preg_match('@^(\w+)\(@', $m[1], $mm) && !function_exists($mm[1])) return $m[0]; $echo = 'echo '; if($m[1]{0} == '@') { $echo = ''; $m[1] = substr($m[1], 1); } return '_replaceVar($m[1]).' ?>'; } if($m[3]) { $attr = array(); if($m[5]) { if(preg_match_all('@,(\w+)="([^"]+)"@', $m[6], $mm)) { foreach($mm[1] as $idx=>$name) { $attr[$name] = $mm[2][$idx]; } } $attr['target'] = $m[5]; } else { if(!preg_match_all('@ (\w+)="([^"]+)"@', $m[6], $mm)) return $m[0]; foreach($mm[1] as $idx=>$name) { $attr[$name] = $mm[2][$idx]; } } switch($m[3]) { // or case 'include': if(!$this->file || !$attr['target']) return ''; $pathinfo = pathinfo($attr['target']); $fileDir = $this->_getRelativeDir($pathinfo['dirname']); if(!$fileDir) return ''; return "compile('{$fileDir}','{$pathinfo['basename']}') ?>"; // case 'load_js_plugin': $plugin = $this->_replaceVar($m[5]); if(strpos($plugin, '$__Context') === false) $plugin = "'{$plugin}'"; return ""; // or or or case 'import': case 'load': case 'unload': $metafile = ''; $pathinfo = pathinfo($attr['target']); $doUnload = ($m[3] === 'unload'); $isRemote = !!preg_match('@^https?://@i', $attr['target']); if(!$isRemote) { if(!preg_match('@^\.?/@',$attr['target'])) $attr['target'] = './'.$attr['target']; if(substr($attr['target'], -5) == '/lang') { $pathinfo['dirname'] .= '/lang'; $pathinfo['basename'] = ''; $pathinfo['extension'] = 'xml'; } $relativeDir = $this->_getRelativeDir($pathinfo['dirname']); $attr['target'] = $relativeDir.'/'.$pathinfo['basename']; } switch($pathinfo['extension']) { case 'xml': if($isRemote || $doUnload) return ''; // language file? if($pathinfo['basename'] == 'lang.xml' || substr($pathinfo['dirname'],-5) == '/lang') { $result = "Context::loadLang('{$relativeDir}');"; } else { $result = "require_once('./classes/xml/XmlJsFilter.class.php');\$__xmlFilter=new XmlJsFilter('{$relativeDir}','{$pathinfo['basename']}');\$__xmlFilter->compile();"; } break; case 'js': if($doUnload) { $result = "Context::unloadFile('{$attr['target']}','{$attr['targetie']}');"; } else { $metafile = $attr['target']; $result = "\$__tmp=array('{$attr['target']}','{$attr['type']}','{$attr['targetie']}','{$attr['index']}');Context::loadFile(\$__tmp,'{$attr['usecdn']}','{$attr['cdnprefix']}','{$attr['cdnversion']}');unset(\$__tmp);"; } break; case 'css': if($doUnload) { $result = "Context::unloadFile('{$attr['target']}','{$attr['targetie']}','{$attr['media']}');"; } else { $metafile = $attr['target']; $result = "\$__tmp=array('{$attr['target']}','{$attr['media']}','{$attr['targetie']}','{$attr['index']}');Context::loadFile(\$__tmp,'{$attr['usecdn']}','{$attr['cdnprefix']}','{$attr['cdnversion']}');unset(\$__tmp);"; } break; } $result = ""; if(__DEBUG__) if($metafile) $result = "".$result; return $result; } } // such as , , if($m[7]) { $m[7] = substr($m[7],1); if(!$m[7]) return '_replaceVar($m[8]).'{ ?>'.$m[9]; if(!preg_match('/^(?:((?:end)?(?:if|switch|for(?:each)?|while)|end)|(else(?:if)?)|(break@)?(case|default)|(break))$/', $m[7], $mm)) return ''; if($mm[1]) { if($mm[1]{0} == 'e') return ''.$m[9]; $precheck = ''; if($mm[1] == 'switch') { $m[9] = ''; } elseif($mm[1] == 'foreach') { $var = preg_replace('/^\s*\(\s*(.+?) .*$/', '$1', $m[8]); $precheck = "if({$var}&&count({$var}))"; } return '_replaceVar($precheck.$m[7].$m[8]).'{ ?>'.$m[9]; } if($mm[2]) return "_replaceVar($m[8])."{ ?>".$m[9]; if($mm[4]) return "".$m[9]; if($mm[5]) return ""; return ''; } return $m[0]; } /** * change relative path * @param string $path * @return string **/ function _getRelativeDir($path) { $_path = $path; $fileDir = strtr(realpath($this->path),'\\','/'); if($path{0} != '/') $path = strtr(realpath($fileDir.'/'.$path),'\\','/'); // for backward compatibility if(!$path) { $dirs = explode('/', $fileDir); $paths = explode('/', $_path); $idx = array_search($paths[0], $dirs); if($idx !== false) { while($dirs[$idx] && $dirs[$idx] === $paths[0]) { array_splice($dirs, $idx, 1); array_shift($paths); } $path = strtr(realpath($fileDir.'/'.implode('/', $paths)),'\\','/'); } } $path = preg_replace('/^'.preg_quote(_XE_PATH_,'/').'/', '', $path); return $path; } /** * replace PHP variables of $ character * @param string $php * @return string $__Context->varname **/ function _replaceVar($php) { if(!strlen($php)) return ''; return preg_replace('@(?$1', $php); } } /* End of File: TemplateHandler.class.php */