Index: branches/5.1.x/core/kernel/application.php =================================================================== diff -u -N -r13086 -r13168 --- branches/5.1.x/core/kernel/application.php (.../application.php) (revision 13086) +++ branches/5.1.x/core/kernel/application.php (.../application.php) (revision 13168) @@ -1,6 +1,6 @@ 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,34 +351,32 @@ $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(); if (defined('DEBUG_MODE') && $this->isDebugMode()) { $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,26 +580,46 @@ $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; } 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);