Index: branches/5.1.x/core/kernel/nparser/nparser.php
===================================================================
diff -u -N -r13086 -r13168
--- branches/5.1.x/core/kernel/nparser/nparser.php (.../nparser.php) (revision 13086)
+++ branches/5.1.x/core/kernel/nparser/nparser.php (.../nparser.php) (revision 13168)
@@ -1,6 +1,6 @@
_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')
@@ -383,14 +429,126 @@
$this->TemplateName = $t;
$this->TempalteFullPath = $pre_parsed['tname'];
- $output =& $this->Application->TemplatesCache->runTemplate($this, $pre_parsed);
+ 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 "' . $page->GetOriginalField('PageCacheKey') . '" to "' . $page_cache_key . '".');
+ }
+
+ $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 = ' . $page->Conn->qstr($page_cache_key) . '
+ WHERE ' . $page->IDField . ' = ' . $page->GetID();
+ $page->Conn->Query($sql);
+
+ // increment serial, because we issue direct sql above!
+ $this->Application->incrementCacheSerial('c');
+ $this->Application->incrementCacheSerial('c', $page->GetID());
+ }
+ }
+
function &GetProcessor($prefix)
{
static $Processors = array();
@@ -443,8 +601,12 @@
function ParseBlock($params, $pass_params=false)
{
- if (isset($params['cache_timeout']) && ($ret = $this->CacheGet($this->FormCacheKey('element_'.$params['name'])))) {
- return $ret;
+ 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:') {
@@ -517,8 +679,9 @@
$this->DataExists = $data_exists_bak || $this->DataExists;
- if (isset($original_params['cache_timeout'])) {
- $this->CacheSet($this->FormCacheKey('element_'.$original_params['name']), $ret, $original_params['cache_timeout']);
+ 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']) {
@@ -563,9 +726,9 @@
if (!$decorate) {
return $block_content;
}
- else {
- $block_content = /*$prepend .*/ $block_content;
- }
+ /*else {
+ $block_content = $prepend . $block_content;
+ }*/
$block_name = $block_params['name'];
$function_name = $is_template ? $block_name : $this->Elements[$block_name];
@@ -629,9 +792,15 @@
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 (isset($params['cache_timeout']) && ($ret = $this->CacheGet('template:'.$t))) {
- return $ret;
+ if ($cache_timeout) {
+ $cache_key = $this->FormCacheKey('template:' . $t);
+ $ret = $this->getCache($cache_key);
+
+ if ($ret !== false) {
+ return $ret;
+ }
}
$t = preg_replace('/\.tpl$/', '', $t);
@@ -654,8 +823,8 @@
$this->CheckNoData($ret, $params);
$this->DataExists = $data_exists_bak || $this->DataExists;
- if (isset($params['cache_timeout'])) {
- $this->CacheSet('template:'.$t, $ret, $params['cache_timeout']);
+ if ($cache_timeout) {
+ $this->setCache($cache_key, $ret, (int)$cache_timeout);
}
return $ret;
@@ -674,53 +843,216 @@
}
}
- function CacheGet($name)
+ function getCache($name)
{
- if (!$this->Application->ConfigValue('SystemTagCache')) return false;
- return $this->Application->CacheGet($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 CacheSet($name, $value, $expiration=0)
+ function setCache($name, $value, $expiration = 0)
{
- if (!$this->Application->ConfigValue('SystemTagCache')) return false;
- return $this->Application->CacheSet($name, $value, $expiration);
+ 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, $file=null, $add_prefixes=null)
+ function FormCacheKey($element, $key_string = '')
{
- if (!isset($file)) {
- $file = str_replace(FULL_PATH, '', $this->TempalteFullPath).':'.$this->Application->GetVar('t');
+ 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(
- 'file_'.$file.'('.filemtime($this->TempalteFullPath).')' => 'serials:file_ts', // theme + template timestamp
- 'm_lang_'.$this->Application->GetVar('m_lang') => 'serials:lang_ts',
- 'm_cat_id_'.$this->Application->GetVar('m_cat_id') => 'serials:cat_'.$this->Application->GetVar('m_cat_id').'_ts',
- 'm_cat_page'.$this->Application->GetVar('m_cat_page') => false,
- );
- if (isset($add_prefixes)) {
- foreach ($add_prefixes as $prefix) {
- $parts[$prefix.'_id_'.$this->Application->GetVar("{$prefix}_id")] = "serials:$prefix_".$this->Application->GetVar("{$prefix}_id").'_ts';
- $parts[$prefix.'_page_'.$this->Application->GetVar("{$prefix}_Page")] = false;
+
+ $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);
}
}
- $key = '';
- foreach ($parts as $part => $ts_name) {
- if ($ts_name) {
- $ts = $this->Application->CacheGet($ts_name);
- $key .= "$part($ts):";
+
+ // 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:[,,] - include global serial for given prefix(-es)
+ * skip_prefix:[,,] - exclude global serial for given prefix(-es)
+ * prefix_id:[,,] - include id-based serial for given prefix(-es)
+ * skip_prefix_id:[,,] - exclude id-based serial for given prefix(-es)
+ * var:[,,] - include request variable value(-s)
+ * skip_var:[,,] - 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="
+ $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 {
- $key .= "$part:";
+ if ($this->Application->isDebugMode()) {
+ $this->Application->Debugger->appendTrace();
+ }
+
+ trigger_error('Unknown key part "' . $key . '" used in "key" parameter of tag.', E_USER_ERROR);
}
}
- $key .= $element;
- return crc32($key);
+ // 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)
+ function PushPointer($pointer, $key)
{
- $this->CachePointers[++$this->CacheLevel] = $this->FormCacheKey('pointer:'.$pointer);
+ $cache_key = $this->FullCachePage || !$this->CachingEnabled ? '' : $this->FormCacheKey('pointer:' . $pointer, $key);
+
+ $this->CachePointers[++$this->CacheLevel] = $cache_key;
+
return $this->CachePointers[$this->CacheLevel];
}
@@ -729,21 +1061,45 @@
return $this->CachePointers[$this->CacheLevel--];
}
- function CacheStart($pointer=null)
+ function CacheStart($pointer, $key)
{
- if ($ret = $this->CacheGet($this->PushPointer($pointer)) ) {
- echo $ret;
- $this->PopPointer();
- return true;
+ $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 ? '' . $ret . '' : $ret;
+ $this->PopPointer();
+
+ return true;
+ }
+
+ if ($debug_mode) {
+ echo '';
+ }
}
+
ob_start();
+
return false;
}
- function CacheEnd($elem=null)
+ function CacheEnd($expiration = 0)
{
$ret = ob_get_clean();
- $this->CacheSet($this->PopPointer(), $ret); // . ($this->CurrentKeyPart ? ':'.$this->CurrentKeyPart : '')
+ $pointer = $this->PopPointer();
+
+ if ($pointer) {
+ $res = $this->setCache($pointer, $ret, $expiration);
+
+ if (defined('DEBUG_MODE') && DEBUG_MODE && $this->Application->isDebugMode()) {
+ echo '';
+ }
+ }
+
echo $ret;
}
}
\ No newline at end of file