Index: branches/5.2.x/core/kernel/utility/cache.php =================================================================== --- branches/5.2.x/core/kernel/utility/cache.php (revision 16752) +++ branches/5.2.x/core/kernel/utility/cache.php (revision 16753) @@ -1,977 +1,1087 @@ siteKeyName = 'site_serial:' . crc32(SQL_TYPE . '://' . SQL_USER . ':' . SQL_PASS . '@' . SQL_SERVER . ':' . TABLE_PREFIX . ':' . SQL_DB); // get cache handler class to use $handler_class = kUtil::getSystemConfig()->get('CacheHandler', '') . 'CacheHandler'; // defined cache handler doesn't exist -> use default if ( !class_exists($handler_class) ) { $handler_class = 'FakeCacheHandler'; } $handler = new $handler_class($this); if ( !$handler->isWorking() ) { // defined cache handler is not working -> use default trigger_error('Failed to initialize "' . $handler_class . '" caching handler.', E_USER_WARNING); $handler = new FakeCacheHandler($this); } elseif ( $this->Application->isDebugMode() && ($handler->getCachingType() == CACHING_TYPE_MEMORY) ) { $this->Application->Debugger->appendHTML('Memory Caching: "' . $handler_class . '"'); } $this->_handler = $handler; $this->cachingType = $handler->getCachingType(); $this->debugCache = $handler->getCachingType() == CACHING_TYPE_MEMORY && $this->Application->isDebugMode(); $this->_storeStatistics = defined('DBG_CACHE') && DBG_CACHE; if ( $this->_storeStatistics ) { // don't use FileHelper, since kFactory isn't ready yet if ( !file_exists(RESTRICTED . DIRECTORY_SEPARATOR . 'cache_usage') ) { mkdir(RESTRICTED . DIRECTORY_SEPARATOR . 'cache_usage'); } } } /** * Returns caching type of current storage engine * * @return int */ function getCachingType() { return $this->cachingType; } /** * Stores value to cache * * @param string $name * @param mixed $value * @param int $expiration cache record expiration time in seconds * @return bool */ function setCache($name, $value, $expiration) { // 1. stores current version of serial for given cache key $this->_setCache($name . '_serials', $this->replaceSerials($name), $expiration); $this->storeStatistics($name, 'W'); // 2. don't replace serials within the key $saved = $this->_setCache($name, $value, $expiration); $this->storeStatistics($name, 'U'); // 3. remove rebuilding mark $this->delete($name . '_rebuilding'); return $saved; } /** * Stores value to cache * * @param string $name * @param mixed $value * @param int $expiration cache record expiration time in seconds * @return bool */ function _setCache($name, $value, $expiration) { $prepared_name = $this->prepareKeyName($name); $this->_localStorage[$prepared_name] = $value; return $this->_handler->set($prepared_name, $value, $expiration); } /** * Stores value to cache (only if it's not there already) * * @param string $name * @param mixed $value * @param int $expiration cache record expiration time in seconds * @return bool */ function addCache($name, $value, $expiration) { // 1. stores current version of serial for given cache key $this->_setCache($name . '_serials', $this->replaceSerials($name), $expiration); // 2. remove rebuilding mark $this->delete($name . '_rebuilding'); // 3. don't replace serials within the key return $this->_addCache($name, $value, $expiration); } /** * Stores value to cache (only if it's not there already) * * @param string $name * @param mixed $value * @param int $expiration cache record expiration time in seconds * @return bool */ function _addCache($name, $value, $expiration) { $prepared_name = $this->prepareKeyName($name); $added = $this->_handler->add($prepared_name, $value, $expiration); if ( $added ) { $this->_localStorage[$prepared_name] = $value; } return $added; } /** * Sets rebuilding mode for given cache * * @param string $name * @param int $mode * @param int $max_rebuilding_time * @param string $miss_type * @return bool */ function rebuildCache($name, $mode = null, $max_rebuilding_time = 0, $miss_type = 'M') { if ( !isset($mode) || $mode == self::REBUILD_NOW ) { $this->storeStatistics($name, $miss_type); if ( !$max_rebuilding_time ) { return true; } // prevent parallel rebuild attempt by using "add" instead of "set" method if ( !$this->_addCache($name . '_rebuilding', 1, $max_rebuilding_time) ) { $this->storeStatistics($name, 'l'); return false; } $this->storeStatistics($name, 'L'); $this->delete($name . '_rebuild'); } elseif ( $mode == self::REBUILD_LATER ) { $this->_setCache($name . '_rebuild', 1, 0); $this->delete($name . '_rebuilding'); } return true; } /** * Returns value from cache * * @param string $name * @param bool $store_locally store data locally after retrieved * @param int $max_rebuild_seconds * @return mixed */ function getCache($name, $store_locally = true, $max_rebuild_seconds = 0) { $cached_data = $this->_getCache(Array ($name . '_rebuild', $name . '_serials'), Array (true, true)); if ( $cached_data[$name . '_rebuild'] ) { // cache rebuild requested -> rebuild now $this->delete($name . '_rebuild'); if ( $this->rebuildCache($name, self::REBUILD_NOW, $max_rebuild_seconds, '[M1]') ) { return false; } } // There are 2 key types: // - with serials, e.g. with_serial_key[%LangSerial%] // - without serials, e.g. without_serial // Evaluated serials of each cache key are stored in '{$name}_serials' cache key. // If cache is present, but serial is outdated, then cache value is assumed to be outdated. $new_serial = $this->replaceSerials($name); $old_serial = $cached_data[$name . '_serials']; if ( $name == $new_serial || $new_serial != $old_serial ) { // no serials in cache key OR cache is outdated $wait_seconds = $max_rebuild_seconds; while (true) { $cached_data = $this->_getCache(Array ($name, $name . '_rebuilding'), Array ($store_locally, false)); $cache = $cached_data[$name]; $rebuilding = $cached_data[$name . '_rebuilding']; if ( ($cache === false) && (!$rebuilding || $wait_seconds == 0) ) { // cache missing and nobody rebuilding it -> rebuild; enough waiting for cache to be ready $this->rebuildCache($name, self::REBUILD_NOW, $max_rebuild_seconds, '[M2' . ($rebuilding ? 'R' : '!R') . ',WS=' . $wait_seconds . ']'); return false; } elseif ( $cache !== false ) { // re-read serial, since it might have been changed in parallel process !!! $old_serial = $this->_getCache($name . '_serials', false); // cache present (if other user is rebuilding it, then it's outdated cache) -> return it if ( $rebuilding || $new_serial == $old_serial ) { $this->storeStatistics($name, $rebuilding ? 'h' : 'H'); return $cache; } $this->rebuildCache($name, self::REBUILD_NOW, $max_rebuild_seconds, '[M3' . ($rebuilding ? 'R' : '!R') . ',WS=' . $wait_seconds . ']'); return false; } $wait_seconds -= self::WAIT_STEP; sleep(self::WAIT_STEP); } } $cache = $this->_getCache($name, $store_locally); if ( $cache === false ) { $this->rebuildCache($name, self::REBUILD_NOW, $max_rebuild_seconds, '[M4]'); } else { $this->storeStatistics($name, 'H'); } return $cache; } /** * Returns cached value from local cache * * @param string $prepared_name Prepared key name from kCache::prepareKeyName() function * @return mixed * @see prepareKeyName * @access public */ public function getFromLocalStorage($prepared_name) { return array_key_exists($prepared_name, $this->_localStorage) ? $this->_localStorage[$prepared_name] : false; } /** * Returns value from cache * * @param string|Array $names * @param bool|Array $store_locally store data locally after retrieved * @return mixed */ function _getCache($names, $store_locally = true) { static $request_number = 1; $res = Array (); $names = (array)$names; $store_locally = (array)$store_locally; $to_get = $prepared_names = array_map(Array (&$this, 'prepareKeyName'), $names); foreach ($prepared_names as $index => $prepared_name) { $name = $names[$index]; if ( $store_locally[$index] && array_key_exists($prepared_name, $this->_localStorage) ) { $res[$name] = $this->_localStorage[$prepared_name]; unset($to_get[$index]); } } if ( $to_get ) { $multi_res = $this->_handler->get($to_get); foreach ($to_get as $index => $prepared_name) { $name = $names[$index]; if ( array_key_exists($prepared_name, $multi_res) ) { $res[$name] =& $multi_res[$prepared_name]; } else { $res[$name] = false; } $this->_postProcessGetCache($prepared_name, $res[$name], $store_locally[$index], $request_number); } $request_number++; } return count($res) == 1 ? array_pop($res) : $res; } /** * Stores variable in local cache & collects debug info about cache * * @param string $name * @param mixed $res * @param bool $store_locally * @param int $request_number * @return void * @access protected */ protected function _postProcessGetCache($name, &$res, $store_locally = true, $request_number) { if ( $this->debugCache ) { // don't display subsequent serial cache retrievals (ones, that are part of keys) if ( is_array($res) ) { $this->Application->Debugger->appendHTML('r' . $request_number . ': Restoring key "' . $name . '". Type: ' . gettype($res) . '.'); } else { $res_display = strip_tags($res); if ( strlen($res_display) > 200 ) { $res_display = substr($res_display, 0, 50) . ' ...'; } $this->Application->Debugger->appendHTML('r' . $request_number . ': Restoring key "' . $name . '" resulted [' . $res_display . ']'); } } if ( $store_locally /*&& ($res !== false)*/ ) { $this->_localStorage[$name] = $res; } } /** * Deletes value from cache * * @param string $name * @return mixed */ function delete($name) { $name = $this->prepareKeyName($name); unset($this->_localStorage[$name]); return $this->_handler->delete($name); } /** * Reset's all memory cache at once */ function reset() { // don't check for enabled, because we maybe need to reset cache anyway if ($this->cachingType == CACHING_TYPE_TEMPORARY) { return ; } $site_key = $this->_cachePrefix(true); $this->_handler->set($site_key, $this->_handler->get($site_key) + 1); } /** * Replaces serials and adds unique site prefix to cache variable name * * @param string $name * @return string */ protected function prepareKeyName($name) { if ( $this->cachingType == CACHING_TYPE_TEMPORARY ) { return $name; } // add site-wide prefix to key return $this->_cachePrefix() . $name; } /** * Replaces serials within given string * * @param string $value * @return string * @access protected */ protected function replaceSerials($value) { if ( preg_match_all('/\[%(.*?)%\]/', $value, $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 $serial_names = $regs[1]; $serial_count = count($serial_names); $store_locally = Array (); for ($i = 0; $i < $serial_count; $i++) { $store_locally[] = true; } $serial_values = $this->_getCache($serial_names, $store_locally); if ( !is_array($serial_values) ) { $serial_values = Array (current($serial_names) => $serial_values); } foreach ($serial_names as $serial_name) { $value = str_replace('[%' . $serial_name . '%]', '[' . $serial_name . '=' . $serial_values[$serial_name] . ']', $value); } } return $value; } /** * Returns site-wide caching prefix * * @param bool $only_site_key_name * @return string */ function _cachePrefix($only_site_key_name = false) { if ($only_site_key_name) { return $this->siteKeyName; } if ( !isset($this->siteKeyValue) ) { $this->siteKeyValue = $this->_handler->get($this->siteKeyName); if (!$this->siteKeyValue) { $this->siteKeyValue = 1; $this->_handler->set($this->siteKeyName, $this->siteKeyValue); } } return "{$this->siteKeyName}:{$this->siteKeyValue}:"; } /** * Stores statistics about cache usage in a file (one file per cache) * * @param string $name * @param string $action_type {M - miss, L - lock, W - write, U - unlock, H - actual hit, h - outdated hit} * @return void * @access public */ public function storeStatistics($name, $action_type) { if ( !$this->_storeStatistics ) { return; } $name = str_replace(Array ('/', '\\', ':'), '_', $name); $fp = fopen(RESTRICTED . DIRECTORY_SEPARATOR . 'cache_usage' . DIRECTORY_SEPARATOR . $name, 'a'); fwrite($fp, $action_type); fclose($fp); } } abstract class kCacheHandler { /** * Remembers status of cache handler (working or not) * * @var bool * @access protected */ protected $_enabled = false; /** * Caching type that caching handler implements * * @var int * @access protected */ protected $cachingType; /** * * @var kCache * @access protected */ protected $parent; public function __construct(kCache $parent) { $this->parent = $parent; } /** * Retrieves value from cache * * @param string $names * @return mixed * @access public */ abstract public function get($names); /** * Stores value in cache * * @param string $name * @param mixed $value * @param int $expiration * @return bool * @access public */ abstract public function set($name, $value, $expiration = 0); /** * Stores value in cache (only if it's not there already) * * @param string $name * @param mixed $value * @param int $expiration * @return bool * @access public */ abstract public function add($name, $value, $expiration = 0); /** * Deletes key from cach * * @param string $name * @return bool * @access public */ abstract public function delete($name); /** * Determines, that cache storage is working fine * * @return bool * @access public */ public function isWorking() { return $this->_enabled; } /** * Returns caching type of current storage engine * * @return int * @access public */ public function getCachingType() { return $this->cachingType; } } class FakeCacheHandler extends kCacheHandler { public function __construct(kCache $parent) { parent::__construct($parent); $this->_enabled = true; $this->cachingType = CACHING_TYPE_TEMPORARY; } /** * Retrieves value from cache * * @param string|Array $names * @return mixed * @access public */ public function get($names) { if ( is_array($names) ) { $res = Array (); foreach ($names as $name) { $res[$name] = $this->parent->getFromLocalStorage($name); } return $res; } return $this->parent->getFromLocalStorage($names); } /** * Stores value in cache * * @param string $name * @param mixed $value * @param int $expiration * @return bool * @access public */ public function set($name, $value, $expiration = 0) { return true; } /** * Stores value in cache (only if it's not there already) * * @param string $name * @param mixed $value * @param int $expiration * @return bool * @access public */ public function add($name, $value, $expiration = 0) { return true; } /** * Deletes key from cach * * @param string $name * @return bool * @access public */ public function delete($name) { return true; } } class MemcacheCacheHandler extends kCacheHandler { /** * Memcache connection * * @var Memcache * @access protected */ protected $_handler = null; public function __construct(kCache $parent, $default_servers = '') { parent::__construct($parent); $this->cachingType = CACHING_TYPE_MEMORY; $memcached_servers = kUtil::getSystemConfig()->get('MemcacheServers', $default_servers); if ( $memcached_servers && class_exists('Memcache') ) { $this->_enabled = true; $this->_handler = new Memcache(); $servers = explode(';', $memcached_servers); foreach ($servers as $server) { if ( preg_match('/(.*):([\d]+)$/', $server, $regs) ) { // "hostname:port" OR "unix:///path/to/socket:0" $server = $regs[1]; $port = $regs[2]; } else { $port = 11211; } $this->_handler->addServer($server, $port); } // verify, that memcache server is working if ( !$this->_handler->set('test', 1) ) { $this->_enabled = false; } } } /** * Retrieves value from cache * * @param string $name * @return mixed * @access public */ public function get($name) { return $this->_handler->get($name); } /** * Stores value in cache * * @param string $name * @param mixed $value * @param int $expiration * @return bool * @access public */ public function set($name, $value, $expiration = 0) { // 0 - don't use compression return $this->_handler->set($name, $value, 0, $expiration); } /** * Stores value in cache (only if it's not there already) * * @param string $name * @param mixed $value * @param int $expiration * @return bool * @access public */ public function add($name, $value, $expiration = 0) { // 0 - don't use compression return $this->_handler->add($name, $value, 0, $expiration); } /** * Deletes key from cache * * @param string $name * @return bool * @access public */ public function delete($name) { return $this->_handler->delete($name, 0); } } + class MemcachedCacheHandler extends kCacheHandler { + + /** + * Memcache connection + * + * @var Memcached + * @access protected + */ + protected $_handler = null; + + /** + * MemcachedCacheHandler constructor. + * + * @param kCache $parent Parent. + * @param string $default_servers Default servers. + */ + public function __construct(kCache $parent, $default_servers = '') + { + parent::__construct($parent); + + $this->cachingType = CACHING_TYPE_MEMORY; + + $memcached_servers = kUtil::getSystemConfig()->get('MemcacheServers', $default_servers); + + if ( $memcached_servers && class_exists('Memcached') ) { + $this->_enabled = true; + $this->_handler = new Memcached(); + $servers = explode(';', $memcached_servers); + + foreach ( $servers as $server ) { + if ( preg_match('/(.*):([\d]+)$/', $server, $regs) ) { + // Possible format: "hostname:port" OR "unix:///path/to/socket:0". + $server = $regs[1]; + $port = $regs[2]; + } + else { + $port = 11211; + } + + $this->_handler->addServer($server, $port); + } + + // Verify, that memcache server is working. + if ( !$this->_handler->set('test', 1) ) { + $this->_enabled = false; + } + } + } + + /** + * Retrieves value from cache + * + * @param string|array $name Name. + * + * @return mixed + * @access public + */ + public function get($name) + { + if ( is_array($name) ) { + return $this->_handler->getMulti($name); + } + + return $this->_handler->get($name); + } + + /** + * Stores value in cache + * + * @param string $name Name. + * @param mixed $value Value. + * @param integer $expiration Expiration. + * + * @return boolean + * @access public + */ + public function set($name, $value, $expiration = 0) + { + return $this->_handler->set($name, $value, $expiration); + } + + /** + * Stores value in cache (only if it's not there already) + * + * @param string $name Name. + * @param mixed $value Value. + * @param integer $expiration Expiration. + * + * @return boolean + * @access public + */ + public function add($name, $value, $expiration = 0) + { + return $this->_handler->add($name, $value, $expiration); + } + + /** + * Deletes key from cache + * + * @param string $name Name. + * + * @return boolean + * @access public + */ + public function delete($name) + { + return $this->_handler->delete($name); + } + + } class ApcCacheHandler extends kCacheHandler { public function __construct(kCache $parent) { parent::__construct($parent); $this->cachingType = CACHING_TYPE_MEMORY; $this->_enabled = function_exists('apc_fetch'); // verify, that apc is working if ( $this->_enabled && !$this->set('test', 1) ) { $this->_enabled = false; } } /** * Retrieves value from cache * * @param string $name * @return mixed * @access public */ public function get($name) { return apc_fetch($name); } /** * Stores value in cache * * @param string $name * @param mixed $value * @param int $expiration * @return bool * @access public */ public function set($name, $value, $expiration = 0) { return apc_store($name, $value, $expiration); } /** * Stores value in cache (only if it's not there already) * * @param string $name * @param mixed $value * @param int $expiration * @return bool * @access public */ public function add($name, $value, $expiration = 0) { return apc_add($name, $value, $expiration); } /** * Deletes key from cache * * @param string $name * @return bool * @access public */ public function delete($name) { return apc_delete($name); } } class XCacheCacheHandler extends kCacheHandler { public function __construct(kCache $parent) { parent::__construct($parent); $this->cachingType = CACHING_TYPE_MEMORY; $this->_enabled = function_exists('xcache_get'); // verify, that xcache is working if ( $this->_enabled && !$this->set('test', 1) ) { $this->_enabled = false; } } /** * Retrieves value from cache * * @param string|Array $names * @return mixed * @access public */ public function get($names) { if ( is_array($names) ) { $res = Array (); foreach ($names as $name) { $res[$name] = $this->get($name); } return $res; } return xcache_isset($names) ? xcache_get($names) : false; } /** * Stores value in cache * * @param string $name * @param mixed $value * @param int $expiration * @return bool * @access public */ public function set($name, $value, $expiration = 0) { return xcache_set($name, $value, $expiration); } /** * Stores value in cache (only if it's not there already) * * @param string $name * @param mixed $value * @param int $expiration * @return bool * @access public */ public function add($name, $value, $expiration = 0) { // not atomic operation, like in Memcached and may fail if ( xcache_isset($name) ) { return false; } return $this->set($name, $value, $expiration); } /** * Deletes key from cache * * @param string $name * @return bool * @access public */ public function delete($name) { return xcache_unset($name); } - } \ No newline at end of file + } Index: branches/5.2.x/core/install/install_toolkit.php =================================================================== --- branches/5.2.x/core/install/install_toolkit.php (revision 16752) +++ branches/5.2.x/core/install/install_toolkit.php (revision 16753) @@ -1,1026 +1,1030 @@ systemConfig = new kSystemConfig(true, false); if ( class_exists('kApplication') ) { // auto-setup in case of separate module install $this->Application =& kApplication::Instance(); $this->Application->Init(); // needed for standalone module install $this->Conn =& $this->Application->GetADODBConnection(); } } /** * Sets installator * * @param kInstallator $instance */ function setInstallator(&$instance) { $this->_installator =& $instance; } /** * Checks prerequisities before module install or upgrade * * @param string $module_path * @param string $versions * @param string $mode upgrade mode = {install, standalone, upgrade} * @return bool */ function CheckPrerequisites($module_path, $versions, $mode) { if ( !$versions ) { return Array (); } /** @var InPortalPrerequisites $prerequisite_object */ $prerequisite_object =& $this->getPrerequisiteObject($module_path); // some errors possible return is_object($prerequisite_object) ? $prerequisite_object->CheckPrerequisites($versions, $mode) : Array (); } /** * Call prerequisites method * * @param string $module_path * @param string $method * @return array */ function CallPrerequisitesMethod($module_path, $method) { /** @var InPortalPrerequisites $prerequisite_object */ $prerequisite_object =& $this->getPrerequisiteObject($module_path); return is_object($prerequisite_object) ? $prerequisite_object->$method() : false; } /** * Returns prerequisite object to be used for checks * * @param string $module_path * @return kHelper * @access protected */ protected function &getPrerequisiteObject($module_path) { static $prerequisite_classes = Array (); $prerequisites_file = sprintf(PREREQUISITE_FILE, $module_path); if ( !file_exists($prerequisites_file) ) { $false = false; return $false; } if ( !isset($prerequisite_classes[$module_path]) ) { // save class name, because 2nd time // (in after call $prerequisite_class variable will not be present) include_once $prerequisites_file; $prerequisite_classes[$module_path] = $prerequisite_class; } /** @var InPortalPrerequisites $prerequisite_object */ $prerequisite_object = new $prerequisite_classes[$module_path](); if ( method_exists($prerequisite_object, 'setToolkit') ) { $prerequisite_object->setToolkit($this); } return $prerequisite_object; } /** * Processes one license, received from server * * @param string $file_data */ function processLicense($file_data) { /** @var kModulesHelper $modules_helper */ $modules_helper = $this->Application->recallObject('ModulesHelper'); $file_data = explode('Code==:', $file_data); $file_data[0] = str_replace('In-Portal License File - do not edit!' . "\n", '', $file_data[0]); $file_data = array_map('trim', $file_data); if ($modules_helper->verifyLicense($file_data[0])) { $this->systemConfig->set('License', 'Intechnic', $file_data[0]); if (array_key_exists(1, $file_data)) { $this->systemConfig->set('LicenseCode', 'Intechnic', $file_data[1]); } else { $this->systemConfig->set('LicenseCode', 'Intechnic'); } $this->systemConfig->save(); } else { // invalid license received from licensing server $this->_installator->errorMessage = 'Invalid License File'; } } /** * Saves given configuration values to database * * @param Array $config */ function saveConfigValues($config) { foreach ($config as $config_var => $value) { $sql = 'UPDATE ' . TABLE_PREFIX . 'SystemSettings SET VariableValue = ' . $this->Conn->qstr($value) . ' WHERE VariableName = ' . $this->Conn->qstr($config_var); $this->Conn->Query($sql); } } /** * Sets module version to passed * * @param string $module_name * @param string|bool $module_path * @param string|bool $version */ function SetModuleVersion($module_name, $module_path = false, $version = false) { if ($version === false) { if (!$module_path) { throw new Exception('Module path must be given to "SetModuleVersion" method to auto-detect version'); return ; } $version = $this->GetMaxModuleVersion($module_path); } // get table prefix from config, because application may not be available here $table_prefix = $this->systemConfig->get('TablePrefix', 'Database'); if ($module_name == 'kernel') { $module_name = 'in-portal'; } // don't use "adodb_mktime" here, because it's not yet included $sql = 'UPDATE ' . $table_prefix . 'Modules SET Version = "' . $version . '", BuildDate = ' . time() . ' WHERE LOWER(Name) = "' . strtolower($module_name) . '"'; $this->Conn->Query($sql); } /** * Sets module root category to passed * * @param string $module_name * @param int $category_id */ function SetModuleRootCategory($module_name, $category_id = 0) { // get table prefix from config, because application may not be available here $table_prefix = $this->systemConfig->get('TablePrefix', 'Database'); if ($module_name == 'kernel') { $module_name = 'in-portal'; } $sql = 'UPDATE ' . $table_prefix . 'Modules SET RootCat = ' . $category_id . ' WHERE LOWER(Name) = "' . strtolower($module_name) . '"'; $this->Conn->Query($sql); } /** * Returns maximal version of given module by scanning it's upgrade scripts * * @param string $module_path * @return string */ function GetMaxModuleVersion($module_path) { $module_path = rtrim(mb_strtolower($module_path), '/'); $upgrades_file = sprintf(UPGRADES_FILE, $module_path . '/', 'sql'); if (!file_exists($upgrades_file)) { // no upgrade file return '5.0.0'; } $sqls = file_get_contents($upgrades_file); $versions_found = preg_match_all('/'.VERSION_MARK.'/s', $sqls, $regs); if (!$versions_found) { // upgrades file doesn't contain version definitions return '5.0.0'; } return end($regs[1]); } /** * Runs SQLs from file * * @param string $filename * @param mixed $replace_from * @param mixed $replace_to */ function RunSQL($filename, $replace_from = null, $replace_to = null) { if (!file_exists(FULL_PATH.$filename)) { return ; } $sqls = file_get_contents(FULL_PATH.$filename); if (!$this->RunSQLText($sqls, $replace_from, $replace_to)) { if (is_object($this->_installator)) { $this->_installator->Done(); } else { if (isset($this->Application)) { $this->Application->Done(); } exit; } } } /** * Runs SQLs from string * * @param string $sqls * @param mixed $replace_from * @param mixed $replace_to * @param int $start_from * @return bool */ function RunSQLText(&$sqls, $replace_from = null, $replace_to = null, $start_from = 0) { $table_prefix = $this->systemConfig->get('TablePrefix', 'Database'); // add prefix to all tables if (strlen($table_prefix) > 0) { $replacements = Array ('INSERT INTO ', 'UPDATE ', 'ALTER TABLE ', 'DELETE FROM ', 'REPLACE INTO '); foreach ($replacements as $replacement) { $sqls = str_replace($replacement, $replacement . $table_prefix, $sqls); } } $sqls = str_replace('CREATE TABLE ', 'CREATE TABLE IF NOT EXISTS ' . $table_prefix, $sqls); $sqls = str_replace('DROP TABLE ', 'DROP TABLE IF EXISTS ' . $table_prefix, $sqls); $sqls = str_replace('<%TABLE_PREFIX%>', $table_prefix, $sqls); $primary_language = is_object($this->Application) ? $this->Application->GetDefaultLanguageId() : 1; $sqls = str_replace('<%PRIMARY_LANGUAGE%>', $primary_language, $sqls); if (isset($replace_from) && isset($replace_to)) { // replace something additionally, e.g. module root category $sqls = str_replace($replace_from, $replace_to, $sqls); } $sqls = str_replace("\r\n", "\n", $sqls); // convert to linux line endings $no_comment_sqls = preg_replace("/#\s([^;]*?)\n/is", '', $sqls); // remove all comments "#" on new lines if ($no_comment_sqls === null) { // "ini.pcre.backtrack-limit" reached and error happened $sqls = explode(";\n", $sqls . "\n"); // ensures that last sql won't have ";" in it $sqls = array_map('trim', $sqls); // remove all comments "#" on new lines (takes about 2 seconds for 53000 sqls) $sqls = preg_replace("/#\s([^;]*?)/", '', $sqls); } else { $sqls = explode(";\n", $no_comment_sqls . "\n"); // ensures that last sql won't have ";" in it $sqls = array_map('trim', $sqls); } $sql_count = count($sqls); $db_collation = $this->systemConfig->get('DBCollation', 'Database'); for ($i = $start_from; $i < $sql_count; $i++) { $sql = $sqls[$i]; if (!$sql || (substr($sql, 0, 1) == '#')) { continue; // usually last line } if (substr($sql, 0, 13) == 'CREATE TABLE ' && $db_collation) { // it is CREATE TABLE statement -> add collation $sql .= ' COLLATE \'' . $db_collation . '\''; } $this->Conn->Query($sql); if ($this->Conn->getErrorCode() != 0) { if (is_object($this->_installator)) { $this->_installator->errorMessage = 'Error: ('.$this->Conn->getErrorCode().') '.$this->Conn->getErrorMsg().'

Last Database Query:
'; $this->_installator->LastQueryNum = $i + 1; } return false; } } return true; } /** * Performs clean language import from given xml file * * @param string $lang_file * @param bool $upgrade * @todo Import for "core/install/english.lang" (322KB) takes 18 seconds to work on Windows */ function ImportLanguage($lang_file, $upgrade = false) { $lang_file = FULL_PATH.$lang_file.'.lang'; if (!file_exists($lang_file)) { return ; } /** @var LanguageImportHelper $language_import_helper */ $language_import_helper = $this->Application->recallObject('LanguageImportHelper'); $language_import_helper->performImport($lang_file, '|0|1|2|', '', $upgrade ? LANG_SKIP_EXISTING : LANG_OVERWRITE_EXISTING); } /** * Converts module version in format X.Y.Z[-BN/-RCM] to signle integer * * @param string $version * @return int */ function ConvertModuleVersion($version) { if (preg_match('/(.*)-(B|RC)([\d]+)/', $version, $regs)) { // -B or RC- $parts = explode('.', $regs[1]); $parts[] = $regs[2] == 'B' ? 1 : 2; // B reliases goes before RC releases $parts[] = $regs[3]; } else { // releases without B/RC marks go after any B/RC releases $parts = explode('.', $version . '.3.100'); } $bin = ''; foreach ($parts as $part_index => $part) { if ($part_index == 3) { // version type only can be 1/2/3 (11 in binary form), so don't use padding at all $pad_count = 2; } else { $pad_count = 8; } $bin .= str_pad(decbin($part), $pad_count, '0', STR_PAD_LEFT); } return bindec($bin); } /** * Returns themes, found in system * * @param bool $rebuild * @return int */ function getThemes($rebuild = false) { if ($rebuild) { $this->rebuildThemes(); } $id_field = $this->Application->getUnitOption('theme', 'IDField'); $table_name = $this->Application->getUnitOption('theme', 'TableName'); $sql = 'SELECT Name, ' . $id_field . ' FROM ' . $table_name . ' ORDER BY Name ASC'; return $this->Conn->GetCol($sql, $id_field); } /** * Checks if system config is present and is not empty * * @return bool */ function systemConfigFound() { return $this->systemConfig->exists(); } /** * Checks if given section is present in config * * @param string $section * @return bool */ function sectionFound($section) { return $this->systemConfig->sectionFound($section); } /** * Returns formatted module name based on it's root folder * * @param string $module_folder * @return string */ function getModuleName($module_folder) { return implode('-', array_map('ucfirst', explode('-', $module_folder))); } /** * Returns information about module (based on "install/module_info.xml" file) * * @param string $module_name * @return Array */ function getModuleInfo($module_name) { if ( $module_name == 'core' ) { $info_file = FULL_PATH . '/' . $module_name . '/install/module_info.xml'; } else { $info_file = MODULES_PATH . '/' . $module_name . '/install/module_info.xml'; } if ( !file_exists($info_file) ) { return Array (); } $ret = Array (); /** @var SimpleXMLElement[] $module_info */ $module_info = simplexml_load_file($info_file); if ( $module_info === false ) { // non-valid xml file return Array (); } foreach ($module_info as $node) { $ret[strtolower($node->getName())] = trim($node); } return $ret; } /** * Returns nice module string to be used on install/upgrade screens * * @param string $module_name * @param string $version_string * @return string */ function getModuleString($module_name, $version_string) { // image (if exists) ( ) $ret = Array (); $module_info = $this->getModuleInfo($module_name); if (array_key_exists('name', $module_info) && $module_info['name']) { $module_name = $module_info['name']; } else { $module_name = $this->getModuleName($module_name); } if (array_key_exists('image', $module_info) && $module_info['image']) { $image_src = $module_info['image']; if (!preg_match('/^(http|https):\/\//', $image_src)) { // local image -> make absolute url $image_src = $this->Application->BaseURL() . $image_src; } $ret[] = '' . htmlspecialchars($module_name, ENT_QUOTES, 'UTF-8') . ''; } if (array_key_exists('description', $module_info) && $module_info['description']) { $ret[] = $module_info['description']; } else { $ret[] = $module_name; } $ret[] = '(' . $module_name . ' ' . $version_string . ')'; return implode(' ', $ret); } /** * Creates module root category in "Home" category using given data and returns it * * @param string $name * @param string $description * @param string $category_template * @param string $category_icon * @return kDBItem */ function &createModuleCategory($name, $description, $category_template = null, $category_icon = null) { static $fields = null; if ( !isset($fields) ) { /** @var kMultiLanguage $ml_formatter */ $ml_formatter = $this->Application->recallObject('kMultiLanguage'); $fields['name'] = $ml_formatter->LangFieldName('Name'); $fields['description'] = $ml_formatter->LangFieldName('Description'); } /** @var kDBItem $category */ $category = $this->Application->recallObject('c', null, Array ('skip_autoload' => true)); $category_fields = Array ( $fields['name'] => $name, 'Filename' => $name, 'AutomaticFilename' => 1, $fields['description'] => $description, 'Status' => STATUS_ACTIVE, 'Priority' => -9999, // prevents empty link to module category on spearate module install 'NamedParentPath' => 'Content/' . $name, ); $category_fields['ParentId'] = $this->Application->getBaseCategory(); if ( isset($category_template) ) { $category_fields['Template'] = $category_template; $category_fields['CachedTemplate'] = $category_template; } if ( isset($category_icon) ) { $category_fields['UseMenuIconUrl'] = 1; $category_fields['MenuIconUrl'] = $category_icon; } $category->Clear(); $category->SetDBFieldsFromHash($category_fields); $category->Create(); /** @var kPriorityHelper $priority_helper */ $priority_helper = $this->Application->recallObject('PriorityHelper'); $event = new kEvent('c:OnListBuild'); // ensure, that newly created category has proper value in Priority field $priority_helper->recalculatePriorities($event, 'ParentId = ' . $category_fields['ParentId']); // update Priority field in object, becase "CategoriesItem::Update" method will be called // from "kInstallToolkit::setModuleItemTemplate" and otherwise will set 0 to Priority field $sql = 'SELECT Priority FROM ' . $category->TableName . ' WHERE ' . $category->IDField . ' = ' . $category->GetID(); $category->SetDBField('Priority', $this->Conn->GetOne($sql)); return $category; } /** * Sets category item template into custom field for given prefix * * @param kDBItem $category * @param string $prefix * @param string $item_template */ function setModuleItemTemplate(&$category, $prefix, $item_template) { $this->Application->removeObject('c-cdata'); // recreate all fields, because custom fields are added during install script $category->Configure(); $category->SetDBField('cust_' . $prefix .'_ItemTemplate', $item_template); $category->Update(); } /** * Link custom field records with search config records + create custom field columns * * @param string $module_folder * @param string $prefix * @param int $item_type */ function linkCustomFields($module_folder, $prefix, $item_type) { $module_folder = strtolower($module_folder); $module_name = $module_folder; if ( $module_folder == 'kernel' ) { $module_name = 'in-portal'; $module_folder = 'core'; } $db =& $this->Application->GetADODBConnection(); $sql = 'SELECT FieldName, CustomFieldId FROM ' . TABLE_PREFIX . 'CustomFields WHERE Type = ' . $item_type . ' AND IsSystem = 0'; // config is not read here yet :( $this->Application->getUnitOption('p', 'ItemType'); $custom_fields = $db->GetCol($sql, 'CustomFieldId'); foreach ($custom_fields as $cf_id => $cf_name) { $sql = 'UPDATE ' . TABLE_PREFIX . 'SearchConfig SET CustomFieldId = ' . $cf_id . ' WHERE (TableName = "CustomFields") AND (LOWER(ModuleName) = "' . $module_name . '") AND (FieldName = ' . $db->qstr($cf_name) . ')'; $db->Query($sql); } // because of configs was read only from installed before modules (in-portal), then reread configs $this->Application->UnitConfigReader->scanModules(MODULES_PATH . DIRECTORY_SEPARATOR . $module_folder); // create correct columns in CustomData table /** @var kMultiLanguageHelper $ml_helper */ $ml_helper = $this->Application->recallObject('kMultiLanguageHelper'); $ml_helper->createFields($prefix . '-cdata', true); } /** * Deletes cache, useful after separate module install and installator last step * * @param bool $refresh_permissions * @return void */ function deleteCache($refresh_permissions = false) { $this->Application->HandleEvent(new kEvent('adm:OnResetMemcache')); // not in DB = 100% invalidate $this->Application->HandleEvent(new kEvent('adm:OnResetConfigsCache')); $this->Application->HandleEvent(new kEvent('adm:OnResetSections')); $this->Application->HandleEvent(new kEvent('c:OnResetCMSMenuCache')); $this->Conn->Query('DELETE FROM ' . TABLE_PREFIX . 'CachedUrls'); if ( $refresh_permissions ) { $rebuild_mode = $this->Application->ConfigValue('CategoryPermissionRebuildMode'); if ( $rebuild_mode == CategoryPermissionRebuild::SILENT ) { // refresh permission without progress bar /** @var kPermCacheUpdater $updater */ $updater = $this->Application->makeClass('kPermCacheUpdater'); $updater->OneStepRun(); } elseif ( $rebuild_mode == CategoryPermissionRebuild::AUTOMATIC ) { // refresh permissions with ajax progress bar (when available) $this->Application->setDBCache('ForcePermCacheUpdate', 1); } } } /** * Deletes all temp tables (from active sessions too) * */ function deleteEditTables() { $table_prefix = $this->systemConfig->get('TablePrefix', 'Database'); $tables = $this->Conn->GetCol('SHOW TABLES'); $mask_edit_table = '/' . $table_prefix . 'ses_(.*)_edit_(.*)/'; $mask_search_table = '/' . $table_prefix . 'ses_(.*?)_(.*)/'; foreach ($tables as $table) { if ( preg_match($mask_edit_table, $table, $rets) || preg_match($mask_search_table, $table, $rets) ) { $this->Conn->Query('DROP TABLE IF EXISTS ' . $table); } } } /** * Perform redirect after separate module install * * @param string $module_folder * @param bool $refresh_permissions */ function finalizeModuleInstall($module_folder, $refresh_permissions = false) { $this->SetModuleVersion(basename($module_folder), $module_folder); if (!$this->Application->GetVar('redirect')) { return ; } /** @var kThemesHelper $themes_helper */ $themes_helper = $this->Application->recallObject('ThemesHelper'); // use direct query, since module isn't yet in kApplication::ModuleInfo array $sql = 'SELECT Name FROM ' . TABLE_PREFIX . 'Modules WHERE Path = ' . $this->Conn->qstr(rtrim($module_folder, '/') . '/'); $module_name = $this->Conn->GetOne($sql); $themes_helper->synchronizeModule($module_name); /** @var kMultiLanguageHelper $ml_helper */ $ml_helper = $this->Application->recallObject('kMultiLanguageHelper'); $ml_helper->massCreateFields(); $this->deleteCache($refresh_permissions); $url_params = Array ( 'pass' => 'm', 'admin' => 1, 'RefreshTree' => 1, 'index_file' => 'index.php', ); $this->Application->Redirect('modules/modules_list', $url_params); } /** * Performs rebuild of themes * */ function rebuildThemes() { $this->Application->HandleEvent(new kEvent('adm:OnRebuildThemes')); } /** * Checks that file is writable by group or others * * @param string $file * @return boolean */ function checkWritePermissions($file) { if (DIRECTORY_SEPARATOR == '\\') { // windows doen't allow to check permissions (always returns null) return null; } $permissions = fileperms($file); return $permissions & 0x0010 || $permissions & 0x0002; } /** * Upgrades primary skin to the latest version * * @param Array $module_info * @return string|bool */ function upgradeSkin($module_info) { $upgrades_file = sprintf(UPGRADES_FILE, $module_info['Path'], 'css'); $data = file_get_contents($upgrades_file); // get all versions with their positions in file $versions = Array (); preg_match_all('/(' . VERSION_MARK . ')/s', $data, $matches, PREG_SET_ORDER + PREG_OFFSET_CAPTURE); $from_version_int = $this->ConvertModuleVersion($module_info['FromVersion']); foreach ($matches as $index => $match) { $version_int = $this->ConvertModuleVersion($match[2][0]); if ( $version_int < $from_version_int ) { // only process versions, that were released after currently used version continue; } $start_pos = $match[0][1] + strlen($match[0][0]); $end_pos = array_key_exists($index + 1, $matches) ? $matches[$index + 1][0][1] : mb_strlen($data); $patch_data = str_replace("\r\n", "\n", substr($data, $start_pos, $end_pos - $start_pos)); $versions[] = Array ( 'Version' => $match[2][0], // fixes trimmed leading spaces by modern text editor 'Data' => ltrim( str_replace("\n\n", "\n \n", $patch_data) ), ); } if ( !$versions ) { // not skin changes -> quit return true; } /** @var kDBItem $primary_skin */ $primary_skin = $this->Application->recallObject('skin.primary', null, Array ('skip_autoload' => true)); $primary_skin->Load(1, 'IsPrimary'); if ( !$primary_skin->isLoaded() ) { // we always got primary skin, but just in case return false; } /** @var kTempTablesHandler $temp_handler */ $temp_handler = $this->Application->recallObject('skin_TempHandler', 'kTempTablesHandler'); // clone current skin $cloned_ids = $temp_handler->CloneItems('skin', '', Array ($primary_skin->GetID())); if ( !$cloned_ids ) { // can't clone return false; } /** @var kDBItem $skin */ $skin = $this->Application->recallObject('skin.tmp', null, Array ('skip_autoload' => true)); $skin->Load($cloned_ids[0]); // save css to temp file (for patching) $skin_file = tempnam('/tmp', 'skin_css_'); $fp = fopen($skin_file, 'w'); fwrite($fp, str_replace("\r\n", "\n", $skin->GetDBField('CSS'))); fclose($fp); $output = Array (); $patch_file = tempnam('/tmp', 'skin_patch_'); foreach ($versions as $version_info) { // for each left version get it's patch and apply to temp file $fp = fopen($patch_file, 'w'); fwrite($fp, $version_info['Data']); fclose($fp); $output[ $version_info['Version'] ] = shell_exec('patch ' . $skin_file . ' ' . $patch_file . ' 2>&1') . "\n"; } // place temp file content into cloned skin $skin->SetDBField('Name', 'Upgraded to ' . $module_info['ToVersion']); $skin->SetDBField('CSS', file_get_contents($skin_file)); $skin->Update(); unlink($skin_file); unlink($patch_file); $has_errors = false; foreach ($output as $version => $version_output) { $version_errors = trim(preg_replace("/(^|\n)(patching file .*?|Hunk #.*?\.)(\n|$)/m", '', $version_output)); if ( $version_errors ) { $has_errors = true; $output[$version] = trim(preg_replace("/(^|\n)(patching file .*?)(\n|$)/m", '', $output[$version])); } else { unset($output[$version]); } } if ( !$has_errors ) { // copy patched css back to primary skin $primary_skin->SetDBField('CSS', $skin->GetDBField('CSS')); $primary_skin->Update(); // delete temporary skin record $temp_handler->DeleteItems('skin', '', Array ($skin->GetID())); return true; } // put clean skin from new version $skin->SetDBField('CSS', file_get_contents(FULL_PATH . '/core/admin_templates/incs/style_template.css')); $skin->Update(); // return output in case of errors return $output; } /** * Returns cache handlers, that are working * * @param string $current * @return Array */ public function getWorkingCacheHandlers($current = null) { if ( !isset($current) ) { $current = $this->systemConfig->get('CacheHandler', 'Misc'); } $cache_handler = $this->Application->makeClass('kCache'); $cache_handlers = Array ( - 'Fake' => 'None', 'Memcache' => 'Memcached', 'XCache' => 'XCache', 'Apc' => 'Alternative PHP Cache' + 'Fake' => 'None', + 'Memcached' => 'Memcached (via "Memcached" extension)', + 'Memcache' => 'Memcached (via "Memcache" extension)', + 'XCache' => 'XCache', + 'Apc' => 'Alternative PHP Cache', ); foreach ($cache_handlers AS $class_prefix => $title) { $handler_class = $class_prefix . 'CacheHandler'; if ( !class_exists($handler_class) ) { unset($cache_handlers[$class_prefix]); } else { /** @var FakeCacheHandler $handler */ $handler = new $handler_class($cache_handler, 'localhost:11211'); if ( !$handler->isWorking() ) { if ( $current == $class_prefix ) { $cache_handlers[$class_prefix] .= ' (offline)'; } else { unset($cache_handlers[$class_prefix]); } } } } return $cache_handlers; } /** * Returns compression engines, that are working * * @param string $current * @return Array */ public function getWorkingCompressionEngines($current = null) { if ( !isset($current) ) { $current = $this->systemConfig->get('CompressionEngine', 'Misc'); } $output = shell_exec('java -version 2>&1'); $compression_engines = Array ('' => 'None', 'yui' => 'YUICompressor (Java)', 'php' => 'PHP-based'); if ( stripos($output, 'java version') === false ) { if ( $current == 'yui' ) { $compression_engines['yui'] .= ' (offline)'; } else { unset($compression_engines['yui']); } } return $compression_engines; } }