Index: releases/5.0.0/core/kernel/parser/template_parser.php =================================================================== diff -u -N --- releases/5.0.0/core/kernel/parser/template_parser.php (revision 0) +++ releases/5.0.0/core/kernel/parser/template_parser.php (revision 12099) @@ -0,0 +1,766 @@ +Ses =& $this->Application->recallObject('Session'); + } + + function AddParam($pattern, $value, $dont_sort=0) + { + $this->ForSort[] = Array($pattern, $value); + if (!$dont_sort) //used when mass-adding params, to escape sorting after every new param + $this->SortParams(); //but do sort by default! + } + + //We need to sort params by its name length desc, so that params starting with same word get parsed correctly + function SortParams() + { + uasort($this->ForSort, array ("TemplateParser", "CmpParams")); + +// commented out by Kostja, otherwise when rednerElement is done into var (result_to_var) the other params are reset +// $this->Pattern = Array(); +// $this->Values = Array(); + foreach($this->ForSort as $pair) + { + $this->Pattern[] = $pair[0]; + $this->Values[] = $pair[1]; + } + } + + function CmpParams($a, $b) + { + $a_len = strlen($a[0]); + $b_len = strlen($b[0]); + if ($a_len == $b_len) return 0; + return $a_len > $b_len ? -1 : 1; + } + + function SetParams($params, $for_parsing=true) + { + if (!is_array($params)) $params = Array(); + + $this->ForSort = array(); + $this->Params = $params; + $this->ParamsStack[$this->ParamsRecursionIndex] = $params; + + if (!$for_parsing) return ; + + foreach ($params as $key => $val) { + $this->AddParam('/[{]{0,1}\$'.$key.'[}]{0,1}/i', $val, 1); //Do not sort every time + } + $this->SortParams(); //Sort once after adding is done + } + + /** + * Returns parser parameter value at specified deep level + * + * @param string $name + * @param int $deep_level if greather then 0 then use from ParamsStack + * @return mixed + */ + function GetParam($name, $deep_level = 0) + { + if ($deep_level > 0) { + return isset($this->ParamsStack[$deep_level][$name]) ? $this->ParamsStack[$deep_level][$name] : false; + } + + return isset($this->Params[$name]) ? $this->Params[$name] : false; + } + + /** + * Set's template parser parameter, that could be retrieved from template + * + * @param string $name + * @param mixed $value + */ + function SetParam($name, $value) + { + $this->Params[strtolower($name)] = $value; + $this->AddParam('/[{]{0,1}\$'.$name.'[}]{0,1}/i', $value, $this->FromPreParseCache); + $this->ParamsStack[$this->ParamsRecursionIndex][$name] = $value; + } + + function SetBuffer($body) + { + $this->Buffers[$this->RecursionIndex] = $body; + } + + function GetBuffer() + { + return $this->Buffers[$this->RecursionIndex]; + } + + function GetCode() + { + return $this->Code[$this->RecursionIndex]; + } + + function AppendBuffer($append) + { + $this->Buffers[$this->RecursionIndex] .= $append; + $this->AppendCode( $this->ConvertToCode($append) ); + } + + function AppendOutput($append, $append_code=false) + { + if ($this->SkipMode == parse) { + $this->Output .= $append; //append to Ouput only if we are parsing + if ($append_code) $this->AppendCompiledHTML($append); + } + elseif ($this->SkipMode == skip) { + if ($append_code) $this->AppendCompiledHTML($append); + } + elseif ($this->SkipMode == skip_tags) { + $this->AppendBuffer($append); //append to buffer if we are skipping tags + } + } + + function ConvertToCode($data) + { + $data = str_replace("\\", "\\\\", $data); // escape any "\" + $data = str_replace("'", "\'", $data); // escape "'" + + $code = '$o .= \''. $data .'\';'; + $code = explode("\n", $code); + return $code; + } + + function AppendCode($code, $level_offset=0) + { + if ($this->RecursionIndex+$level_offset <= 0 ) $level_offset = 0; + if (defined('EXPERIMENTAL_PRE_PARSE')) { + if (!isset($this->Code[$this->RecursionIndex+$level_offset])) { + $this->Code[$this->RecursionIndex+$level_offset] = Array(); + } + if (is_array($code)) { + foreach ($code as $line) { + $this->Code[$this->RecursionIndex+$level_offset][] = rtrim($line, "\n")."\n"; + } + } + else { + $this->Code[$this->RecursionIndex+$level_offset][] .= rtrim($code, "\n")."\n"; + } + } + } + + function PrepareCompiledFunction($f_name, $f_body, $add_reference=true) + { + $real_name = 'f_'.abs(crc32($this->TemplateName)).'_'.$f_name; + $real_code = ''; + if (defined('EXPERIMENTAL_PRE_PARSE')) { + // if such function already compiled + if ( isset($this->Application->CompiledFunctions[$f_name]) || + function_exists($real_name) + ) + { + if (!isset($this->Application->CompiledFunctions[$f_name])) { + $real_name = $real_name.'_'; + } + else { + $real_name = $this->Application->CompiledFunctions[$f_name].'_'; + } + } + + if (is_array($f_body)) { + $real_body = ''; + foreach ($f_body as $line) { + if (preg_match('/^\/\*LAMBDA-ONLY\*\//', $line)) continue; + $real_body .= "\t\t".rtrim($line, "\n")."\n"; + }; + $f_body = $real_body; + } + else { + $f_body = preg_replace('/\/\*LAMBDA-ONLY\*\/.*/m', '', $f_body); + } + + $ref = "\t".'$application->PreParsedBlocks[\''.$f_name.'\'] = \''.$real_name.'\';'."\n"; + if ($add_reference) $real_code .= $ref; + $real_code .= 'if (!function_exists(\''.$real_name.'\')) {'."\n"; + $real_code .= "\t".'function '.$real_name.'($params)'."\n\t{\n"; + $real_code .= $f_body; + $real_code .= "\t}\n\n"; + $real_code .= '}'."\n"; + + /*if (defined('DEBUG_MODE') && DEBUG_MODE && defined('DBG_PRE_PARSE') && DBG_PRE_PARSE) { + // this shows newly compiled functions (blocks) + global $debugger; + $f_body = "\t".'function '.$real_name.'($params)'."\n\t{\n".$f_body."\t}\n\n"; + $debugger->appendHTML($debugger->highlightString($f_body)); + }*/ + $this->Application->CompiledFunctions[$f_name] = $real_name; + + return array($real_name, $real_code, $ref); + } + } + + function AppendCompiledFunction($f_name, $f_body) + { + $f = $this->PrepareCompiledFunction($f_name, $f_body); + $this->CompiledBuffer .= $f[1]; + } + + function AppendCompiledCode($code) + { + if (defined('EXPERIMENTAL_PRE_PARSE')) { + if (is_array($code)) { + foreach ($code as $line) { + if (preg_match('/^\/\*LAMBDA-ONLY\*\//', $line)) continue; + $this->CompiledBuffer .= "\t".rtrim($line, "\n")."\n"; + } + } + else { + $this->CompiledBuffer .= $code; + } + $this->CompiledBuffer .= "\t".'echo $o;'."\n\t".'$o = \'\';'."\n"; + } + } + + function AppendCompiledHTML($append) + { + if (defined('EXPERIMENTAL_PRE_PARSE')) { + $this->CompiledBuffer .= '?'.'>'."\n"; + $this->CompiledBuffer .= rtrim($append, "\t"); + $this->CompiledBuffer .= '<'.'?php'."\n"; + } + } + + function ResetCode() + { + $this->Code[$this->RecursionIndex] = Array(); + } + + function FindTag2() + { + $openings = Array('<%' => '%>', ' Array('>', '/>'), ' '>', '' => '', ''); + + $tag_open_pos = false; + foreach ($openings as $an_opening => $closings) { + $pos = strpos($this->Template, $an_opening, $this->Position); + if ($pos !== false && ($tag_open_pos === false || (int) $pos <= (int) $tag_open_pos)) { + $tag_open_pos = $pos; + $open_len = strlen($an_opening); + $opening_tag = $an_opening; + $tag_closings = $closings; + } + } + + if ($tag_open_pos === false) { //If no tags left - adding all other data + $this->AppendOutput(substr($this->Template, $this->Position), true); + return false; + } + + //Adding all data before tag open + $this->AppendOutput(substr($this->Template, $this->Position, $tag_open_pos - $this->Position), true); + + if (is_array($tag_closings)) { + $tag_close_pos = false; + foreach ($tag_closings as $a_closing) { + $pos = strpos($this->Template, $a_closing, $tag_open_pos); + if ($pos !== false && ($tag_close_pos === false || (int) $pos <= (int) $tag_close_pos)) { + $tag_close_pos = $pos; + $closing_tag = $a_closing; + } + } + } + elseif ($opening_tag == '') { + $closing_tag = ''; + $tag_close_pos = $tag_open_pos + $open_len; + } + else { + $closing_tag = $tag_closings; + $tag_close_pos = strpos($this->Template, $closing_tag, $tag_open_pos); + } + $close_len = strlen($closing_tag); + + + // Cutting trailing line-breaks after tags (same way PHP does it) + if (substr($this->Template, $tag_close_pos+$close_len, 2) == "\r\n") { + $this->Template = substr_replace($this->Template, '', $tag_close_pos, 2); + } + elseif (substr($this->Template, $tag_close_pos+$close_len, 1) == "\n") { + $this->Template = substr_replace($this->Template, '', $tag_close_pos, 1); + } + + + //Cutting out the tag itself + $tag = substr($this->Template, $tag_open_pos + $open_len, $tag_close_pos - $tag_open_pos - $open_len); + + + if ($opening_tag == '') { //empty closing means old style in-portal if .... + $tag = 'm:endif'; + } + + if ($opening_tag == 'Params, 'PrefixSpecial'); + $tag = $prefix.$tag.' _auto_prefix_="1"'; + } + + // temporary - for backward compatability with in-portal style if + $compat_tags = array('m_if', 'm_DefineElement', 'm_Capture', 'm_RenderElement'); + if ($opening_tag == '' && !in_array($tag_part, $compat_tags)) { + if (strpos($the_tag, ' ') !== false) { + list($function, $params) = explode(' ', $the_tag, 2); + } + else { + $function = $the_tag; + $params = ''; + } + $tag = 'm:if prefix="'.$prefix.'" function="'.$function.'" '.$params; + } + + if ($opening_tag == ''); + } + else { + $this->AppendOutput(''); + $this->AppendCompiledHTML(''); + } + $tag = '__COMMENT__'; + } + + if ($closing_tag == '>') $tag .= ' _short_closing_="1"'; + + $this->Position = $tag_close_pos + $close_len; + return $tag; + } + + function CurrentLineNumber() + { + return substr_count(substr($this->Template, 0, $this->Position), "\n")+1; + } + + function SkipModeName() + { + switch ($this->SkipMode) { + case skip: return 'skip'; + case skip_tags: return 'skip_tags'; + case parse: return 'parse'; + } + } + + /** + * Recursive mkdir + * + * @param string $dir + * @param string $base_path base path to directory where folders should be created in + */ + function CheckDir($dir, $base_path = '') + { + if (file_exists($dir)) { + return; + } + else { + // remove $base_path from beggining because it is already created during install + $dir = preg_replace('/^'.preg_quote($base_path.'/', '/').'/', '', $dir, 1); + $segments = explode('/', $dir); + $cur_path = $base_path; + + foreach ($segments as $segment) { + // do not add leading / for windows paths (c:\...) + $cur_path .= preg_match('/^[a-zA-Z]{1}:/', $segment) ? $segment : '/'.$segment; + if (!file_exists($cur_path)) { + mkdir($cur_path); + } + } + } + } + + function ParseTemplate($name, $pre_parse = 1, $params=array(), $silent=0) + { + $this->FromPreParseCache = false; + if ($this->GetParam('from_inportal')) $pre_parse = 0; + if ($pre_parse) { + $pre_parsed = $this->Application->TemplatesCache->GetPreParsed($name); + if ($pre_parsed === false) { + // template not found -> don't compile + return ''; + } + + if ($pre_parsed && $pre_parsed['active']) { // active means good (not expired) pre-parsed cache + $this->FromPreParseCache = true; + $this->SetParams($params, 0); // 0 to disable params sorting and regexp generation - not needed when processing pre-parsed + ob_start(); + if ($pre_parsed['mode'] == 'file') { + $this->TemplateName = str_replace(FULL_PATH, '', realpath($pre_parsed['fname'])); + include($pre_parsed['fname']); + } + else { + eval('?'.'>'.$pre_parsed['content']); + } + $output = ob_get_contents(); + ob_end_clean(); + } + else { + $this->SetParams($params); + + $this->CompiledBuffer .= '<'.'?php'."\n"; + $this->CompiledBuffer .= 'global $application;'."\n"; + + $this->CompiledBuffer .= '$params =& $application->Parser->Params;'."\n"; + $this->CompiledBuffer .= 'extract($params);'."\n"; + + $this->CompiledBuffer .= '$o = \'\';'."\n"; + + $body = $this->Application->TemplatesCache->GetTemplateBody($name, $silent); + $this->TemplateName = $name; + $output = $this->NewParse($body, $name); + + $this->CompiledBuffer .= '?'.'>'."\n"; + + if (defined('SAFE_MODE') && SAFE_MODE) { + if (!isset($conn)) $conn =& $this->Application->GetADODBConnection(); + $conn->Query('REPLACE INTO '.TABLE_PREFIX.'Cache (VarName, Data, Cached) VALUES ('.$conn->qstr($pre_parsed['fname']).','.$conn->qstr($this->CompiledBuffer).','.adodb_mktime().')'); + } + else { + $compiled = fopen($pre_parsed['fname'], 'w'); + fwrite($compiled, $this->CompiledBuffer); + fclose($compiled); + } + + } + if ( !$this->GetParam('from_inportal') && strpos($output, 'Application->recallObject('Inp1Parser'); +// $name = $this->Application->TemplatesCache->GetTemplateFileName($name) . '-block:' . $name; // may be is needed (by Alex) + $output = $inp1_parser->Parse($name, $output); + } + return $output; + } + + // pre-parse is OFF + $this->SetParams($params); + return $this->NewParse($this->Application->TemplatesCache->GetTemplateBody($name), $name, $pre_parse); + } + + function NewParse($template, $name='unknown', $pre_parse = 1) + { + $this->Template = $template; + $this->TemplateName = $name; + $this->Position = 0; + $this->Output = ''; + $this->TagHolder = new MyTagHolder(); + + $has_inp_tags = false; + + if (!getArrayValue($this->Params, 'PrefixSpecial')) { + $this->Params['PrefixSpecial'] = '$PrefixSpecial'; + } + + //While we have more tags + while ($tag_data = $this->FindTag2()) + { + if ($tag_data == '__COMMENT__') continue; + //Create tag object from passed tag data + if( $this->Application->isDebugMode() && constOn('DBG_SHOW_TAGS') ) + { + global $debugger; + $debugger->appendHTML('mode: '.$this->SkipModeName().' tag '.$debugger->highlightString($tag_data).' in '.$debugger->getFileLink($debugger->getLocalFile(FULL_PATH.THEMES_PATH.'/'.$this->TemplateName).'.tpl', $this->CurrentLineNumber(), '', true)); + } + $tag =& $this->TagHolder->GetTag($tag_data, $this); + + if (!$this->CheckRecursion($tag)) //we do NOT process closing tags + { + $tag->Process(); + } + } + return $this->Output; + } + + function Parse($template, $name='unknown', $pre_parse = 1) + { + $this->Template = $template; + $this->TemplateName = $name; + $this->Position = 0; + $this->Output = ''; + $this->TagHolder = new MyTagHolder(); + + $has_inp_tags = false; + + if ($this->GetParam('from_inportal')) $pre_parse = 0; + + if (defined('EXPERIMENTAL_PRE_PARSE') && $pre_parse) { + $fname = $this->Application->TemplatesCache->GetRealFilename($this->TemplateName).'.php'; + + $fname = str_replace(FULL_PATH, FULL_PATH.'/kernel/cache', $fname); + + if (!defined('SAFE_MODE') || !SAFE_MODE) { + $this->CheckDir(dirname($fname), FULL_PATH.'/kernel/cache'); + } + + $tname = $this->Application->TemplatesCache->GetRealFilename($this->TemplateName).'.tpl'; + $output = ''; + $is_cached = false; + ob_start(); + if (defined('SAFE_MODE') && SAFE_MODE) { + $conn =& $this->Application->GetADODBConnection(); + $cached = $conn->GetRow('SELECT * FROM '.TABLE_PREFIX.'Cache WHERE VarName = "'.$fname.'"'); + if ($cached !== false && $cached['Cached'] > filemtime($tname)) { + eval('?'.'>'.$cached['Data']); + $is_cached = true; + } + } + else { + if (file_exists($fname) && file_exists($tname) && filemtime($fname) > filemtime($tname)) { + include($fname); + $is_cached = true; + } + } + $output = ob_get_contents(); + ob_end_clean(); + + if ( $is_cached && !$this->GetParam('from_inportal') ) { + if ( strpos($output, 'Application->recallObject('Inp1Parser'); + $output = $inp1_parser->Parse($name, $output); + } + return $output; + } + + $this->CompiledBuffer .= '<'.'?php'."\n"; + $this->CompiledBuffer .= 'global $application;'."\n"; + + $this->CompiledBuffer .= '$params =& $application->Parser->Params;'."\n"; + $this->CompiledBuffer .= 'extract($params);'."\n"; + + $this->CompiledBuffer .= '$o = \'\';'."\n"; + } + + if (!getArrayValue($this->Params, 'PrefixSpecial')) { + $this->Params['PrefixSpecial'] = '$PrefixSpecial'; + } + + //While we have more tags + while ($tag_data = $this->FindTag2()) + { + if ($tag_data == '__COMMENT__') continue; + //Create tag object from passed tag data + if( $this->Application->isDebugMode() && constOn('DBG_SHOW_TAGS') ) + { + global $debugger; + $debugger->appendHTML('mode: '.$this->SkipModeName().' tag '.$debugger->highlightString($tag_data).' in '.$debugger->getFileLink($debugger->getLocalFile(FULL_PATH.THEMES_PATH.'/'.$this->TemplateName).'.tpl', $this->CurrentLineNumber(), '', true)); + } +// $tag = new MyTag($tag_data, $this); + $tag =& $this->TagHolder->GetTag($tag_data, $this); + + if (!$this->CheckRecursion($tag)) //we do NOT process closing tags + { + $tag->Process(); + } + } + + if ( !$this->GetParam('from_inportal') ) { + if ( strpos($this->Output, 'Application->recallObject('Inp1Parser'); + $this->Output = $inp1_parser->Parse($name, $this->Output); + $has_inp_tags = true; + } + } + + + if (defined('EXPERIMENTAL_PRE_PARSE') && $pre_parse && !$has_inp_tags) { +// $this->CompiledBuffer .= 'echo $o;'."\n"; + $this->CompiledBuffer .= '?'.'>'."\n"; + + if (defined('SAFE_MODE') && SAFE_MODE) { + if (!isset($conn)) $conn =& $this->Application->GetADODBConnection(); + $conn->Query('REPLACE INTO '.TABLE_PREFIX.'Cache (VarName, Data, Cached) VALUES ('.$conn->qstr($fname).','.$conn->qstr($this->CompiledBuffer).','.adodb_mktime().')'); + } + else { + $compiled = fopen($fname, 'w'); + fwrite($compiled, $this->CompiledBuffer); + fclose($compiled); + } + } + + return $this->Output; + } + + function ParseBlock($params, $force_pass_params=0, $as_template=false) + { + if( $this->Application->isDebugMode() && constOn('DBG_SHOW_TAGS') ) + { + global $debugger; + $debugger->appendHTML('ParseBlock '.$params['name'].' pass_params is '.$params['pass_params'].' force is '.$force_pass_params.' in '.$debugger->getFileLink($debugger->getLocalFile(FULL_PATH.THEMES_PATH.'/'.$this->TemplateName).'.tpl', $this->CurrentLineNumber(), '', true)); + + } + /*if ( $this->Application->isDebugMode() && constOn('DBG_PRE_PARSE') ) { + global $debugger; + $debugger->CurrentPreParsedBlock = $params['name']; + }*/ + if (defined('EXPERIMENTAL_PRE_PARSE')) { + $this->MainParser = false; + if (isset($this->Application->PreParsedBlocks[$params['name']]) ) { + + if ($this->ParamsRecursionIndex == 0) { + $this->ParamsStack[$this->ParamsRecursionIndex] = $this->Params; + } + + if (isset($params['pass_params']) || $force_pass_params) { + $pass_params = array_merge($this->ParamsStack[$this->ParamsRecursionIndex], $params); + } + else { + $pass_params = $params; + } + + $this->ParamsStack[++$this->ParamsRecursionIndex] = $pass_params; + $this->Params = $pass_params; + + $f = $this->Application->PreParsedBlocks[$params['name']]; + +// $this->ParamsRecursionIndex--; + + //$this->SetParams($params); + if( !isset($pass_params['PrefixSpecial']) && isset($pass_params['prefix']) ) $pass_params['PrefixSpecial'] = $pass_params['prefix']; + + $ret = $f($pass_params); + + if (isset($params['return_params']) && $params['return_params']) { + $this->ParamsStack[$this->ParamsRecursionIndex - 1] = array_merge($this->ParamsStack[$this->ParamsRecursionIndex - 1], $this->ParamsStack[$this->ParamsRecursionIndex]); + } + + unset($this->ParamsStack[$this->ParamsRecursionIndex--]); + $this->Params = $this->ParamsStack[$this->ParamsRecursionIndex]; + $this->MainParser = true; + return defined('DBG_DECORATE_BLOCKS') && DBG_DECORATE_BLOCKS ? $this->decorateBlock($ret, $pass_params) : $ret; + } + } + + $BlockParser =& $this->Application->makeClass('TemplateParser'); + if (isset($params['pass_params']) || $force_pass_params) { + $BlockParser->SetParams(array_merge($this->Params, $params)); + } + else + $BlockParser->SetParams($params); + $this->Application->Parser =& $BlockParser; + if (!isset($params['name'])) { + trigger_error('***Error: Block name not passed to ParseBlock', E_USER_ERROR); + } + $templates_cache =& $this->Application->recallObject('TemplatesCache'); + + $template_name = $as_template ? $params['name'] : $templates_cache->GetTemplateFileName($params['name']) . '-block:'.$params['name']; + + $silent = getArrayValue($params, 'from_inportal') && !defined('DBG_TEMPLATE_FAILURE'); + + $o = $BlockParser->Parse( + $templates_cache->GetTemplateBody($params['name'], $silent), + $template_name + ); + if (getArrayValue($params, 'BlockNoData') && !$BlockParser->DataExists) { + $template_name = $as_template ? $params['BlockNoData'] : $templates_cache->GetTemplateFileName($params['BlockNoData']) . '-block:'.$params['BlockNoData']; + $o = $BlockParser->Parse( + $templates_cache->GetTemplateBody($params['BlockNoData'], $silent), + $template_name + ); + } + + $this->Application->Parser =& $this; + $this->Application->Parser->DataExists = $this->Application->Parser->DataExists || $BlockParser->DataExists; + + return $o; + } + + function decorateBlock(&$block_content, $block_params) + { + if (preg_match('/^(\s*)(.*)<\/td>(.*)$/is', $block_content, $regs)) { + // block with td -> put div inside td + return $regs[1].'
'.$regs[3].'
'.$regs[4]; + } + + return '
'.$block_content.'
'; + } + + function Recurve(&$tag) + { + $this->Recursion[++$this->RecursionIndex] =& $tag; + } + + function CheckRecursion(&$tag) + { + if ($this->RecursionIndex > 0) { //If we are inside the recursion + if ($this->Recursion[$this->RecursionIndex]->CheckRecursion($tag)) { //If we can close this recursion + unset($this->Recursion[$this->RecursionIndex--]); //unsetting current recursion level and decreasing it at the same time + return true; //we should inform not to process closing tag + } + } + return false; + } + + function SetSkipMode($mode) + { + $this->SkipMode = $mode; + } +} + +?> \ No newline at end of file