Index: branches/5.2.x/core/kernel/nparser/nparser.php =================================================================== --- branches/5.2.x/core/kernel/nparser/nparser.php (revision 16035) +++ branches/5.2.x/core/kernel/nparser/nparser.php (revision 16036) @@ -1,1206 +1,1202 @@ <?php /** * @version $Id$ * @package In-Portal * @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved. * @license GNU/GPL * In-Portal is Open Source software. * This means that this software may have been modified pursuant * the GNU General Public License, and as distributed it includes * or is derivative of works licensed under the GNU General Public License * or other free or open source software licenses. * See http://www.in-portal.org/license for copyright notices and details. */ defined('FULL_PATH') or die('restricted access!'); include_once(KERNEL_PATH.'/nparser/ntags.php'); define('TAG_NAMESPACE', 'inp2:'); define('TAG_NAMESPACE_LENGTH', 5); class NParser extends kBase { var $Stack = Array (); var $Level = 0; var $Buffers = array(); var $InsideComment = false; /** * Parse tags inside HTML comments * * @var bool */ var $SkipComments = true; var $Params = array(); var $ParamsStack = array(); var $ParamsLevel = 0; var $Definitions = ''; /** * Holds dynamic elements to function names mapping during execution * * @var Array */ var $Elements = Array (); /** * Holds location of element definitions inside templates. * key - element function name, value - array of 2 keys: {from_pos, to_pos} * * @var Array */ var $ElementLocations = Array (); var $DataExists = false; var $TemplateName = null; var $TempalteFullPath = null; var $CachePointers = Array (); var $Cachable = Array (); /** * Deep level during parsing * * @var int */ var $CacheLevel = 0; /** * Caching in templates enabled * * @var bool */ var $CachingEnabled = false; /** * Completely cache given page * * @var bool */ var $FullCachePage = false; /** * Prefixes, that are used on current page * * @var Array */ var $PrefixesInUse = Array (); /** * Parser parameter names, that are created via m_Capture tag are listed here * * @var Array */ var $Captures = array(); /** * Phrases, used on "Edit" buttons, that parser adds during block decoration * * @var Array */ var $_btnPhrases = Array (); /** * Mod-rewrite system enabled * * @var bool */ var $RewriteUrls = false; /** * Current user is logged-in * * @var bool */ var $UserLoggedIn = false; /** * Creates template parser object * * @access public */ public function __construct() { parent::__construct(); if (defined('EDITING_MODE') && (EDITING_MODE == EDITING_MODE_DESIGN)) { $this->_btnPhrases['design'] = $this->Application->Phrase('la_btn_EditDesign', false, true); $this->_btnPhrases['block'] = $this->Application->Phrase('la_btn_EditBlock', false, true); } $this->RewriteUrls = $this->Application->RewriteURLs(); $this->UserLoggedIn = $this->Application->LoggedIn(); // cache only Front-End templated, when memory caching is available and template caching is enabled in configuration $this->CachingEnabled = !$this->Application->isAdmin && $this->Application->ConfigValue('SystemTagCache') && $this->Application->isCachingType(CACHING_TYPE_MEMORY); } function Compile($pre_parsed, $template_name = 'unknown') { $data = file_get_contents($pre_parsed['tname']); if (!$this->CompileRaw($data, $pre_parsed['tname'], $template_name)) { // compilation failed during errors in template // trigger_error('Template "<strong>' . $template_name . '</strong>" not compiled because of errors', E_USER_WARNING); return false; } // saving compiled version (only when compilation was successful) $this->Application->TemplatesCache->saveTemplate($pre_parsed['fname'], $this->Buffers[0]); return true; } function Parse($raw_template, $name = null) { $this->CompileRaw($raw_template, $name); ob_start(); $_parser =& $this; eval('?'.'>'.$this->Buffers[0]); return ob_get_clean(); } function CompileRaw($data, $t_name, $template_name = 'unknown') { $code = "extract (\$_parser->Params);\n"; $code .= "\$_parser->ElementLocations['{$template_name}'] = Array('template' => '{$template_name}', 'start_pos' => 0, 'end_pos' => " . strlen($data) . ");\n"; // $code .= "__@@__DefinitionsMarker__@@__\n"; // $code .= "if (!\$this->CacheStart('".abs(crc32($t_name))."_0')) {\n"; $this->Buffers[0] = '<?'."php $code ?>\n"; $this->Cacheable[0] = true; $this->Definitions = ''; // finding all the tags $reg = '(.*?)(<[\\/]?)' . TAG_NAMESPACE . '([^>]*?)([\\/]?>)(\r\n){0,1}'; preg_match_all('/'.$reg.'/s', $data, $results, PREG_SET_ORDER + PREG_OFFSET_CAPTURE); $this->InsideComment = false; foreach ($results as $tag_data) { $tag = array( 'opening' => $tag_data[2][0], 'tag' => $tag_data[3][0], 'closing' => $tag_data[4][0], 'line' => substr_count(substr($data, 0, $tag_data[2][1]), "\n")+1, 'pos' => $tag_data[2][1], 'file' => $t_name, 'template' => $template_name, ); // the idea is to count number of comment openings and closings before current tag // if the numbers do not match we inverse the status of InsideComment if ($this->SkipComments && (substr_count($tag_data[1][0], '<!--') != substr_count($tag_data[1][0], '-->'))) { $this->InsideComment = !$this->InsideComment; } // appending any text/html data found before tag $this->Buffers[$this->Level] .= $tag_data[1][0]; if (!$this->InsideComment) { $tmp_tag = $this->Application->CurrentNTag; $this->Application->CurrentNTag = $tag; if ($this->ProcessTag($tag) === false) { $this->Application->CurrentNTag = $tmp_tag; return false; } $this->Application->CurrentNTag = $tmp_tag; } else { $this->Buffers[$this->Level] .= $tag_data[2][0] . TAG_NAMESPACE . $tag_data[3][0] . $tag_data[4][0]; } } if ($this->Level > 0) { $error_tag = Array ( 'file' => $this->Stack[$this->Level]->Tag['file'], 'line' => $this->Stack[$this->Level]->Tag['line'], ); throw new ParserException('Unclosed tag opened by ' . $this->TagInfo($this->Stack[$this->Level]->Tag), 0, null, $error_tag); return false; } // appending text data after last tag (after its closing pos), // if no tag was found at all ($tag_data is not set) - append the whole $data $this->Buffers[$this->Level] .= isset($tag_data) ? substr($data, $tag_data[4][1]+strlen($tag_data[4][0])) : $data; $this->Buffers[$this->Level] = preg_replace('/<!--##(.*?)##-->/s', '', $this->Buffers[$this->Level]); // remove hidden comments IB#23065 // $this->Buffers[$this->Level] .= '<?'.'php '."\n\$_parser->CacheEnd();\n}\n"." ?".">\n"; // $this->Buffers[$this->Level] = str_replace('__@@__DefinitionsMarker__@@__', $this->Definitions, $this->Buffers[$this->Level]); return true; } function SplitParamsStr($params_str) { preg_match_all('/([\${}a-zA-Z0-9_.\\-\\\\#\\[\\]]+)=(["\']{1,1})(.*?)(?<!\\\)\\2/s', $params_str, $rets, PREG_SET_ORDER); $values = Array(); // we need to replace all occurences of any current param $key with {$key} for correct variable substitution foreach ($rets AS $key => $val){ $values[$val[1]] = str_replace('\\' . $val[2], $val[2], $val[3]); } return $values; } function SplitTag($tag) { if (!preg_match('/([^_ \t\r\n]*)[_]?([^ \t\r\n]*)[ \t\r\n]*(.*)$$/s', $tag['tag'], $parts)) { // this is virtually impossible, but just in case throw new ParserException('Incorrect tag format: ' . $tag['tag'], 0, null, $tag); return false; } $splited['prefix'] = $parts[2] ? $parts[1] : '__auto__'; $splited['name'] = $parts[2] ? $parts[2] : $parts[1]; $splited['attrs'] = $parts[3]; return $splited; } function ProcessTag($tag) { $splited = $this->SplitTag($tag); if ($splited === false) { return false; } $tag = array_merge($tag, $splited); $tag['processed'] = false; $tag['NP'] = $this->SplitParamsStr($tag['attrs']); $o = ''; $tag['is_closing'] = $tag['opening'] == '</' || $tag['closing'] == '/>'; if (class_exists('_Tag_'.$tag['name'])) { // block tags should have special handling class if ($tag['opening'] == '<') { $class = '_Tag_'.$tag['name']; $instance = new $class($tag); $instance->Parser =& $this; /* @var $instance _BlockTag */ $this->Stack[++$this->Level] =& $instance; $this->Buffers[$this->Level] = ''; $this->Cachable[$this->Level] = true; $open_code = $instance->Open($tag); if ($open_code === false) { return false; } $o .= $open_code; } if ($tag['is_closing']) { // not ELSE here, because tag may be <empty/> and still has a handler-class if ($this->Level == 0) { $dump = array(); foreach ($this->Stack as $instance) { $dump[] = $instance->Tag; } if ( $this->Application->isDebugMode() ) { $this->Application->Debugger->dumpVars($dump); } $error_msg = 'Closing tag without an opening: ' . $this->TagInfo($tag) . ' - <strong>probably opening tag was removed or nested tags error</strong>'; throw new ParserException($error_msg, 0, null, $tag); return false; } if ($this->Stack[$this->Level]->Tag['name'] != $tag['name']) { $opening_tag = $this->Stack[$this->Level]->Tag; $error_msg = ' Closing tag ' . $this->TagInfo($tag) . ' does not match opening tag at current nesting level (' . $this->TagInfo($opening_tag) . ' opened at line ' . $opening_tag['line'] . ')'; throw new ParserException($error_msg, 0, null, $tag); return false; } $o .= $this->Stack[$this->Level]->Close($tag); // DO NOT use $this->Level-- here because it's used inside Close $this->Level--; } } else { // regular tags - just compile if (!$tag['is_closing']) { $error_msg = 'Tag without a handler: ' . $this->TagInfo($tag) . ' - <strong>probably missing <empty <span style="color: red">/</span>> tag closing</strong>'; throw new ParserException($error_msg, 0, null, $tag); return false; } if ($this->Level > 0) $o .= $this->Stack[$this->Level]->PassThrough($tag); if (!$tag['processed']) { $compiled = $this->CompileTag($tag); if ($compiled === false) return false; if (isset($tag['NP']['cachable']) && (!$tag['NP']['cachable'] || $tag['NP']['cachable'] == 'false')) { $this->Cachable[$this->Level] = false; } $o .= '<?'.'php ' . $compiled . " ?>\n"; // $o .= '<?'.'php '; // $o .= (isset($tag['NP']['cachable']) && (!$tag['NP']['cachable'] || $tag['NP']['cachable'] == 'false')) ? $this->BreakCache($compiled, $this->GetPointer($tag)) : $compiled; // $o .= " ?".">\n"; } } $this->Buffers[$this->Level] .= $o; return true; } function GetPointer($tag) { return abs(crc32($tag['file'])).'_'.$tag['line']; } function BreakCache($code, $pointer, $condition='') { return "\$_parser->CacheEnd();\n}\n" . $code."\nif ( !\$_parser->CacheStart('{$pointer}'" . ($condition ? ", {$condition}" : '') . ") ) {\n"; } function TagInfo($tag, $with_params=false) { return "<b>{$tag['prefix']}_{$tag['name']}".($with_params ? ' '.$tag['attrs'] : '')."</b>"; } function CompileParamsArray($arr) { $to_pass = 'Array('; foreach ($arr as $name => $val) { $to_pass .= '"'.$name.'" => "'.str_replace('"', '\"', $val).'",'; } $to_pass .= ')'; return $to_pass; } function CompileTag($tag) { $code = ''; $to_pass = $this->CompileParamsArray($tag['NP']); if ($tag['prefix'] == '__auto__') { $prefix = $this->GetParam('PrefixSpecial'); $code .= '$_p_ =& $_parser->GetProcessor($PrefixSpecial);'."\n"; $code .= 'echo $_p_->ProcessParsedTag("'.$tag['name'].'", '.$to_pass.', "$PrefixSpecial", \''.$tag['file'].'\', '.$tag['line'].');'."\n"; } else { $prefix = $tag['prefix']; $code .= '$_p_ =& $_parser->GetProcessor("'.$tag['prefix'].'");'."\n"; $code .= 'echo $_p_->ProcessParsedTag("'.$tag['name'].'", '.$to_pass.', "'.$tag['prefix'].'", \''.$tag['file'].'\', '.$tag['line'].');'."\n"; } if (array_key_exists('result_to_var', $tag['NP']) && $tag['NP']['result_to_var']) { $code .= "\$params['{$tag['NP']['result_to_var']}'] = \$_parser->GetParam('{$tag['NP']['result_to_var']}');\n"; $code .= "\${$tag['NP']['result_to_var']} = \$params['{$tag['NP']['result_to_var']}'];\n"; } if ($prefix && strpos($prefix, '$') === false) { $p =& $this->GetProcessor($prefix); if (!is_object($p) || !$p->CheckTag($tag['name'], $tag['prefix'])) { $error_msg = 'Unknown tag: ' . $this->TagInfo($tag) . ' - <strong>incorrect tag name or prefix</strong>'; throw new ParserException($error_msg, 0, null, $tag); return false; } } return $code; } function CheckTemplate($t, $silent = null) { $pre_parsed = $this->Application->TemplatesCache->GetPreParsed($t); if (!$pre_parsed) { if (!$silent) { throw new ParserException('Cannot include "<strong>' . $t . '</strong>" - file does not exist'); } return false; } $force_compile = defined('DBG_NPARSER_FORCE_COMPILE') && DBG_NPARSER_FORCE_COMPILE; if (!$pre_parsed || !$pre_parsed['active'] || $force_compile) { $inc_parser = new NParser(); if ($force_compile) { // remove Front-End theme markings during total compilation $t = preg_replace('/^theme:.*?\//', '', $t); } if (!$inc_parser->Compile($pre_parsed, $t)) { return false; } } return $pre_parsed; } function Run($t, $silent = null) { if ((strpos($t, '../') !== false) || (trim($t) !== $t)) { // when relative paths or special chars are found template names from url, then it's hacking attempt return false; } $pre_parsed = $this->CheckTemplate($t, $silent); if (!$pre_parsed) { return false; } $backup_template = $this->TemplateName; $backup_fullpath = $this->TempalteFullPath; $this->TemplateName = $t; $this->TempalteFullPath = $pre_parsed['tname']; if (!isset($backup_template) && $this->CachingEnabled && !$this->UserLoggedIn && !EDITING_MODE) { // this is main page template -> check for page-based aggressive caching settings $output =& $this->RunMainPage($pre_parsed); } else { $output =& $this->Application->TemplatesCache->runTemplate($this, $pre_parsed); } $this->TemplateName = $backup_template; $this->TempalteFullPath = $backup_fullpath; return $output; } function &RunMainPage($pre_parsed) { $page = $this->Application->recallObject('st.-virtual'); /* @var $page kDBItem */ if ($page->isLoaded()) { // page found in database $debug_mode = $this->Application->isDebugMode(); // don't cache debug output $template_path = preg_replace('/^' . preg_quote(FULL_PATH, '/') . '/', '', $this->TempalteFullPath, 1); $element = ($debug_mode ? 'DEBUG_MODE:' : '') . 'file=' . $template_path; $this->FullCachePage = $page->GetDBField('EnablePageCache'); if ($this->FullCachePage && $page->GetDBField('PageCacheKey')) { // page caching enabled -> try to get from cache $cache_key = $this->FormCacheKey($element, $page->GetDBField('PageCacheKey')); $output = $this->getCache($cache_key); if ($output !== false) { return $output; } } // page not cached OR cache expired $output =& $this->Application->TemplatesCache->runTemplate($this, $pre_parsed); $this->generatePageCacheKey($page); if ($this->FullCachePage && $page->GetDBField('PageCacheKey')) { $cache_key = $this->FormCacheKey($element, $page->GetDBField('PageCacheKey')); $this->setCache($cache_key, $output, (int)$page->GetDBField('PageExpiration')); } } else { // page not found in database $output =& $this->Application->TemplatesCache->runTemplate($this, $pre_parsed); } return $output; } /** * Generate page caching key based on prefixes used on it + prefix IDs passed in url * * @param kDBItem $page */ function generatePageCacheKey(&$page) { if (!$page->isLoaded() || $page->GetDBField('OverridePageCacheKey')) { return ; } $page_cache_key = Array (); // nobody resets "m" prefix serial, don't count no user too unset($this->PrefixesInUse['m'], $this->PrefixesInUse['u']); if (array_key_exists('st', $this->PrefixesInUse)) { // prefix "st" serial will never be changed unset($this->PrefixesInUse['st']); $this->PrefixesInUse['c'] = 1; } $prefix_ids = Array (); $prefixes = array_keys($this->PrefixesInUse); asort($prefixes); foreach ($prefixes as $index => $prefix) { $id = $this->Application->GetVar($prefix . '_id'); if (is_numeric($id)) { if (defined('DEBUG_MODE') && DEBUG_MODE && $this->Application->isDebugMode()) { $this->Application->Debugger->appendHTML('Found: "' . $prefix . '_id" = ' . $id . ' during PageCacheKey forming.'); } $prefix_ids[] = $prefix; unset($prefixes[$index]); } } if ($prefix_ids) { $page_cache_key[] = 'prefix_id:' . implode(',', $prefix_ids); } if ($prefixes) { $page_cache_key[] = 'prefix:' . implode(',', $prefixes); } $page_cache_key = implode(';', $page_cache_key); if ($page_cache_key != $page->GetOriginalField('PageCacheKey')) { if (defined('DEBUG_MODE') && DEBUG_MODE && $this->Application->isDebugMode()) { $this->Application->Debugger->appendHTML('Canging PageCacheKey from "<strong>' . $page->GetOriginalField('PageCacheKey') . '</strong>" to "<strong>' . $page_cache_key . '</strong>".'); } $page->SetDBField('PageCacheKey', $page_cache_key); // don't use kDBItem::Update(), because it will change ModifiedById to current front-end user $sql = 'UPDATE ' . $page->TableName . ' SET PageCacheKey = ' . $this->Conn->qstr($page_cache_key) . ' WHERE ' . $page->IDField . ' = ' . $page->GetID(); $this->Conn->Query($sql); - - // increment serial, because we issue direct sql above! - $this->Application->incrementCacheSerial('c'); - $this->Application->incrementCacheSerial('c', $page->GetID()); } } /** * Creates tag processor and stores it in local cache + factory * * @param string $prefix * @return kTagProcessor */ function &GetProcessor($prefix) { static $processors = Array (); if ( !isset($processors[$prefix]) ) { $processors[$prefix] = $this->Application->recallObject($prefix . '_TagProcessor'); } return $processors[$prefix]; } /** * Not tag. Method for parameter selection from list in this TagProcessor * * @param Array $params * @param Array $possible_names * * @return string * @access protected */ protected function SelectParam($params, $possible_names) { if ( !is_array($params) ) { return ''; } if ( !is_array($possible_names) ) { $possible_names = explode(',', $possible_names); } foreach ($possible_names as $name) { if ( isset($params[$name]) ) { return $params[$name]; } } return ''; } function SetParams($params) { $this->Params = $params; $keys = array_keys($this->Params); } function GetParam($name) { return isset($this->Params[$name]) ? $this->Params[$name] : false; } function SetParam($name, $value) { $this->Params[$name] = $value; } function PushParams($params) { $this->ParamsStack[$this->ParamsLevel++] = $this->Params; $this->Params = $params; } function PopParams() { $this->Params = $this->ParamsStack[--$this->ParamsLevel]; } function ParseBlock($params, $pass_params=false) { if (array_key_exists('cache_timeout', $params) && $params['cache_timeout']) { $ret = $this->getCache( $this->FormCacheKey('element_' . $params['name']) ); if ($ret) { return $ret; } } if (substr($params['name'], 0, 5) == 'html:') { return substr($params['name'], 5); } if (!array_key_exists($params['name'], $this->Elements) && array_key_exists('default_element', $params)) { // when given element not found, but default element name given, then render it instead $params['name'] = $params['default_element']; unset($params['default_element']); return $this->ParseBlock($params, $pass_params); } $original_params = $params; if ($pass_params || isset($params['pass_params'])) $params = array_merge($this->Params, $params); $this->PushParams($params); $data_exists_bak = $this->DataExists; // if we are parsing design block and we have block_no_data - we need to wrap block_no_data into design, // so we should set DataExists to true manually, otherwise the design block will be skipped because of data_exists in params (by Kostja) // // keep_data_exists is used by block RenderElement (always added in ntags.php), to keep the DataExists value // from inside-content block, otherwise when parsing the design block DataExists will be reset to false resulting missing design block (by Kostja) // // Inside-content block parsing result is given to design block in "content" parameter (ntags.php) and "keep_data_exists" // is only passed, when parsing design block. In case, when $this->DataExists is set to true, but // zero-length content (in 2 cases: method NParser::CheckNoData set it OR really empty block content) // is returned from inside-content block, then design block also should not be shown (by Alex) $this->DataExists = (isset($params['keep_data_exists']) && isset($params['content']) && $params['content'] != '' && $this->DataExists) || (isset($params['design']) && isset($params['block_no_data']) && $params['name'] == $params['design']); if (!array_key_exists($params['name'], $this->Elements)) { $pre_parsed = $this->Application->TemplatesCache->GetPreParsed($params['name']); if ($pre_parsed) { $ret = $this->IncludeTemplate($params); if (array_key_exists('no_editing', $params) && $params['no_editing']) { // when individual render element don't want to be edited return $ret; } return defined('EDITING_MODE') ? $this->DecorateBlock($ret, $params, true) : $ret; } $trace_results = debug_backtrace(); $error_tag = Array ( 'file' => $trace_results[0]['file'], 'line' => $trace_results[0]['line'], ); $error_msg = '<strong>Rendering of undefined element ' . $params['name'] . '</strong>'; throw new ParserException($error_msg, 0, null, $error_tag); return false; } $m_processor =& $this->GetProcessor('m'); $flag_values = $m_processor->PreparePostProcess($params); $f_name = $this->Elements[$params['name']]; /* @var $f_name Closure */ $ret = $f_name($this, $params); $ret = $m_processor->PostProcess($ret, $flag_values); $block_params = $this->Params; // input parameters, but modified inside rendered block $this->PopParams(); if (array_key_exists('result_to_var', $flag_values) && $flag_values['result_to_var']) { // when "result_to_var" used inside ParseBlock, then $$result_to_var parameter is set inside ParseBlock, // but not outside it as expected and got lost at all after PopParams is called, so make it work by // setting it's value on current parameter deep level (from where ParseBlock was called) $this->SetParam($flag_values['result_to_var'], $block_params[ $flag_values['result_to_var'] ]); } $this->CheckNoData($ret, $params); $this->DataExists = $data_exists_bak || $this->DataExists; if (array_key_exists('cache_timeout', $original_params) && $original_params['cache_timeout']) { $cache_key = $this->FormCacheKey('element_' . $original_params['name']); $this->setCache($cache_key, $ret, (int)$original_params['cache_timeout']); } if (array_key_exists('no_editing', $block_params) && $block_params['no_editing']) { // when individual render element don't want to be edited return $ret; } return defined('EDITING_MODE') ? $this->DecorateBlock($ret, $params) : $ret; } /** * Checks, that given block is defined * * @param string $name * @return bool */ function blockFound($name) { return array_key_exists($name, $this->Elements); } function DecorateBlock($block_content, $block_params, $is_template = false) { static $used_ids = Array (), $base_url = null; if (!isset($base_url)) { $base_url = $this->Application->BaseURL(); } // $prepend = '[name: ' . $block_params['name'] . '] [params: ' . implode(', ', array_keys($block_params)) . ']'; $decorate = false; $design = false; if (EDITING_MODE == EDITING_MODE_DESIGN) { $decorate = true; if ($is_template) { // content inside pair RenderElement tag } else { if (strpos($block_params['name'], '__capture_') === 0) { // capture tag (usually inside pair RenderElement) $decorate = false; } elseif (array_key_exists('content', $block_params)) { // pair RenderElement (on template, were it's used) $design = true; } } } if (!$decorate) { return $block_content; } /*else { $block_content = $prepend . $block_content; }*/ $block_name = $block_params['name']; $function_name = $is_template ? $block_name : $this->Elements[$block_name]; $block_title = ''; if (array_key_exists($function_name, $this->Application->Parser->ElementLocations)) { $element_location = $this->Application->Parser->ElementLocations[$function_name]; $block_title .= $element_location['template'] . '.tpl'; $block_title .= ' (' . $element_location['start_pos'] . ' - ' . $element_location['end_pos'] . ')'; } // ensure unique id for every div (used from print lists) $container_num = 1; $container_id = 'parser_block[' . $function_name . ']'; while (in_array($container_id . '_' . $container_num, $used_ids)) { $container_num++; } $container_id .= '_' . $container_num; $used_ids[] = $container_id; // prepare parameter string $param_string = $block_name . ':' . $function_name; if ($design) { $btn_text = $this->_btnPhrases['design']; $btn_class = 'cms-edit-design-btn'; $btn_container_class = 'block-edit-design-btn-container'; $btn_name = 'design'; } else { $btn_text = $this->_btnPhrases['block']; $btn_class = 'cms-edit-block-btn'; $btn_container_class = 'block-edit-block-btn-container'; $btn_name = 'content'; } $icon_url = $base_url . 'core/admin_templates/img/top_frame/icons/' . $btn_name . '_mode.png'; $block_editor = ' <div id="' . $container_id . '" params="' . $param_string . '" class="' . $btn_container_class . '" title="' . kUtil::escape($block_title, kUtil::ESCAPE_HTML) . '"> <button style="background-image: url(' . $icon_url . ');" class="cms-btn-new ' . $btn_class . '" id="' . $container_id . '_btn">' . $btn_text . '</button> <div class="cms-btn-content"> %s </div> </div>'; // 1 - text before, 2 - open tag, 3 - open tag attributes, 4 - content inside tag, 5 - closing tag, 6 - text after closing tag if (preg_match('/^(\s*)<(td|span)(.*?)>(.*)<\/(td|span)>(.*)$/is', $block_content, $regs)) { // div inside span -> put div outside span return $regs[1] . '<' . $regs[2] . ' ' . $regs[3] . '>' . str_replace('%s', $regs[4], $block_editor) . '</' . $regs[5] . '>' . $regs[6]; } return str_replace('%s', $block_content, $block_editor); } function IncludeTemplate($params, $silent=null) { $t = is_array($params) ? $this->SelectParam($params, 't,template,block,name') : $params; $cache_timeout = array_key_exists('cache_timeout', $params) ? $params['cache_timeout'] : false; if ($cache_timeout) { $cache_key = $this->FormCacheKey('template:' . $t); $ret = $this->getCache($cache_key); if ($ret !== false) { return $ret; } } $t = preg_replace('/\.tpl$/', '', $t); $data_exists_bak = $this->DataExists; $this->DataExists = false; if (!isset($silent) && array_key_exists('is_silent', $params)) { $silent = $params['is_silent']; } if (isset($params['pass_params'])) { // ability to pass params from block to template $params = array_merge($this->Params, $params); } $m_processor =& $this->GetProcessor('m'); $flag_values = $m_processor->PreparePostProcess($params); $this->PushParams($params); $ret = $this->Run($t, $silent); $this->PopParams(); $ret = $m_processor->PostProcess($ret, $flag_values); $this->CheckNoData($ret, $params); $this->DataExists = $data_exists_bak || $this->DataExists; if ($cache_timeout) { $this->setCache($cache_key, $ret, (int)$cache_timeout); } return $ret; } function CheckNoData(&$ret, $params) { if (array_key_exists('data_exists', $params) && $params['data_exists'] && !$this->DataExists) { $block_no_data = isset($params['BlockNoData']) ? $params['BlockNoData'] : (isset($params['block_no_data']) ? $params['block_no_data'] : false); if ($block_no_data) { $ret = $this->ParseBlock(array('name'=>$block_no_data)); } else { $ret = ''; } } } function getCache($name) { if (!$this->CachingEnabled) { return false; } $ret = $this->Application->getCache($name, false); if (preg_match('/^\[DE_MARK:(.*?)\]$/', substr($ret, -11), $regs)) { $this->DataExists = $regs[1] ? true : false; $ret = substr($ret, 0, -11); } return $ret; } function setCache($name, $value, $expiration = 0) { if (!$this->CachingEnabled) { return false; } // remeber DataExists in cache, because after cache will be restored // it will not be available naturally (no tags, that set it will be called) $value .= '[DE_MARK:' . (int)$this->DataExists . ']'; return $this->Application->setCache($name, $value, $expiration); } function FormCacheKey($element, $key_string = '') { if (strpos($key_string, 'guest_only') !== false && $this->UserLoggedIn) { // don't cache, when user is logged-in "guest_only" is specified in key return ''; } $parts = Array (); // 1. replace INLINE variable (from request) into key parts if (preg_match_all('/\(%(.*?)\)/', $key_string, $regs)) { // parts in form "(%variable_name)" were found foreach ($regs[1] as $variable_name) { $variable_value = $this->Application->GetVar($variable_name); $key_string = str_replace('(%' . $variable_name . ')', $variable_value, $key_string); } } // 2. replace INLINE serial numbers (they may not be related to any prefix at all) // Serial number also could be composed of inline variables! if (preg_match_all('/\[%(.*?)%\]/', $key_string, $regs)) { // format "[%LangSerial%]" - prefix-wide serial in case of any change in "lang" prefix // format "[%LangIDSerial:5%]" - one id-wide serial in case of data, associated with given id was changed // format "[%CiIDSerial:ItemResourceId:5%]" - foreign key-based serial in case of data, associated with given foreign key was changed foreach ($regs[1] as $serial_name) { $serial_value = $this->Application->getCache('[%' . $serial_name . '%]'); $key_string = str_replace('[%' . $serial_name . '%]', '[%' . $serial_name . '=' . $serial_value . '%]', $key_string); } } /* Always add: =========== * "var:m_lang" - show content on current language * "var:t" - template from url, used to differ multiple pages using same physical template (like as design) * "var:admin,editing_mode" - differ cached content when different editing modes are used * "var:m_cat_id,m_cat_page" - pass current category * "var:page,per_page,sort_by" - list pagination/sorting parameters * "prefix:theme-file" - to be able to reset all cached templated using "Rebuild Theme Files" function * "prefix:phrases" - use latest phrase translations * "prefix:conf" - output could slighly differ based on configuration settings */ $key_string = rtrim('var:m_lang,t,admin,editing_mode,m_cat_id,m_cat_page,page,per_page,sort_by;prefix:theme-file,phrases,conf;' . $key_string, ';'); $keys = explode(';', $key_string); /* Possible parts of a $key_string (all can have multiple occurencies): ==================================================================== * prefix:<prefixA>[,<prefixB>,<prefixC>] - include global serial for given prefix(-es) * skip_prefix:<prefix1>[,<prefix2>,<prefix3>] - exclude global serial for given prefix(-es) * prefix_id:<prefixA>[,<prefixB>,<prefixC>] - include id-based serial for given prefix(-es) * skip_prefix_id:<prefix1>[,<prefix2>,<prefix3>] - exclude id-based serial for given prefix(-es) * var:<aaa>[,<bbb>,<ccc>] - include request variable value(-s) * skip_var:<varA>[,<varB>,<varC>] - exclude request variable value(-s) * (%variable_name) - include request variable value (only value without variable name ifself, like in "var:variable_name") * [%SerialName%] - use to retrieve serial value in free form */ // 3. get variable names, prefixes and prefix ids, that should be skipped $skip_prefixes = $skip_prefix_ids = $skip_variables = Array (); foreach ($keys as $index => $key) { if (preg_match('/^(skip_var|skip_prefix|skip_prefix_id):(.*?)$/i', $key, $regs)) { unset($keys[$index]); $tmp_parts = explode(',', $regs[2]); switch ($regs[1]) { case 'skip_var': $skip_variables = array_merge($skip_variables, $tmp_parts); break; case 'skip_prefix': $skip_prefixes = array_merge($skip_prefixes, $tmp_parts); break; case 'skip_prefix_id': $skip_prefix_ids = array_merge($skip_prefix_ids, $tmp_parts); break; } } } $skip_prefixes = array_unique($skip_prefixes); $skip_variables = array_unique($skip_variables); $skip_prefix_ids = array_unique($skip_prefix_ids); // 4. process keys foreach ($keys as $key) { if (preg_match('/^(var|prefix|prefix_id):(.*?)$/i', $key, $regs)) { $tmp_parts = explode(',', $regs[2]); switch ($regs[1]) { case 'var': // format: "var:country_id" will become "country_id=<country_id>" $tmp_parts = array_diff($tmp_parts, $skip_variables); foreach ($tmp_parts as $variable_name) { $variable_value = $this->Application->GetVar($variable_name); if ($variable_value !== false) { $parts[] = $variable_name . '=' . $variable_value; } } break; case 'prefix': // format: "prefix:country" will become "[%CountrySerial%]" $tmp_parts = array_diff($tmp_parts, $skip_prefixes); foreach ($tmp_parts as $prefix) { $serial_name = $this->Application->incrementCacheSerial($prefix, null, false); $parts[] = '[%' . $serial_name . '=' . $this->Application->getCache($serial_name) . '%]'; if (!$this->RewriteUrls) { // add env-style page and per-page variable, when mod-rewrite is off $prefix_variables = Array ($prefix . '_Page', $prefix . '_PerPage'); foreach ($prefix_variables as $variable_name) { $variable_value = $this->Application->GetVar($variable_name); if ($variable_value !== false) { $parts[] = $variable_name . '=' . $variable_value; } } } } break; case 'prefix_id': // format: "id:country" will become "[%CountryIDSerial:5%]" $tmp_parts = array_diff($tmp_parts, $skip_prefix_ids); foreach ($tmp_parts as $prefix_id) { $id = $this->Application->GetVar($prefix_id . '_id'); if ($id !== false) { $serial_name = $this->Application->incrementCacheSerial($prefix_id, $id, false); $parts[] = '[%' . $serial_name . '=' . $this->Application->getCache($serial_name) . '%]'; } } break; } } elseif ($key == 'currency') { // based on current currency $parts[] = 'curr_iso=' . $this->Application->RecallVar('curr_iso'); } elseif ($key == 'groups') { // based on logged-in user groups $parts[] = 'groups=' . $this->Application->RecallVar('UserGroups'); } elseif ($key == 'guest_only') { // we know this key, but process it at method beginning } else { throw new ParserException('Unknown key part "<strong>' . $key . '</strong>" used in "<strong>key</strong>" parameter of <inp2:m_Cache key="..."/> tag'); } } // 5. add unique given cache key identifier on this page $parts[] = $element; $key = implode(':', $parts); if (defined('DEBUG_MODE') && DEBUG_MODE && $this->Application->isDebugMode()) { $this->Application->Debugger->appendHTML('Parser Key: ' . $key); } return 'parser_' . crc32($key); } function PushPointer($pointer, $key) { $cache_key = $this->FullCachePage || !$this->CachingEnabled ? '' : $this->FormCacheKey('pointer:' . $pointer, $key); $this->CachePointers[++$this->CacheLevel] = $cache_key; return $this->CachePointers[$this->CacheLevel]; } function PopPointer() { return $this->CachePointers[$this->CacheLevel--]; } function CacheStart($pointer, $key) { $pointer = $this->PushPointer($pointer, $key); if ($pointer) { $ret = $this->getCache($pointer); $debug_mode = defined('DEBUG_MODE') && DEBUG_MODE && $this->Application->isDebugMode(); if ($ret !== false) { echo $debug_mode ? '<!-- CACHED OUTPUT START -->' . $ret . '<!-- /CACHED OUTPUT END -->' : $ret; $this->PopPointer(); return true; } if ($debug_mode) { echo '<!-- NO CACHE FOR POINTER: ' . $pointer . ' -->'; } } ob_start(); return false; } function CacheEnd($expiration = 0) { $ret = ob_get_clean(); $pointer = $this->PopPointer(); if ($pointer) { $res = $this->setCache($pointer, $ret, $expiration); if (defined('DEBUG_MODE') && DEBUG_MODE && $this->Application->isDebugMode()) { echo '<!-- STORING CACHE FOR POINTER: ' . $pointer . ' [' . $res . '] -->'; } } echo $ret; } /** * Performs compression of given files or text * * @param mixed $data * @param bool $raw_script * @param string $file_extension * @return string */ function CompressScript($data, $raw_script = false, $file_extension = '') { $minify_helper = $this->Application->recallObject('MinifyHelper'); /* @var $minify_helper MinifyHelper */ if ($raw_script) { $minify_helper->compressString($data, $file_extension); return $data; } return $minify_helper->CompressScriptTag($data); } } class ParserException extends Exception { public function __construct($message = null, $code = 0, $previous = null, $tag = null) { parent::__construct($message, $code, $previous); if ( isset($tag) ) { $this->file = $tag['file']; $this->line = $tag['line']; } } -} \ No newline at end of file +}