Index: admin/system_presets/simple/categories_c.php =================================================================== --- admin/system_presets/simple/categories_c.php (revision 13152) +++ admin/system_presets/simple/categories_c.php (working copy) @@ -46,7 +46,7 @@ 'MetaDescription', 'HotItem',*/ 'NewItem', /*'PopItem', 'Modified', 'ModifiedById', 'CachedTemplate',*/ 'Template', /*'UseExternalUrl', 'ExternalUrl',*/ 'UseMenuIconUrl', 'MenuIconUrl', 'Title', 'MenuTitle', /*'MetaTitle', 'IndexTools', 'IsMenu', 'IsSystem',*/ 'FormId', 'FormSubmittedTemplate', - /*'FriendlyURL', 'ThemeId'*/ + /*'FriendlyURL', 'ThemeId', 'EnablePageCache', 'OverridePageCacheKey', 'PageCacheKey', 'PageExpiration'*/ ); // virtual fields to hide Index: core/admin_templates/categories/categories_edit.tpl =================================================================== --- core/admin_templates/categories/categories_edit.tpl (revision 13152) +++ core/admin_templates/categories/categories_edit.tpl (working copy) @@ -109,6 +109,13 @@ + + + + + + + @@ -168,17 +175,42 @@ function reflectFilename() { var $filename = getControl('Filename'); if ($filename) { - var $checked = getControl('AutomaticFilename', null, '_cb').checked; - $filename.readOnly = $checked; + var $checked = getControl('AutomaticFilename', null, '_cb').checked; + $filename.readOnly = $checked; } } + function reflectCachingSettings() { + var $checked = getControl('EnablePageCache', null, '_cb').checked; + + getControl('OverridePageCacheKey', null, '_cb').disabled = !$checked; + getControl('PageCacheKey').disabled = !$checked || !getControl('OverridePageCacheKey', null, '_cb').checked; +// getControl('PageExpiration').disabled = !$checked; + } + + function reflectCacheKeyOverride() { + var $checked = getControl('OverridePageCacheKey', null, '_cb').checked; + getControl('PageCacheKey').disabled = !$checked; + } + + Application.setHook( + 'c:*', + function () { + getControl('OverridePageCacheKey', null, '_cb').disabled = false; + getControl('PageCacheKey').disabled = false; +// getControl('PageExpiration').disabled = false; + } + ); + $(document).ready( function() { reflectMenuIcon(); reflectExternalUrl(); reflectFilename(); -// OnSystemClick(); + // OnSystemClick(); + + reflectCacheKeyOverride(); + reflectCachingSettings(); } ); Index: core/admin_templates/tools/system_tools.tpl =================================================================== --- core/admin_templates/tools/system_tools.tpl (revision 13152) +++ core/admin_templates/tools/system_tools.tpl (working copy) @@ -17,10 +17,10 @@ - "> + - : + ', '');" value="Run"> @@ -28,18 +28,20 @@ - - +
+ + + + - - "> + - "> + - "> + + + + + + + + + + + + + + + +
- Compile Templates (with NParser): + Re-compile Templates @@ -47,7 +49,7 @@
Table Structure: @@ -55,27 +57,154 @@ - Table Name (table prefix is optional) OR "Unit-config" Prefix + table name (table prefix is optional) OR "unit config" prefix
- Prefix: + Locate Unit Config File: - Unit-config Prefix + unit config prefix
+ Key Name: + + + + + + + + + +
+ master:configs_parsed, + master:config_files, + master:sections_parsed, + master:cms_menu, + master:template_mapping, + master:StructureTree +
+
+
+ + + +
+
+ Key Value: + + + + + + +
+ + + +
+
\ No newline at end of file Index: core/install/install_data.sql =================================================================== --- core/install/install_data.sql (revision 13152) +++ core/install/install_data.sql (working copy) @@ -175,6 +175,8 @@ INSERT INTO ConfigurationValues VALUES (DEFAULT, 'CSVExportSeparator', '0', 'In-Portal', 'in-portal:configure_advanced'); INSERT INTO ConfigurationAdmin VALUES ('CSVExportEncoding', 'la_section_SettingsCSVExport', 'la_config_CSVExportEncoding', 'radio', NULL, '0=la_Unicode,1=la_Regular', 70.04, 0, 1); INSERT INTO ConfigurationValues VALUES (DEFAULT, 'CSVExportEncoding', '0', 'In-Portal', 'in-portal:configure_advanced'); +INSERT INTO ConfigurationAdmin VALUES ('MemcacheServers', 'la_section_SettingsCaching', 'la_config_MemcacheServers', 'text', '', '', 80.01, 0, 0); +INSERT INTO ConfigurationValues VALUES (DEFAULT, 'MemcacheServers', 'localhost:11211', 'In-Portal', 'in-portal:configure_advanced'); # Section "in-portal:configure_users": INSERT INTO ConfigurationAdmin VALUES ('User_Allow_New', 'la_title_General', 'la_users_allow_new', 'radio', '', '1=la_opt_UserInstantRegistration,2=la_opt_UserNotAllowedRegistration,3=la_opt_UserUponApprovalRegistration,4=la_opt_UserEmailActivation', 10.01, 0, 1); Index: core/install/install_schema.sql =================================================================== --- core/install/install_schema.sql (revision 13152) +++ core/install/install_schema.sql (working copy) @@ -448,6 +448,10 @@ FormSubmittedTemplate varchar(255) DEFAULT NULL, FriendlyURL varchar(255) NOT NULL DEFAULT '', ThemeId int(10) unsigned NOT NULL DEFAULT '0', + EnablePageCache tinyint(4) NOT NULL DEFAULT '0', + OverridePageCacheKey tinyint(4) NOT NULL DEFAULT '0', + PageCacheKey varchar(255) NOT NULL DEFAULT '', + PageExpiration int(11) DEFAULT NULL, PRIMARY KEY (CategoryId), UNIQUE KEY ResourceId (ResourceId), KEY ParentId (ParentId), @@ -471,7 +475,10 @@ KEY `Status` (`Status`), KEY CreatedOn (CreatedOn), KEY EditorsPick (EditorsPick), - KEY ThemeId (ThemeId) + KEY ThemeId (ThemeId), + KEY EnablePageCache (EnablePageCache), + KEY OverridePageCacheKey (OverridePageCacheKey), + KEY PageExpiration (PageExpiration) ); CREATE TABLE CategoryCustomData ( @@ -1095,4 +1102,20 @@ KEY SessionKey (SessionKey), KEY `Timestamp` (`Timestamp`), KEY MainPrefix (MainPrefix) +); + +CREATE TABLE CachedUrls ( + UrlId int(11) NOT NULL AUTO_INCREMENT, + Url varchar(255) NOT NULL DEFAULT '', + `Hash` int(11) NOT NULL DEFAULT '0', + Prefixes varchar(255) NOT NULL DEFAULT '', + ParsedVars text NOT NULL, + Cached int(10) unsigned DEFAULT NULL, + LifeTime int(11) NOT NULL DEFAULT '-1', + PRIMARY KEY (UrlId), + KEY Url (Url), + KEY `Hash` (`Hash`), + KEY Prefixes (Prefixes), + KEY Cached (Cached), + KEY LifeTime (LifeTime) ); \ No newline at end of file Index: core/install/install_toolkit.php =================================================================== --- core/install/install_toolkit.php (revision 13152) +++ core/install/install_toolkit.php (working copy) @@ -789,10 +789,7 @@ */ function deleteCache($refresh_permissions = false) { - $sql = 'DELETE FROM ' . TABLE_PREFIX . 'Cache - WHERE VarName IN ("config_files", "configs_parsed", "sections_parsed")'; - $this->Conn->Query($sql); - + $this->Application->HandleEvent($event, 'adm:OnResetConfigsCache'); $this->Application->HandleEvent($event, 'c:OnResetCMSMenuCache'); if ($refresh_permissions) { @@ -805,12 +802,7 @@ } else { // refresh permissions with ajax progress bar (when available) - $fields_hash = Array ( - 'VarName' => 'ForcePermCacheUpdate', - 'Data' => 1, - ); - - $this->Conn->doInsert($fields_hash, TABLE_PREFIX . 'Cache'); + $this->Application->setDBCache('ForcePermCacheUpdate', 1); } } } Index: core/install/remove_schema.sql =================================================================== --- core/install/remove_schema.sql (revision 13152) +++ core/install/remove_schema.sql (working copy) @@ -68,3 +68,4 @@ DROP TABLE FormSubmissions; DROP TABLE Forms; DROP TABLE Semaphores; +DROP TABLE CachedUrls; Index: core/install/upgrades.sql =================================================================== --- core/install/upgrades.sql (revision 13161) +++ core/install/upgrades.sql (working copy) @@ -1652,3 +1652,33 @@ DELETE FROM PersistantSessionData WHERE VariableName = 'phrases_columns_.'; UPDATE Category SET FormId = NULL WHERE FormId = 0; + +INSERT INTO ConfigurationAdmin VALUES ('MemcacheServers', 'la_section_SettingsCaching', 'la_config_MemcacheServers', 'text', '', '', 80.01, 0, 0); +INSERT INTO ConfigurationValues VALUES (DEFAULT, 'MemcacheServers', 'localhost:11211', 'In-Portal', 'in-portal:configure_advanced'); + +ALTER TABLE Category + ADD EnablePageCache TINYINT NOT NULL DEFAULT '0', + ADD OverridePageCacheKey TINYINT NOT NULL DEFAULT '0', + ADD PageCacheKey VARCHAR(255) NOT NULL DEFAULT '', + ADD PageExpiration INT NULL DEFAULT NULL , + ADD INDEX (EnablePageCache), + ADD INDEX (OverridePageCacheKey), + ADD INDEX (PageExpiration); + +DELETE FROM Cache WHERE VarName LIKE 'mod_rw_%'; + +CREATE TABLE CachedUrls ( + UrlId int(11) NOT NULL AUTO_INCREMENT, + Url varchar(255) NOT NULL DEFAULT '', + `Hash` int(11) NOT NULL DEFAULT '0', + Prefixes varchar(255) NOT NULL DEFAULT '', + ParsedVars text NOT NULL, + Cached int(10) unsigned DEFAULT NULL, + LifeTime int(11) NOT NULL DEFAULT '-1', + PRIMARY KEY (UrlId), + KEY Url (Url), + KEY `Hash` (`Hash`), + KEY Prefixes (Prefixes), + KEY Cached (Cached), + KEY LifeTime (LifeTime) +); \ No newline at end of file Index: core/kernel/application.php =================================================================== --- core/kernel/application.php (revision 13152) +++ core/kernel/application.php (working copy) @@ -198,11 +198,11 @@ var $CurrentNTag = null; /** - * Memcache object pointer + * Object of memory caching class * - * @var Memcache + * @var kCache */ - var $Memcached = null; + var $memoryCache = null; /** * Tells, that administrator has authentificated in administrative console @@ -254,40 +254,6 @@ return $instance; } - function InitMemcached() - { - return ; - - $memcached_servers = 'localhost:11211'; // $this->Application->ConfigValue('MemcachedServers'); - - if ($memcached_servers && class_exists('Memcache')) { - $this->Memcached = new Memcache(); - $servers = explode(';', $memcached_servers); - foreach ($servers as $server) { - list ($server, $port) = strpos($server, ':') !== false ? explode(':', $server, 2) : Array ($server, 11211); - $this->Memcached->addServer($server, $port); - } - } - - //try to set something to cache, if not working - set $this->Memcached to null - } - - function CacheSet($name, $value, $expiration) - { - if (isset($this->Memcached)) { - return $this->Memcached->set($name, $value, 0, $expiration); - } - return false; - } - - function CacheGet($name) - { - if (isset($this->Memcached)) { - return $this->Memcached->get($name); - } - return false; - } - /** * Initializes the Application * @@ -305,8 +271,6 @@ $this->isAdmin = constOn('ADMIN'); - $this->InitMemcached(); - if (!constOn('SKIP_OUT_COMPRESSION')) { ob_start(); // collect any output from method (other then tags) into buffer } @@ -333,6 +297,7 @@ $this->Factory = new kFactory(); $this->registerDefaultClasses(); $this->Phrases = new PhrasesCache(); + $this->memoryCache =& $this->Factory->makeClass('Cache'); $this->EventManager =& $this->Factory->makeClass('EventManager'); $this->Factory->Storage['EventManager'] =& $this->EventManager; $this->RegisterDefaultBuildEvents(); @@ -386,13 +351,11 @@ $this->LoadCache(); $this->InitConfig(); - $this->Phrases->Init('phrases'); - if (defined('DEBUG_MODE') && $this->isDebugMode()) { $this->Debugger->appendTimestamp('Loaded cache and phrases'); } - $this->ValidateLogin(); + $this->ValidateLogin(); // must be called before AfterConfigRead, because current user should be available there $this->UnitConfigReader->AfterConfigRead(); @@ -400,20 +363,20 @@ $this->Debugger->appendTimestamp('Processed AfterConfigRead'); } - /*// Module items are recalled during url parsing & PhrasesCache is needed already there, - // because it's used in their build events. That's why phrases cache initialization is - // called from kHTTPQuery in case when mod_rewrite is used - if (!$this->RewriteURLs()) { - $this->Phrases = new PhrasesCache(); - }*/ + if ($this->GetVar('m_cat_id') === false) { + $this->SetVar('m_cat_id', 0); + } - if ($this->GetVar('m_cat_id') === false) $this->SetVar('m_cat_id', 0); if (!$this->RecallVar('curr_iso')) { $this->StoreVar('curr_iso', $this->GetPrimaryCurrency(), true); // true for optional } - $this->SetVar('visits_id', $this->RecallVar('visit_id') ); + $visit_id = $this->RecallVar('visit_id'); + if ($visit_id !== false) { + $this->SetVar('visits_id', $visit_id); + } + $language =& $this->recallObject( 'lang.current', null, Array('live_table' => true) ); if (preg_match('/utf-8/', $language->GetDBField('Charset'))) { setlocale(LC_ALL, 'en_US.UTF-8'); @@ -494,8 +457,8 @@ $language_id = 'default'; } - $this->SetVar('lang.current_id', $language_id ); - $this->SetVar('m_lang', $language_id ); + $this->SetVar('lang.current_id', $language_id); + $this->SetVar('m_lang', $language_id); $lang_mode = $this->GetVar('lang_mode'); $this->SetVar('lang_mode', ''); @@ -567,17 +530,23 @@ function GetDefaultLanguageId($init = false) { - static $language_info = null; + $cache_key = 'primary_language_info[%LangSerial%]'; + $language_info = $this->getCache($cache_key); - if (!isset($language_info)) { + if ($language_info === false) { // cache primary language info first $table = $this->getUnitOption('lang', 'TableName'); $id_field = $this->getUnitOption('lang', 'IDField'); + $this->Conn->nextQueryCachable = true; $sql = 'SELECT ' . $id_field . ', IF(AdminInterfaceLang, "Admin", "Front") AS LanguageKey FROM ' . $table . ' WHERE (AdminInterfaceLang = 1 OR PrimaryLang = 1) AND (Enabled = 1)'; $language_info = $this->Conn->GetCol($sql, 'LanguageKey'); + + if ($language_info !== false) { + $this->setCache($cache_key, $language_info); + } } $language_key = ($this->isAdmin && $init) || count($language_info) == 1 ? 'Admin' : 'Front'; @@ -611,12 +580,20 @@ $theme_id = 999; } else { - $table = $this->getUnitOption('theme','TableName'); - $id_field = $this->getUnitOption('theme','IDField'); - $sql = 'SELECT '.$id_field.' - FROM '.$table.' - WHERE (PrimaryTheme = 1) AND (Enabled = 1)'; - $theme_id = $this->Conn->GetOne($sql); + $cache_key = 'primary_theme[%ThemeSerial%]'; + $theme_id = $this->getCache($cache_key); + + if ($theme_id === false) { + $this->Conn->nextQueryCachable = true; + $sql = 'SELECT ' . $this->getUnitOption('theme', 'IDField') . ' + FROM ' . $this->getUnitOption('theme', 'TableName') . ' + WHERE (PrimaryTheme = 1) AND (Enabled = 1)'; + $theme_id = $this->Conn->GetOne($sql); + + if ($theme_id !== false) { + $this->setCache($cache_key, $theme_id); + } + } } return $theme_id; @@ -624,13 +601,25 @@ function GetPrimaryCurrency() { - if ($this->isModuleEnabled('In-Commerce')) { - $table = $this->getUnitOption('curr', 'TableName'); - return $this->Conn->GetOne('SELECT ISO FROM '.$table.' WHERE IsPrimary = 1'); + $cache_key = 'primary_currency[%CurrSerial%]'; + $primary_currency = $this->getCache($cache_key); + + if ($primary_currency === false) { + if ($this->isModuleEnabled('In-Commerce')) { + $this->Conn->nextQueryCachable = true; + $sql = 'SELECT ISO + FROM ' . $this->getUnitOption('curr', 'TableName') . ' + WHERE IsPrimary = 1'; + $primary_currency = $this->Conn->GetOne($sql); + } + else { + $primary_currency = 'USD'; + } + + $this->setCache($cache_key, $primary_currency); } - else { - return 'USD'; - } + + return $primary_currency; } /** @@ -693,99 +682,231 @@ } /** - * Returns item's filename that corresponds id passed. If possible, then get it from cache + * Returns cached category informaton by given cache name. All given category + * information is recached, when at least one of 4 caches is missing. * - * @param string $prefix - * @param int $id + * @param int $category_id + * @param string $name cache name = {filenames, category_designs, category_tree} * @return string */ - function getFilename($prefix, $id, $category_id=null) + function getCategoryCache($category_id, $name) { - $filename = $this->getCache('filenames', $prefix.'_'.$id); - if ($filename === false) { - $table = $this->getUnitOption($prefix, 'TableName'); - $id_field = $this->getUnitOption($prefix, 'IDField'); + $serial_name = '[%CIDSerial:' . $category_id . '%]'; + $cache_key = $name . $serial_name; + $ret = $this->getCache($cache_key); - if ($prefix == 'c') { - if(!$id) { - $this->setCache('filenames', $prefix.'_'.$id, ''); - return ''; - } + if ($ret === false) { + if (!$category_id) { + // don't query database for "Home" category (ID = 0), because it doesn't exist in database + return false; + } - // this allows to save 2 sql queries for each category - $sql = 'SELECT NamedParentPath, CachedTemplate, TreeLeft, TreeRight - FROM '.$table.' - WHERE '.$id_field.' = '.$this->Conn->qstr($id); - $category_data = $this->Conn->GetRow($sql); + // this allows to save 2 sql queries for each category + $this->Conn->nextQueryCachable = true; + $sql = 'SELECT NamedParentPath, CachedTemplate, TreeLeft, TreeRight + FROM ' . TABLE_PREFIX . 'Category + WHERE CategoryId = ' . (int)$category_id; + $category_data = $this->Conn->GetRow($sql); + if ($category_data !== false) { // only direct links to category pages work (symlinks, container pages and so on won't work) - $filename = $category_data['NamedParentPath']; - $this->setCache('category_templates', $id, $filename); - $this->setCache('category_designs', $id, ltrim($category_data['CachedTemplate'], '/')); - $this->setCache('category_tree', $id, $category_data['TreeLeft'] . ';' . $category_data['TreeRight']); + $this->setCache('filenames' . $serial_name, $category_data['NamedParentPath']); + $this->setCache('category_designs' . $serial_name, ltrim($category_data['CachedTemplate'], '/')); + $this->setCache('category_tree' . $serial_name, $category_data['TreeLeft'] . ';' . $category_data['TreeRight']); } - else { - $sql = 'SELECT ResourceId - FROM ' . $table . ' - WHERE ' . $id_field . ' = ' . $this->Conn->qstr($id); - $resource_id = $this->Conn->GetOne($sql); + } - if (is_null($category_id)) { - $category_id = $this->GetVar('m_cat_id'); - } + return $this->getCache($cache_key); + } - $sql = 'SELECT Filename - FROM ' . TABLE_PREFIX . 'CategoryItems - WHERE (ItemResourceId = ' . $resource_id . ') AND (CategoryId = ' . (int)$category_id . ')'; - $filename = $this->Conn->GetOne($sql); + /** + * Returns item's filename that corresponds id passed. If possible, then get it from cache + * + * @param string $prefix + * @param int $id + * @param int $category_id + * @return string + */ + function getFilename($prefix, $id, $category_id = null) + { + if ($prefix == 'c') { + trigger_error('Method "' . __FUNCTION__ . '" no longer work with "c" prefix. Please use "getCategoryCache" method instead.', E_USER_ERROR); + return false; + } - /*if (!$filename) { - $sql = 'SELECT Filename - FROM ' . TABLE_PREFIX . 'CategoryItems - WHERE ItemResourceId = ' . $resource_id . ' AND PrimaryCat = 1'; - $filename = $this->Conn->GetOne($sql); - } + $category_id = isset($category_id) ? $category_id : $this->GetVar('m_cat_id'); - $sql = 'SELECT Filename - FROM ' . $table . ' - WHERE ' . $id_field . ' = ' . $this->Conn->qstr($id); - $filename = $this->Conn->GetOne($sql);*/ + $cache_key = 'filenames[%' . $this->incrementCacheSerial($prefix, $id, false) . '%]:' . (int)$category_id; + $filename = $this->getCache($cache_key); + + if ($filename === false) { + $this->Conn->nextQueryCachable = true; + $sql = 'SELECT ResourceId + FROM ' . $this->getUnitOption($prefix, 'TableName') . ' + WHERE ' . $this->getUnitOption($prefix, 'IDField') . ' = ' . $this->Conn->qstr($id); + $resource_id = $this->Conn->GetOne($sql); + + $this->Conn->nextQueryCachable = true; + $sql = 'SELECT Filename + FROM ' . TABLE_PREFIX . 'CategoryItems + WHERE (ItemResourceId = ' . $resource_id . ') AND (CategoryId = ' . (int)$category_id . ')'; + $filename = $this->Conn->GetOne($sql); + + if ($filename !== false) { + $this->setCache($cache_key, $filename); } - $this->setCache('filenames', $prefix.'_'.$id, $filename); } + return $filename; } + /** + * Returns caching type (none, memory, temporary) + * + * @return int + */ + function isCachingType($caching_type) + { + return $this->memoryCache->getCachingType() == $caching_type; + } /** + * Increments serial based on prefix and it's ID (optional) + * + * @param string $prefix + * @param int $id ID (value of IDField) or ForeignKeyField:ID + * @param bool $increment + */ + function incrementCacheSerial($prefix, $id = null, $increment = true) + { + $pascal_case_prefix = implode('', array_map('ucfirst', explode('-', $prefix))); + $serial_name = $pascal_case_prefix . (isset($id) ? 'IDSerial:' . $id : 'Serial'); + + if ($increment) { + if (defined('DEBUG_MODE') && DEBUG_MODE && $this->isDebugMode()) { + $this->Application->Debugger->appendHTML('Incrementing serial: ' . $serial_name . '.'); + } + + if ($this->isCachingType(CACHING_TYPE_MEMORY)) { + $this->setCache($serial_name, (int)$this->getCache($serial_name) + 1); + } + + // delete cached mod-rewrite urls related to given prefix and id + $delete_clause = isset($id) ? $prefix . ':' . $id : $prefix; + + $sql = 'DELETE FROM ' . TABLE_PREFIX . 'CachedUrls + WHERE Prefixes LIKE ' . $this->Conn->qstr('%|' . $delete_clause . '|%'); + $this->Conn->Query($sql); + } + + return $serial_name; + } + + /** * Adds new value to cache $cache_name and identified by key $key * - * @param string $cache_name cache name * @param int $key key name to add to cache * @param mixed $value value of chached record + * @param int $expiration when value expires (0 - doesn't expire) */ - function setCache($cache_name, $key, $value, $expiration=3600) + function setCache($key, $value, $expiration = 0) { - $cache =& $this->recallObject('Cache'); - /* @var $cache kCache */ + return $this->memoryCache->setCache($key, $value, $expiration); + } - return $cache->setCache($cache_name, $key, $value, $expiration); + /** + * Sets value to database cache + * + * @param string $name + * @param mixed $value + * @param int $expiration + */ + function setDBCache($name, &$value, $expiration = false) + { + if ((int)$expiration <= 0) { + $expiration = -1; + } + + $fields_hash = Array ( + 'VarName' => $name, + 'Data' => &$value, + 'Cached' => adodb_mktime(), + 'LifeTime' => (int)$expiration, + ); + + $this->Conn->nextQueryCachable = true; + $this->Conn->doInsert($fields_hash, TABLE_PREFIX . 'Cache', 'REPLACE'); } /** * Returns cached $key value from cache named $cache_name * - * @param string $cache_name cache name * @param int $key key name from cache + * @param bool $store_locally store data locally after retrieved * @return mixed */ - function getCache($cache_name, $key) + function getCache($key, $store_locally = true) { - $cache =& $this->recallObject('Cache'); - return $cache->getCache($cache_name, $key); + return $this->memoryCache->getCache($key, $store_locally); } /** + * Returns value from database cache + * + * @param string $name key name + * @return mixed + */ + function getDBCache($name) + { + $this->Conn->nextQueryCachable = true; + + $sql = 'SELECT Data, Cached, LifeTime + FROM ' . TABLE_PREFIX . 'Cache + WHERE VarName = ' . $this->Conn->qstr($name); + $data = $this->Conn->GetRow($sql); + + if ($data) { + $lifetime = (int)$data['LifeTime']; // in seconds + if (($lifetime > 0) && ($data['Cached'] + $lifetime < adodb_mktime())) { + // delete expired + $this->Conn->nextQueryCachable = true; + + $sql = 'DELETE FROM ' . TABLE_PREFIX . 'Cache + WHERE VarName = ' . $this->Conn->qstr($name); + $this->Conn->Query($sql); + + return false; + } + + return $data['Data']; + } + + return false; + } + + /** + * Deletes key from cache + * + * @param string $key + */ + function deleteCache($key) + { + $this->memoryCache->delete($key); + } + + /** + * Deletes key from database cache + * + * @param string $name + */ + function deleteDBCache($name) + { + $sql = 'DELETE FROM ' . TABLE_PREFIX . 'Cache + WHERE VarName = ' . $this->Conn->qstr($name); + $this->Conn->Query($sql); + } + + /** * Defines default constants if it's not defined before - in config.php * * @access private @@ -805,13 +926,18 @@ k4_include_once(KERNEL_PATH.'/constants.php'); } - if (!$this->ModuleInfo) return false; - foreach($this->ModuleInfo as $module_name => $module_info) - { - $module_path = '/'.$module_info['Path']; - $contants_file = FULL_PATH.$module_path.'constants.php'; - if( file_exists($contants_file) ) k4_include_once($contants_file); + if (!$this->ModuleInfo) { + return false; } + + foreach ($this->ModuleInfo as $module_name => $module_info) { + $contants_file = FULL_PATH . '/' . $module_info['Path'] . 'constants.php'; + + if (file_exists($contants_file)) { + k4_include_once($contants_file); + } + } + return true; } @@ -937,8 +1063,7 @@ $this->Session->SaveData(); if (constOn('DBG_CACHE')) { - $cache =& $this->recallObject('Cache'); - $cache->printStatistics(); + $this->memoryCache->printStatistics(); } $this->HTML = ob_get_clean() . $this->HTML . $this->Debugger->printReport(true); @@ -1411,7 +1536,8 @@ $target_url = 'http://'.DOMAIN.$this->ConfigValue('Site_Path'); } - if (!preg_match('#'.preg_quote($cookie_url).'#', $target_url)) { + // set session to GET_ONLY, to pass sid only if sid is REAL AND session is set + if (!preg_match('#'.preg_quote($cookie_url).'#', $target_url) && $session->SessionSet) { $session->SetMode(smGET_ONLY); } } @@ -1847,17 +1973,15 @@ } if (strtolower($t) == '__default__') { - // to put category & item templates into cache - $filename = $this->getFilename('c', $category_id); if (is_numeric($item_id)) { $mod_rw_helper =& $this->Application->recallObject('ModRewriteHelper'); /* @var $mod_rw_helper kModRewriteHelper */ $t = $mod_rw_helper->GetItemTemplate($category_id, $pass_element); // $pass_element should be the last processed element -// $t = $this->getCache('item_templates', $category_id); + // $t = $this->getCategoryCache($category_id, 'item_templates'); } elseif ($category_id) { - $t = strtolower( preg_replace('/^Content\//i', '', $this->getCache('category_templates', $category_id)) ); + $t = strtolower(preg_replace('/^Content\//i', '', $this->getCategoryCache($category_id, 'filenames') )); } else { $t = 'index'; @@ -2057,39 +2181,44 @@ $this->Session->LoadPersistentVars(); } - function LoadCache() { - $cache_key = $this->GetVar('t').$this->GetVar('m_theme').$this->GetVar('m_lang').$this->isAdmin; - $query = sprintf("SELECT PhraseList, ConfigVariables FROM %s WHERE Template = %s", - TABLE_PREFIX.'PhraseCache', - $this->Conn->qstr(md5($cache_key))); - $res = $this->Conn->GetRow($query); + function LoadCache() + { + // TODO: maybe language part isn't required, since same phrase from different languages have one ID now + $cache_key = $this->GetVar('t') . $this->GetVar('m_theme') . $this->GetVar('m_lang') . $this->isAdmin; + $sql = 'SELECT PhraseList, ConfigVariables + FROM ' . TABLE_PREFIX . 'PhraseCache + WHERE Template = ' . $this->Conn->qstr( md5($cache_key) ); + $res = $this->Conn->GetRow($sql); + if ($res) { - $this->Caches['PhraseList'] = $res['PhraseList'] ? explode(',', $res['PhraseList']) : array(); + $this->Caches['PhraseList'] = $res['PhraseList'] ? explode(',', $res['PhraseList']) : Array (); + $config_ids = $res['ConfigVariables'] ? explode(',', $res['ConfigVariables']) : Array (); - $config_ids = $res['ConfigVariables'] ? explode(',', $res['ConfigVariables']) : array(); if (isset($this->Caches['ConfigVariables'])) { $config_ids = array_diff($config_ids, $this->Caches['ConfigVariables']); } } else { - $config_ids = array(); + $config_ids = Array (); } + + $this->Phrases->Init('phrases'); $this->Caches['ConfigVariables'] = $config_ids; $this->ConfigCacheIds = $config_ids; } + /** + * Loads template mapping for Front-End + * + */ function LoadStructureTemplateMapping() { - // get template mapping - $sql = 'SELECT Data - FROM ' . TABLE_PREFIX . 'Cache - WHERE VarName = "template_mapping"'; - $template_mapping = $this->Conn->GetOne($sql); + if (!$this->isAdmin) { + $category_helper =& $this->Application->recallObject('CategoryHelper'); + /* @var $category_helper CategoryHelper */ - if (!$this->isAdmin && $template_mapping) { - // template mappings only for Front-End - $this->structureTemplateMapping = unserialize($template_mapping); + $this->structureTemplateMapping = $category_helper->getTemplateMapping(); } } @@ -2099,14 +2228,15 @@ //something changed $update = $update || $this->Phrases->NeedsCacheUpdate(); $update = $update || (count($this->ConfigCacheIds) && $this->ConfigCacheIds != $this->Caches['ConfigVariables']); + if ($update) { $cache_key = $this->GetVar('t').$this->GetVar('m_theme').$this->GetVar('m_lang').$this->isAdmin; $query = sprintf("REPLACE %s (PhraseList, CacheDate, Template, ConfigVariables) VALUES (%s, %s, %s, %s)", TABLE_PREFIX.'PhraseCache', - $this->Conn->Qstr(join(',', $this->Phrases->Ids)), + $this->Conn->qstr(join(',', $this->Phrases->Ids)), adodb_mktime(), - $this->Conn->Qstr(md5($cache_key)), + $this->Conn->qstr(md5($cache_key)), $this->Conn->qstr(implode(',', array_unique($this->ConfigCacheIds)))); $this->Conn->Query($query); } @@ -2115,9 +2245,10 @@ function InitConfig() { if (isset($this->Caches['ConfigVariables']) && count($this->Caches['ConfigVariables']) > 0) { - $this->ConfigHash = array_merge($this->ConfigHash, $this->Conn->GetCol( - 'SELECT VariableValue, VariableName FROM '.TABLE_PREFIX.'ConfigurationValues - WHERE VariableId IN ('.implode(',', $this->Caches['ConfigVariables']).')', 'VariableName')); + $sql = 'SELECT VariableValue, VariableName + FROM ' . TABLE_PREFIX . 'ConfigurationValues + WHERE VariableId IN (' . implode(',', $this->Caches['ConfigVariables']) . ')'; + $this->ConfigHash = array_merge($this->ConfigHash, $this->Conn->GetCol($sql, 'VariableName')); } } @@ -2988,10 +3119,8 @@ */ function getTreeIndex($category_id) { - $category_template = $this->getFilename('c', $category_id); // to rebuild "category_tree" cache + $tree_index = $this->getCategoryCache($category_id, 'category_tree'); - $tree_index = $this->getCache('category_tree', $category_id); - if ($tree_index) { $ret = Array (); list ($ret['TreeLeft'], $ret['TreeRight']) = explode(';', $tree_index); Index: core/kernel/db/cat_event_handler.php =================================================================== --- core/kernel/db/cat_event_handler.php (revision 13159) +++ core/kernel/db/cat_event_handler.php (working copy) @@ -814,13 +814,18 @@ )'); // hot items (cache updated every hour) - $sql = 'SELECT Data - FROM '.TABLE_PREFIX.'Cache - WHERE (VarName = "'.$property_map['HotLimit'].'") AND (Cached >'.(adodb_mktime() - 3600).')'; - $hot_limit = $this->Conn->GetOne($sql); + if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { + $serial_name = $this->Application->incrementCacheSerial($event->Prefix, null, false); + $hot_limit = $this->Application->getCache($property_map['HotLimit'] . '[%' . $serial_name . '%]'); + } + else { + $hot_limit = $this->Application->getDBCache($property_map['HotLimit']); + } + if ($hot_limit === false) { $hot_limit = $this->CalculateHotLimit($event); } + $object->addCalculatedField('IsHot', ' IF(%1$s.HotItem = 2, IF(%1$s.'.$property_map['ClickField'].' >= '.$hot_limit.', 1, 0), %1$s.HotItem @@ -840,9 +845,11 @@ function CalculateHotLimit(&$event) { $property_map = $this->Application->getUnitOption($event->Prefix, 'ItemPropertyMappings'); + if (!$property_map) { return; } + $click_field = $property_map['ClickField']; $last_hot = $this->Application->ConfigValue($property_map['MaxHotNumber']) - 1; @@ -851,10 +858,16 @@ LIMIT '.$last_hot.', 1'; $res = $this->Conn->GetCol($sql); $hot_limit = (double)array_shift($res); - $this->Conn->Query('REPLACE INTO '.TABLE_PREFIX.'Cache (VarName, Data, Cached) VALUES ("'.$property_map['HotLimit'].'", "'.$hot_limit.'", '.adodb_mktime().')'); + + if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { + $serial_name = $this->Application->incrementCacheSerial($event->Prefix, null, false); + $this->Application->setCache($property_map['HotLimit'] . '[%' . $serial_name . '%]', $hot_limit); + } + else { + $this->Application->setDBCache($property_map['HotLimit'], $hot_limit, 3600); + } + return $hot_limit; - - return 0; } /** @@ -1604,10 +1617,7 @@ case 'HotItem': $hot_limit_var = getArrayValue($property_mappings, 'HotLimit'); if ($hot_limit_var) { - $sql = 'SELECT Data - FROM '.TABLE_PREFIX.'Cache - WHERE VarName = "'.$hot_limit_var.'"'; - $hot_limit = (int)$this->Conn->GetOne($sql); + $hot_limit = (int)$this->Application->getDBCache($hot_limit_var); $condition = 'IF('.$items_table.'.HotItem = 2, IF('.$items_table.'.Hits >= '. Index: core/kernel/db/cat_tag_processor.php =================================================================== --- core/kernel/db/cat_tag_processor.php (revision 13159) +++ core/kernel/db/cat_tag_processor.php (working copy) @@ -241,7 +241,15 @@ $object =& $this->getObject($params); $category_id = isset($params['cat_id']) ? $params['cat_id'] : $object->GetDBField('CategoryId'); - $category_path = $this->Application->getCache('category_paths', $category_id); + $cache_key = 'category_paths[%CIDSerial:' . $category_id . '%]'; + + if ($category_id == 0) { + // home category name is phrase AND phrase name is defined in configuration + $cache_key .= '[%PhrasesSerial%][%ConfSerial%]'; + } + + $category_path = $this->Application->getCache($cache_key); + if ($category_path === false) { // not chached if ($category_id > 0) { @@ -251,8 +259,10 @@ else { $category_path = $this->Application->Phrase( $this->Application->ConfigValue('Root_Name') ); } - $this->Application->setCache('category_paths', $category_id, $category_path); + + $this->Application->setCache($cache_key, $category_path); } + return $category_path; } @@ -336,10 +346,22 @@ function HasAdditionalImages($params) { $object =& $this->getObject($params); - $sql = 'SELECT ImageId - FROM '.$this->Application->getUnitOption('img', 'TableName').' - WHERE ResourceId = '.$object->GetDBField('ResourceId').' AND DefaultImg != 1 AND Enabled = 1'; - return $this->Conn->GetOne($sql) ? 1 : 0; + /* @var $object kDBItem */ + + $cache_key = 'product_additional_images[%PIDSerial:' . $object->GetID() . '%]'; + $ret = $this->Application->getCache($cache_key); + + if ($ret === false) { + $this->Conn->nextQueryCachable = true; + $sql = 'SELECT ImageId + FROM ' . $this->Application->getUnitOption('img', 'TableName') . ' + WHERE ResourceId = ' . $object->GetDBField('ResourceId') . ' AND DefaultImg != 1 AND Enabled = 1'; + $ret = $this->Conn->GetOne($sql) ? 1 : 0; + + $this->Application->setCache($cache_key, $ret); + } + + return $ret; } /** @@ -589,29 +611,37 @@ */ function LastUpdated($params) { - $category_id = $this->Application->GetVar('m_cat_id'); - $table_name = $this->Application->getUnitOption($this->Prefix, 'TableName'); + $category_id = (int)$this->Application->GetVar('m_cat_id'); + $local = array_key_exists('local', $params) && ($category_id > 0) ? $params['local'] : false; - if (isset($params['local']) && $params['local'] && $category_id > 0) { - // scan only current category & it's children - $sql = 'SELECT TreeLeft, TreeRight - FROM ' . TABLE_PREFIX . 'Category - WHERE CategoryId = ' . (int)$category_id; - $tree_info = $this->Conn->GetRow($sql); + $serial_name1 = $this->Application->incrementCacheSerial('c', $local ? $category_id : null, false); + $serial_name2 = $this->Application->incrementCacheSerial($this->Prefix, null, false); + $cache_key = 'categoryitems_last_updated[%' . $serial_name1 . '%][%' . $serial_name2 . '%]'; - $sql = 'SELECT MAX(item_table.Modified) AS ModDate, MAX(item_table.CreatedOn) AS NewDate - FROM '.$table_name.' item_table - LEFT JOIN '.TABLE_PREFIX.'CategoryItems ci ON (item_table.ResourceId = ci.ItemResourceId) - LEFT JOIN '.TABLE_PREFIX.'Category c ON c.CategoryId = ci.CategoryId - WHERE c.TreeLeft BETWEEN '.$tree_info['TreeLeft'].' AND '.$tree_info['TreeRight']; - } - else { - // scan all categories in system - $sql = 'SELECT MAX(Modified) AS ModDate, MAX(CreatedOn) AS NewDate - FROM '.$table_name; - } + $row_data = $this->Application->getCache($cache_key); - $row_data = $this->Conn->GetRow($sql); + if ($row_data === false) { + if ($local && ($category_id > 0)) { + // scan only current category & it's children + list ($tree_left, $tree_right) = $this->Application->getTreeIndex($category_id); + + $sql = 'SELECT MAX(item_table.Modified) AS ModDate, MAX(item_table.CreatedOn) AS NewDate + FROM ' . $this->Application->getUnitOption($this->Prefix, 'TableName') . ' item_table + LEFT JOIN ' . TABLE_PREFIX . 'CategoryItems ci ON (item_table.ResourceId = ci.ItemResourceId) + LEFT JOIN ' . TABLE_PREFIX . 'Category c ON c.CategoryId = ci.CategoryId + WHERE c.TreeLeft BETWEEN ' . $tree_left . ' AND ' . $tree_right; + } + else { + // scan all categories in system + $sql = 'SELECT MAX(Modified) AS ModDate, MAX(CreatedOn) AS NewDate + FROM ' . $this->Application->getUnitOption($this->Prefix, 'TableName'); + } + + $this->Conn->nextQueryCachable = true; + $row_data = $this->Conn->GetRow($sql); + $this->Application->setCache($cache_key, $row_data); + } + if (!$row_data) { return ''; } Index: core/kernel/db/db_connection.php =================================================================== --- core/kernel/db/db_connection.php (revision 13152) +++ core/kernel/db/db_connection.php (working copy) @@ -122,6 +122,13 @@ var $_queryTime = 0; /** + * Indicates, that next database query could be cached, when memory caching is enabled + * + * @var bool + */ + var $nextQueryCachable = false; + + /** * Initializes connection class with * db type to used in future * @@ -489,10 +496,11 @@ } // set 2nd checkpoint: begin - $first_cell = count($ret) == 1 && count(current($ret)) == 1 ? current(current($ret)) : null; if ($profileSQLs) { - $debugger->profileFinish('sql_'.$queryID, null, null, $this->getAffectedRows(), $first_cell, $this->_queryCount); + $first_cell = count($ret) == 1 && count(current($ret)) == 1 ? current(current($ret)) : null; + $debugger->profileFinish('sql_'.$queryID, null, null, $this->getAffectedRows(), $first_cell, $this->_queryCount, $this->nextQueryCachable); $debugger->profilerAddTotal('sql', 'sql_'.$queryID); + $this->nextQueryCachable = false; } // set 2nd checkpoint: end @@ -502,8 +510,9 @@ else { // set 2nd checkpoint: begin if ($profileSQLs) { - $debugger->profileFinish('sql_'.$queryID, null, null, $this->getAffectedRows(), null, $this->_queryCount); + $debugger->profileFinish('sql_'.$queryID, null, null, $this->getAffectedRows(), null, $this->_queryCount, $this->nextQueryCachable); $debugger->profilerAddTotal('sql', 'sql_'.$queryID); + $this->nextQueryCachable = false; } // set 2nd checkpoint: end } Index: core/kernel/db/db_event_handler.php =================================================================== --- core/kernel/db/db_event_handler.php (revision 13161) +++ core/kernel/db/db_event_handler.php (working copy) @@ -279,7 +279,7 @@ $resulting_ids = $direct_ids ? array_values($direct_ids) : ($ids ? array_values($ids) : false); if ($resulting_ids) { $this->Application->SetVar($event->getPrefixSpecial() . '_selected_ids', implode(',', $resulting_ids)); - $this->Application->LinkVar($event->getPrefixSpecial() . '_selected_ids', $session_name); + $this->Application->LinkVar($event->getPrefixSpecial() . '_selected_ids', $session_name, '', true); $this->Application->SetVar($event->getPrefixSpecial() . '_id', $resulting_ids[0]); return $resulting_ids; @@ -1600,6 +1600,44 @@ return ; } + // TODO: 2. optimize change log records (replace multiple changes to same record with one change record) + + $to_increment = Array (); + + // 3. collect serials to reset based on foreign keys + foreach ($changes as $index => $rec) { + if (array_key_exists('DependentFields', $rec)) { + + foreach ($rec['DependentFields'] as $field_name => $field_value) { + // will be "ci|ItemResourceId:345" + $to_increment[] = $rec['Prefix'] . '|' . $field_name . ':' . $field_value; + } + + unset($changes[$index]['DependentFields']); + } + + unset($changes[$index]['ParentId'], $changes[$index]['ParentPrefix']); + } + + // 4. collect serials to reset based on changed ids + foreach ($changes as $change) { + $to_increment[] = $change['MasterPrefix'] . '|' . $change['MasterId']; + + if ($change['MasterPrefix'] != $change['Prefix']) { + $to_increment[] = $change['Prefix'] . '|' . $change['ItemId']; + } + } + + // 5. reset serials collected before + $to_increment = array_unique($to_increment); + $this->Application->incrementCacheSerial($this->Prefix); + + foreach ($to_increment as $to_increment_mixed) { + list ($to_increment_prefix, $to_increment_id) = explode('|', $to_increment_mixed, 2); + + $this->Application->incrementCacheSerial($to_increment_prefix, $to_increment_id); + } + // save changes to database $sesion_log_id = $this->Application->RecallVar('_SessionLogId_'); @@ -1619,10 +1657,14 @@ $this->Conn->doInsert(array_merge($rec, $add_fields), $change_log_table); } + $this->Application->incrementCacheSerial('change-log'); + $sql = 'UPDATE ' . $this->Application->getUnitOption('session-log', 'TableName') . ' SET AffectedItems = AffectedItems + ' . count($changes) . ' WHERE SessionLogId = ' . $sesion_log_id; $this->Conn->Query($sql); + + $this->Application->incrementCacheSerial('session-log'); } /** Index: core/kernel/db/db_tag_processor.php =================================================================== --- core/kernel/db/db_tag_processor.php (revision 13159) +++ core/kernel/db/db_tag_processor.php (working copy) @@ -526,11 +526,12 @@ $displayed = array(); $column_number = 1; - $cache_mod_rw = $this->Application->getUnitOption($this->Prefix, 'CacheModRewrite') && $this->Application->RewriteURLs(); + $cache_mod_rw = $this->Application->getUnitOption($this->Prefix, 'CacheModRewrite') && + $this->Application->RewriteURLs() && !$this->Application->isCachingType(CACHING_TYPE_MEMORY); + $limit = isset($params['limit']) ? $params['limit'] : false; - while (!$list->EOL() && (!$limit || $i<$limit)) - { + while (!$list->EOL() && (!$limit || $i<$limit)) { $this->Application->SetVar( $this->getPrefixSpecial().'_id', $list->GetDBField($id_field) ); // for edit/delete links using GET $this->Application->SetVar( $this->Prefix.'_id', $list->GetDBField($id_field) ); $block_params['is_last'] = ($i == $list->SelectedCount - 1); @@ -538,14 +539,18 @@ $block_params['not_last'] = !$block_params['is_last']; // for front-end if ($cache_mod_rw) { + $serial_name = $this->Application->incrementCacheSerial($this->Prefix, $list->GetDBField($id_field), false); + if ($this->Prefix == 'c') { // for listing subcategories in category - $this->Application->setCache('filenames', $this->Prefix.'_'.$list->GetDBField($id_field), $list->GetDBField('NamedParentPath')); - $this->Application->setCache('category_tree', $list->GetDBField($id_field), $list->GetDBField('TreeLeft') . ';' . $list->GetDBField('TreeRight')); + $this->Application->setCache('filenames[%' . $serial_name . '%]' , $list->GetDBField('NamedParentPath')); + $this->Application->setCache('category_tree[%CIDSerial:' . $list->GetDBField($id_field) . '%]', $list->GetDBField('TreeLeft') . ';' . $list->GetDBField('TreeRight')); } else { // for listing items in category - $this->Application->setCache('filenames', 'c_'.$list->GetDBField('CategoryId'), $list->GetDBField('CategoryFilename')); - $this->Application->setCache('filenames', $this->Prefix.'_'.$list->GetDBField($id_field), $list->GetDBField('Filename')); + $this->Application->setCache('filenames[%' . $serial_name . '%]', $list->GetDBField('Filename')); + + $serial_name = $this->Application->incrementCacheSerial('c', $list->GetDBField('CategoryId'), false); + $this->Application->setCache('filenames[%' . $serial_name . '%]', $list->GetDBField('CategoryFilename')); } } @@ -782,18 +787,23 @@ function AddCurrencySymbol($value, $iso) { - $currency =& $this->Application->recallObject('curr.-'.$iso, null, Array('skip_autoload' => true)); - if( !$currency->isLoaded() ) $currency->Load($iso, 'ISO'); + $cache_key = 'iso_masks[%CurrSerial%]'; + $iso_masks = $this->Application->getCache($cache_key); - $symbol = $currency->GetDBField('Symbol'); - if (!$symbol) $symbol = $currency->GetDBField('ISO').' '; - if ($currency->GetDBField('SymbolPosition') == 0) { - $value = $symbol.$value; + if ($iso_masks === false) { + $this->Conn->nextQueryCachable = true; + $symbol_sql = 'IF(COALESCE(Symbol, "") = "", CONCAT(ISO, " "), Symbol)'; + + $sql = 'SELECT IF(SymbolPosition = 0, CONCAT(' . $symbol_sql . ', "%s"), CONCAT("%s", ' . $symbol_sql . ')), LOWER(ISO) AS ISO + FROM ' . $this->Application->getUnitOption('curr', 'TableName') . ' + WHERE Status = ' . STATUS_ACTIVE; + $iso_masks = $this->Conn->GetCol($sql, 'ISO'); + $this->Application->setCache($cache_key, $iso_masks); } - if ($currency->GetDBField('SymbolPosition') == 1) { - $value = $value.$symbol; - } - return $value; + + $iso = strtolower($iso); + + return array_key_exists($iso, $iso_masks) ? sprintf($iso_masks[$iso], $value) : $value; } /** @@ -2562,7 +2572,7 @@ if (!array_key_exists($check_field, $fields)) { // field not found in real fields array -> it's 100% virtual then - $fields = $this->Application->getUnitOption($this->Prefix, 'VirtualFields'); + $fields = $this->Application->getUnitOption($this->Prefix, 'VirtualFields', Array ()); } if (!array_key_exists($check_field, $fields)) { Index: core/kernel/db/dbitem.php =================================================================== --- core/kernel/db/dbitem.php (revision 13161) +++ core/kernel/db/dbitem.php (working copy) @@ -354,9 +354,10 @@ * @access public * @param mixed $id item id of keys->values hash to load item by * @param string $id_field_name Optional parameter to load item by given Id field + * @param bool $cachable cache this query result based on it's prefix serial * @return bool True if item has been loaded, false otherwise */ - function Load($id, $id_field_name = null) + function Load($id, $id_field_name = null, $cachable = false) { if ( isset($id_field_name) ) { $this->SetIDField($id_field_name); // set new IDField @@ -385,8 +386,25 @@ } $q = $this->GetSelectSQL() . ' WHERE ' . $keys_sql; - $field_values = $this->Conn->GetRow($q); + if ($cachable && $this->Application->isCachingType(CACHING_TYPE_MEMORY)) { + $serial_name = $this->Application->incrementCacheSerial($this->Prefix == 'st' ? 'c' : $this->Prefix, isset($id_field_name) ? null : $id, false); + $cache_key = 'kDBItem::Load_' . crc32(serialize($id) . '-' . $this->IDField) . '[%' . $serial_name . '%]'; + $field_values = $this->Application->getCache($cache_key, false); + + if ($field_values === false) { + $field_values = $this->Conn->GetRow($q); + + if ($field_values !== false) { + // only cache, when data was retrieved + $this->Application->setCache($cache_key, $field_values); + } + } + } + else { + $field_values = $this->Conn->GetRow($q); + } + if ($field_values) { $this->FieldValues = array_merge_recursive2($this->FieldValues, $field_values); $this->OriginalFieldValues = $this->FieldValues; @@ -1082,8 +1100,22 @@ } if (in_array($this->Prefix, $rec['ParentPrefix']) && $rec['ParentId'][$this->Prefix] == $this->GetID()) { - // change log record of given item's sub item + // change log record of given item's sub item -> update changed id's in dependent fields $changes[$key]['ParentId'][$this->Prefix] = $new_id; + + if (array_key_exists('DependentFields', $rec)) { + // these are fields from table of $rec['Prefix'] table! + // when one of dependent fields goes into idfield of it's parent item, that was changed + $parent_table_key = $this->Application->getUnitOption($rec['Prefix'], 'ParentTableKey'); + $parent_table_key = is_array($parent_table_key) ? $parent_table_key[$this->Prefix] : $parent_table_key; + + if ($parent_table_key == $this->IDField) { + $foreign_key = $this->Application->getUnitOption($rec['Prefix'], 'ForeignKey'); + $foreign_key = is_array($foreign_key) ? $foreign_key[$this->Prefix] : $foreign_key; + + $changes[$key]['DependentFields'][$foreign_key] = $new_id; + } + } } } } @@ -1162,19 +1194,24 @@ // sub item // collect foreign key values (for serial reset) $foreign_keys = $this->Application->getUnitOption($this->Prefix, 'ForeignKey'); - $fields_hash['ParentId'] = $fields_hash['ParentPrefix'] = Array (); + $dependent_fields = $fields_hash['ParentId'] = $fields_hash['ParentPrefix'] = Array (); if (is_array($foreign_keys)) { foreach ($foreign_keys as $prefix => $field_name) { + $dependent_fields[$field_name] = $this->GetDBField($field_name); $fields_hash['ParentPrefix'][] = $prefix; $fields_hash['ParentId'][$prefix] = $this->getParentId($prefix); } } else { + $dependent_fields[$foreign_keys] = $this->GetDBField($foreign_keys); $fields_hash['ParentPrefix'] = Array ( $this->Application->getUnitOption($this->Prefix, 'ParentPrefix') ); $fields_hash['ParentId'][ $fields_hash['ParentPrefix'][0] ] = $this->getParentId('auto'); } + $fields_hash['DependentFields'] = $dependent_fields; + + // works only, when main item is present in url, when subitem is changed $master_id = $this->Application->GetVar($main_prefix . '_id'); Index: core/kernel/db/dblist.php =================================================================== --- core/kernel/db/dblist.php (revision 13159) +++ core/kernel/db/dblist.php (working copy) @@ -375,7 +375,7 @@ if (!$this->Records && ($this->Page > 1)) { // no records & page > 1, try to reset to 1st page (works only when list in not counted before) - $this->Application->StoreVar($this->getPrefixSpecial().'_Page', 1); + $this->Application->StoreVar($this->getPrefixSpecial() . '_Page', 1, true); $this->SetPage(1); $this->Query($force); } Index: core/kernel/kbase.php =================================================================== --- core/kernel/kbase.php (revision 13152) +++ core/kernel/kbase.php (working copy) @@ -650,8 +650,23 @@ $select_clause = $this->escapeField($field_options['option_title_field']) . ',' . $this->escapeField($field_options['option_key_field']); $sql = sprintf($field_options['options_sql'], $select_clause); - $dynamic_options = $this->Conn->GetCol($sql, preg_replace('/^.*?\./', '', $field_options['option_key_field'])); + if (array_key_exists('serial_name', $field_options)) { + // try to cache option sql on serial basis + $cache_key = 'sql_' . crc32($sql) . '[%' . $field_options['serial_name'] . '%]'; + $dynamic_options = $this->Application->getCache($cache_key); + + if ($dynamic_options === false) { + $this->Conn->nextQueryCachable = true; + $dynamic_options = $this->Conn->GetCol($sql, preg_replace('/^.*?\./', '', $field_options['option_key_field'])); + $this->Application->setCache($cache_key, $dynamic_options); + } + } + else { + // don't cache options sql + $dynamic_options = $this->Conn->GetCol($sql, preg_replace('/^.*?\./', '', $field_options['option_key_field'])); + } + $options_hash = array_key_exists('options', $field_options) ? $field_options['options'] : Array (); $field_options['options'] = array_merge_recursive2($options_hash, $dynamic_options); } Index: core/kernel/nparser/nparser.php =================================================================== --- core/kernel/nparser/nparser.php (revision 13152) +++ core/kernel/nparser/nparser.php (working copy) @@ -21,7 +21,7 @@ class NParser extends kBase { - var $Stack = array(); + var $Stack = Array (); var $Level = 0; var $Buffers = array(); @@ -40,7 +40,12 @@ var $Definitions = ''; - var $Elements = array(); // holds dynamic elements to function names mapping during execution + /** + * Holds dynamic elements to function names mapping during execution + * + * @var Array + */ + var $Elements = Array (); /** * Holds location of element definitions inside templates. @@ -55,10 +60,31 @@ var $TemplateName = null; var $TempalteFullPath = null; - var $CachePointers = array(); - var $Cachable = array(); + var $CachePointers = Array (); + var $Cachable = Array (); /** + * 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 @@ -72,6 +98,20 @@ */ var $_btnPhrases = Array (); + /** + * Mod-rewrite system enabled + * + * @var bool + */ + var $RewriteUrls = false; + + /** + * Current user is logged-in + * + * @var bool + */ + var $UserLoggedIn = false; + function NParser() { parent::kBase(); @@ -80,6 +120,12 @@ $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') @@ -383,7 +429,13 @@ $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; @@ -391,6 +443,112 @@ 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 Index: core/kernel/nparser/ntags.php =================================================================== --- core/kernel/nparser/ntags.php (revision 13152) +++ core/kernel/nparser/ntags.php (working copy) @@ -404,14 +404,14 @@ $code[] = '$_tag_params = ' . $to_pass . ';'; $code[] = "\${$tag['NP']['name']} = \$_p_->PostProcess(\${$tag['NP']['name']}, \$_p_->PreparePostProcess(\$_tag_params));"; $code[] = "}"; - + 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']}');"; - + if (array_key_exists('plus', $tag['NP'])) { $code[] = "\$params['{$tag['NP']['result_to_var']}'] += {$tag['NP']['plus']};"; } - + $code[] = "\${$tag['NP']['result_to_var']} = \$params['{$tag['NP']['result_to_var']}'];"; } elseif (array_key_exists('plus', $tag['NP'])) { @@ -573,16 +573,20 @@ function Open($tag) { + $o = ''; $pointer = abs(crc32($tag['file'])).'_'.$tag['line']; - $o = ''; - $this->AppendCode($o, "if (!\$_parser->CacheStart('{$pointer}')) {\n"); + $key = array_key_exists('key', $tag['NP']) ? $tag['NP']['key'] : ''; + $this->AppendCode($o, "if (!\$_parser->CacheStart('{$pointer}', \"{$key}\")) {\n"); + return $o; } function Close($tag) { $o = $this->Parser->Buffers[$this->Parser->Level]; - $this->AppendCode($o, "\$_parser->CacheEnd();\n}\n"); + $cache_timeout = array_key_exists('cache_timeout', $this->Tag['NP']) ? $this->Tag['NP']['cache_timeout'] : 0; + $this->AppendCode($o, "\$_parser->CacheEnd(" . (int)$cache_timeout . ");\n}\n"); + return $o; } Index: core/kernel/processors/tag_processor.php =================================================================== --- core/kernel/processors/tag_processor.php (revision 13152) +++ core/kernel/processors/tag_processor.php (working copy) @@ -49,18 +49,12 @@ function FormCacheKey($tag, $params, $prefix) { - $k = $this->Application->Parser->TemplateName; - $k .= $prefix.'_'.$tag; - foreach (array('m_cat_id','m_cat_page','m_lang','m_theme') as $m_var) { - $params[$m_var] = $this->Application->GetVar($m_var); - } + // link tag to it's template + $reg_exp = '/^' . preg_quote(FULL_PATH, '/') . '/'; + $template_path = preg_replace($reg_exp, '', $this->Application->Parser->TempalteFullPath, 1); + $element = 'file=' . $template_path . ':' . $prefix . '_' . $tag . '_' . crc32( serialize($params) ); - ksort($params); - foreach ($params as $p => $v) { - $k .= "$p=$v&"; - } - - return md5($k); + return $this->Application->Parser->FormCacheKey($element); } function ProcessParsedTag($tag, $params, $prefix, $file='unknown', $line=0) @@ -71,11 +65,18 @@ $this->Application->Debugger->appendHTML('Processing PreParsed Tag '.$Method.' in '.$this->Prefix); } + list ($prefix_only, ) = explode('.', $prefix); + $this->Application->Parser->PrefixesInUse[$prefix_only] = 1; + $backup_prefix = $this->Prefix; $backup_special = $this->Special; - if ($this->Application->ConfigValue('SystemTagCache') && isset($params['cache_timeout'])) { - if ($res = $this->Application->CacheGet($this->FormCacheKey($tag, $params, $prefix))) { + if ($this->Application->Parser->CachingEnabled && array_key_exists('cache_timeout', $params)) { + // individual tag caching + $cache_key = $this->FormCacheKey($tag, $params, $prefix); + $res = $this->Application->Parser->getCache($cache_key); + + if ($res !== false) { return $res; } } @@ -94,9 +95,11 @@ $this->Special = $backup_special; $ret = $this->PostProcess($ret, $flag_values); - if ($this->Application->ConfigValue('SystemTagCache') && $flag_values['cache_timeout'] && isset($this->Application->Memcached)) { - $this->Application->CacheSet($this->FormCacheKey($tag, $original_params, $prefix), $ret, $flag_values['cache_timeout']); + + if ($this->Application->Parser->CachingEnabled && $flag_values['cache_timeout']) { + $this->Application->Parser->setCache($cache_key, $ret, (int)$flag_values['cache_timeout']); } + return $ret; } else { Index: core/kernel/startup.php =================================================================== --- core/kernel/startup.php (revision 13152) +++ core/kernel/startup.php (working copy) @@ -103,6 +103,11 @@ safeDefine('EDITOR_PATH', isset($vars['EditorPath']) ? $vars['EditorPath'] : '/core/editor/'); + // caching types + define('CACHING_TYPE_NONE', 0); + define('CACHING_TYPE_MEMORY', 1); + define('CACHING_TYPE_TEMPORARY', 2); + if (ini_get('safe_mode')) { // safe mode will be removed at all in PHP6 define('SAFE_MODE', 1); Index: core/kernel/utility/cache.php =================================================================== --- core/kernel/utility/cache.php (revision 13152) +++ core/kernel/utility/cache.php (working copy) @@ -23,22 +23,29 @@ */ var $_storage = null; + /** + * Cache usage statistics (per script run) + * + * @var Array + */ var $statistics = Array (); + + /** + * Debug cache usage + * + * @var bool + */ var $debugCache = false; - function kCache() { + function kCache() + { parent::kBase(); - $this->debugCache = $this->Application->isDebugMode() && constOn('DBG_CACHE'); + $this->debugCache = defined('DBG_CACHE') && DBG_CACHE && $this->Application->isDebugMode(); - $memcached_servers = false; // 'localhost:11211'; // $this->Application->ConfigValue('MemcachedServers'); - - if ($memcached_servers && class_exists('Memcache')) { - $this->_storage = new MemcacheCacheStorage($memcached_servers); + if (class_exists('Memcache')) { + $this->_storage = new MemcacheCacheStorage(); } - /*else if (false || $this->Application->ConfigValue('UseFileCache')) { - $this->_storage = new FileCacheStorage('file_cache.tmp'); - }*/ else { $this->_storage = new CacheStorage(); } @@ -50,58 +57,90 @@ } /** - * Adds new value to cache $cache_name and identified by key $key + * Adds new value to cache $cache_name and identified by key $name * - * @param string $cache_name cache name - * @param int $key key name to add to cache + * @param int $name key name to add to cache * @param mixed $value value of chached record + * @param int $expires expiration */ - function setCache($cache_name, $key, $value, $expires = 3600) + function setCache($name, $value, $expires = 0) { - return $this->_storage->set($cache_name, $key, $value, $expires); + return $this->_storage->set($name, $value, $expires); } + function reset() + { + return $this->_storage->reset(); + } + /** - * Returns cached $key value from cache named $cache_name + * Returns cached $name value from cache named $cache_name * - * @param string $cache_name cache name - * @param int $key key name from cache + * @param int $name key name from cache + * @param bool $store_locally store data locally after retrieved * @return mixed */ - function getCache($cache_name, $key) + function getCache($name, $store_locally = true) { - $ret = $this->_storage->get($cache_name, $key); + $ret = $this->_storage->get($name, $store_locally); - $this->setStatistics($cache_name, $key, $ret); + if ($this->debugCache && $store_locally) { + $this->setStatistics($name, $ret); + } return $ret; } - function setStatistics($cache_name, $key, $found) + /** + * Deletes cached $name value from cache named $cache_name + * + * @param int $name key name from cache + * @return mixed + */ + function delete($name) { - if (!$this->debugCache) { - return true; + $this->_storage->delete($name); + } + + /** + * Returns caching type of current storage engine + * + * @return int + */ + function getCachingType() + { + return $this->_storage->cachingType; + } + + function setStatistics($name, $found) + { + if (strpos($name, ']:') !== false) { + list ($cache_name, $name) = explode(']:', $name, 2); } + else { + $cache_name = '-'; + } if (!array_key_exists($cache_name, $this->statistics)) { $this->statistics[$cache_name] = Array (); } - if (!array_key_exists($key, $this->statistics[$cache_name])) { - $this->statistics[$cache_name][$key] = Array (); + if (!array_key_exists($name, $this->statistics[$cache_name])) { + $this->statistics[$cache_name][$name] = Array (); } $status_key = $found ? 'found' : 'not_found'; - if (!isset($this->statistics[$cache_name][$key][$status_key])) { - $this->statistics[$cache_name][$key][$status_key] = 0; + + if (!isset($this->statistics[$cache_name][$name][$status_key])) { + $this->statistics[$cache_name][$name][$status_key] = 0; } - $this->statistics[$cache_name][$key][$status_key]++; + $this->statistics[$cache_name][$name][$status_key]++; } function printStatistics() { - $cache_size = strlen(serialize($this->_storage)); + $cache_size = $this->_storage->getStorageSize(); $this->Application->Debugger->appendHTML('Cache Size: ' . formatSize($cache_size) . ' (' . $cache_size . ')'); @@ -120,7 +159,19 @@ class CacheStorage extends Params { + var $cachingType = CACHING_TYPE_TEMPORARY; + /** + * Returns storage size in bytes + * + * @return int + */ + function getStorageSize() + { + return strlen( serialize($this->_Params) ); + } + + /** * Determines, that cache storage is working fine * * @return bool @@ -130,18 +181,31 @@ return true; } + function _getKeyInfo($name) + { + if (strpos($name, ':') !== false) { + list ($cache_name, $name) = explode(':', $name, 2); + } + else { + $cache_name = '-'; + } + + return Array ($cache_name, $name); + } + /** * Stores value to cache * - * @param string $cache_name - * @param string $key + * @param string $name * @param mixed $value * @param int $expires cache record expiration time in seconds */ - function set($cache_name, $key, $value, $expires) + function set($name, $value, $expires) { + list ($cache_name, $name) = $this->_getKeyInfo($name); + $cache = parent::Get($cache_name, Array()); - $cache[$key] = $value; + $cache[$name] = $value; parent::Set($cache_name, $cache); } @@ -149,141 +213,260 @@ /** * Returns value from cache * - * @param string $cache_name - * @param string $key + * @param string $name + * @param bool $store_locally store data locally after retrieved * @return mixed */ - function get($cache_name, $key) + function get($name, $store_locally = false) { + list ($cache_name, $name) = $this->_getKeyInfo($name); + $cache = parent::Get($cache_name, Array()); - $ret = array_key_exists($key, $cache) ? $cache[$key] : false; + $ret = array_key_exists($name, $cache) ? $cache[$name] : false; return $ret; } + + /** + * Deletes value from cache + * + * @param string $name + * @return mixed + */ + function delete($name) + { + list ($cache_name, $name) = $this->_getKeyInfo($name); + + $this->set($cache_name, $name, false, -3600); + } + + function reset() + { + + } } - class MemcacheCacheStorage { + class MemcacheCacheStorage extends kBase { + var $cachingType = CACHING_TYPE_MEMORY; + /** + * Part of what we retrieve will be stored locally (per script run) not to bother memcache a lot + * + * @var Array + */ + var $_localStorage = Array (); + + /** * Memcache connection * * @var Memcache */ var $_handler = null; - function MemcacheCacheStorage($servers) + var $_enabled = false; + + var $_debugMode = false; + + function MemcacheCacheStorage() { - $this->_handler = new Memcache; + parent::kBase(); - $servers = explode(';', $servers); - foreach ($servers as $server) { - list ($server, $port) = strpos($server, ':') !== false ? explode(':', $server, 2) : Array ($server, 11211); - $this->_handler->addServer($server, $port); - } + if (array_key_exists('MemcacheServers', $GLOBALS['vars'])) { + // for advanced users, who want to save one SQL on each page load + $memcached_servers = $GLOBALS['vars']['MemcacheServers']; + } + else { + $memcached_servers = $this->Application->ConfigValue('MemcacheServers'); + } + + if ($memcached_servers && class_exists('Memcache')) { + $this->_enabled = true; + $this->_handler = new Memcache(); + $servers = explode(';', $memcached_servers); + + foreach ($servers as $server) { + list ($server, $port) = strpos($server, ':') !== false ? explode(':', $server, 2) : Array ($server, 11211); + $this->_handler->addServer($server, $port); + } + + // try to set something to cache, if not working - set $this->Memcached to null + if (!$this->_handler->set('test', 1)) { + $this->_handler = null; + $this->_enabled = false; + } + } + + $this->_debugMode = $this->Application->isDebugMode(); + + if ($this->_debugMode) { + $this->Application->Debugger->appendHTML('MemCache Enabled: ' . ($this->_enabled ? 'YES' : 'NO')); + } } /** + * Returns storage size in bytes + * + * @return int + */ + function getStorageSize() + { + return strlen( serialize($this->_localStorage) ); + } + + /** + * Returns site-wide caching prefix + * + * @param bool $only_site_key_name + * @return string + */ + function _cachePrefix($only_site_key_name = false) + { + if (!$this->_enabled) { + return false; + } + + // don't use SERVER_NAME here, because it may be cron, or command line request also + $site_key = 'site_serial:' . crc32(FULL_PATH); + + if ($only_site_key_name) { + return $site_key; + } + + $site_serial = $this->_handler->get($site_key); + + if (!$site_serial) { + $site_serial = 1; + $this->_handler->set($site_key, $site_serial); + } + + return "$site_key:$site_serial:"; + } + + /** * Determines, that cache storage is working fine * * @return bool */ function isWorking() { - return $this->_handler->getVersion() !== false; + return $this->_enabled; } /** * Stores value to cache * - * @param string $cache_name - * @param string $key + * @param string $name * @param mixed $value * @param int $expires cache record expiration time in seconds */ - function set($cache_name, $key, $value, $expires) + function set($name, $value, $expiration) { - return $this->_handler->set($cache_name . '-' . $key, $value, false, $expires); // false could be MEMCACHE_COMPRESSED to compress values in memory + if (!$this->_enabled) { + return false; + } + + $name = $this->prepareKeyName($name); + unset($this->_localStorage[$name]); + + // 0 - don't use compression + return $this->_handler->set($name, $value, 0, $expiration); } /** * Returns value from cache * - * @param string $cache_name - * @param string $key + * @param string $name + * @param bool $store_locally store data locally after retrieved + * @param bool $replace_serials * @return mixed */ - function get($cache_name, $key) + function get($name, $store_locally = true, $replace_serials = true) { - return $this->_handler->get($cache_name . '-' . $key); - } - } + if (!$this->_enabled) { + return false; + } - class FileCacheStorage extends Params { + $name = $this->prepareKeyName($name, $replace_serials); - /** - * Expiration time for each variable in cache - * - * @var resource - */ - var $_expiration = Array (); + if ($store_locally) { + if (array_key_exists($name, $this->_localStorage)) { + return $this->_localStorage[$name]; + } + } - /** - * Filename for storing cache - * - * @var string - */ - var $_filename = ''; + $res = $this->_handler->get($name); - function FileCacheStorage($filename = '') - { - $this->_filename = WRITEABLE . '/cache' . '/' . $filename; + if ($replace_serials && $this->_debugMode) { + // don't display subsequent serial cache retrievals (ones, that are part of keys) + if (is_array($res)) { + $this->Application->Debugger->appendHTML('Restoring key "' . $name . '". Type: ' . gettype($res) . '.'); + } + else { + $res_display = strip_tags($res); - if (file_exists($this->_filename)) { - $cache_data = unserialize(file_get_contents($this->_filename)); + if (strlen($res_display) > 200) { + $res_display = substr($res_display, 0, 50) . ' ...'; + } + + $this->Application->Debugger->appendHTML('Restoring key "' . $name . '" resulted [' . $res_display . ']'); + } } - else { - $cache_data = Array (); - } + + $this->_localStorage[$name] = $res; + + return $res; } /** - * Determines, that cache storage is working fine + * Replaces serials in cache variable name * - * @return bool + * @param string $name + * @param bool $replace_serials + * @return string */ - function isWorking() + function prepareKeyName($name, $replace_serials = true) { - return false; + // replace serials in key name + if ($replace_serials && preg_match_all('/\[%(.*?)%\]/', $name, $regs)) { + // [%LangSerial%] - prefix-wide serial in case of any change in "lang" prefix + // [%LangIDSerial:5%] - one id-wide serial in case of data, associated with given id was changed + // [%CiIDSerial:ItemResourceId:5%] - foreign key-based serial in case of data, associated with given foreign key was changed + foreach ($regs[1] as $serial_name) { + $name = str_replace('[%' . $serial_name . '%]', '[' . $serial_name . '=' . $this->get($serial_name, true, false) . ']', $name); + } + } + + // add site-wide prefix to key + return $this->_cachePrefix() . $name; } /** - * Stores value to cache + * Deletes value from cache * - * @param string $cache_name - * @param string $key - * @param mixed $value - * @param int $expires cache record expiration time in seconds + * @param string $name + * @return mixed */ - function set($cache_name, $key, $value, $expires) + function delete($name) { - $cache = parent::Get($cache_name, Array()); - $cache[$key] = $value; + if (!$this->_enabled) { + return false; + } - parent::Set($cache_name, $cache); + $name = $this->prepareKeyName($name); + unset($this->_localStorage[$name]); + + return $this->_handler->delete($name); } /** - * Returns value from cache - * - * @param string $cache_name - * @param string $key - * @return mixed + * Reset's all memory cache at once */ - function get($cache_name, $key) + function reset() { - $cache = parent::Get($cache_name, Array()); - $ret = array_key_exists($key, $cache) ? $cache[$key] : false; + // don't check for enabled, because we maybe need to reset cache anyway + $site_key = $this->_cachePrefix(true); - return $ret; + $this->_handler->set($site_key, $this->_handler->get($site_key) + 1); } - } \ No newline at end of file + } Index: core/kernel/utility/debugger.php =================================================================== --- core/kernel/utility/debugger.php (revision 13152) +++ core/kernel/utility/debugger.php (working copy) @@ -187,7 +187,7 @@ } $this->safeDefine('DBG_ZEND_PRESENT', 0); // set this constant value to 0 (zero) to debug debugger using Zend Studio - + // set default values for debugger constants $dbg_constMap = Array ( 'DBG_USE_HIGHLIGHT' => 1, // highlight output same as php code using "highlight_string" function @@ -417,7 +417,8 @@ $div_width['current'] = round(($runtime / $total) * $total_width); $div_width['left'] = round((($total - $total_before - $runtime) / $total) * $total_width); - $ret = 'Name: '.$Data['description'].'
'; + $subtitle = array_key_exists('subtitle', $Data) ? ' (' . $Data['subtitle'] . ')' : ''; + $ret = 'Name' . $subtitle . ': '.$Data['description'].'
'; $additional = isset($Data['additional']) ? $Data['additional'] : Array (); if (isset($Data['file'])) { @@ -885,8 +886,16 @@ } $i++; } + $this->ProfilerData[$key]['file'] = $trace_results[$i]['file']; $this->ProfilerData[$key]['line'] = $trace_results[$i]['line']; + + if (array_key_exists('object', $trace_results[$i + 1]) && isset($trace_results[$i + 1]['object']->Prefix)) { + $object =& $trace_results[$i + 1]['object']; + $prefix_special = rtrim($object->Prefix . '.' . $object->Special, '.'); + $this->ProfilerData[$key]['prefix_special'] = $prefix_special; + } + unset($trace_results); } @@ -923,6 +932,16 @@ } $additional[] = Array ('name' => 'Query Number', 'value' => $func_arguments[5]); + + if ($func_arguments[6]) { + $this->profilerAddTotal('cachable_queries', $key); + $this->ProfilerData[$key]['subtitle'] = 'cachable'; + } + + if (array_key_exists('prefix_special', $this->ProfilerData[$key])) { + $additional[] = Array ('name' => 'PrefixSpecial', 'value' => $this->ProfilerData[$key]['prefix_special']); + } + $this->ProfilerData[$key]['additional'] =& $additional; } } @@ -1097,7 +1116,14 @@ if ($this->constOn('DBG_SQL_PROFILE') && isset($this->ProfilerTotals['sql'])) { // sql query profiling was enabled -> show totals - $this->appendHTML('SQL Total time: '.$this->ProfilerTotals['sql'].' Number of queries: '.$this->ProfilerTotalCount['sql']); + if (array_key_exists('cachable_queries', $this->ProfilerTotalCount)) { + $append = ' Cachable queries: ' . $this->ProfilerTotalCount['cachable_queries']; + } + else { + $append = ''; + } + + $this->appendHTML('SQL Total time: '.$this->ProfilerTotals['sql'].' Number of queries: '.$this->ProfilerTotalCount['sql'] . $append); } if ($this->constOn('DBG_PROFILE_INCLUDES') && isset($this->ProfilerTotals['includes'])) { Index: core/kernel/utility/formatters/multilang_formatter.php =================================================================== --- core/kernel/utility/formatters/multilang_formatter.php (revision 13152) +++ core/kernel/utility/formatters/multilang_formatter.php (working copy) @@ -24,13 +24,23 @@ */ function LangFieldName($field_name, $from_primary = false) { - if (preg_match('/^l[0-9]+_/', $field_name)) return $field_name; - $lang = $from_primary ? $this->Application->GetDefaultLanguageId() : $this->Application->GetVar('m_lang'); + static $primary_language = null; + + if (preg_match('/^l[0-9]+_/', $field_name)) { + return $field_name; + } + + if (!isset($primary_language)) { + $primary_language = $this->Application->GetDefaultLanguageId(); + } + + $lang = $from_primary ? $primary_language : $this->Application->GetVar('m_lang'); + if (!$lang || ($lang == 'default')) { - $lang = $this->Application->GetDefaultLanguageId(); + $lang = $primary_language; } - return 'l'.$lang.'_'.$field_name; + return 'l' . $lang . '_' . $field_name; } function PrepareOptions($field_name, &$field_options, &$object) Index: core/kernel/utility/temp_handler.php =================================================================== --- core/kernel/utility/temp_handler.php (revision 13162) +++ core/kernel/utility/temp_handler.php (working copy) @@ -702,6 +702,20 @@ if (in_array($prefix, $rec['ParentPrefix']) && $rec['ParentId'][$prefix] == $temp_id) { // parent item change log record $changes[$key]['ParentId'][$prefix] = $live_id; + + if (array_key_exists('DependentFields', $rec)) { + // these are fields from table of $rec['Prefix'] table! + // when one of dependent fields goes into idfield of it's parent item, that was changed + $parent_table_key = $this->Application->getUnitOption($rec['Prefix'], 'ParentTableKey'); + $parent_table_key = is_array($parent_table_key) ? $parent_table_key[$prefix] : $parent_table_key; + + if ($parent_table_key == $master['IdField']) { + $foreign_key = $this->Application->getUnitOption($rec['Prefix'], 'ForeignKey'); + $foreign_key = is_array($foreign_key) ? $foreign_key[$prefix] : $foreign_key; + + $changes[$key]['DependentFields'][$foreign_key] = $live_id; + } + } } } Index: core/kernel/utility/unit_config_reader.php =================================================================== --- core/kernel/utility/unit_config_reader.php (revision 13152) +++ core/kernel/utility/unit_config_reader.php (working copy) @@ -79,23 +79,29 @@ $aggregator =& $this->Application->recallObject('TagsAggregator', 'kArray'); $config_vars = Array ( + // session related 'SessionTimeout', 'SessionCookieName', 'SessionBrowserSignatureCheck', 'SessionIPAddressCheck', 'CookieSessions', - 'UseCronForRegularEvent', + 'SessionTimeout', + 'KeepSessionOnBrowserClose', 'User_GuestGroup', 'User_LoggedInGroup', - 'SessionTimeout', + + // output related 'UseModRewrite', 'UseOutputCompression', 'OutputCompressionLevel', - 'KeepSessionOnBrowserClose', 'Config_Server_Time', 'Config_Site_Time', + 'SystemTagCache', + + // tracking related 'UseChangeLog', 'UseVisitorTracking', + 'UseCronForRegularEvent', ); foreach ($config_vars as $var) { @@ -123,18 +129,17 @@ 'Application.ModuleInfo' => $this->Application->ModuleInfo, ); - $conn =& $this->Application->GetADODBConnection(); - if (isset($this->Application->Memcached)) { - $this->Application->Memcached->set('master:configs_parsed', serialize($cache)); - $this->Application->Memcached->set('master:config_files', serialize($this->configFiles)); + if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { + $this->Application->setCache('master:configs_parsed', serialize($cache)); + $this->Application->setCache('master:config_files', serialize($this->configFiles)); } else { - $conn->Query('REPLACE '.TABLE_PREFIX.'Cache (VarName, Data, Cached) VALUES ("configs_parsed", '.$conn->qstr(serialize($cache)).', '.adodb_mktime().')'); - $conn->Query('REPLACE '.TABLE_PREFIX.'Cache (VarName, Data, Cached) VALUES ("config_files", '.$conn->qstr(serialize($this->configFiles)).', '.adodb_mktime().')'); + $this->Application->setDBCache('configs_parsed', serialize($cache)); + $this->Application->setDBCache('config_files', serialize($this->configFiles)); } $cache_rebuild_by = SERVER_NAME . ' (' . getenv('REMOTE_ADDR') . ') - ' . adodb_date('d/m/Y H:i:s'); - $conn->Query('REPLACE ' . TABLE_PREFIX . 'Cache (VarName, Data, Cached) VALUES ("last_cache_rebuild", ' . $conn->qstr($cache_rebuild_by) . ', ' . adodb_mktime() . ')'); + $this->Application->setDBCache('last_cache_rebuild', $cache_rebuild_by); unset($this->configFiles); } @@ -142,9 +147,14 @@ function RestoreParsedData() { $conn =& $this->Application->GetADODBConnection(); - if (!isset($this->Application->Memcached) || !($data = $this->Application->Memcached->get('master:configs_parsed'))) { - $data = $conn->GetOne('SELECT Data FROM '.TABLE_PREFIX.'Cache WHERE VarName = "configs_parsed"'); + + if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { + $data = $this->Application->getCache('master:configs_parsed', false); } + else { + $data = $this->Application->getDBCache('configs_parsed'); + } + if ($data) { $cache = unserialize($data); $this->Application->Factory->Files = $cache['Factory.Files']; @@ -181,16 +191,21 @@ function ResetParsedData($include_sections = false) { $conn =& $this->Application->GetADODBConnection(); - $conn->Query('DELETE FROM '.TABLE_PREFIX.'Cache WHERE VarName = "configs_parsed"'); - if (isset($this->Application->Memcached)) { - $this->Application->Memcached->delete('master:configs_parsed'); + + if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { + $this->Application->deleteCache('master:configs_parsed'); } + else { + $this->Application->deleteDBCache('configs_parsed'); + } if ($include_sections) { - $conn->Query('DELETE FROM '.TABLE_PREFIX.'Cache WHERE VarName = "sections_parsed"'); - if (isset($this->Application->Memcached)) { - $this->Application->Memcached->delete('master:sections_parsed'); + if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { + $this->Application->deleteCache('master:sections_parsed'); } + else { + $this->Application->deleteDBCache('sections_parsed'); + } } } @@ -273,10 +288,12 @@ { $this->Application->refreshModuleInfo(); - $conn =& $this->Application->GetADODBConnection(); - if (!isset($this->Application->Memcached) || !($data = $this->Application->Memcached->get('master:config_files'))) { - $data = $conn->GetOne('SELECT Data FROM ' . TABLE_PREFIX . 'Cache WHERE VarName = "config_files"'); + if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { + $data = $this->Application->getCache('master:config_files', false); } + else { + $data = $this->Application->getDBCache('config_files'); + } if ($cache && $data) { $this->configFiles = unserialize($data); Index: core/units/admin/admin_events_handler.php =================================================================== --- core/units/admin/admin_events_handler.php (revision 13152) +++ core/units/admin/admin_events_handler.php (working copy) @@ -43,14 +43,14 @@ $perm_value = null; $system_events = Array ( - 'OnResetModRwCache', 'OnResetSections', 'OnResetConfigsCache', + 'OnResetModRwCache', 'OnResetSections', 'OnResetConfigsCache', 'OnResetMemcache', 'OnDeleteCompiledTemplates', 'OnCompileTemplates', 'OnGenerateTableStructure', - 'OnRebuildThemes', 'OnCheckPrefixConfig', + 'OnRebuildThemes', 'OnCheckPrefixConfig', 'OnMemoryCacheGet', 'OnMemoryCacheSet' ); if (in_array($event->Name, $system_events)) { // events from "Tools -> System Tools" section are controlled via that section "edit" permission - $perm_value = $this->Application->CheckPermission($event->getSection() . '.edit'); + $perm_value = /*$this->Application->isDebugMode() ||*/ $this->Application->CheckPermission($event->getSection() . '.edit'); } $tools_events = Array ( @@ -95,7 +95,7 @@ $event->status = erSTOP; } - $this->Conn->Query('DELETE FROM '.TABLE_PREFIX.'Cache WHERE VarName LIKE "mod_rw%"'); + $this->Conn->Query('DELETE FROM ' . TABLE_PREFIX . 'CachedUrls'); } function OnResetSections(&$event) @@ -104,13 +104,12 @@ $event->status = erSTOP; } - $sql = 'DELETE FROM ' . TABLE_PREFIX . 'Cache - WHERE VarName = "sections_parsed"'; - $this->Conn->Query($sql); - - if (isset($this->Application->Memcached)) { - $this->Application->Memcached->delete('master:sections_parsed'); + if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { + $this->Application->deleteCache('master:sections_parsed'); } + else { + $this->Application->deleteDBCache('sections_parsed'); + } $event->SetRedirectParam('refresh_tree', 1); } @@ -121,22 +120,34 @@ $event->status = erSTOP; } - $sql = 'DELETE FROM ' . TABLE_PREFIX . 'Cache - WHERE VarName IN("config_files", "configs_parsed", "sections_parsed")'; - $this->Conn->Query($sql); + if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { + $this->Application->deleteCache('master:config_files'); + $this->Application->deleteCache('master:configs_parsed'); + $this->Application->deleteCache('master:sections_parsed'); + } + else { + $this->Application->deleteDBCache('config_files'); + $this->Application->deleteDBCache('configs_parsed'); + $this->Application->deleteDBCache('sections_parsed'); + } $skin_helper =& $this->Application->recallObject('SkinHelper'); /* @var $skin_helper SkinHelper */ $skin_helper->deleteCompiled(); - if (isset($this->Application->Memcached)) { - $this->Application->Memcached->delete('master:config_files'); - $this->Application->Memcached->delete('master:configs_parsed'); - $this->Application->Memcached->delete('master:sections_parsed'); + $event->SetRedirectParam('refresh_tree', 1); + } + + function OnResetMemcache(&$event) + { + if ($this->Application->GetVar('ajax') == 'yes') { + $event->status = erSTOP; } - $event->SetRedirectParam('refresh_tree', 1); + if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { + $this->Application->memoryCache->reset(); + } } function OnCompileTemplates(&$event) @@ -1247,4 +1258,75 @@ } } + /** + * Retrieves data from memory cache + * + * @param kEvent $event + */ + function OnMemoryCacheGet(&$event) + { + $event->status = erSTOP; + + $ret = Array ('message' => '', 'code' => 0); // 0 - ok, > 0 - error + $key = $this->Application->GetVar('key'); + + if (!$key) { + $ret['code'] = 1; + $ret['message'] = 'Key name missing'; + } + else { + $value = $this->Application->getCache($key); + + $ret['value'] =& $value; + $ret['size'] = is_string($value) ? formatSize( strlen($value) ) : '?'; + $ret['type'] = gettype($value); + + if (IsSerialized($value)) { + $value = unserialize($value); + } + + if (is_array($value)) { + $ret['value'] = print_r($value, true); + } + + if ($ret['value'] === false) { + $ret['code'] = 2; + $ret['message'] = 'Key "' . $key . '" doesn\'t exist'; + } + } + + $json_helper =& $this->Application->recallObject('JSONHelper'); + /* @var $json_helper JSONHelper */ + + echo $json_helper->encode($ret); + } + + /** + * Retrieves data from memory cache + * + * @param kEvent $event + */ + function OnMemoryCacheSet(&$event) + { + $event->status = erSTOP; + + $ret = Array ('message' => '', 'code' => 0); // 0 - ok, > 0 - error + $key = $this->Application->GetVar('key'); + + if (!$key) { + $ret['code'] = 1; + $ret['message'] = 'Key name missing'; + } + else { + $value = $this->Application->GetVar('value'); + $res = $this->Application->setCache($key, $value); + + $ret['result'] = $res ? 'OK' : 'FAILED'; + } + + $json_helper =& $this->Application->recallObject('JSONHelper'); + /* @var $json_helper JSONHelper */ + + echo $json_helper->encode($ret); + } } \ No newline at end of file Index: core/units/admin/admin_tag_processor.php =================================================================== --- core/units/admin/admin_tag_processor.php (revision 13152) +++ core/units/admin/admin_tag_processor.php (working copy) @@ -587,11 +587,7 @@ function CheckPermCache($params) { // we have separate session between install wizard and admin console, so store in cache - $sql = 'SELECT Data - FROM '.TABLE_PREFIX.'Cache - WHERE VarName = "ForcePermCacheUpdate"'; - $global_mark = $this->Conn->GetOne($sql); - + $global_mark = $this->Application->getDBCache('ForcePermCacheUpdate'); $local_mark = $this->Application->RecallVar('PermCache_UpdateRequired'); if ($global_mark || $local_mark) { @@ -1139,4 +1135,15 @@ return false; } + + /** + * Checks, that we are using memory cache + * + * @param Array $params + * @return bool + */ + function MemoryCacheEnabled($params) + { + return $this->Application->isCachingType(CACHING_TYPE_MEMORY); + } } \ No newline at end of file Index: core/units/categories/cache_updater.php =================================================================== --- core/units/categories/cache_updater.php (revision 13152) +++ core/units/categories/cache_updater.php (working copy) @@ -322,10 +322,13 @@ function clearData() { + // some templates use this + $this->Conn->Query('UPDATE '.TABLE_PREFIX.'ConfigurationValues SET VariableValue = VariableValue+1 WHERE VariableName = \'CategoriesRebuildSerial\''); + + // always drop temporary tables $this->Conn->Query('DROP TABLE IF EXISTS '.$this->progressTable); $this->Conn->Query('DROP TABLE IF EXISTS '.$this->permCacheTable); - $this->Conn->Query('DELETE FROM '.TABLE_PREFIX.'Cache WHERE VarName = \'ForcePermCacheUpdate\''); - $this->Conn->Query('UPDATE '.TABLE_PREFIX.'ConfigurationValues SET VariableValue = VariableValue+1 WHERE VariableName = \'CategoriesRebuildSerial\''); + $this->Application->deleteDBCache('ForcePermCacheUpdate'); } function SaveData() @@ -338,6 +341,8 @@ FROM '.$this->permCacheTable; $this->Conn->Query($sql); $this->clearData(); + + $this->Application->incrementCacheSerial('c'); } function DoTheJob() @@ -449,6 +454,10 @@ } $this->Conn->doUpdate($fields_hash, TABLE_PREFIX.'Category', 'CategoryId = '.$data['current_id']); + + if ($this->Conn->getAffectedRows() > 0) { + $this->Application->incrementCacheSerial('c', $data['current_id']); + } } function QueryTitle(&$data) Index: core/units/categories/categories_config.php =================================================================== --- core/units/categories/categories_config.php (revision 13167) +++ core/units/categories/categories_config.php (working copy) @@ -259,7 +259,8 @@ FROM %1$s LEFT JOIN '.TABLE_PREFIX.'Images img ON img.ResourceId = %1$s.ResourceId AND img.DefaultImg = 1 LEFT JOIN '.TABLE_PREFIX.'PermCache ON '.TABLE_PREFIX.'PermCache.CategoryId = %1$s.CategoryId - LEFT JOIN '.TABLE_PREFIX.'%3$sCategoryCustomData cust ON %1$s.ResourceId = cust.ResourceId' + LEFT JOIN '.TABLE_PREFIX.'%3$sCategoryCustomData cust ON %1$s.ResourceId = cust.ResourceId', + '-virtual' => 'SELECT %1$s.* %2$s FROM %1$s', ), 'SubItems' => Array ('c-rel', 'c-search','c-img', 'c-cdata', 'c-perm', 'content'), @@ -282,7 +283,8 @@ 'LocalPath' => 'img.LocalPath', 'FullUrl' => 'img.Url', 'CreatedBySystem' => 'IF(ThemeId != 0, 1, 0)', - ) + ), + '-virtual' => Array (), ), 'CacheModRewrite' => true, @@ -363,6 +365,19 @@ 'FormSubmittedTemplate' => Array ('type' => 'string', 'default' => null), 'FriendlyURL' => Array ('type' => 'string', 'not_null' => 1, 'default' => ''), 'ThemeId' => Array ('type' => 'int', 'not_null' => 1, 'default' => 0), + + 'EnablePageCache' => Array ( + 'type' => 'int', + 'formatter' => 'kOptionsFormatter', 'options' => Array (1 => 'la_Yes', 0 => 'la_No'), 'use_phrases' => 1, + 'not_null' => 1, 'default' => 0 + ), + 'OverridePageCacheKey' => Array ( + 'type' => 'int', + 'formatter' => 'kOptionsFormatter', 'options' => Array (1 => 'la_Yes', 0 => 'la_No'), 'use_phrases' => 1, + 'not_null' => 1, 'default' => 0 + ), + 'PageCacheKey' => Array ('type' => 'string', 'max_len' => 255, 'not_null' => 1, 'default' => ''), + 'PageExpiration' => Array ('type' => 'int', 'default' => NULL), ), 'VirtualFields' => Array ( Index: core/units/categories/categories_event_handler.php =================================================================== --- core/units/categories/categories_event_handler.php (revision 13152) +++ core/units/categories/categories_event_handler.php (working copy) @@ -648,16 +648,18 @@ */ function prepareObject(&$object, &$event) { - $object =& $event->getObject( Array('skip_autoload' => true) ); + if ($event->Special != '-virtual') { + $object =& $event->getObject( Array('skip_autoload' => true) ); - $object->addCalculatedField( - 'IsNew', - ' IF(%1$s.NewItem = 2, - IF(%1$s.CreatedOn >= (UNIX_TIMESTAMP() - '. - $this->Application->ConfigValue('Category_DaysNew'). - '*3600*24), 1, 0), - %1$s.NewItem - )'); + $object->addCalculatedField( + 'IsNew', + ' IF(%1$s.NewItem = 2, + IF(%1$s.CreatedOn >= (UNIX_TIMESTAMP() - '. + $this->Application->ConfigValue('Category_DaysNew'). + '*3600*24), 1, 0), + %1$s.NewItem + )'); + } } /** @@ -1553,6 +1555,8 @@ $object->SetDBField('Modified_date', $now); $object->SetDBField('Modified_time', $now); + + $object->setRequired('PageCacheKey', $object->GetDBField('OverridePageCacheKey')); $object->SetDBField('Template', $this->_stripTemplateExtension( $object->GetDBField('Template') )); if ($object->GetDBField('IsSystem') == 1) { @@ -1810,55 +1814,17 @@ function _resetMenuCache() { - // reset cms menu cache - $sql = 'DELETE FROM ' . TABLE_PREFIX . 'Cache - WHERE VarName IN ("cms_menu", "StructureTree")'; - $this->Conn->Query($sql); - - // build structure template mapping - $sql = 'SELECT - IF(c.IsSystem, CONCAT(c.Template, ":", c.ThemeId), CONCAT("id:", c.CategoryId)) AS SrcTemplate, - LOWER( - IF( - c.SymLinkCategoryId IS NOT NULL, - (SELECT cc.NamedParentPath FROM ' . TABLE_PREFIX . 'Category AS cc WHERE cc.CategoryId = c.SymLinkCategoryId), - c.NamedParentPath - ) - ) AS DstTemplate, - c.UseExternalUrl, c.ExternalUrl - FROM ' . TABLE_PREFIX . 'Category AS c - WHERE c.Status = ' . STATUS_ACTIVE; - $pages = $this->Conn->Query($sql, 'SrcTemplate'); - - $mapping = Array (); - $base_url = $this->Application->BaseURL(); - - foreach ($pages as $src_template => $page) { - // process external url, before placing in cache - if ($page['UseExternalUrl']) { - $external_url = $page['ExternalUrl']; - - if (!preg_match('/^(.*?):\/\/(.*)$/', $external_url)) { - // url without protocol will be relative url to our site - $external_url = $base_url . $external_url; - } - - $dst_template = 'external:' . $external_url; - } - else { - $dst_template = preg_replace('/^Content\//i', '', $page['DstTemplate']); - } - - $mapping[$src_template] = $dst_template; + // reset cms menu cache (all variables are automatically rebuild, when missing) + if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { + $this->Application->deleteCache('master:cms_menu'); + $this->Application->deleteCache('master:StructureTree'); + $this->Application->deleteCache('master:template_mapping'); } - - $fields_hash = Array ( - 'VarName' => 'template_mapping', - 'Data' => serialize($mapping), - 'Cached' => adodb_mktime(), - ); - - $this->Conn->doInsert($fields_hash, TABLE_PREFIX . 'Cache', 'REPLACE'); + else { + $this->Application->deleteDBCache('cms_menu'); + $this->Application->deleteDBCache('StructureTree'); + $this->Application->deleteDBCache('template_mapping'); + } } /** @@ -2418,4 +2384,35 @@ $this->Application->SetVar('search_logged', 1); } } + + /** + * Load item if id is available + * + * @param kEvent $event + */ + function LoadItem(&$event) + { + if ($event->Special != '-virtual') { + parent::LoadItem($event); + return ; + } + + $object =& $event->getObject(); + /* @var $object kDBItem */ + + $id = $this->getPassedID($event); + + if ($object->isLoaded() && !is_array($id) && ($object->GetID() == $id)) { + // object is already loaded by same id + return ; + } + + if ($object->Load($id, null, true)) { + $actions =& $this->Application->recallObject('kActions'); + $actions->Set($event->Prefix_Special.'_id', $object->GetID() ); + } + else { + $object->setID($id); + } + } } \ No newline at end of file Index: core/units/categories/categories_tag_processor.php =================================================================== --- core/units/categories/categories_tag_processor.php (revision 13159) +++ core/units/categories/categories_tag_processor.php (working copy) @@ -220,22 +220,26 @@ */ function getCategorySymLink($category_id) { - static $cache = null; - if (!$category_id) { // don't bother to get symlink for "Home" category return $category_id; } - if (!isset($cache)) { + $cache_key = 'category_symlinks[%CSerial%]'; + $cache = $this->Application->getCache($cache_key); + + if ($cache === false) { $id_field = $this->Application->getUnitOption($this->Prefix, 'IDField'); $table_name = $this->Application->getUnitOption($this->Prefix, 'TableName'); // get symlinked categories, that are not yet deleted + $this->Conn->nextQueryCachable = true; $sql = 'SELECT c1.SymLinkCategoryId, c1.' . $id_field . ' FROM ' . $table_name . ' c1 JOIN ' . $table_name . ' c2 ON c1.SymLinkCategoryId = c2.' . $id_field; $cache = $this->Conn->GetCol($sql, $id_field); + + $this->Application->setCache($cache_key, $cache); } return array_key_exists($category_id, $cache) ? $cache[$category_id] : $category_id; @@ -748,27 +752,34 @@ */ function LastUpdated($params) { - $category_id = $this->Application->GetVar('m_cat_id'); - $table_name = $this->Application->getUnitOption($this->Prefix, 'TableName'); + $category_id = (int)$this->Application->GetVar('m_cat_id'); + $local = array_key_exists('local', $params) && ($category_id > 0) ? $params['local'] : false; - if (isset($params['local']) && $params['local'] && $category_id > 0) { - // scan only current category & it's children - $sql = 'SELECT TreeLeft, TreeRight - FROM '.TABLE_PREFIX.'Category - WHERE CategoryId = '.$category_id; - $tree_info = $this->Conn->GetRow($sql); + $serial_name = $this->Application->incrementCacheSerial('c', $local ? $category_id : null, false); + $cache_key = 'category_last_updated[%' . $serial_name . '%]'; - $sql = 'SELECT MAX(c.Modified) AS ModDate, MAX(c.CreatedOn) AS NewDate - FROM '.TABLE_PREFIX.'Category c - WHERE c.TreeLeft BETWEEN '.$tree_info['TreeLeft'].' AND '.$tree_info['TreeRight']; + $row_data = $this->Application->getCache($cache_key); + + if ($row_data === false) { + if ($local && ($category_id > 0)) { + // scan only current category & it's children + list ($tree_left, $tree_right) = $this->Application->getTreeIndex($category_id); + + $sql = 'SELECT MAX(Modified) AS ModDate, MAX(CreatedOn) AS NewDate + FROM ' . TABLE_PREFIX . 'Category + WHERE TreeLeft BETWEEN ' . $tree_left . ' AND ' . $tree_right; + } + else { + // scan all categories in system + $sql = 'SELECT MAX(Modified) AS ModDate, MAX(CreatedOn) AS NewDate + FROM ' . TABLE_PREFIX . 'Category'; + } + + $this->Conn->nextQueryCachable = true; + $row_data = $this->Conn->GetRow($sql); + $this->Application->setCache($cache_key, $row_data); } - else { - // scan all categories in system - $sql = 'SELECT MAX(Modified) AS ModDate, MAX(CreatedOn) AS NewDate - FROM '.$table_name; - } - $row_data = $this->Conn->GetRow($sql); if (!$row_data) { return ''; } @@ -781,7 +792,7 @@ $lang =& $this->Application->recallObject('lang.current'); if ($regs[1] == 'DateTimeFormat') { // combined format - $format = $lang->GetDBField('DateFormat').' '.$lang->GetDBField('TimeFormat'); + $format = $lang->GetDBField('DateFormat') . ' ' . $lang->GetDBField('TimeFormat'); } else { // simple format @@ -891,7 +902,9 @@ } // 1. try to get already cached suggestion - $suggestion = $this->Application->getCache('search.suggestion', $keywords); + $cache_key = 'search.suggestion[%SpellingDictionary%]:' . $keywords; + $suggestion = $this->Application->getCache($cache_key); + if ($suggestion !== false) { return $suggestion; } @@ -899,12 +912,14 @@ $table_name = $this->Application->getUnitOption('spelling-dictionary', 'TableName'); // 2. search suggestion in database + $this->Conn->nextQueryCachable = true; $sql = 'SELECT SuggestedCorrection FROM ' . $table_name . ' WHERE MisspelledWord = ' . $this->Conn->qstr($keywords); $suggestion = $this->Conn->GetOne($sql); + if ($suggestion !== false) { - $this->Application->setCache('search.suggestion', $keywords, $suggestion); + $this->Application->setCache($cache_key, $suggestion); return $suggestion; } @@ -933,7 +948,7 @@ ); $this->Conn->doInsert($fields_hash, $table_name); - $this->Application->setCache('search.suggestion', $keywords, $result->Data); + $this->Application->setCache($cache_key, $result->Data); return $result->Data; } @@ -1003,7 +1018,15 @@ $object =& $this->getObject($params); $category_id = isset($params['cat_id']) ? $params['cat_id'] : $object->GetDBField('CategoryId'); - $category_path = $this->Application->getCache('category_paths', $category_id); + $cache_key = 'category_paths[%CIDSerial:' . $category_id . '%]'; + + if ($category_id == 0) { + // home category name is phrase AND phrase name is defined in configuration + $cache_key .= '[%PhrasesSerial%][%ConfSerial%]'; + } + + $category_path = $this->Application->getCache($cache_key); + if ($category_path === false) { // not chached if ($category_id > 0) { @@ -1035,8 +1058,10 @@ else { $category_path = $this->Application->Phrase( $this->Application->ConfigValue('Root_Name') ); } - $this->Application->setCache('category_paths', $category_id, $category_path); + + $this->Application->setCache($cache_key, $category_path); } + return $category_path; } Index: core/units/custom_data/custom_data_event_handler.php =================================================================== --- core/units/custom_data/custom_data_event_handler.php (revision 13152) +++ core/units/custom_data/custom_data_event_handler.php (working copy) @@ -57,11 +57,20 @@ if (!$custom_fields || (defined('IS_INSTALL') && IS_INSTALL) || (defined('CUSTOM_FIELD_ADDED') && CUSTOM_FIELD_ADDED)) { // query all custom fields at once -> saves 4 sqls queries - $sql = 'SELECT * - FROM '.TABLE_PREFIX.'CustomField'; - $all_custom_fields = $this->Conn->Query($sql, 'CustomFieldId'); - ksort($all_custom_fields); + $cache_key = 'all_custom_fields[%CfSerial%][%ModSerial%]'; + $all_custom_fields = $this->Application->getCache($cache_key, false); + + if ($all_custom_fields === false) { + $this->Conn->nextQueryCachable = true; + $sql = 'SELECT * + FROM '.TABLE_PREFIX.'CustomField'; + $all_custom_fields = $this->Conn->Query($sql, 'CustomFieldId'); + ksort($all_custom_fields); + + $this->Application->setCache($cache_key, $all_custom_fields); + } + foreach ($all_custom_fields as $custom_field_id => $custom_field_data) { $cf_type = $custom_field_data['Type']; if (!array_key_exists($cf_type, $custom_fields)) { @@ -168,6 +177,10 @@ $config_calculated_fields = $this->Application->getUnitOption($prefix, 'CalculatedFields', Array()); foreach ($config_calculated_fields as $special => $special_fields) { + if ($special == '-virtual') { + continue; + } + $config_calculated_fields[$special] = array_merge_recursive2($config_calculated_fields[$special], $calculated_fields); } $this->Application->setUnitOption($prefix, 'CalculatedFields', $config_calculated_fields); Index: core/units/forms/forms_eh.php =================================================================== --- core/units/forms/forms_eh.php (revision 13152) +++ core/units/forms/forms_eh.php (working copy) @@ -70,12 +70,11 @@ function OnSave(&$event) { parent::OnSave($event); + if ($event->status == erSUCCESS) { - $this->OnCreateFormFields($event); - $this->Conn->Query('DELETE FROM '.TABLE_PREFIX.'Cache WHERE VarName = "sections_parsed"'); - $this->Application->StoreVar('RefreshStructureTree', 1); + $this->_deleteSectionCache(); } } @@ -84,11 +83,18 @@ parent::OnMassDelete($event); if ($event->status == erSUCCESS) { - $this->Conn->Query('DELETE FROM '.TABLE_PREFIX.'Cache WHERE VarName = "sections_parsed"'); - $this->Application->StoreVar('RefreshStructureTree', 1); + $this->_deleteSectionCache(); } } + function _deleteSectionCache() + { + $reset_event = new kEvent('adm:OnResetSections'); + $this->Application->HandleEvent($reset_event); + + $this->Application->StoreVar('RefreshStructureTree', 1); + } + /** * Dynamically fills customdata config * Index: core/units/helpers/category_helper.php =================================================================== --- core/units/helpers/category_helper.php (revision 13152) +++ core/units/helpers/category_helper.php (working copy) @@ -283,27 +283,30 @@ */ function getCategoryParentPath($main_category_id) { - static $cached_path = null; - if ($main_category_id == 0) { // don't query path for "Home" category return Array (); } - if (!isset($cached_path[$main_category_id])) { + $cache_key = 'parent_paths[%CIDSerial:' . $main_category_id . '%]'; + $cached_path = $this->Application->getCache($cache_key); + + if ($cached_path === false) { $ml_formatter =& $this->Application->recallObject('kMultiLanguage'); $navbar_field = $ml_formatter->LangFieldName('CachedNavBar'); $id_field = $this->Application->getUnitOption('c', 'IDField'); $table_name = $this->Application->getUnitOption('c', 'TableName'); + $this->Conn->nextQueryCachable = true; $sql = 'SELECT '.$navbar_field.', ParentPath FROM '.$table_name.' WHERE '.$id_field.' = '.$main_category_id; $category_data = $this->Conn->GetRow($sql); + $cached_path = Array (); $skip_category = $this->Application->findModule('Name', 'Core', 'RootCat'); - $cached_path[$main_category_id] = Array (); + if ($category_data) { $category_names = explode('&|&', $category_data[$navbar_field]); $category_ids = explode('|', substr($category_data['ParentPath'], 1, -1)); @@ -312,11 +315,15 @@ if ($category_id == $skip_category) { continue; } - $cached_path[$main_category_id][$category_id] = $category_names[$category_index]; + + $cached_path[$category_id] = $category_names[$category_index]; } } + + $this->Application->setCache($cache_key, $cached_path); } - return $cached_path[$main_category_id]; + + return $cached_path; } /** @@ -419,10 +426,12 @@ function &_getStructureTree() { // get cached version of structure tree - $sql = 'SELECT Data - FROM ' . TABLE_PREFIX . 'Cache - WHERE VarName = "StructureTree"'; - $data = $this->Conn->GetOne($sql); + if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { + $data = $this->Application->getCache('master:StructureTree', false); + } + else { + $data = $this->Application->getDBCache('StructureTree'); + } if ($data) { $data = unserialize($data); @@ -439,17 +448,75 @@ $root_category = $this->Application->findModule('Name', 'Core', 'RootCat'); $data = $this->_getChildren($root_category, $language_count); - $fields_hash = Array ( - 'VarName' => 'StructureTree', - 'Data' => serialize($data), - 'Cached' => adodb_mktime(), - ); + if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { + $this->Application->setCache('master:StructureTree', serialize($data)); + } + else { + $this->Application->setDBCache('StructureTree', serialize($data)); + } - $this->Conn->doInsert($fields_hash, TABLE_PREFIX.'Cache', 'REPLACE'); - return $data; } + function getTemplateMapping() + { + if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { + $data = $this->Application->getCache('master:template_mapping', false); + } + else { + $data = $this->Application->getDBCache('template_mapping'); + } + + if ($data) { + return unserialize($data); + } + + $sql = 'SELECT + IF(c.IsSystem, CONCAT(c.Template, ":", c.ThemeId), CONCAT("id:", c.CategoryId)) AS SrcTemplate, + LOWER( + IF( + c.SymLinkCategoryId IS NOT NULL, + (SELECT cc.NamedParentPath FROM ' . TABLE_PREFIX . 'Category AS cc WHERE cc.CategoryId = c.SymLinkCategoryId), + c.NamedParentPath + ) + ) AS DstTemplate, + c.UseExternalUrl, c.ExternalUrl + FROM ' . TABLE_PREFIX . 'Category AS c + WHERE c.Status = ' . STATUS_ACTIVE; + $pages = $this->Conn->Query($sql, 'SrcTemplate'); + + $mapping = Array (); + $base_url = $this->Application->BaseURL(); + + foreach ($pages as $src_template => $page) { + // process external url, before placing in cache + if ($page['UseExternalUrl']) { + $external_url = $page['ExternalUrl']; + + if (!preg_match('/^(.*?):\/\/(.*)$/', $external_url)) { + // url without protocol will be relative url to our site + $external_url = $base_url . $external_url; + } + + $dst_template = 'external:' . $external_url; + } + else { + $dst_template = preg_replace('/^Content\//i', '', $page['DstTemplate']); + } + + $mapping[$src_template] = $dst_template; + } + + if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { + $data = $this->Application->setCache('master:template_mapping', serialize($mapping)); + } + else { + $this->Application->setDBCache('template_mapping', serialize($mapping)); + } + + return $mapping; + } + /** * Returns category structure as field option list * @@ -457,8 +524,9 @@ */ function getStructureTreeAsOptions() { - if (defined('IS_INSTALL') && IS_INSTALL) { - // no need to create category structure during install, because it's not used there + if ((defined('IS_INSTALL') && IS_INSTALL) || !$this->Application->isAdmin) { + // no need to create category structure during install + // OR on Front-End, because it's not used there return Array (); } Index: core/units/helpers/count_helper.php =================================================================== --- core/units/helpers/count_helper.php (revision 13152) +++ core/units/helpers/count_helper.php (working copy) @@ -16,17 +16,6 @@ class kCountHelper extends kHelper { - function kCountHelper() - { - parent::kHelper(); - - // reset expired counts - $sql = 'UPDATE '.TABLE_PREFIX.'Counters - SET CountValue = NULL - WHERE (LifeTime > 0) AND (LastCounted < '.adodb_mktime().' - LifeTime)'; - $this->Conn->Query($sql); - } - /** * Returns counter value * @@ -38,6 +27,8 @@ */ function getCounter($name, $params = Array (), $query_name = null, $multiple_results = false) { + $this->resetExpiredCounters(); + $clone_counter = false; $query_name = isset($query_name) ? $query_name : rtrim($name.'-'.implode('-', array_values($params)), '-'); $sql = 'SELECT * @@ -107,6 +98,20 @@ $this->Conn->Query($sql); } + function resetExpiredCounters() + { + static $reset = false; + + if (!$reset) { + // reset expired counts + $sql = 'UPDATE '.TABLE_PREFIX.'Counters + SET CountValue = NULL + WHERE (LifeTime > 0) AND (LastCounted < '.adodb_mktime().' - LifeTime)'; + $this->Conn->Query($sql); + $reset = true; + } + } + /** * Counts items (of specific type) in category & subcategories * @@ -206,25 +211,42 @@ */ function CategoryCount($today = false) { - $table_name = $this->Application->getUnitOption('c', 'TableName'); + $cache_key = 'category_count[%CSerial%]'; - $sql = 'SELECT COUNT(*) - FROM '.$table_name.' c - INNER JOIN '.TABLE_PREFIX.'PermCache perm_cache ON c.CategoryId = perm_cache.CategoryId'; + if ($today) { + $today_date = adodb_mktime(0, 0, 0, adodb_date('m'), adodb_date('d'), adodb_date('Y')); + $cache_key .= ':date=' . $today_date; + } - list ($view_perm, $view_filter) = $this->GetPermissionClause('c', 'perm_cache'); - $where_clauses = Array ( - $view_filter, 'perm_cache.PermId = '.$view_perm, 'c.Status = '.STATUS_ACTIVE, - ); + $count = $this->Application->getCache($cache_key); - if ($today) { - $today_date = adodb_mktime(0, 0, 0, adodb_date('m'), adodb_date('d'), adodb_date('Y')); - $where_clauses[] = 'c.CreatedOn >= '.$today_date; + if ($count === false) { + $sql = 'SELECT COUNT(*) + FROM ' . $this->Application->getUnitOption('c', 'TableName') . ' c + INNER JOIN ' . TABLE_PREFIX . 'PermCache perm_cache ON c.CategoryId = perm_cache.CategoryId'; + + list ($view_perm, $view_filter) = $this->GetPermissionClause('c', 'perm_cache'); + + $where_clauses = Array ( + $view_filter, + 'perm_cache.PermId = ' . $view_perm, + 'c.Status = ' . STATUS_ACTIVE, + ); + + if ($today) { + $where_clauses[] = 'c.CreatedOn >= ' . $today_date; + } + + $sql .= ' WHERE ('.implode(') AND (', $where_clauses).')'; + + $count = $this->Conn->GetOne($sql); + + if ($count !== false) { + $this->Application->setCache($cache_key, $count); + } } - $sql .= ' WHERE ('.implode(') AND (', $where_clauses).')'; - - return $this->Conn->GetOne($sql); + return $count; } /** @@ -236,17 +258,24 @@ */ function GetPermissionClause($prefix, $table_alias) { - $view_perm = $this->Application->getCache(__CLASS__ . __FUNCTION__, $prefix); - if ($view_perm === false) { - $sql = 'SELECT PermissionConfigId + $permissions_ids = $this->Application->getCache(__CLASS__ . '::' . __FUNCTION__); + + if ($permissions_ids === false) { + $this->Conn->nextQueryCachable = true; + + $sql = 'SELECT PermissionConfigId, PermissionName FROM '.TABLE_PREFIX.'PermissionConfig - WHERE PermissionName = "'.$this->Application->getUnitOption($prefix, 'PermItemPrefix').'.VIEW"'; - $view_perm = $this->Conn->GetOne($sql); + WHERE PermissionName LIKE "%.VIEW"'; + $permissions_ids = $this->Conn->GetCol($sql, 'PermissionName'); - $this->Application->setCache(__CLASS__ . __FUNCTION__, $prefix, $view_perm); + $this->Application->setCache(__CLASS__ . '::' . __FUNCTION__, $permissions_ids); } + $permission_prefix = $this->Application->getUnitOption($prefix, 'PermItemPrefix'); + $view_perm = $permissions_ids[$permission_prefix . '.VIEW']; + $groups = explode(',', $this->Application->RecallVar('UserGroups')); + foreach ($groups as $group) { $view_filters[] = 'FIND_IN_SET('.$group.', '.$table_alias.'.acl)'; } Index: core/units/helpers/language_import_helper.php =================================================================== --- core/units/helpers/language_import_helper.php (revision 13152) +++ core/units/helpers/language_import_helper.php (working copy) @@ -103,6 +103,13 @@ */ var $_latestVersion = 2; + /** + * Prefix-based serial numbers, that should be changed after import is finished + * + * @var Array + */ + var $changedPrefixes = Array (); + function LanguageImportHelper() { parent::kHelper(); @@ -166,7 +173,12 @@ } $this->_initImportTables(true); + $this->changedPrefixes = array_unique($this->changedPrefixes); + foreach ($this->changedPrefixes as $prefix) { + $this->Application->incrementCacheSerial($prefix); + } + if ($this->_debugMode) { $this->Application->Debugger->appendHTML(__CLASS__ . '::' . __FUNCTION__ . '("' . $filename . '"): ' . (getmicrotime() - $start_time)); } @@ -331,6 +343,9 @@ FROM ' . $this->_tables[$prefix] . ' WHERE ' . $unique_field . ' IN (' . implode(',', $to_insert) . ')'; $this->Conn->Query($sql); + + // new records were added + $this->changedPrefixes[] = $prefix; } // perform update for records, that are present in live table @@ -370,6 +385,11 @@ } $this->Conn->Query($sql); + + if ($this->Conn->getAffectedRows() > 0) { + // existing records were updated + $this->changedPrefixes[] = $prefix; + } } } Index: core/units/helpers/menu_helper.php =================================================================== --- core/units/helpers/menu_helper.php (revision 13152) +++ core/units/helpers/menu_helper.php (working copy) @@ -115,24 +115,41 @@ if (!$root_cat) { $root_cat = $this->Application->ModuleInfo['Core']['RootCat']; + $cache_key = 'parent_paths[%CIDSerial:' . $root_cat . '%]'; + $root_path = $this->Application->getCache($cache_key); - $sql = 'SELECT ParentPath - FROM ' . TABLE_PREFIX . 'Category - WHERE CategoryId = ' . $root_cat; - $root_path = $this->Conn->GetOne($sql); + if ($root_path === false) { + $this->Conn->nextQueryCachable = true; + $sql = 'SELECT ParentPath + FROM ' . TABLE_PREFIX . 'Category + WHERE CategoryId = ' . $root_cat; + $root_path = $this->Conn->GetOne($sql); + $this->Application->setCache($cache_key, $root_path); + } } if (!$this->Menu) { - $menu = $this->Conn->GetRow('SELECT Data, Cached FROM '.TABLE_PREFIX.'Cache WHERE VarName = "cms_menu"'); - if ($menu && $menu['Cached'] > 0) { - $menu = unserialize($menu['Data']); + if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { + $menu = $this->Application->getCache('master:cms_menu', false); + } + else { + $menu = $this->Application->getDBCache('cms_menu'); + } + + if ($menu) { + $menu = unserialize($menu); $this->parentPaths = $menu['parentPaths']; } else { $menu = $this->_altBuildMenuStructure(Array ('CategoryId' => $root_cat, 'ParentPath' => $root_path)); $menu['parentPaths'] = $this->parentPaths; - $this->Conn->Query('REPLACE '.TABLE_PREFIX.'Cache (VarName, Data, Cached) VALUES ("cms_menu", '.$this->Conn->qstr(serialize($menu)).', '.adodb_mktime().')'); + if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { + $this->Application->setCache('master:cms_menu', serialize($menu)); + } + else { + $this->Application->setDBCache('cms_menu', serialize($menu)); + } } unset($menu['parentPaths']); Index: core/units/helpers/mod_rewrite_helper.php =================================================================== --- core/units/helpers/mod_rewrite_helper.php (revision 13159) +++ core/units/helpers/mod_rewrite_helper.php (working copy) @@ -65,16 +65,12 @@ } $restored = false; - $sql = 'SELECT Data, Cached - FROM ' . TABLE_PREFIX . 'Cache - WHERE VarName = "mod_rw_' . md5($url) . '"'; - $cache = $this->Conn->GetRow($sql); - if (false && $cache && $cache['Cached'] > 0) { - // not used for now - $cache = unserialize($cache['Data']); - $vars = $cache['vars']; - $passed = $cache['passed']; + $cached = $this->_getCachedUrl($url); + + if ($cached !== false) { + $vars = $cached['vars']; + $passed = $cached['passed']; $restored = true; } else { @@ -82,15 +78,8 @@ $passed = $vars['pass']; // also used in bottom of this method unset($vars['pass']); - $cache = Array ('vars' => $vars, 'passed' => $passed); + $this->_setCachedUrl($url, Array ('vars' => $vars, 'passed' => $passed)); - $fields_hash = Array ( - 'VarName' => 'mod_rw_' . md5($url), - 'Data' => serialize($cache), - 'Cached' => adodb_mktime(), - ); - $this->Conn->doInsert($fields_hash, TABLE_PREFIX . 'Cache', 'REPLACE'); - if (array_key_exists('t', $this->HTTPQuery->Post) && $this->HTTPQuery->Post['t']) { // template from POST overrides template from URL. $vars['t'] = $this->HTTPQuery->Post['t']; @@ -111,19 +100,90 @@ $this->HTTPQuery->finalizeParsing($passed); } - function parseRewriteURL($url) + function _getCachedUrl($url) { - $sql = 'SELECT Data, Cached - FROM ' . TABLE_PREFIX . 'Cache - WHERE VarName = "mod_rw_' . md5($url) . '"'; - $vars = $this->Conn->GetRow($sql); + if (!$url) { + return false; + } - if (false && $vars && $vars['Cached'] > 0) { - // not used for now - $vars = unserialize($menu['Data']); - return $vars; + $sql = 'SELECT * + FROM ' . TABLE_PREFIX . 'CachedUrls + WHERE Hash = ' . crc32($url); + $data = $this->Conn->GetRow($sql); + + if ($data) { + $lifetime = (int)$data['LifeTime']; // in seconds + if (($lifetime > 0) && ($data['Cached'] + $lifetime < adodb_mktime())) { + // delete expired + $sql = 'DELETE FROM ' . TABLE_PREFIX . 'CachedUrls + WHERE UrlId = ' . $data['UrlId']; + $this->Conn->Query($sql); + + return false; + } + + return unserialize($data['ParsedVars']); } + return false; + } + + function _setCachedUrl($url, $data) + { + if (!$url) { + return ; + } + + $vars = $data['vars']; + $passed = $data['passed']; + sort($passed); + + // get expiration + if ($vars['m_cat_id'] > 0) { + $sql = 'SELECT PageExpiration + FROM ' . TABLE_PREFIX . 'Category + WHERE CategoryId = ' . $vars['m_cat_id']; + $expiration = $this->Conn->GetOne($sql); + } + + // get prefixes + $prefixes = Array (); + $m_index = array_search('m', $passed); + + if ($m_index !== false) { + unset($passed[$m_index]); + + if ($vars['m_cat_id'] > 0) { + $prefixes[] = 'c:' . $vars['m_cat_id']; + } + + $prefixes[] = 'lang:' . $vars['m_lang']; + $prefixes[] = 'theme:' . $vars['m_theme']; + } + + foreach ($passed as $prefix) { + if (array_key_exists($prefix . '_id', $vars) && is_numeric($vars[$prefix . '_id'])) { + $prefixes[] = $prefix . ':' . $vars[$prefix . '_id']; + } + else { + $prefixes[] = $prefix; + } + } + + $fields_hash = Array ( + 'Url' => $url, + 'Hash' => crc32($url), + 'Prefixes' => $prefixes ? '|' . implode('|', $prefixes) . '|' : '', + 'ParsedVars' => serialize($data), + 'Cached' => adodb_mktime(), + 'LifeTime' => isset($expiration) && is_numeric($expiration) ? $expiration : -1 + ); + + $this->Conn->doInsert($fields_hash, TABLE_PREFIX . 'CachedUrls'); + } + + function parseRewriteURL($url) + { $vars = Array ('pass' => Array ('m')); $url_parts = $url ? explode('/', trim(mb_strtolower($url, 'UTF-8'), '/')) : Array (); @@ -329,14 +389,14 @@ // add language $default_language_id = $this->Application->GetDefaultLanguageId(); if ($processed_params['m_lang'] && ($processed_params['m_lang'] != $default_language_id)) { - $language_name = $this->Application->getCache('language_names', $processed_params['m_lang']); + $language_name = $this->Application->getCache('language_names[%LangIDSerial:' . $processed_params['m_lang'] . '%]'); if ($language_name === false) { $sql = 'SELECT PackName FROM ' . TABLE_PREFIX . 'Language WHERE LanguageId = ' . $processed_params['m_lang']; $language_name = $this->Conn->GetOne($sql); - $this->Application->setCache('language_names', $processed_params['m_lang'], $language_name); + $this->Application->setCache('language_names[%LangIDSerial:' . $processed_params['m_lang'] . '%]', $language_name); } $ret .= $language_name . '/'; @@ -345,14 +405,14 @@ // add theme $default_theme_id = $this->Application->GetDefaultThemeId(true); if ($processed_params['m_theme'] && ($processed_params['m_theme'] != $default_theme_id)) { - $theme_name = $this->Application->getCache('theme_names', $processed_params['m_theme']); + $theme_name = $this->Application->getCache('theme_names[%ThemeIDSerial:' . $processed_params['m_theme'] . '%]'); if ($theme_name === false) { $sql = 'SELECT Name FROM ' . TABLE_PREFIX . 'Theme WHERE ThemeId = ' . $processed_params['m_theme']; $theme_name = $this->Conn->GetOne($sql); - $this->Application->setCache('theme_names', $processed_params['m_theme'], $theme_name); + $this->Application->setCache('theme_names[%ThemeIDSerial:' . $processed_params['m_theme'] . '%]', $theme_name); } @@ -366,7 +426,7 @@ // add category if ($processed_params['m_cat_id'] > 0 && $params['pass_category']) { - $category_filename = $this->Application->getFilename('c', $processed_params['m_cat_id']); + $category_filename = $this->Application->getCategoryCache($processed_params['m_cat_id'], 'filenames'); preg_match('/^Content\/(.*)/i', $category_filename, $regs); @@ -401,7 +461,7 @@ } $template = array_key_exists('t', $params) ? $params['t'] : false; - $category_template = ($processed_params['m_cat_id'] > 0) && $params['pass_category'] ? $this->Application->getCache('category_designs', $processed_params['m_cat_id']) : ''; + $category_template = ($processed_params['m_cat_id'] > 0) && $params['pass_category'] ? $this->Application->getCategoryCache($processed_params['m_cat_id'], 'category_designs') : ''; if ((strtolower($template) == '__default__') && ($processed_params['m_cat_id'] == 0)) { // for "Home" category set template to index when not set @@ -696,13 +756,10 @@ } if ($processed_params[$prefix_special . '_id']) { - // this allows to fill 3 cache records with one query (see this method for details) $category_id = array_key_exists('m_cat_id', $params) ? $params['m_cat_id'] : $this->Application->GetVar('m_cat_id'); - $category_filename = $this->Application->getFilename('c', $category_id); // if template is also item template of category, then remove template $template = array_key_exists('t', $params) ? $params['t'] : false; - $item_template = $this->GetItemTemplate($category_id, $prefix); if ($template == $item_template || strtolower($template) == '__default__') { @@ -887,9 +944,10 @@ */ function GetItemTemplate($category, $module_prefix) { - $cache_key = serialize($category) . '_' . $module_prefix; + $category_id = is_array($category) ? $category['CategoryId'] : $category; + $cache_key = __CLASS__ . '::' . __FUNCTION__ . '[%CIDSerial:' . $category_id . '%]:' . $module_prefix; - $cached_value = $this->Application->getCache(__CLASS__ . __FUNCTION__, $cache_key); + $cached_value = $this->Application->getCache($cache_key); if ($cached_value !== false) { return $cached_value; } @@ -938,7 +996,7 @@ $item_template = $this->_templateAliases[$item_template]; } - $this->Application->setCache(__CLASS__ . __FUNCTION__, $cache_key, $item_template); + $this->Application->setCache($cache_key, $item_template); return $item_template; } @@ -973,7 +1031,9 @@ */ function getItemTemplateCustomField($module_prefix) { - $cached_value = $this->Application->getCache(__CLASS__ . __FUNCTION__, $module_prefix); + $cache_key = __CLASS__ . '::' . __FUNCTION__ . '[%CfSerial%]:' . $module_prefix; + $cached_value = $this->Application->getCache($cache_key); + if ($cached_value !== false) { return $cached_value; } @@ -983,7 +1043,7 @@ WHERE FieldName = ' . $this->Conn->qstr($module_prefix . '_ItemTemplate'); $item_template_field_id = $this->Conn->GetOne($sql); - $this->Application->setCache(__CLASS__ . __FUNCTION__, $module_prefix, $item_template_field_id); + $this->Application->setCache($cache_key, $item_template_field_id); return $item_template_field_id; } Index: core/units/helpers/permissions_helper.php =================================================================== --- core/units/helpers/permissions_helper.php (revision 13152) +++ core/units/helpers/permissions_helper.php (working copy) @@ -536,12 +536,6 @@ $cat_id = $this->Application->GetVar('m_cat_id'); } - $cache_key = $name.'|'.$type.'|'.$cat_id; - $perm_value = $this->Application->getCache('permissions', $cache_key); - if ($perm_value !== false) { - return $perm_value; - } - // perm cache is build only based on records in db, that's why if permission is not explicitly denied, then // that (perm cache creator) code thinks that it is allowed & adds corresponding record and code below will // return incorrect results @@ -557,6 +551,13 @@ array_push($groups, $this->Application->ConfigValue('User_LoggedInGroup') ); } + $cache_key = $name . '|' . $type . '|' . $cat_id . '|' . implode(',', $groups); + $perm_value = $this->Application->getCache('permissions[%' . ($type == 1 ? 'G' : 'C') . 'PermSerial%]:' . $cache_key); + + if ($perm_value !== false) { + return $perm_value; + } + if (preg_match('/(.*)\.VIEW$/', $name) && ($type == 0)) { // cached view permission of category: begin if (strpos($cat_id, '|') !== false) { @@ -580,7 +581,7 @@ $sql .= ' AND ('.implode(' OR ', $view_filters).')'; $perm_value = $this->Conn->GetOne($sql) ? 1 : 0; - $this->Application->setCache('permissions', $cache_key, $perm_value); + $this->Application->setCache('permissions[%CPermSerial%]:' . $cache_key, $perm_value); return $perm_value; // cached view permission of category: end } @@ -621,7 +622,8 @@ } } - $this->Application->setCache('permissions', $cache_key, $perm_value); + $this->Application->setCache('permissions[%' . ($type == 1 ? 'G' : 'C') . 'PermSerial%]:' . $cache_key, $perm_value); + return $perm_value; } Index: core/units/helpers/sections_helper.php =================================================================== --- core/units/helpers/sections_helper.php (revision 13152) +++ core/units/helpers/sections_helper.php (working copy) @@ -47,9 +47,13 @@ */ function BuildTree() { - if (!isset($this->Application->Memcached) || !($data = $this->Application->Memcached->get('master:sections_parsed'))) { - $data = $this->Conn->GetOne('SELECT Data FROM '.TABLE_PREFIX.'Cache WHERE VarName = "sections_parsed"'); + if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { + $data = $this->Application->getCache('master:sections_parsed', false); } + else { + $data = $this->Application->getDBCache('sections_parsed'); + } + if ($data) { $this->Tree = unserialize($data); return ; @@ -174,12 +178,12 @@ $this->Application->HandleEvent( new kEvent('adm:OnAfterBuildTree') ); - if (isset($this->Application->Memcached)) { - $this->Application->Memcached->set('master:sections_parsed',serialize($this->Tree), 0, 0); - return; + if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { + $this->Application->setCache('master:sections_parsed', serialize($this->Tree)); } - - $this->Conn->Query('REPLACE '.TABLE_PREFIX.'Cache (VarName, Data, Cached) VALUES ("sections_parsed", '.$this->Conn->qstr(serialize($this->Tree)).', '.adodb_mktime().')'); + else { + $this->Application->setDBCache('sections_parsed', serialize($this->Tree)); + } } function _processPrefixSections($prefix) Index: core/units/helpers/skin_helper.php =================================================================== --- core/units/helpers/skin_helper.php (revision 13152) +++ core/units/helpers/skin_helper.php (working copy) @@ -98,6 +98,9 @@ WHERE ' . $object->IDField . ' = ' . $object->GetID(); $this->Conn->Query($sql); + $this->Application->incrementCacheSerial('skin'); + $this->Application->incrementCacheSerial('skin', $object->GetID()); + return $compile_ts; } @@ -108,16 +111,20 @@ */ function _getStyleInfo() { - static $style = null; + $cache_key = 'primary_skin_info[%SkinSerial%]'; + $ret = $this->Application->getCache($cache_key); - if (!isset($style)) { + if ($ret === false) { + $this->Conn->nextQueryCachable = true; $sql = 'SELECT * FROM ' . TABLE_PREFIX . 'Skins WHERE IsPrimary = 1'; - $style = $this->Conn->GetRow($sql); + $ret = $this->Conn->GetRow($sql); + + $this->Application->setCache($cache_key, $ret); } - return $style; + return $ret; } /** Index: core/units/helpers/themes_helper.php =================================================================== --- core/units/helpers/themes_helper.php (revision 13152) +++ core/units/helpers/themes_helper.php (working copy) @@ -381,6 +381,8 @@ $theme_ids = $this->Conn->GetCol($sql); $this->deleteThemes($theme_ids); + $this->Application->incrementCacheSerial('theme'); + $this->Application->incrementCacheSerial('theme-file'); } /** Index: core/units/languages/languages_event_handler.php =================================================================== --- core/units/languages/languages_event_handler.php (revision 13152) +++ core/units/languages/languages_event_handler.php (working copy) @@ -31,14 +31,28 @@ 'OnExportProgress' => Array('self' => 'advanced:export'), 'OnReflectMultiLingualFields' => Array ('self' => 'view'), 'OnSynchronizeLanguages' => Array ('self' => 'edit'), - - 'OnItemBuild' => Array('self' => true), ); $this->permMapping = array_merge($this->permMapping, $permissions); } /** + * Permission check override + * + * @param kEvent $event + */ + function CheckPermission(&$event) + { + if ($event->Name == 'OnItemBuild') { + // check permission without using $event->getSection(), + // so first cache rebuild won't lead to "ldefault_Name" field being used + return true; + } + + return parent::CheckPermission($event); + } + + /** * [HOOK] Updates table structure on new language adding/removing language * * @param kEvent $event Index: core/units/languages/languages_item.php =================================================================== --- core/units/languages/languages_item.php (revision 13152) +++ core/units/languages/languages_item.php (working copy) @@ -65,7 +65,7 @@ $default = true; } - $res = parent::Load($id, $id_field_name); + $res = parent::Load($id, $id_field_name, true); if ($default) { if (!$res) { Index: core/units/permissions/permissions_tag_processor.php =================================================================== --- core/units/permissions/permissions_tag_processor.php (revision 13152) +++ core/units/permissions/permissions_tag_processor.php (working copy) @@ -181,7 +181,15 @@ function CategoryPath($params) { $category_id = $params['cat_id']; - $category_path = $this->Application->getCache('category_paths', $category_id); + $cache_key = 'category_paths[%CIDSerial:' . $category_id . '%]'; + + if ("$category_id" == '0') { + // home category name is phrase AND phrase name is defined in configuration + $cache_key .= '[%PhrasesSerial%][%ConfSerial%]'; + } + + $category_path = $this->Application->getCache($cache_key); + if ($category_path === false) { // not chached if ($category_id > 0) { @@ -198,8 +206,10 @@ else { $category_path = $this->Application->Phrase( $this->Application->ConfigValue('Root_Name') ); } - $this->Application->setCache('category_paths', $category_id, $category_path); + + $this->Application->setCache($cache_key, $category_path); } + return $category_path; } Index: core/units/statistics/statistics_tag_processor.php =================================================================== --- core/units/statistics/statistics_tag_processor.php (revision 13152) +++ core/units/statistics/statistics_tag_processor.php (working copy) @@ -208,7 +208,9 @@ function CountPending($params) { $prefix = $params['prefix']; - $value = $this->Application->getCache('statistics.pending', $prefix); + $cache_key = 'statistics.pending[%' . $this->Application->incrementCacheSerial($prefix, null, false) . '%]'; + $value = $this->Application->getCache($cache_key); + if ($value === false) { $statistics_info = $this->Application->getUnitOption($prefix.'.pending', 'StatisticsInfo'); if (!$statistics_info) { @@ -217,12 +219,14 @@ $table = $this->Application->getUnitOption($prefix, 'TableName'); $status_field = array_shift( $this->Application->getUnitOption($prefix, 'StatusField') ); + $this->Conn->nextQueryCachable = true; $sql = 'SELECT COUNT(*) FROM '.$table.' WHERE '.$status_field.' = '.$statistics_info['status']; $value = $this->Conn->GetOne($sql); - $this->Application->setCache('statistics.pending', $prefix, $value); + $this->Application->setCache($cache_key, $value); } + return $value; } Index: core/units/structure/structure_config.php =================================================================== --- core/units/structure/structure_config.php (revision 13166) +++ core/units/structure/structure_config.php (working copy) @@ -77,14 +77,13 @@ 'PermItemPrefix' => 'CATEGORY', 'PermSection' => Array('main' => 'CATEGORY:in-portal:structure', 'email' => 'in-portal:configemail'), - 'ListSQLs' => Array( ''=> ' SELECT %1$s.* %2$s - FROM %1$s - LEFT JOIN '.TABLE_PREFIX.'PermCache ON '.TABLE_PREFIX.'PermCache.CategoryId = %1$s.CategoryId'), + 'ListSQLs' => Array ( + '' => ' SELECT %1$s.* %2$s + FROM %1$s + LEFT JOIN '.TABLE_PREFIX.'PermCache ON '.TABLE_PREFIX.'PermCache.CategoryId = %1$s.CategoryId', + '-virtual' => 'SELECT %1$s.* %2$s FROM %1$s', + ), - 'ItemSQLs' => Array( ''=> ' SELECT %1$s.* %2$s - FROM %1$s - LEFT JOIN '.TABLE_PREFIX.'PermCache ON '.TABLE_PREFIX.'PermCache.CategoryId = %1$s.CategoryId'), - 'SubItems' => Array('content'), 'ListSortings' => Array( @@ -97,7 +96,8 @@ 'CalculatedFields' => Array( '' => Array( 'CurrentSort' => "REPLACE(ParentPath, CONCAT('|', ".'%1$s'.".CategoryId, '|'), '')", - ) + ), + '-virtual' => Array (), ), 'Fields' => Array ( @@ -168,6 +168,19 @@ 'FormSubmittedTemplate' => Array('type' => 'string', 'default' => null), 'FriendlyURL' => Array ('type' => 'string', 'not_null' => 1, 'default' => ''), 'ThemeId' => Array ('type' => 'int', 'not_null' => 1, 'default' => 0), + + 'EnablePageCache' => Array ( + 'type' => 'int', + 'formatter' => 'kOptionsFormatter', 'options' => Array (1 => 'la_Yes', 0 => 'la_No'), 'use_phrases' => 1, + 'not_null' => 1, 'default' => 0 + ), + 'OverridePageCacheKey' => Array ( + 'type' => 'int', + 'formatter' => 'kOptionsFormatter', 'options' => Array (1 => 'la_Yes', 0 => 'la_No'), 'use_phrases' => 1, + 'not_null' => 1, 'default' => 0 + ), + 'PageCacheKey' => Array ('type' => 'string', 'max_len' => 255, 'not_null' => 1, 'default' => ''), + 'PageExpiration' => Array ('type' => 'int', 'default' => NULL), ), 'VirtualFields' => Array( Index: core/units/themes/theme_item.php =================================================================== --- core/units/themes/theme_item.php (revision 13152) +++ core/units/themes/theme_item.php (working copy) @@ -25,7 +25,7 @@ $default = true; } - $res = parent::Load($id, $id_field_name); + $res = parent::Load($id, $id_field_name, true); if ($default) { if (!$res) { @@ -36,6 +36,7 @@ $this->Application->SetVar('theme.current_id', $this->GetID() ); $this->Application->SetVar('m_theme', $this->GetID() ); } + return $res; } } \ No newline at end of file Index: core/units/themes/themes_eh.php =================================================================== --- core/units/themes/themes_eh.php (revision 13152) +++ core/units/themes/themes_eh.php (working copy) @@ -24,7 +24,6 @@ { parent::mapPermissions(); $permissions = Array( - 'OnItemBuild' => Array('self' => true), 'OnChangeTheme' => Array('self' => true), ); @@ -32,6 +31,22 @@ } /** + * Permission check override + * + * @param kEvent $event + */ + function CheckPermission(&$event) + { + if ($event->Name == 'OnItemBuild') { + // check permission without using $event->getSection(), + // so first cache rebuild won't lead to "ldefault_Name" field being used + return true; + } + + return parent::CheckPermission($event); + } + + /** * Allows to set selected theme as primary * * @param kEvent $event Index: core/units/users/users_event_handler.php =================================================================== --- core/units/users/users_event_handler.php (revision 13152) +++ core/units/users/users_event_handler.php (working copy) @@ -1925,4 +1925,26 @@ $object->setRequired($required_field); } } + + /** + * Load item if id is available + * + * @param kEvent $event + */ + function LoadItem(&$event) + { + $id = $this->getPassedID($event); + + if ($id < 0) { + // when root, guest and so on + + $object =& $event->getObject(); + /* @var $object kDBItem */ + + $object->Clear($id); + return ; + } + + parent::LoadItem($event); + } } Index: tools/show_cache.php =================================================================== --- tools/show_cache.php (revision 13152) +++ tools/show_cache.php (working copy) @@ -33,10 +33,7 @@ $show_var = $application->GetVar('show_var'); if ($show_var) { - $sql = 'SELECT Data - FROM ' . TABLE_PREFIX . 'Cache - WHERE VarName = "' . $show_var . '"'; - $var_data = $application->Conn->GetOne($sql); + $var_data = $application->getDBCache($show_var); if ($var_data === false) { echo 'Not Found'; @@ -47,7 +44,7 @@ } else { $sql = 'SELECT VarName - FROM ' . TABLE_PREFIX . 'Cache'; + FROM ' . TABLE_PREFIX . 'Cache'; $var_names = $application->Conn->GetCol($sql);