Index: branches/5.2.x/core/kernel/application.php =================================================================== --- branches/5.2.x/core/kernel/application.php (revision 16771) +++ branches/5.2.x/core/kernel/application.php (revision 16772) @@ -1,3161 +1,3161 @@ * The class encapsulates the main run-cycle of the script, provide access to all other objects in the framework.
*
* The class is a singleton, which means that there could be only one instance of kApplication in the script.
* This could be guaranteed by NOT calling the class constructor directly, but rather calling kApplication::Instance() method, * which returns an instance of the application. The method guarantees that it will return exactly the same instance for any call.
* See singleton pattern by GOF. */ class kApplication implements kiCacheable { /** * Location of module helper class (used in installator too) */ const MODULE_HELPER_PATH = '/../units/helpers/modules_helper.php'; /** * Is true, when Init method was called already, prevents double initialization * * @var bool */ public $InitDone = false; /** * Holds internal NParser object * * @var NParser * @access public */ public $Parser; /** * Holds parser output buffer * * @var string * @access protected */ protected $HTML = ''; /** * The main Factory used to create * almost any class of kernel and * modules * * @var kFactory * @access protected */ protected $Factory; /** * Template names, that will be used instead of regular templates * * @var Array * @access public */ public $ReplacementTemplates = Array (); /** * Mod-Rewrite listeners used during url building and parsing * * @var Array * @access public */ public $RewriteListeners = Array (); /** * Reference to debugger * * @var Debugger * @access public */ public $Debugger = null; /** * Holds all phrases used * in code and template * * @var PhrasesCache * @access public */ public $Phrases; /** * Modules table content, key - module name * * @var Array * @access public */ public $ModuleInfo = Array (); /** * Holds DBConnection * * @var IDBConnection * @access public */ public $Conn = null; /** * Reference to event log * * @var Array|kLogger * @access public */ protected $_logger = Array (); // performance needs: /** * Holds a reference to httpquery * * @var kHttpQuery * @access public */ public $HttpQuery = null; /** * Holds a reference to UnitConfigReader * * @var kUnitConfigReader * @access public */ public $UnitConfigReader = null; /** * Holds a reference to Session * * @var Session * @access public */ public $Session = null; /** * Holds a ref to kEventManager * * @var kEventManager * @access public */ public $EventManager = null; /** * Holds a ref to kUrlManager * * @var kUrlManager * @access public */ public $UrlManager = null; /** * Ref for TemplatesCache * * @var TemplatesCache * @access public */ public $TemplatesCache = null; /** * Holds current NParser tag while parsing, can be used in error messages to display template file and line * * @var _BlockTag * @access public */ public $CurrentNTag = null; /** * Object of unit caching class * * @var kCacheManager * @access public */ public $cacheManager = null; /** * Tells, that administrator has authenticated in administrative console * Should be used to manipulate data change OR data restrictions! * * @var bool * @access public */ public $isAdminUser = false; /** * Tells, that admin version of "index.php" was used, nothing more! * Should be used to manipulate data display! * * @var bool * @access public */ public $isAdmin = false; /** * Instance of site domain object * * @var kDBItem * @access public * @todo move away into separate module */ public $siteDomain = null; /** * Prevent kApplication class to be created directly, only via Instance method * * @access private */ private function __construct() { } final private function __clone() {} /** * Returns kApplication instance anywhere in the script. * * This method should be used to get single kApplication object instance anywhere in the * Kernel-based application. The method is guaranteed to return the SAME instance of kApplication. * Anywhere in the script you could write: * * $application =& kApplication::Instance(); * * or in an object: * * $this->Application =& kApplication::Instance(); * * to get the instance of kApplication. Note that we call the Instance method as STATIC - directly from the class. * To use descendant of standard kApplication class in your project you would need to define APPLICATION_CLASS constant * BEFORE calling kApplication::Instance() for the first time. If APPLICATION_CLASS is not defined the method would * create and return default KernelApplication instance. * * Pattern: Singleton * * @static * @return kApplication * @access public */ public static function &Instance() { static $instance = false; if ( !$instance ) { $class = defined('APPLICATION_CLASS') ? APPLICATION_CLASS : 'kApplication'; $instance = new $class(); } return $instance; } /** * Initializes the Application * * @param string $factory_class * @return bool Was Init actually made now or before * @access public * @see kHTTPQuery * @see Session * @see TemplatesCache */ public function Init($factory_class = 'kFactory') { if ( $this->InitDone ) { return false; } if ( preg_match('/utf-8/i', CHARSET) ) { setlocale(LC_ALL, 'en_US.UTF-8'); mb_internal_encoding('UTF-8'); } $this->isAdmin = kUtil::constOn('ADMIN'); if ( !kUtil::constOn('SKIP_OUT_COMPRESSION') ) { ob_start(); // collect any output from method (other then tags) into buffer } if ( defined('DEBUG_MODE') && $this->isDebugMode() && kUtil::constOn('DBG_PROFILE_MEMORY') ) { $this->Debugger->appendMemoryUsage('Application before Init:'); } $this->_logger = new kLogger($this->_logger); $this->Factory = new $factory_class(); $this->registerDefaultClasses(); $system_config = new kSystemConfig(true); $vars = $system_config->getData(); $db_class = isset($vars['Databases']) ? 'kDBLoadBalancer' : ($this->isDebugMode() ? 'kDBConnectionDebug' : 'kDBConnection'); $this->Conn = $this->Factory->makeClass($db_class, Array (SQL_TYPE, Array ($this->_logger, 'handleSQLError'))); $this->Conn->setup($vars); $this->cacheManager = $this->makeClass('kCacheManager'); $this->cacheManager->InitCache(); define('MAX_UPLOAD_SIZE', $this->getMaxUploadSize()); if ( defined('DEBUG_MODE') && $this->isDebugMode() ) { $this->Debugger->appendTimestamp('Before UnitConfigReader'); } // init config reader and all managers $this->UnitConfigReader = $this->makeClass('kUnitConfigReader'); $this->UnitConfigReader->scanModules(MODULES_PATH); // will also set RewriteListeners when existing cache is read $this->registerModuleConstants(); if ( defined('DEBUG_MODE') && $this->isDebugMode() ) { $this->Debugger->appendTimestamp('After UnitConfigReader'); } define('MOD_REWRITE', $this->ConfigValue('UseModRewrite') && !$this->isAdmin ? 1 : 0); // start processing request $this->HttpQuery = $this->recallObject('HTTPQuery'); $this->HttpQuery->process(); if ( defined('DEBUG_MODE') && $this->isDebugMode() ) { $this->Debugger->appendTimestamp('Processed HTTPQuery initial'); } $this->Session = $this->recallObject('Session'); if ( defined('DEBUG_MODE') && $this->isDebugMode() ) { $this->Debugger->appendTimestamp('Processed Session'); } $this->Session->ValidateExpired(); // needs mod_rewrite url already parsed to keep user at proper template after session expiration if ( defined('DEBUG_MODE') && $this->isDebugMode() ) { $this->Debugger->appendTimestamp('Processed HTTPQuery AfterInit'); } $this->cacheManager->LoadApplicationCache(); $site_timezone = $this->ConfigValue('Config_Site_Time'); if ( $site_timezone ) { date_default_timezone_set($site_timezone); } if ( defined('DEBUG_MODE') && $this->isDebugMode() ) { $this->Debugger->appendTimestamp('Loaded cache and phrases'); } $this->ValidateLogin(); // must be called before AfterConfigRead, because current user should be available there $this->UnitConfigReader->AfterConfigRead(); // will set RewriteListeners when missing cache is built first time if ( defined('DEBUG_MODE') && $this->isDebugMode() ) { $this->Debugger->appendTimestamp('Processed AfterConfigRead'); } if ( $this->GetVar('m_cat_id') === false ) { $this->SetVar('m_cat_id', 0); } if ( !$this->RecallVar('curr_iso') && !(defined('IS_INSTALL') && IS_INSTALL) ) { $this->StoreVar('curr_iso', $this->GetPrimaryCurrency(), true); // true for optional } $visit_id = $this->RecallVar('visit_id'); if ( $visit_id !== false ) { $this->SetVar('visits_id', $visit_id); } if ( defined('DEBUG_MODE') && $this->isDebugMode() ) { $this->Debugger->profileFinish('kernel4_startup'); } $this->InitDone = true; if ( PHP_SAPI !== 'cli' && !$this->isAdmin ) { $this->HandleEvent(new kEvent('adm:OnStartup')); } else { // User can't edit anything in Admin Console or in CLI mode. kUtil::safeDefine('EDITING_MODE', ''); } return true; } /** * Calculates maximal upload file size * * @return integer */ protected function getMaxUploadSize() { $cache_key = 'max_upload_size'; $max_upload_size = $this->getCache($cache_key); if ( $max_upload_size === false ) { $max_upload_size = kUtil::parseIniSize(ini_get('upload_max_filesize')); $post_max_size = ini_get('post_max_size'); if ( $post_max_size !== '0' ) { $max_upload_size = min($max_upload_size, kUtil::parseIniSize($post_max_size)); } $memory_limit = ini_get('memory_limit'); if ( $memory_limit !== '-1' ) { $max_upload_size = min($max_upload_size, kUtil::parseIniSize($memory_limit)); } $this->setCache($cache_key, $max_upload_size); } return $max_upload_size; } /** * Performs initialization of manager classes, that can be overridden from unit configs * * @return void * @access public * @throws Exception */ public function InitManagers() { if ( $this->InitDone ) { throw new Exception('Duplicate call of ' . __METHOD__, E_USER_ERROR); return; } $this->UrlManager = $this->makeClass('kUrlManager'); $this->EventManager = $this->makeClass('EventManager'); $this->Phrases = $this->makeClass('kPhraseCache'); $this->RegisterDefaultBuildEvents(); } /** * Returns module information. Searches module by requested field * * @param string $field * @param mixed $value * @param string $return_field field value to returns, if not specified, then return all fields * @return Array */ public function findModule($field, $value, $return_field = null) { $found = $module_info = false; foreach ($this->ModuleInfo as $module_info) { if ( strtolower($module_info[$field]) == strtolower($value) ) { $found = true; break; } } if ( $found ) { return isset($return_field) ? $module_info[$return_field] : $module_info; } return false; } /** * Refreshes information about loaded modules * * @return void * @access public */ public function refreshModuleInfo() { if ( defined('IS_INSTALL') && IS_INSTALL && !$this->TableFound('Modules', true) ) { $this->registerModuleConstants(); return; } // use makeClass over recallObject, since used before kApplication initialization during installation /** @var kModulesHelper $modules_helper */ $modules_helper = $this->makeClass('ModulesHelper'); $this->Conn->nextQueryCachable = true; $sql = 'SELECT * FROM ' . TABLE_PREFIX . 'Modules WHERE ' . $modules_helper->getWhereClause() . ' ORDER BY LoadOrder'; $this->ModuleInfo = $this->Conn->Query($sql, 'Name'); $this->registerModuleConstants(); } /** * Checks if passed language id if valid and sets it to primary otherwise * * @return void * @access public */ public function VerifyLanguageId() { /** @var LanguagesItem $lang */ $lang = $this->recallObject('lang.current'); if ( !$lang->isLoaded() || (!$this->isAdmin && !$lang->GetDBField('Enabled')) ) { if ( !defined('IS_INSTALL') ) { $this->ApplicationDie('Unknown or disabled language'); } } } /** * Checks if passed theme id if valid and sets it to primary otherwise * * @return void * @access public */ public function VerifyThemeId() { if ( $this->isAdmin ) { kUtil::safeDefine('THEMES_PATH', '/core/admin_templates'); return; } $path = $this->GetFrontThemePath(); if ( $path === false ) { $this->ApplicationDie('No Primary Theme Selected or Current Theme is Unknown or Disabled'); } kUtil::safeDefine('THEMES_PATH', $path); } /** * Returns relative path to current front-end theme * * @param bool $force * @return string * @access public */ public function GetFrontThemePath($force = false) { static $path = null; if ( !$force && isset($path) ) { return $path; } /** @var ThemeItem $theme */ $theme = $this->recallObject('theme.current'); if ( !$theme->isLoaded() || !$theme->GetDBField('Enabled') ) { return false; } // assign & then return, since it's static variable $path = '/themes/' . $theme->GetDBField('Name'); return $path; } /** * Returns primary front/admin language id * * @param bool $init * @return int * @access public */ public function GetDefaultLanguageId($init = false) { $cache_key = 'primary_language_info[%LangSerial%]'; $language_info = $this->getCache($cache_key); 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'; if ( array_key_exists($language_key, $language_info) && $language_info[$language_key] > 0 ) { // get from cache return $language_info[$language_key]; } $language_id = $language_info && array_key_exists($language_key, $language_info) ? $language_info[$language_key] : false; if ( !$language_id && defined('IS_INSTALL') && IS_INSTALL ) { $language_id = 1; } return $language_id; } /** * Returns front-end primary theme id (even, when called from admin console) * * @param bool $force_front * @return int * @access public */ public function GetDefaultThemeId($force_front = false) { static $cache = array('force_front=yes' => 0, 'force_front=no' => 0); $static_cache_key = $force_front ? 'force_front=yes' : 'force_front=no'; if ( $cache[$static_cache_key] > 0 ) { return $cache[$static_cache_key]; } if ( kUtil::constOn('DBG_FORCE_THEME') ) { $cache[$static_cache_key] = DBG_FORCE_THEME; } elseif ( !$force_front && $this->isAdmin ) { $cache[$static_cache_key] = 999; } else { $cache_key = 'primary_theme[%ThemeSerial%]'; $cache[$static_cache_key] = $this->getCache($cache_key); if ( $cache[$static_cache_key] === false ) { $this->Conn->nextQueryCachable = true; $sql = 'SELECT ' . $this->getUnitOption('theme', 'IDField') . ' FROM ' . $this->getUnitOption('theme', 'TableName') . ' WHERE (PrimaryTheme = 1) AND (Enabled = 1)'; $cache[$static_cache_key] = $this->Conn->GetOne($sql); if ( $cache[$static_cache_key] !== false ) { $this->setCache($cache_key, $cache[$static_cache_key]); } } } return $cache[$static_cache_key]; } /** * Returns site primary currency ISO code * * @return string * @access public * @todo Move into In-Commerce */ public function GetPrimaryCurrency() { $cache_key = 'primary_currency[%CurrSerial%][%SiteDomainSerial%]:' . $this->siteDomainField('DomainId'); $currency_iso = $this->getCache($cache_key); if ( $currency_iso === false ) { if ( $this->prefixRegistred('curr') ) { $this->Conn->nextQueryCachable = true; $currency_id = $this->siteDomainField('PrimaryCurrencyId'); $sql = 'SELECT ISO FROM ' . $this->getUnitOption('curr', 'TableName') . ' WHERE ' . ($currency_id > 0 ? 'CurrencyId = ' . $currency_id : 'IsPrimary = 1'); $currency_iso = $this->Conn->GetOne($sql); } else { $currency_iso = 'USD'; } $this->setCache($cache_key, $currency_iso); } return $currency_iso; } /** * Returns site domain field. When none of site domains are found false is returned. * * @param string $field * @param bool $formatted * @param string $format * @return mixed * @todo Move into separate module */ public function siteDomainField($field, $formatted = false, $format = null) { if ( $this->isAdmin ) { // don't apply any filtering in administrative console return false; } if ( !$this->siteDomain ) { $this->siteDomain = $this->recallObject('site-domain.current', null, Array ('live_table' => true)); /** @var kDBItem $site_domain */ } if ( $this->siteDomain->isLoaded() ) { return $formatted ? $this->siteDomain->GetField($field, $format) : $this->siteDomain->GetDBField($field); } return false; } /** * Registers default classes such as kDBEventHandler, kUrlManager * * Called automatically while initializing kApplication * * @return void * @access public */ public function RegisterDefaultClasses() { $this->registerClass('kHelper', KERNEL_PATH . '/kbase.php'); $this->registerClass('kMultipleFilter', KERNEL_PATH . '/utility/filters.php'); $this->registerClass('kiCacheable', KERNEL_PATH . '/interfaces/cacheable.php'); $this->registerClass('kEventManager', KERNEL_PATH . '/event_manager.php', 'EventManager'); $this->registerClass('kHookManager', KERNEL_PATH . '/managers/hook_manager.php'); $this->registerClass('kScheduledTaskManager', KERNEL_PATH . '/managers/scheduled_task_manager.php'); $this->registerClass('kRequestManager', KERNEL_PATH . '/managers/request_manager.php'); $this->registerClass('kSubscriptionManager', KERNEL_PATH . '/managers/subscription_manager.php'); $this->registerClass('kSubscriptionItem', KERNEL_PATH . '/managers/subscription_manager.php'); $this->registerClass('kUrlManager', KERNEL_PATH . '/managers/url_manager.php'); $this->registerClass('kUrlProcessor', KERNEL_PATH . '/managers/url_processor.php'); $this->registerClass('kPlainUrlProcessor', KERNEL_PATH . '/managers/plain_url_processor.php'); $this->registerClass('kRewriteUrlProcessor', KERNEL_PATH . '/managers/rewrite_url_processor.php'); $this->registerClass('kCacheManager', KERNEL_PATH . '/managers/cache_manager.php'); $this->registerClass('PhrasesCache', KERNEL_PATH . '/languages/phrases_cache.php', 'kPhraseCache'); $this->registerClass('kTempTablesHandler', KERNEL_PATH . '/utility/temp_handler.php'); $this->registerClass('kValidator', KERNEL_PATH . '/utility/validator.php'); $this->registerClass('kOpenerStack', KERNEL_PATH . '/utility/opener_stack.php'); $this->registerClass('kLogger', KERNEL_PATH . '/utility/logger.php'); $this->registerClass('kUnitConfigReader', KERNEL_PATH . '/utility/unit_config_reader.php'); $this->registerClass('PasswordHash', KERNEL_PATH . '/utility/php_pass.php'); // Params class descendants $this->registerClass('kArray', KERNEL_PATH . '/utility/params.php'); $this->registerClass('Params', KERNEL_PATH . '/utility/params.php'); $this->registerClass('Params', KERNEL_PATH . '/utility/params.php', 'kActions'); $this->registerClass('kCache', KERNEL_PATH . '/utility/cache.php', 'kCache', 'Params'); $this->registerClass('kHTTPQuery', KERNEL_PATH . '/utility/http_query.php', 'HTTPQuery'); // security $this->registerClass('SecurityGenerator', KERNEL_PATH . '/security/SecurityGenerator.php'); $this->registerClass('SecurityGeneratorPromise', KERNEL_PATH . '/security/SecurityGeneratorPromise.php'); $this->registerClass('SecurityEncrypter', KERNEL_PATH . '/security/SecurityEncrypter.php'); // session $this->registerClass('Session', KERNEL_PATH . '/session/session.php'); $this->registerClass('SessionStorage', KERNEL_PATH . '/session/session_storage.php'); $this->registerClass('InpSession', KERNEL_PATH . '/session/inp_session.php', 'Session'); $this->registerClass('InpSessionStorage', KERNEL_PATH . '/session/inp_session_storage.php', 'SessionStorage'); // template parser $this->registerClass('kTagProcessor', KERNEL_PATH . '/processors/tag_processor.php'); $this->registerClass('kMainTagProcessor', KERNEL_PATH . '/processors/main_processor.php', 'm_TagProcessor'); $this->registerClass('kDBTagProcessor', KERNEL_PATH . '/db/db_tag_processor.php'); $this->registerClass('kCatDBTagProcessor', KERNEL_PATH . '/db/cat_tag_processor.php'); $this->registerClass('NParser', KERNEL_PATH . '/nparser/nparser.php'); $this->registerClass('TemplatesCache', KERNEL_PATH . '/nparser/template_cache.php'); // database $this->registerClass('kDBConnection', KERNEL_PATH . '/db/db_connection.php'); $this->registerClass('kDBConnectionDebug', KERNEL_PATH . '/db/db_connection.php'); $this->registerClass('kDBLoadBalancer', KERNEL_PATH . '/db/db_load_balancer.php'); $this->registerClass('kDBItem', KERNEL_PATH . '/db/dbitem.php'); $this->registerClass('kCatDBItem', KERNEL_PATH . '/db/cat_dbitem.php'); $this->registerClass('kDBList', KERNEL_PATH . '/db/dblist.php'); $this->registerClass('kCatDBList', KERNEL_PATH . '/db/cat_dblist.php'); $this->registerClass('kDBEventHandler', KERNEL_PATH . '/db/db_event_handler.php'); $this->registerClass('kCatDBEventHandler', KERNEL_PATH . '/db/cat_event_handler.php'); // email sending $this->registerClass('kEmail', KERNEL_PATH . '/utility/email.php'); $this->registerClass('kEmailSendingHelper', KERNEL_PATH . '/utility/email_send.php', 'EmailSender'); $this->registerClass('kSocket', KERNEL_PATH . '/utility/socket.php', 'Socket'); // Testing. $this->registerClass('Page', KERNEL_PATH . '/tests/Page/Page.php'); $this->registerClass('QAToolsUrlBuilder', KERNEL_PATH . '/tests/Url/QAToolsUrlBuilder.php'); $this->registerClass('QAToolsUrlFactory', KERNEL_PATH . '/tests/Url/QAToolsUrlFactory.php'); $this->registerClass('AbstractTestCase', KERNEL_PATH . '/../tests/AbstractTestCase.php'); $this->registerClass('AbstractBrowserTestCase', KERNEL_PATH . '/../tests/AbstractBrowserTestCase.php'); // do not move to config - this helper is used before configs are read $this->registerClass('kModulesHelper', KERNEL_PATH . self::MODULE_HELPER_PATH, 'ModulesHelper'); $this->registerClass('CKEditor', FULL_PATH . '/core/ckeditor/ckeditor_php5.php'); } /** * Registers default build events * * @return void */ public function RegisterDefaultBuildEvents() { $this->EventManager->registerBuildEvent('kTempTablesHandler', 'OnTempHandlerBuild'); } /** * Returns cached category information by given cache name. All given category * information is recached, when at least one of 4 caches is missing. * * @param int $category_id * @param string $name cache name = {filenames, category_designs, category_tree} * @return string * @access public */ public function getCategoryCache($category_id, $name) { return $this->cacheManager->getCategoryCache($category_id, $name); } /** * Returns caching type (none, memory, temporary) * * @param int $caching_type * @return bool * @access public */ public function isCachingType($caching_type) { return $this->cacheManager->isCachingType($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 * @return string * @access public */ public function incrementCacheSerial($prefix, $id = null, $increment = true) { return $this->cacheManager->incrementCacheSerial($prefix, $id, $increment); } /** * Returns cached $key value from cache named $cache_name * * @param int $key key name from cache * @param bool $store_locally store data locally after retrieved * @param int $max_rebuild_seconds * @return mixed * @access public */ public function getCache($key, $store_locally = true, $max_rebuild_seconds = 0) { return $this->cacheManager->getCache($key, $store_locally, $max_rebuild_seconds); } /** - * Stores new $value in cache with $key name + * Stores new $value in cache with $name name. * - * @param int $key key name to add to cache - * @param mixed $value value of cached record - * @param int $expiration when value expires (0 - doesn't expire) - * @return bool - * @access public + * @param string $name Key name to add to cache. + * @param mixed $value Value of cached record. + * @param integer|null $expiration When value expires (0 - doesn't expire). + * + * @return boolean */ - public function setCache($key, $value, $expiration = 0) + public function setCache($name, $value, $expiration = null) { - return $this->cacheManager->setCache($key, $value, $expiration); + return $this->cacheManager->setCache($name, $value, $expiration); } /** * Stores new $value in cache with $key name (only if it's not there) * - * @param int $key key name to add to cache - * @param mixed $value value of cached record - * @param int $expiration when value expires (0 - doesn't expire) - * @return bool - * @access public + * @param string $name Key name to add to cache. + * @param mixed $value Value of cached record. + * @param integer|null $expiration When value expires (0 - doesn't expire). + * + * @return boolean */ - public function addCache($key, $value, $expiration = 0) + public function addCache($name, $value, $expiration = null) { - return $this->cacheManager->addCache($key, $value, $expiration); + return $this->cacheManager->addCache($name, $value, $expiration); } /** * Sets rebuilding mode for given cache * * @param string $name * @param int $mode * @param int $max_rebuilding_time * @return bool * @access public */ public function rebuildCache($name, $mode = null, $max_rebuilding_time = 0) { return $this->cacheManager->rebuildCache($name, $mode, $max_rebuilding_time); } /** * Deletes key from cache * * @param string $key * @return void * @access public */ public function deleteCache($key) { $this->cacheManager->deleteCache($key); } /** * Reset's all memory cache at once * * @return void * @access public */ public function resetCache() { $this->cacheManager->resetCache(); } /** * Returns value from database cache * * @param string $name key name * @param int $max_rebuild_seconds * @return mixed * @access public */ public function getDBCache($name, $max_rebuild_seconds = 0) { return $this->cacheManager->getDBCache($name, $max_rebuild_seconds); } /** - * Sets value to database cache + * Sets value to database cache. + * + * @param string $name Key name to add to cache. + * @param mixed $value Value of cached record. + * @param integer|null $expiration When value expires (0 - doesn't expire). * - * @param string $name - * @param mixed $value - * @param int|bool $expiration * @return void - * @access public */ - public function setDBCache($name, $value, $expiration = false) + public function setDBCache($name, $value, $expiration = null) { $this->cacheManager->setDBCache($name, $value, $expiration); } /** * Sets rebuilding mode for given cache * * @param string $name * @param int $mode * @param int $max_rebuilding_time * @return bool * @access public */ public function rebuildDBCache($name, $mode = null, $max_rebuilding_time = 0) { return $this->cacheManager->rebuildDBCache($name, $mode, $max_rebuilding_time); } /** * Deletes key from database cache * * @param string $name * @return void * @access public */ public function deleteDBCache($name) { $this->cacheManager->deleteDBCache($name); } /** * Registers each module specific constants if any found * * @return bool * @access protected */ protected function registerModuleConstants() { if ( file_exists(KERNEL_PATH . '/constants.php') ) { kUtil::includeOnce(KERNEL_PATH . '/constants.php'); } if ( !$this->ModuleInfo ) { return false; } foreach ($this->ModuleInfo as $module_info) { $constants_file = FULL_PATH . '/' . $module_info['Path'] . 'constants.php'; if ( file_exists($constants_file) ) { kUtil::includeOnce($constants_file); } } return true; } /** * Performs redirect to hard maintenance template * * @return void * @access public */ public function redirectToMaintenance() { $maintenance_page = WRITEBALE_BASE . '/maintenance.html'; $query_string = ''; // $this->isAdmin ? '' : '?next_template=' . kUtil::escape($_SERVER['REQUEST_URI'], kUtil::ESCAPE_URL); if ( file_exists(FULL_PATH . $maintenance_page) ) { header('Location: ' . BASE_PATH . $maintenance_page . $query_string); exit; } } /** * Actually runs the parser against current template and stores parsing result * * This method gets 't' variable passed to the script, loads the template given in 't' variable and * parses it. The result is store in {@link $this->HTML} property. * * @return void * @access public */ public function Run() { // process maintenance mode redirect: begin $maintenance_mode = $this->getMaintenanceMode(); if ( $maintenance_mode == MaintenanceMode::HARD ) { $this->redirectToMaintenance(); } elseif ( $maintenance_mode == MaintenanceMode::SOFT ) { $maintenance_template = $this->isAdmin ? 'login' : $this->ConfigValue('SoftMaintenanceTemplate'); if ( $this->GetVar('t') != $maintenance_template ) { $redirect_params = Array (); if ( !$this->isAdmin ) { $redirect_params['next_template'] = $_SERVER['REQUEST_URI']; } $this->Redirect($maintenance_template, $redirect_params); } } // process maintenance mode redirect: end if ( defined('DEBUG_MODE') && $this->isDebugMode() && kUtil::constOn('DBG_PROFILE_MEMORY') ) { $this->Debugger->appendMemoryUsage('Application before Run:'); } if ( $this->isAdminUser ) { // for permission checking in events & templates $this->LinkVar('module'); // for common configuration templates $this->LinkVar('module_key'); // for common search templates $this->LinkVar('section'); // for common configuration templates if ( $this->GetVar('m_opener') == 'p' ) { $this->LinkVar('main_prefix'); // window prefix, that opened selector $this->LinkVar('dst_field'); // field to set value choosed in selector } if ( $this->GetVar('ajax') == 'yes' && !$this->GetVar('debug_ajax') ) { // hide debug output from ajax requests automatically kUtil::safeDefine('DBG_SKIP_REPORTING', 1); // safeDefine, because debugger also defines it } } $this->Phrases->setPhraseEditing(); $this->EventManager->ProcessRequest(); $this->InitParser(); $t = $this->GetVar('render_template', $this->GetVar('t')); if ( !$this->TemplatesCache->TemplateExists($t) && !$this->isAdmin ) { /** @var CategoriesEventHandler $cms_handler */ $cms_handler = $this->recallObject('st_EventHandler'); $t = ltrim($cms_handler->GetDesignTemplate(), '/'); if ( defined('DEBUG_MODE') && $this->isDebugMode() ) { $this->Debugger->appendHTML('Design Template: ' . $t . '; CategoryID: ' . $this->GetVar('m_cat_id')); } } /*else { $cms_handler->SetCatByTemplate(); }*/ if ( defined('DEBUG_MODE') && $this->isDebugMode() && kUtil::constOn('DBG_PROFILE_MEMORY') ) { $this->Debugger->appendMemoryUsage('Application before Parsing:'); } $this->HTML = $this->Parser->Run($t); if ( defined('DEBUG_MODE') && $this->isDebugMode() && kUtil::constOn('DBG_PROFILE_MEMORY') ) { $this->Debugger->appendMemoryUsage('Application after Parsing:'); } } /** * Replaces current rendered template with given one. * * @param string|null $template Template. * * @return void */ public function QuickRun($template) { /** @var kThemesHelper $themes_helper */ $themes_helper = $this->recallObject('ThemesHelper'); // Set Web Request variables to affect link building on template itself. $this->SetVar('t', $template); $this->SetVar('m_cat_id', $themes_helper->getPageByTemplate($template)); $this->SetVar('passed', 'm'); // Replace current page content with given template. $this->InitParser(); $this->Parser->Clear(); $this->HTML = $this->Parser->Run($template); } /** * Performs template parser/cache initialization * * @param bool|string $theme_name * @return void * @access public */ public function InitParser($theme_name = false) { if ( !is_object($this->Parser) ) { $this->Parser = $this->recallObject('NParser'); $this->TemplatesCache = $this->recallObject('TemplatesCache'); } $this->TemplatesCache->forceThemeName = $theme_name; } /** * Send the parser results to browser * * Actually send everything stored in {@link $this->HTML}, to the browser by echoing it. * * @return void * @access public */ public function Done() { $this->HandleEvent(new kEvent('adm:OnBeforeShutdown')); $debug_mode = defined('DEBUG_MODE') && $this->isDebugMode(); if ( $debug_mode ) { if ( kUtil::constOn('DBG_PROFILE_MEMORY') ) { $this->Debugger->appendMemoryUsage('Application before Done:'); } $this->Session->SaveData(); // adds session data to debugger report $this->HTML = ob_get_clean() . $this->HTML . $this->Debugger->printReport(true); } else { // send "Set-Cookie" header before any output is made $this->Session->SetSession(); $this->HTML = ob_get_clean() . $this->HTML; } $this->_outputPage(); $this->cacheManager->UpdateApplicationCache(); if ( !$debug_mode ) { $this->Session->SaveData(); } $this->EventManager->runScheduledTasks(); if ( defined('DBG_CAPTURE_STATISTICS') && DBG_CAPTURE_STATISTICS && !$this->isAdmin ) { $this->_storeStatistics(); } } /** * Outputs generated page content to end-user * * @return void * @access protected */ protected function _outputPage() { $this->setContentType(); ob_start(); if ( $this->UseOutputCompression() ) { $compression_level = $this->ConfigValue('OutputCompressionLevel'); if ( !$compression_level || $compression_level < 0 || $compression_level > 9 ) { $compression_level = 7; } header('Content-Encoding: gzip'); echo gzencode($this->HTML, $compression_level); } else { // when gzip compression not used connection won't be closed early! echo $this->HTML; } // send headers to tell the browser to close the connection header('Content-Length: ' . ob_get_length()); header('Connection: close'); // flush all output ob_end_flush(); if ( ob_get_level() ) { ob_flush(); } flush(); // close current session if ( session_id() ) { session_write_close(); } } /** * Stores script execution statistics to database * * @return void * @access protected */ protected function _storeStatistics() { global $start; $this->Conn->noDebuggingState = true; $script_time = microtime(true) - $start; $query_statistics = $this->Conn->getQueryStatistics(); // time & count $sql = 'SELECT * FROM ' . TABLE_PREFIX . 'StatisticsCapture WHERE TemplateName = ' . $this->Conn->qstr($this->GetVar('t')); $data = $this->Conn->GetRow($sql); if ( $data ) { $this->_updateAverageStatistics($data, 'ScriptTime', $script_time); $this->_updateAverageStatistics($data, 'SqlTime', $query_statistics['time']); $this->_updateAverageStatistics($data, 'SqlCount', $query_statistics['count']); $data['Hits']++; $data['LastHit'] = adodb_mktime(); $this->Conn->doUpdate($data, TABLE_PREFIX . 'StatisticsCapture', 'StatisticsId = ' . $data['StatisticsId']); } else { $data = array(); $data['ScriptTimeMin'] = $data['ScriptTimeAvg'] = $data['ScriptTimeMax'] = $script_time; $data['SqlTimeMin'] = $data['SqlTimeAvg'] = $data['SqlTimeMax'] = $query_statistics['time']; $data['SqlCountMin'] = $data['SqlCountAvg'] = $data['SqlCountMax'] = $query_statistics['count']; $data['TemplateName'] = $this->GetVar('t'); $data['Hits'] = 1; $data['LastHit'] = adodb_mktime(); $this->Conn->doInsert($data, TABLE_PREFIX . 'StatisticsCapture'); } $this->Conn->noDebuggingState = false; } /** * Calculates average time for statistics * * @param Array $data * @param string $field_prefix * @param float $current_value * @return void * @access protected */ protected function _updateAverageStatistics(&$data, $field_prefix, $current_value) { $data[$field_prefix . 'Avg'] = (($data['Hits'] * $data[$field_prefix . 'Avg']) + $current_value) / ($data['Hits'] + 1); if ( $current_value < $data[$field_prefix . 'Min'] ) { $data[$field_prefix . 'Min'] = $current_value; } if ( $current_value > $data[$field_prefix . 'Max'] ) { $data[$field_prefix . 'Max'] = $current_value; } } /** * Remembers slow query SQL and execution time into log * * @param string $slow_sql * @param int $time * @return void * @access public */ public function logSlowQuery($slow_sql, $time) { $this->Conn->noDebuggingState = true; $query_crc = kUtil::crc32($slow_sql); $sql = 'SELECT * FROM ' . TABLE_PREFIX . 'SlowSqlCapture WHERE QueryCrc = ' . $query_crc; $data = $this->Conn->Query($sql); if ( $data ) { $data = array_shift($data); // Because "Query" method (supports $no_debug) is used instead of "GetRow". $this->_updateAverageStatistics($data, 'Time', $time); $template_names = explode(',', $data['TemplateNames']); array_push($template_names, $this->GetVar('t')); $data['TemplateNames'] = implode(',', array_unique($template_names)); $data['Hits']++; $data['LastHit'] = adodb_mktime(); $this->Conn->doUpdate($data, TABLE_PREFIX . 'SlowSqlCapture', 'CaptureId = ' . $data['CaptureId']); } else { $data = array(); $data['TimeMin'] = $data['TimeAvg'] = $data['TimeMax'] = $time; $data['SqlQuery'] = $slow_sql; $data['QueryCrc'] = $query_crc; $data['TemplateNames'] = $this->GetVar('t'); $data['Hits'] = 1; $data['LastHit'] = adodb_mktime(); $this->Conn->doInsert($data, TABLE_PREFIX . 'SlowSqlCapture'); } $this->Conn->noDebuggingState = false; } /** * Checks if output compression options is available * * @return bool * @access protected */ protected function UseOutputCompression() { if ( kUtil::constOn('IS_INSTALL') || kUtil::constOn('DBG_ZEND_PRESENT') || kUtil::constOn('SKIP_OUT_COMPRESSION') ) { return false; } $accept_encoding = isset($_SERVER['HTTP_ACCEPT_ENCODING']) ? $_SERVER['HTTP_ACCEPT_ENCODING'] : ''; return $this->ConfigValue('UseOutputCompression') && function_exists('gzencode') && strstr($accept_encoding, 'gzip'); } // Facade /** * Returns current session id (SID) * * @return int * @access public */ public function GetSID() { /** @var Session $session */ $session = $this->recallObject('Session'); return $session->GetID(); } /** * Destroys current session * * @return void * @access public * @see UserHelper::logoutUser() */ public function DestroySession() { /** @var Session $session */ $session = $this->recallObject('Session'); $session->Destroy(); } /** * Returns variable passed to the script as GET/POST/COOKIE * * @param string $name Name of variable to retrieve * @param mixed $default default value returned in case if variable not present * @return mixed * @access public */ public function GetVar($name, $default = false) { return isset($this->HttpQuery->_Params[$name]) ? $this->HttpQuery->_Params[$name] : $default; } /** * Removes forceful escaping done to the variable upon Front-End submission. * * @param string|array $value Value. * * @return string|array * @see kHttpQuery::StripSlashes * @todo Temporary method for marking problematic places to take care of, when forceful escaping will be removed. */ public function unescapeRequestVariable($value) { return $this->HttpQuery->unescapeRequestVariable($value); } /** * Returns variable passed to the script as $type * * @param string $name Name of variable to retrieve * @param string $type Get/Post/Cookie * @param mixed $default default value returned in case if variable not present * @return mixed * @access public */ public function GetVarDirect($name, $type, $default = false) { // $type = ucfirst($type); $array = $this->HttpQuery->$type; return isset($array[$name]) ? $array[$name] : $default; } /** * Returns ALL variables passed to the script as GET/POST/COOKIE * * @return Array * @access public * @deprecated */ public function GetVars() { return $this->HttpQuery->GetParams(); } /** * Set the variable 'as it was passed to the script through GET/POST/COOKIE' * * This could be useful to set the variable when you know that * other objects would relay on variable passed from GET/POST/COOKIE * or you could use SetVar() / GetVar() pairs to pass the values between different objects.
* * @param string $var Variable name to set * @param mixed $val Variable value * @return void * @access public */ public function SetVar($var,$val) { $this->HttpQuery->Set($var, $val); } /** * Deletes kHTTPQuery variable * * @param string $var * @return void * @todo Think about method name */ public function DeleteVar($var) { $this->HttpQuery->Remove($var); } /** * Deletes Session variable * * @param string $var * @return void * @access public */ public function RemoveVar($var) { $this->Session->RemoveVar($var); } /** * Removes variable from persistent session * * @param string $var * @return void * @access public */ public function RemovePersistentVar($var) { $this->Session->RemovePersistentVar($var); } /** * Restores Session variable to it's db version * * @param string $var * @return void * @access public */ public function RestoreVar($var) { $this->Session->RestoreVar($var); } /** * Returns session variable value * * Return value of $var variable stored in Session. An optional default value could be passed as second parameter. * * @param string $var Variable name * @param mixed $default Default value to return if no $var variable found in session * @return mixed * @access public * @see Session::RecallVar() */ public function RecallVar($var,$default=false) { return $this->Session->RecallVar($var,$default); } /** * Returns variable value from persistent session * * @param string $var * @param mixed $default * @return mixed * @access public * @see Session::RecallPersistentVar() */ public function RecallPersistentVar($var, $default = false) { return $this->Session->RecallPersistentVar($var, $default); } /** * Stores variable $val in session under name $var * * Use this method to store variable in session. Later this variable could be recalled. * * @param string $var Variable name * @param mixed $val Variable value * @param bool $optional * @return void * @access public * @see kApplication::RecallVar() */ public function StoreVar($var, $val, $optional = false) { /** @var Session $session */ $session = $this->recallObject('Session'); $this->Session->StoreVar($var, $val, $optional); } /** * Stores variable to persistent session * * @param string $var * @param mixed $val * @param bool $optional * @return void * @access public */ public function StorePersistentVar($var, $val, $optional = false) { $this->Session->StorePersistentVar($var, $val, $optional); } /** * Stores default value for session variable * * @param string $var * @param string $val * @param bool $optional * @return void * @access public * @see Session::RecallVar() * @see Session::StoreVar() */ public function StoreVarDefault($var, $val, $optional = false) { /** @var Session $session */ $session = $this->recallObject('Session'); $this->Session->StoreVarDefault($var, $val, $optional); } /** * Links HTTP Query variable with session variable * * If variable $var is passed in HTTP Query it is stored in session for later use. If it's not passed it's recalled from session. * This method could be used for making sure that GetVar will return query or session value for given * variable, when query variable should overwrite session (and be stored there for later use).
* This could be used for passing item's ID into popup with multiple tab - * in popup script you just need to call LinkVar('id', 'current_id') before first use of GetVar('id'). * After that you can be sure that GetVar('id') will return passed id or id passed earlier and stored in session * * @param string $var HTTP Query (GPC) variable name * @param mixed $ses_var Session variable name * @param mixed $default Default variable value * @param bool $optional * @return void * @access public */ public function LinkVar($var, $ses_var = null, $default = '', $optional = false) { if ( !isset($ses_var) ) { $ses_var = $var; } if ( $this->GetVar($var) !== false ) { $this->StoreVar($ses_var, $this->GetVar($var), $optional); } else { $this->SetVar($var, $this->RecallVar($ses_var, $default)); } } /** * Returns variable from HTTP Query, or from session if not passed in HTTP Query * * The same as LinkVar, but also returns the variable value taken from HTTP Query if passed, or from session if not passed. * Returns the default value if variable does not exist in session and was not passed in HTTP Query * * @param string $var HTTP Query (GPC) variable name * @param mixed $ses_var Session variable name * @param mixed $default Default variable value * @return mixed * @access public * @see LinkVar */ public function GetLinkedVar($var, $ses_var = null, $default = '') { $this->LinkVar($var, $ses_var, $default); return $this->GetVar($var); } /** * Renders given tag and returns it's output * * @param string $prefix * @param string $tag * @param Array $params * @return mixed * @access public * @see kApplication::InitParser() */ public function ProcessParsedTag($prefix, $tag, $params) { /** @var kDBTagProcessor $processor */ $processor = $this->Parser->GetProcessor($prefix); return $processor->ProcessParsedTag($tag, $params, $prefix); } /** * Return object of IDBConnection interface * * Return object of IDBConnection interface already connected to the project database, configurable in config.php * * @return IDBConnection * @access public */ public function &GetADODBConnection() { return $this->Conn; } /** * Allows to parse given block name or include template * * @param Array $params Parameters to pass to block. Reserved parameter "name" used to specify block name. * @param bool $pass_params Forces to pass current parser params to this block/template. Use with caution, because you can accidentally pass "block_no_data" parameter. * @param bool $as_template * @return string * @access public */ public function ParseBlock($params, $pass_params = false, $as_template = false) { if ( substr($params['name'], 0, 5) == 'html:' ) { return substr($params['name'], 5); } return $this->Parser->ParseBlock($params, $pass_params, $as_template); } /** * Checks, that we have given block defined * * @param string $name * @return bool * @access public */ public function ParserBlockFound($name) { return $this->Parser->blockFound($name); } /** * Allows to include template with a given name and given parameters * * @param Array $params Parameters to pass to template. Reserved parameter "name" used to specify template name. * @return string * @access public */ public function IncludeTemplate($params) { return $this->Parser->IncludeTemplate($params, isset($params['is_silent']) ? 1 : 0); } /** * Return href for template * * @param string $t Template path * @param string $prefix index.php prefix - could be blank, 'admin' * @param Array $params * @param string $index_file * @return string */ public function HREF($t, $prefix = '', $params = Array (), $index_file = null) { return $this->UrlManager->HREF($t, $prefix, $params, $index_file); } /** * Returns theme template filename and it's corresponding page_id based on given seo template * * @param string $seo_template * @return string * @access public */ public function getPhysicalTemplate($seo_template) { return $this->UrlManager->getPhysicalTemplate($seo_template); } /** * Returns template name, that corresponds with given virtual (not physical) page id * * @param int $page_id * @return string|bool * @access public */ public function getVirtualPageTemplate($page_id) { return $this->UrlManager->getVirtualPageTemplate($page_id); } /** * Returns section template for given physical/virtual template * * @param string $template * @param int $theme_id * @return string * @access public */ public function getSectionTemplate($template, $theme_id = null) { return $this->UrlManager->getSectionTemplate($template, $theme_id); } /** * Returns variables with values that should be passed through with this link + variable list * * @param Array $params * @return Array * @access public */ public function getPassThroughVariables(&$params) { return $this->UrlManager->getPassThroughVariables($params); } /** * Builds url * * @param string $t * @param Array $params * @param string $pass * @param bool $pass_events * @param bool $env_var * @return string * @access public */ public function BuildEnv($t, $params, $pass = 'all', $pass_events = false, $env_var = true) { return $this->UrlManager->plain->build($t, $params, $pass, $pass_events, $env_var); } /** * Process QueryString only, create * events, ids, based on config * set template name and sid in * desired application variables. * * @param string $env_var environment string value * @param string $pass_name * @return Array * @access public */ public function processQueryString($env_var, $pass_name = 'passed') { return $this->UrlManager->plain->parse($env_var, $pass_name); } /** * Parses rewrite url and returns parsed variables * * @param string $url * @param string $pass_name * @return Array * @access public */ public function parseRewriteUrl($url, $pass_name = 'passed') { return $this->UrlManager->rewrite->parse($url, $pass_name); } /** * Returns base part of all urls, build on website * * @param string $prefix * @param bool $ssl * @param bool $add_port * @return string * @access public */ public function BaseURL($prefix = '', $ssl = null, $add_port = true) { if ( $ssl === null ) { // stay on same encryption level return PROTOCOL . SERVER_NAME . ($add_port && defined('PORT') ? ':' . PORT : '') . BASE_PATH . $prefix . '/'; } if ( $ssl ) { // going from http:// to https:// $base_url = $this->isAdmin ? $this->ConfigValue('AdminSSL_URL') : false; if ( !$base_url ) { $ssl_url = $this->siteDomainField('SSLUrl'); $base_url = $ssl_url !== false ? $ssl_url : $this->ConfigValue('SSL_URL'); } return rtrim($base_url, '/') . $prefix . '/'; } // going from https:// to http:// $domain = $this->siteDomainField('DomainName'); if ( $domain === false ) { $domain = DOMAIN; } return 'http://' . $domain . ($add_port && defined('PORT') ? ':' . PORT : '') . BASE_PATH . $prefix . '/'; } /** * Redirects user to url, that's build based on given parameters * * @param string $t * @param Array $params * @param string $prefix * @param string $index_file * @return void * @access public */ public function Redirect($t = '', $params = Array(), $prefix = '', $index_file = null) { $js_redirect = getArrayValue($params, 'js_redirect'); if ( $t == '' || $t === true ) { $t = $this->GetVar('t'); } // pass prefixes and special from previous url if ( array_key_exists('js_redirect', $params) ) { unset($params['js_redirect']); } // allows to send custom responce code along with redirect header if ( array_key_exists('response_code', $params) ) { $response_code = (int)$params['response_code']; unset($params['response_code']); } else { $response_code = 302; // Found } if ( !array_key_exists('pass', $params) ) { $params['pass'] = 'all'; } if ( $this->GetVar('ajax') == 'yes' && $t == $this->GetVar('t') ) { // redirects to the same template as current $params['ajax'] = 'yes'; } $location = $this->HREF($t, $prefix, $params, $index_file); if ( $this->isDebugMode() && (kUtil::constOn('DBG_REDIRECT') || (kUtil::constOn('DBG_RAISE_ON_WARNINGS') && $this->Debugger->WarningCount)) ) { $this->Debugger->appendTrace(); echo 'Debug output above !!!
' . "\n"; if ( array_key_exists('HTTP_REFERER', $_SERVER) ) { echo 'Referer: ' . kUtil::escape($_SERVER['HTTP_REFERER'], kUtil::ESCAPE_HTML) . '
' . "\n"; } echo "Proceed to redirect: {$location}
\n"; } else { if ( $js_redirect ) { // show "redirect" template instead of redirecting, // because "Set-Cookie" header won't work, when "Location" // header is used later $this->SetVar('t', 'redirect'); $this->SetVar('redirect_to', $location); // make all additional parameters available on "redirect" template too foreach ($params as $name => $value) { $this->SetVar($name, $value); } return; } else { if ( $this->GetVar('ajax') == 'yes' && ($t != $this->GetVar('t') || !$this->isSOPSafe($location, $t)) ) { // redirection to other then current template during ajax request OR SOP violation kUtil::safeDefine('DBG_SKIP_REPORTING', 1); echo '#redirect#' . $location; } elseif ( headers_sent() != '' ) { // some output occurred -> redirect using javascript echo ''; } else { // no output before -> redirect using HTTP header // header('HTTP/1.1 302 Found'); header('Location: ' . $location, true, $response_code); } } } // session expiration is called from session initialization, // that's why $this->Session may be not defined here /** @var Session $session */ $session = $this->recallObject('Session'); if ( $this->InitDone ) { // if redirect happened in the middle of application initialization don't call event, // that presumes that application was successfully initialized $this->HandleEvent(new kEvent('adm:OnBeforeShutdown')); } $session->SaveData(); ob_end_flush(); exit; } /** * Determines if real redirect should be made within AJAX request. * * @param string $url Location. * @param string $template Template. * * @return boolean * @link http://en.wikipedia.org/wiki/Same-origin_policy */ protected function isSOPSafe($url, $template) { $parsed_url = parse_url($url); if ( $parsed_url['scheme'] . '://' != PROTOCOL ) { return false; } if ( $parsed_url['host'] != SERVER_NAME ) { return false; } if ( defined('PORT') && isset($parsed_url['port']) && $parsed_url['port'] != PORT ) { return false; } return true; } /** * Returns translation of given label * * @param string $label * @param bool $allow_editing return translation link, when translation is missing on current language * @param bool $use_admin use current Admin Console language to translate phrase * @return string * @access public */ public function Phrase($label, $allow_editing = true, $use_admin = false) { return $this->Phrases->GetPhrase($label, $allow_editing, $use_admin); } /** * Replace language tags in exclamation marks found in text * * @param string $text * @param bool $force_escape force escaping, not escaping of resulting string * @return string * @access public */ public function ReplaceLanguageTags($text, $force_escape = null) { return $this->Phrases->ReplaceLanguageTags($text, $force_escape); } /** * Checks if user is logged in, and creates * user object if so. User object can be recalled * later using "u.current" prefix_special. Also you may * get user id by getting "u.current_id" variable. * * @return void * @access protected */ protected function ValidateLogin() { /** @var Session $session */ $session = $this->recallObject('Session'); $user_id = $session->GetField('PortalUserId'); if ( !$user_id && $user_id != USER_ROOT ) { $user_id = USER_GUEST; } $this->SetVar('u.current_id', $user_id); if ( !$this->isAdmin ) { // needed for "profile edit", "registration" forms ON FRONT ONLY $this->SetVar('u_id', $user_id); } $this->StoreVar('user_id', $user_id, $user_id == USER_GUEST); // storing Guest user_id (-2) is optional $this->isAdminUser = $this->isAdmin && $this->LoggedIn(); if ( $this->GetVar('expired') == 1 ) { // this parameter is set only from admin /** @var UsersItem $user */ $user = $this->recallObject('u.login-admin', null, Array ('form_name' => 'login')); $user->SetError('UserLogin', 'session_expired', 'la_text_sess_expired'); } $this->HandleEvent(new kEvent('adm:OnLogHttpRequest')); if ( $user_id != USER_GUEST ) { // normal users + root $this->LoadPersistentVars(); } $user_timezone = $this->Session->GetField('TimeZone'); if ( $user_timezone ) { date_default_timezone_set($user_timezone); } } /** * Loads current user persistent session data * * @return void * @access public */ public function LoadPersistentVars() { $this->Session->LoadPersistentVars(); } /** * Returns configuration option value by name * * @param string $name * @return string * @access public */ public function ConfigValue($name) { return $this->cacheManager->ConfigValue($name); } /** * Changes value of individual configuration variable (+resets cache, when needed) * * @param string $name * @param string $value * @param bool $local_cache_only * @return string * @access public */ public function SetConfigValue($name, $value, $local_cache_only = false) { return $this->cacheManager->SetConfigValue($name, $value, $local_cache_only); } /** * Allows to process any type of event * * @param kEvent $event * @param Array $params * @param Array $specific_params * @return void * @access public */ public function HandleEvent($event, $params = null, $specific_params = null) { if ( isset($params) ) { $event = new kEvent($params, $specific_params); } $this->EventManager->HandleEvent($event); } /** * Notifies event subscribers, that event has occured * * @param kEvent $event * @return void */ public function notifyEventSubscribers(kEvent $event) { $this->EventManager->notifySubscribers($event); } /** * Allows to process any type of event * * @param kEvent $event * @return bool * @access public */ public function eventImplemented(kEvent $event) { return $this->EventManager->eventImplemented($event); } /** * Registers new class in the factory * * @param string $real_class Real name of class as in class declaration * @param string $file Filename in what $real_class is declared * @param string $pseudo_class Name under this class object will be accessed using getObject method * @return void * @access public */ public function registerClass($real_class, $file, $pseudo_class = null) { $this->Factory->registerClass($real_class, $file, $pseudo_class); } /** * Unregisters existing class from factory * * @param string $real_class Real name of class as in class declaration * @param string $pseudo_class Name under this class object is accessed using getObject method * @return void * @access public */ public function unregisterClass($real_class, $pseudo_class = null) { $this->Factory->unregisterClass($real_class, $pseudo_class); } /** * Finds the absolute path to the file where the class is defined. * * @param string $class The name of the class. * * @return string|false */ public function findClassFile($class) { return $this->Factory->findClassFile($class); } /** * Add new scheduled task * * @param string $short_name name to be used to store last maintenance run info * @param string $event_string * @param int $run_schedule run schedule like for Cron * @param int $status * @access public */ public function registerScheduledTask($short_name, $event_string, $run_schedule, $status = STATUS_ACTIVE) { $this->EventManager->registerScheduledTask($short_name, $event_string, $run_schedule, $status); } /** * Registers Hook from subprefix event to master prefix event * * Pattern: Observer * * @param string $hook_event * @param string $do_event * @param int $mode * @param bool $conditional * @access public */ public function registerHook($hook_event, $do_event, $mode = hAFTER, $conditional = false) { $this->EventManager->registerHook($hook_event, $do_event, $mode, $conditional); } /** * Registers build event for given pseudo class * * @param string $pseudo_class * @param string $event_name * @access public */ public function registerBuildEvent($pseudo_class, $event_name) { $this->EventManager->registerBuildEvent($pseudo_class, $event_name); } /** * Allows one TagProcessor tag act as other TagProcessor tag * * @param Array $tag_info * @return void * @access public */ public function registerAggregateTag($tag_info) { /** @var kArray $aggregator */ $aggregator = $this->recallObject('TagsAggregator', 'kArray'); $tag_data = Array ( $tag_info['LocalPrefix'], $tag_info['LocalTagName'], getArrayValue($tag_info, 'LocalSpecial') ); $aggregator->SetArrayValue($tag_info['AggregateTo'], $tag_info['AggregatedTagName'], $tag_data); } /** * Returns object using params specified, creates it if is required * * @param string $name * @param string $pseudo_class * @param Array $event_params * @param Array $arguments * @return kBase */ public function recallObject($name, $pseudo_class = null, array $event_params = array(), array $arguments = array()) { /*if ( !$this->hasObject($name) && $this->isDebugMode() && ($name == '_prefix_here_') ) { // first time, when object with "_prefix_here_" prefix is accessed $this->Debugger->appendTrace(); }*/ return $this->Factory->getObject($name, $pseudo_class, $event_params, $arguments); } /** * Returns tag processor for prefix specified * * @param string $prefix * @return kDBTagProcessor * @access public */ public function recallTagProcessor($prefix) { $this->InitParser(); // because kDBTagProcesor is in NParser dependencies return $this->recallObject($prefix . '_TagProcessor'); } /** * Checks if object with prefix passes was already created in factory * * @param string $name object pseudo_class, prefix * @return bool * @access public */ public function hasObject($name) { return $this->Factory->hasObject($name); } /** * Removes object from storage by given name * * @param string $name Object's name in the Storage * @return void * @access public */ public function removeObject($name) { $this->Factory->DestroyObject($name); } /** * Get's real class name for pseudo class, includes class file and creates class instance * * Pattern: Factory Method * * @param string $pseudo_class * @param Array $arguments * @return kBase * @access public */ public function makeClass($pseudo_class, array $arguments = array()) { return $this->Factory->makeClass($pseudo_class, $arguments); } /** * Checks if application is in debug mode * * @param bool $check_debugger check if kApplication debugger is initialized too, not only for defined DEBUG_MODE constant * @return bool * @author Alex * @access public */ public function isDebugMode($check_debugger = true) { $debug_mode = defined('DEBUG_MODE') && DEBUG_MODE; if ($check_debugger) { $debug_mode = $debug_mode && is_object($this->Debugger); } return $debug_mode; } /** * Apply url rewriting used by mod_rewrite or not * * @param bool|null $ssl Force ssl link to be build * @return bool * @access public */ public function RewriteURLs($ssl = false) { // case #1,#4: // we want to create https link from http mode // we want to create https link from https mode // conditions: ($ssl || PROTOCOL == 'https://') && $this->ConfigValue('UseModRewriteWithSSL') // case #2,#3: // we want to create http link from https mode // we want to create http link from http mode // conditions: !$ssl && (PROTOCOL == 'https://' || PROTOCOL == 'http://') $allow_rewriting = (!$ssl && (PROTOCOL == 'https://' || PROTOCOL == 'http://')) // always allow mod_rewrite for http || // or allow rewriting for redirect TO httpS or when already in httpS (($ssl || PROTOCOL == 'https://') && $this->ConfigValue('UseModRewriteWithSSL')); // but only if it's allowed in config! return kUtil::constOn('MOD_REWRITE') && $allow_rewriting; } /** * Reads unit (specified by $prefix) * option specified by $option * * @param string $prefix * @param string $option * @param mixed $default * @return string * @access public */ public function getUnitOption($prefix, $option, $default = false) { return $this->UnitConfigReader->getUnitOption($prefix, $option, $default); } /** * Set's new unit option value * * @param string $prefix * @param string $option * @param string $value * @access public */ public function setUnitOption($prefix, $option, $value) { $this->UnitConfigReader->setUnitOption($prefix,$option,$value); } /** * Read all unit with $prefix options * * @param string $prefix * @return Array * @access public */ public function getUnitOptions($prefix) { return $this->UnitConfigReader->getUnitOptions($prefix); } /** * Returns true if config exists and is allowed for reading * * @param string $prefix * @return bool */ public function prefixRegistred($prefix) { return $this->UnitConfigReader->prefixRegistred($prefix); } /** * Splits any mixing of prefix and * special into correct ones * * @param string $prefix_special * @return Array * @access public */ public function processPrefix($prefix_special) { return $this->Factory->processPrefix($prefix_special); } /** * Set's new event for $prefix_special * passed * * @param string $prefix_special * @param string $event_name * @return void * @access public */ public function setEvent($prefix_special, $event_name) { $this->EventManager->setEvent($prefix_special, $event_name); } /** * SQL Error Handler * * @param int $code * @param string $msg * @param string $sql * @return bool * @access public * @throws Exception * @deprecated */ public function handleSQLError($code, $msg, $sql) { return $this->_logger->handleSQLError($code, $msg, $sql); } /** * Returns & blocks next ResourceId available in system * * @return int * @access public */ public function NextResourceId() { $table_name = TABLE_PREFIX . 'IdGenerator'; $this->Conn->Query('LOCK TABLES ' . $table_name . ' WRITE'); $this->Conn->Query('UPDATE ' . $table_name . ' SET lastid = lastid + 1'); $id = $this->Conn->GetOne('SELECT lastid FROM ' . $table_name); if ( $id === false ) { $this->Conn->Query('INSERT INTO ' . $table_name . ' (lastid) VALUES (2)'); $id = 2; } $this->Conn->Query('UNLOCK TABLES'); return $id - 1; } /** * Returns genealogical main prefix for sub-table prefix passes * OR prefix, that has been found in REQUEST and some how is parent of passed sub-table prefix * * @param string $current_prefix * @param bool $real_top if set to true will return real topmost prefix, regardless of its id is passed or not * @return string * @access public */ public function GetTopmostPrefix($current_prefix, $real_top = false) { // 1. get genealogical tree of $current_prefix $prefixes = Array ($current_prefix); while ($parent_prefix = $this->getUnitOption($current_prefix, 'ParentPrefix')) { if ( !$this->prefixRegistred($parent_prefix) ) { // stop searching, when parent prefix is not registered break; } $current_prefix = $parent_prefix; array_unshift($prefixes, $current_prefix); } if ( $real_top ) { return $current_prefix; } // 2. find what if parent is passed $passed = explode(',', $this->GetVar('all_passed')); foreach ($prefixes as $a_prefix) { if ( in_array($a_prefix, $passed) ) { return $a_prefix; } } return $current_prefix; } /** * Triggers email event of type Admin * * @param string $email_template_name * @param int $to_user_id * @param array $send_params associative array of direct send params, possible keys: to_email, to_name, from_email, from_name, message, message_text * @return kEvent * @access public */ public function emailAdmin($email_template_name, $to_user_id = null, $send_params = Array ()) { return $this->_email($email_template_name, EmailTemplate::TEMPLATE_TYPE_ADMIN, $to_user_id, $send_params); } /** * Triggers email event of type User * * @param string $email_template_name * @param int $to_user_id * @param array $send_params associative array of direct send params, possible keys: to_email, to_name, from_email, from_name, message, message_text * @return kEvent * @access public */ public function emailUser($email_template_name, $to_user_id = null, $send_params = Array ()) { return $this->_email($email_template_name, EmailTemplate::TEMPLATE_TYPE_FRONTEND, $to_user_id, $send_params); } /** * Triggers general email event * * @param string $email_template_name * @param int $email_template_type (0 for User, 1 for Admin) * @param int $to_user_id * @param array $send_params associative array of direct send params, * possible keys: to_email, to_name, from_email, from_name, message, message_text * @return kEvent * @access protected */ protected function _email($email_template_name, $email_template_type, $to_user_id = null, $send_params = Array ()) { /** @var kEmail $email */ $email = $this->makeClass('kEmail'); if ( !$email->findTemplate($email_template_name, $email_template_type) ) { return false; } $email->setParams($send_params); return $email->send($to_user_id); } /** * Allows to check if user in this session is logged in or not * * @return bool * @access public */ public function LoggedIn() { // no session during expiration process return is_null($this->Session) ? false : $this->Session->LoggedIn(); } /** * Determines if access permissions should not be checked. * * @param integer|null $user_id User ID. * * @return boolean */ public function permissionCheckingDisabled($user_id = null) { if ( !isset($user_id) ) { $user_id = $this->RecallVar('user_id'); } return $user_id == USER_ROOT; } /** * Check current user permissions based on it's group permissions in specified category * * @param string $name permission name * @param int $cat_id category id, current used if not specified * @param int $type permission type {1 - system, 0 - per category} * @return int * @access public */ public function CheckPermission($name, $type = 1, $cat_id = null) { /** @var kPermissionsHelper $perm_helper */ $perm_helper = $this->recallObject('PermissionsHelper'); return $perm_helper->CheckPermission($name, $type, $cat_id); } /** * Check current admin permissions based on it's group permissions in specified category * * @param string $name permission name * @param int $cat_id category id, current used if not specified * @param int $type permission type {1 - system, 0 - per category} * @return int * @access public */ public function CheckAdminPermission($name, $type = 1, $cat_id = null) { /** @var kPermissionsHelper $perm_helper */ $perm_helper = $this->recallObject('PermissionsHelper'); return $perm_helper->CheckAdminPermission($name, $type, $cat_id); } /** * Set's any field of current visit * * @param string $field * @param mixed $value * @return void * @access public * @todo move to separate module */ public function setVisitField($field, $value) { if ( $this->isAdmin || !$this->ConfigValue('UseVisitorTracking') ) { // admin logins are not registered in visits list return; } /** @var kDBItem $visit */ $visit = $this->recallObject('visits', null, Array ('raise_warnings' => 0)); if ( $visit->isLoaded() ) { $visit->SetDBField($field, $value); $visit->Update(); } } /** * Allows to check if in-portal is installed * * @return bool * @access public */ public function isInstalled() { return $this->InitDone && (count($this->ModuleInfo) > 0); } /** * Allows to determine if module is installed & enabled * * @param string $module_name * @return bool * @access public */ public function isModuleEnabled($module_name) { return $this->findModule('Name', $module_name) !== false; } /** * Returns Window ID of passed prefix main prefix (in edit mode) * * @param string $prefix * @return int * @access public */ public function GetTopmostWid($prefix) { $top_prefix = $this->GetTopmostPrefix($prefix); $mode = $this->GetVar($top_prefix . '_mode'); return $mode != '' ? substr($mode, 1) : ''; } /** * Get temp table name * * @param string $table * @param mixed $wid * @return string * @access public */ public function GetTempName($table, $wid = '') { return $this->GetTempTablePrefix($wid) . $table; } /** * Builds temporary table prefix based on given window id * * @param string $wid * @return string * @access public */ public function GetTempTablePrefix($wid = '') { if ( preg_match('/prefix:(.*)/', $wid, $regs) ) { $wid = $this->GetTopmostWid($regs[1]); } return TABLE_PREFIX . 'ses_' . $this->GetSID() . ($wid ? '_' . $wid : '') . '_edit_'; } /** * Checks if given table is a temporary table * * @param string $table * @return bool * @access public */ public function IsTempTable($table) { static $cache = Array (); if ( !array_key_exists($table, $cache) ) { $cache[$table] = preg_match('/' . TABLE_PREFIX . 'ses_' . $this->GetSID() . '(_[\d]+){0,1}_edit_(.*)/', $table); } return (bool)$cache[$table]; } /** * Checks, that given prefix is in temp mode * * @param string $prefix * @param string $special * @return bool * @access public */ public function IsTempMode($prefix, $special = '') { $top_prefix = $this->GetTopmostPrefix($prefix); $var_names = Array ( $top_prefix, rtrim($top_prefix . '_' . $special, '_'), // from post rtrim($top_prefix . '.' . $special, '.'), // assembled locally ); $var_names = array_unique($var_names); $temp_mode = false; foreach ($var_names as $var_name) { $value = $this->GetVar($var_name . '_mode'); if ( $value && (substr($value, 0, 1) == 't') ) { $temp_mode = true; break; } } return $temp_mode; } /** * Return live table name based on temp table name * * @param string $temp_table * @return string */ public function GetLiveName($temp_table) { if ( preg_match('/' . TABLE_PREFIX . 'ses_' . $this->GetSID() . '(_[\d]+){0,1}_edit_(.*)/', $temp_table, $rets) ) { // cut wid from table end if any return $rets[2]; } else { return $temp_table; } } /** * Stops processing of user request and displays given message * * @param string $message * @access public */ public function ApplicationDie($message = '') { while ( ob_get_level() ) { ob_end_clean(); } if ( $this->isDebugMode() ) { $message .= $this->Debugger->printReport(true); } $this->HTML = $message; $this->_outputPage(); } /** * Returns comma-separated list of groups from given user * * @param int $user_id * @return string */ public function getUserGroups($user_id) { switch ($user_id) { case USER_ROOT: $user_groups = $this->ConfigValue('User_LoggedInGroup'); break; case USER_GUEST: $user_groups = $this->ConfigValue('User_LoggedInGroup') . ',' . $this->ConfigValue('User_GuestGroup'); break; default: $sql = 'SELECT GroupId FROM ' . TABLE_PREFIX . 'UserGroupRelations WHERE PortalUserId = ' . (int)$user_id; $res = $this->Conn->GetCol($sql); $user_groups = Array ($this->ConfigValue('User_LoggedInGroup')); if ( $res ) { $user_groups = array_merge($user_groups, $res); } $user_groups = implode(',', $user_groups); } return $user_groups; } /** * Allows to detect if page is browsed by spider (293 scheduled_tasks supported) * * @return bool * @access public */ /*public function IsSpider() { static $is_spider = null; if ( !isset($is_spider) ) { $user_agent = trim($_SERVER['HTTP_USER_AGENT']); $robots = file(FULL_PATH . '/core/robots_list.txt'); foreach ($robots as $robot_info) { $robot_info = explode("\t", $robot_info, 3); if ( $user_agent == trim($robot_info[2]) ) { $is_spider = true; break; } } } return $is_spider; }*/ /** * Allows to detect table's presence in database * * @param string $table_name * @param bool $force * @return bool * @access public */ public function TableFound($table_name, $force = false) { return $this->Conn->TableFound($table_name, $force); } /** * Returns counter value * * @param string $name counter name * @param Array $params counter parameters * @param string $query_name specify query name directly (don't generate from parameters) * @param bool $multiple_results * @return mixed * @access public */ public function getCounter($name, $params = Array (), $query_name = null, $multiple_results = false) { /** @var kCountHelper $count_helper */ $count_helper = $this->recallObject('CountHelper'); return $count_helper->getCounter($name, $params, $query_name, $multiple_results); } /** * Resets counter, which are affected by one of specified tables * * @param string $tables comma separated tables list used in counting sqls * @return void * @access public */ public function resetCounters($tables) { if ( kUtil::constOn('IS_INSTALL') ) { return; } /** @var kCountHelper $count_helper */ $count_helper = $this->recallObject('CountHelper'); $count_helper->resetCounters($tables); } /** * Sends XML header + optionally displays xml heading * * @param string|bool $xml_version * @return string * @access public * @author Alex */ public function XMLHeader($xml_version = false) { $this->setContentType('text/xml'); return $xml_version ? '' : ''; } /** * Returns category tree * * @param int $category_id * @return Array * @access public */ public function getTreeIndex($category_id) { $tree_index = $this->getCategoryCache($category_id, 'category_tree'); if ( $tree_index ) { $ret = Array (); list ($ret['TreeLeft'], $ret['TreeRight']) = explode(';', $tree_index); return $ret; } return false; } /** * Base category of all categories * Usually replaced category, with ID = 0 in category-related operations. * * @return int * @access public */ public function getBaseCategory() { // same, what $this->findModule('Name', 'Core', 'RootCat') does // don't cache while IS_INSTALL, because of kInstallToolkit::createModuleCategory and upgrade return $this->ModuleInfo['Core']['RootCat']; } /** * Deletes all data, that was cached during unit config parsing (excluding unit config locations) * * @param Array $config_variables * @access public */ public function DeleteUnitCache($config_variables = null) { $this->cacheManager->DeleteUnitCache($config_variables); } /** * Deletes cached section tree, used during permission checking and admin console tree display * * @return void * @access public */ public function DeleteSectionCache() { $this->cacheManager->DeleteSectionCache(); } /** * Sets data from cache to object * * @param Array $data * @access public */ public function setFromCache(&$data) { $this->Factory->setFromCache($data); $this->UnitConfigReader->setFromCache($data); $this->EventManager->setFromCache($data); $this->ReplacementTemplates = $data['Application.ReplacementTemplates']; $this->RewriteListeners = $data['Application.RewriteListeners']; $this->ModuleInfo = $data['Application.ModuleInfo']; } /** * Gets object data for caching * The following caches should be reset based on admin interaction (adjusting config, enabling modules etc) * * @access public * @return Array */ public function getToCache() { return array_merge( $this->Factory->getToCache(), $this->UnitConfigReader->getToCache(), $this->EventManager->getToCache(), Array ( 'Application.ReplacementTemplates' => $this->ReplacementTemplates, 'Application.RewriteListeners' => $this->RewriteListeners, 'Application.ModuleInfo' => $this->ModuleInfo, ) ); } public function delayUnitProcessing($method, $params) { $this->cacheManager->delayUnitProcessing($method, $params); } /** * Returns current maintenance mode state * * @param bool $check_ips * @return int * @access public */ public function getMaintenanceMode($check_ips = true) { $exception_ips = defined('MAINTENANCE_MODE_IPS') ? MAINTENANCE_MODE_IPS : ''; $setting_name = $this->isAdmin ? 'MAINTENANCE_MODE_ADMIN' : 'MAINTENANCE_MODE_FRONT'; if ( defined($setting_name) && constant($setting_name) > MaintenanceMode::NONE ) { $exception_ip = $check_ips ? kUtil::ipMatch($exception_ips) : false; if ( !$exception_ip ) { return constant($setting_name); } } return MaintenanceMode::NONE; } /** * Sets content type of the page * * @param string $content_type * @param bool $include_charset * @return void * @access public */ public function setContentType($content_type = 'text/html', $include_charset = null) { static $already_set = false; if ( $already_set ) { return; } $header = 'Content-type: ' . $content_type; if ( !isset($include_charset) ) { $include_charset = $content_type = 'text/html' || $content_type == 'text/plain' || $content_type = 'text/xml'; } if ( $include_charset ) { $header .= '; charset=' . CHARSET; } $already_set = true; header($header); } /** * Posts message to event log * * @param string $message * @param int $code * @param bool $write_now Allows further customization of log record by returning kLog object * @return bool|int|kLogger * @access public */ public function log($message, $code = null, $write_now = false) { $log = $this->_logger->prepare($message, $code)->addSource($this->_logger->createTrace(null, 1)); if ( $write_now ) { return $log->write(); } return $log; } /** * Deletes log with given id from database or disk, when database isn't available * * @param int $unique_id * @param int $storage_medium * @return void * @access public * @throws InvalidArgumentException */ public function deleteLog($unique_id, $storage_medium = kLogger::LS_AUTOMATIC) { $this->_logger->delete($unique_id, $storage_medium); } /** * Returns the client IP address. * * @return string The client IP address * @access public */ public function getClientIp() { return $this->HttpQuery->getClientIp(); } } Index: branches/5.2.x/core/kernel/managers/cache_manager.php =================================================================== --- branches/5.2.x/core/kernel/managers/cache_manager.php (revision 16771) +++ branches/5.2.x/core/kernel/managers/cache_manager.php (revision 16772) @@ -1,870 +1,883 @@ Array (), 'registerScheduledTask' => Array (), 'registerHook' => Array (), 'registerBuildEvent' => Array (), 'registerAggregateTag' => Array (), ); /** * Name of database table, where configuration settings are stored * * @var string * @access protected */ protected $settingTableName = ''; /** + * Maximal cache duration. + * + * @var integer + */ + protected $maxCacheDuration; + + /** * Set's references to kApplication and DBConnection interface class instances * * @access public */ public function __construct() { parent::__construct(); $this->settingTableName = TABLE_PREFIX . 'SystemSettings'; if ( defined('IS_INSTALL') && IS_INSTALL ) { // table substitution required, so "root" can perform login to upgrade to 5.2.0, where setting table was renamed if ( !$this->Application->TableFound(TABLE_PREFIX . 'SystemSettings') ) { $this->settingTableName = TABLE_PREFIX . 'ConfigurationValues'; } } + + $this->maxCacheDuration = 60 * 60 * 24 * kUtil::getSystemConfig()->get('MaxCacheDuration', ''); } + /** * Creates caching manager instance * * @access public */ public function InitCache() { - $this->cacheHandler = $this->Application->makeClass('kCache'); + $this->cacheHandler = $this->Application->makeClass('kCache', array($this->maxCacheDuration)); } /** * Returns cache key, used to cache phrase and configuration variable IDs used on current page * * @return string * @access protected */ protected function getCacheKey() { // TODO: maybe language part isn't required, since same phrase from different languages have one ID now return $this->Application->GetVar('t') . $this->Application->GetVar('m_theme') . $this->Application->GetVar('m_lang') . $this->Application->isAdmin; } /** * Loads phrases and configuration variables, that were used on this template last time * * @access public */ public function LoadApplicationCache() { $phrase_ids = $config_ids = Array (); $sql = 'SELECT PhraseList, ConfigVariables FROM ' . TABLE_PREFIX . 'PhraseCache WHERE Template = ' . $this->Conn->qstr( md5($this->getCacheKey()) ); $res = $this->Conn->GetRow($sql); if ($res) { if ( $res['PhraseList'] ) { $phrase_ids = explode(',', $res['PhraseList']); } if ( $res['ConfigVariables'] ) { $config_ids = array_diff( explode(',', $res['ConfigVariables']), $this->originalConfigIDs); } } $this->Application->Phrases->Init('phrases', '', null, $phrase_ids); $this->configIDs = $this->originalConfigIDs = $config_ids; $this->InitConfig(); } /** * Updates phrases and configuration variables, that were used on this template * * @access public */ public function UpdateApplicationCache() { $update = false; //something changed $update = $update || $this->Application->Phrases->NeedsCacheUpdate(); $update = $update || (count($this->configIDs) && $this->configIDs != $this->originalConfigIDs); if ($update) { $fields_hash = Array ( 'PhraseList' => implode(',', $this->Application->Phrases->Ids), 'CacheDate' => adodb_mktime(), 'Template' => md5( $this->getCacheKey() ), 'ConfigVariables' => implode(',', array_unique($this->configIDs)), ); $this->Conn->doInsert($fields_hash, TABLE_PREFIX . 'PhraseCache', 'REPLACE'); } } /** * Loads configuration variables, that were used on this template last time * * @access protected */ protected function InitConfig() { if (!$this->originalConfigIDs) { return ; } $sql = 'SELECT VariableValue, VariableName FROM ' . $this->settingTableName . ' WHERE VariableId IN (' . implode(',', $this->originalConfigIDs) . ')'; $config_variables = $this->Conn->GetCol($sql, 'VariableName'); $this->configVariables = array_merge($this->configVariables, $config_variables); } /** * Returns configuration option value by name * * @param string $name * @return string * @access public */ public function ConfigValue($name) { $site_domain_override = Array ( 'DefaultEmailSender' => 'AdminEmail', 'DefaultEmailRecipients' => 'DefaultEmailRecipients', ); if ( isset($site_domain_override[$name]) ) { $res = $this->Application->siteDomainField($site_domain_override[$name]); if ( $res ) { return $res; } } if ( array_key_exists($name, $this->configVariables) ) { return $this->configVariables[$name]; } if ( defined('IS_INSTALL') && IS_INSTALL && !$this->Application->TableFound($this->settingTableName, true) ) { return false; } $this->Conn->nextQueryCachable = true; $sql = 'SELECT VariableId, VariableValue FROM ' . $this->settingTableName . ' WHERE VariableName = ' . $this->Conn->qstr($name); $res = $this->Conn->GetRow($sql); if ( $res !== false ) { $this->configIDs[] = $res['VariableId']; $this->configVariables[$name] = $res['VariableValue']; return $res['VariableValue']; } trigger_error('Usage of undefined configuration variable "' . $name . '"', E_USER_NOTICE); return false; } /** * Changes value of individual configuration variable (+resets cache, when needed) * * @param string $name * @param string $value * @param bool $local_cache_only * @return string * @access public */ public function SetConfigValue($name, $value, $local_cache_only = false) { $this->configVariables[$name] = $value; if ( $local_cache_only ) { return; } $fields_hash = Array ('VariableValue' => $value); $this->Conn->doUpdate($fields_hash, $this->settingTableName, 'VariableName = ' . $this->Conn->qstr($name)); if ( array_key_exists($name, $this->originalConfigVariables) && $value != $this->originalConfigVariables[$name] ) { $this->DeleteUnitCache(); } } /** * Loads data, that was cached during unit config parsing * * @return bool * @access public */ public function LoadUnitCache() { if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) { $data = $this->Application->getCache('master:configs_parsed', false, CacheSettings::$unitCacheRebuildTime); } else { $data = $this->Application->getDBCache('configs_parsed', CacheSettings::$unitCacheRebuildTime); } if ( $data ) { $cache = unserialize($data); // 126 KB all modules unset($data); $this->Application->InitManagers(); $this->Application->setFromCache($cache); /** @var kArray $aggregator */ $aggregator = $this->Application->recallObject('TagsAggregator', 'kArray'); $aggregator->setFromCache($cache); $this->setFromCache($cache); unset($cache); return true; } return false; } /** * Empties factory and event manager cache (without storing changes) */ public function EmptyUnitCache() { // maybe discover keys automatically from corresponding classes $cache_keys = Array ( 'Factory.Files', 'Factory.realClasses', 'ConfigReader.prefixFiles', 'EventManager.beforeHooks', 'EventManager.afterHooks', 'EventManager.scheduledTasks', 'EventManager.buildEvents', 'Application.ReplacementTemplates', 'Application.RewriteListeners', 'Application.ModuleInfo', 'Application.ConfigHash', 'Application.ConfigCacheIds', ); $empty_cache = Array (); foreach ($cache_keys as $cache_key) { $empty_cache[$cache_key] = Array (); } $this->Application->setFromCache($empty_cache); $this->setFromCache($empty_cache); // otherwise ModulesHelper indirectly used from includeConfigFiles won't work $this->Application->RegisterDefaultClasses(); $this->Application->RegisterDefaultBuildEvents(); } /** * Updates data, that was parsed from unit configs this time * * @access public */ public function UpdateUnitCache() { /** @var kArray $aggregator */ $aggregator = $this->Application->recallObject('TagsAggregator', 'kArray'); $this->preloadConfigVars(); // preloading will put to cache $cache = array_merge( $this->Application->getToCache(), $aggregator->getToCache(), $this->getToCache() ); $cache_rebuild_by = SERVER_NAME . ' (' . $this->Application->getClientIp() . ') - ' . adodb_date('d/m/Y H:i:s'); if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { - $this->Application->setCache('master:configs_parsed', serialize($cache)); - $this->Application->setCache('master:last_cache_rebuild', $cache_rebuild_by); + $this->Application->setCache('master:configs_parsed', serialize($cache), 0); + $this->Application->setCache('master:last_cache_rebuild', $cache_rebuild_by, 0); } else { - $this->Application->setDBCache('configs_parsed', serialize($cache)); - $this->Application->setDBCache('last_cache_rebuild', $cache_rebuild_by); + $this->Application->setDBCache('configs_parsed', serialize($cache), 0); + $this->Application->setDBCache('last_cache_rebuild', $cache_rebuild_by, 0); } } public function delayUnitProcessing($method, $params) { if ($this->Application->InitDone) { // init already done -> call immediately (happens during installation) $function = Array (&$this->Application, $method); call_user_func_array($function, $params); return ; } $this->temporaryCache[$method][] = $params; } public function applyDelayedUnitProcessing() { foreach ($this->temporaryCache as $method => $method_calls) { $function = Array (&$this->Application, $method); foreach ($method_calls as $method_call) { call_user_func_array($function, $method_call); } $this->temporaryCache[$method] = Array (); } } /** * Deletes all data, that was cached during unit config parsing (excluding unit config locations) * * @param Array $config_variables * @access public */ public function DeleteUnitCache($config_variables = null) { if ( isset($config_variables) && !array_intersect(array_keys($this->originalConfigVariables), $config_variables) ) { // prevent cache reset, when given config variables are not in unit cache return; } if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) { $this->Application->rebuildCache('master:configs_parsed', kCache::REBUILD_LATER, CacheSettings::$unitCacheRebuildTime); } else { $this->rebuildDBCache('configs_parsed', kCache::REBUILD_LATER, CacheSettings::$unitCacheRebuildTime); } } /** * Deletes cached section tree, used during permission checking and admin console tree display * * @return void * @access public */ public function DeleteSectionCache() { if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) { $this->Application->rebuildCache('master:sections_parsed', kCache::REBUILD_LATER, CacheSettings::$sectionsParsedRebuildTime); } else { $this->rebuildDBCache('sections_parsed', kCache::REBUILD_LATER, CacheSettings::$sectionsParsedRebuildTime); } } /** * Preloads 21 widely used configuration variables, so they will get to cache for sure * * @access protected */ protected function preloadConfigVars() { $config_vars = Array ( // session related 'SessionTimeout', 'SessionCookieName', 'SessionCookieDomains', 'SessionBrowserSignatureCheck', 'SessionIPAddressCheck', 'CookieSessions', 'KeepSessionOnBrowserClose', 'User_GuestGroup', 'User_LoggedInGroup', 'RegistrationUsernameRequired', // output related 'UseModRewrite', 'UseContentLanguageNegotiation', 'UseOutputCompression', 'OutputCompressionLevel', 'Config_Site_Time', 'SystemTagCache', 'DefaultGridPerPage', // tracking related 'UseChangeLog', 'UseVisitorTracking', 'ModRewriteUrlEnding', 'ForceModRewriteUrlEnding', 'RunScheduledTasksFromCron', ); $escaped_config_vars = $this->Conn->qstrArray($config_vars); $sql = 'SELECT VariableId, VariableName, VariableValue FROM ' . $this->settingTableName . ' WHERE VariableName IN (' . implode(',', $escaped_config_vars) . ')'; $data = $this->Conn->Query($sql, 'VariableId'); foreach ($data as $variable_id => $variable_info) { $this->configIDs[] = $variable_id; $this->configVariables[ $variable_info['VariableName'] ] = $variable_info['VariableValue']; } } /** * Sets data from cache to object * * Used for cases, when ConfigValue is called before LoadApplicationCache method (e.g. session init, url engine init) * * @param Array $data * @access public */ public function setFromCache(&$data) { $this->configVariables = $this->originalConfigVariables = $data['Application.ConfigHash']; $this->configIDs = $this->originalConfigIDs = $data['Application.ConfigCacheIds']; } /** * Gets object data for caching * The following caches should be reset based on admin interaction (adjusting config, enabling modules etc) * * @access public * @return Array */ public function getToCache() { return Array ( 'Application.ConfigHash' => $this->configVariables, 'Application.ConfigCacheIds' => $this->configIDs, // not in use, since it only represents template specific values, not global ones // 'Application.Caches.ConfigVariables' => $this->originalConfigIDs, ); } /** * Returns caching type (none, memory, temporary) * * @param int $caching_type * @return bool * @access public */ public function isCachingType($caching_type) { return $this->cacheHandler->getCachingType() == $caching_type; } /** * Returns cached $key value from cache named $cache_name * * @param int $key key name from cache * @param bool $store_locally store data locally after retrieved * @param int $max_rebuild_seconds * @return mixed * @access public */ public function getCache($key, $store_locally = true, $max_rebuild_seconds = 0) { return $this->cacheHandler->getCache($key, $store_locally, $max_rebuild_seconds); } /** - * Stores new $value in cache with $key name + * Stores new $value in cache with $name name. * - * @param int $key key name to add to cache - * @param mixed $value value of cached record - * @param int $expiration when value expires (0 - doesn't expire) - * @return bool - * @access public + * @param string $name Key name to add to cache. + * @param mixed $value Value of cached record. + * @param integer|null $expiration When value expires (0 - doesn't expire). + * + * @return boolean */ - public function setCache($key, $value, $expiration = 0) + public function setCache($name, $value, $expiration = null) { - return $this->cacheHandler->setCache($key, $value, $expiration); + return $this->cacheHandler->setCache($name, $value, $expiration); } /** * Stores new $value in cache with $key name (only if not there already) * - * @param int $key key name to add to cache - * @param mixed $value value of cached record - * @param int $expiration when value expires (0 - doesn't expire) - * @return bool - * @access public + * @param string $name Key name to add to cache. + * @param mixed $value Value of cached record. + * @param integer|null $expiration When value expires (0 - doesn't expire). + * + * @return boolean */ - public function addCache($key, $value, $expiration = 0) + public function addCache($name, $value, $expiration = null) { - return $this->cacheHandler->addCache($key, $value, $expiration); + return $this->cacheHandler->addCache($name, $value, $expiration); } /** * Sets rebuilding mode for given cache * * @param string $name * @param int $mode * @param int $max_rebuilding_time * @return bool * @access public */ public function rebuildCache($name, $mode = null, $max_rebuilding_time = 0) { return $this->cacheHandler->rebuildCache($name, $mode, $max_rebuilding_time); } /** * Deletes key from cache * * @param string $key * @return void * @access public */ public function deleteCache($key) { $this->cacheHandler->delete($key); } /** * Reset's all memory cache at once * * @return void * @access public */ public function resetCache() { $this->cacheHandler->reset(); } /** * Returns value from database cache * * @param string $name key name * @param int $max_rebuild_seconds * @return mixed * @access public */ public function getDBCache($name, $max_rebuild_seconds = 0) { // no serials in cache key OR cache is outdated $rebuilding = false; $wait_seconds = $max_rebuild_seconds; while (true) { $cached_data = $this->_getDBCache(Array ($name, $name . '_rebuilding', $name . '_rebuild')); if ( $cached_data[$name . '_rebuild'] ) { // cache rebuild requested -> rebuild now $this->deleteDBCache($name . '_rebuild'); if ( $this->rebuildDBCache($name, kCache::REBUILD_NOW, $max_rebuild_seconds, '[M1]') ) { return 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->rebuildDBCache($name, kCache::REBUILD_NOW, $max_rebuild_seconds, '[M2' . ($rebuilding ? 'R' : '!R') . ',WS=' . $wait_seconds . ']'); return false; } elseif ( $cache !== false ) { // cache present -> return it $this->cacheHandler->storeStatistics($name, $rebuilding ? 'h' : 'H'); return $cache; } $wait_seconds -= kCache::WAIT_STEP; sleep(kCache::WAIT_STEP); } $this->rebuildDBCache($name, kCache::REBUILD_NOW, $max_rebuild_seconds, '[M3' . ($rebuilding ? 'R' : '!R') . ',WS=' . $wait_seconds . ']'); return false; } /** * Returns value from database cache * * @param string|Array $names key name * @return mixed * @access protected */ protected function _getDBCache($names) { $res = Array (); $names = (array)$names; $this->Conn->nextQueryCachable = true; $sql = 'SELECT Data, Cached, LifeTime, VarName FROM ' . TABLE_PREFIX . 'SystemCache WHERE VarName IN (' . implode(',', $this->Conn->qstrArray($names)) . ')'; $cached_data = $this->Conn->Query($sql, 'VarName'); foreach ($names as $name) { if ( !isset($cached_data[$name]) ) { $res[$name] = false; continue; } $lifetime = (int)$cached_data[$name]['LifeTime']; // in seconds if ( ($lifetime > 0) && ($cached_data[$name]['Cached'] + $lifetime < adodb_mktime()) ) { // delete expired $this->Conn->nextQueryCachable = true; $sql = 'DELETE FROM ' . TABLE_PREFIX . 'SystemCache WHERE VarName = ' . $this->Conn->qstr($name); $this->Conn->Query($sql); $res[$name] = false; continue; } $res[$name] = $cached_data[$name]['Data']; } return count($res) == 1 ? array_pop($res) : $res; } /** - * Sets value to database cache + * Sets value to database cache. + * + * @param string $name Key name to add to cache. + * @param mixed $value Value of cached record. + * @param integer|null $expiration When value expires (0 - doesn't expire). * - * @param string $name - * @param mixed $value - * @param int|bool $expiration * @return void - * @access public */ - public function setDBCache($name, $value, $expiration = false) + public function setDBCache($name, $value, $expiration = null) { $this->cacheHandler->storeStatistics($name, 'WU'); $this->deleteDBCache($name . '_rebuilding'); $this->_setDBCache($name, $value, $expiration); } /** - * Sets value to database cache + * Sets value to database cache. * - * @param string $name - * @param mixed $value - * @param int|bool $expiration - * @param string $insert_type - * @return bool - * @access protected + * @param string $name Key name to add to cache. + * @param mixed $value Value of cached record. + * @param integer|null $expiration When value expires (0 - doesn't expire). + * @param string $insert_type Insert type. + * + * @return boolean */ - protected function _setDBCache($name, $value, $expiration = false, $insert_type = 'REPLACE') + protected function _setDBCache($name, $value, $expiration = null, $insert_type = 'REPLACE') { - if ( (int)$expiration <= 0 ) { + if ( $expiration === null ) { + $expiration = $this->maxCacheDuration; + } + elseif ( (int)$expiration <= 0 ) { $expiration = -1; } $fields_hash = Array ( 'VarName' => $name, 'Data' => &$value, 'Cached' => adodb_mktime(), 'LifeTime' => (int)$expiration, ); $this->Conn->nextQueryCachable = true; return $this->Conn->doInsert($fields_hash, TABLE_PREFIX . 'SystemCache', $insert_type); } /** * Sets value to database cache * * @param string $name * @param mixed $value * @param int|bool $expiration * @return bool * @access protected */ protected function _addDBCache($name, $value, $expiration = false) { return $this->_setDBCache($name, $value, $expiration, 'INSERT'); } /** * Sets rebuilding mode for given cache * * @param string $name * @param int $mode * @param int $max_rebuilding_time * @param string $miss_type * @return bool * @access public */ public function rebuildDBCache($name, $mode = null, $max_rebuilding_time = 0, $miss_type = 'M') { if ( !isset($mode) || $mode == kCache::REBUILD_NOW ) { $this->cacheHandler->storeStatistics($name, $miss_type); if ( !$max_rebuilding_time ) { return true; } if ( !$this->_addDBCache($name . '_rebuilding', 1, $max_rebuilding_time) ) { $this->cacheHandler->storeStatistics($name, 'l'); return false; } $this->deleteDBCache($name . '_rebuild'); $this->cacheHandler->storeStatistics($name, 'L'); } elseif ( $mode == kCache::REBUILD_LATER ) { $this->_setDBCache($name . '_rebuild', 1, 0); $this->deleteDBCache($name . '_rebuilding'); } return true; } /** * Deletes key from database cache * * @param string $name * @return void * @access public */ public function deleteDBCache($name) { $sql = 'DELETE FROM ' . TABLE_PREFIX . 'SystemCache WHERE VarName = ' . $this->Conn->qstr($name); $this->Conn->Query($sql); } /** * 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 * @return string * @access public */ public 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->Application->isDebugMode()) { $this->Application->Debugger->appendHTML('Incrementing serial: ' . $serial_name . '.'); } $this->setCache($serial_name, (int)$this->getCache($serial_name) + 1); if (!defined('IS_INSTALL') || !IS_INSTALL) { if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) { $prefixes = $this->Application->getCache('cached_urls_unit_prefixes'); } else { $prefixes = $this->Application->getDBCache('cached_urls_unit_prefixes'); if ( $prefixes !== false ) { $prefixes = unserialize($prefixes); } } if ( !$prefixes ) { $prefixes = array('c', 'lang', 'theme'); } if ( in_array($prefix, $prefixes) ) { // 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; } /** * Returns cached category informaton by given cache name. All given category * information is recached, when at least one of 4 caches is missing. * * @param int $category_id * @param string $name cache name = {filenames, category_designs, category_tree} * @return string * @access public */ public function getCategoryCache($category_id, $name) { $serial_name = '[%CIDSerial:' . $category_id . '%]'; $cache_key = $name . $serial_name; $ret = $this->getCache($cache_key); 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 $this->Conn->nextQueryCachable = true; $sql = 'SELECT NamedParentPath, CachedTemplate, TreeLeft, TreeRight FROM ' . TABLE_PREFIX . 'Categories 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) $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']); } } return $this->getCache($cache_key); } } Index: branches/5.2.x/core/kernel/utility/cache.php =================================================================== --- branches/5.2.x/core/kernel/utility/cache.php (revision 16771) +++ branches/5.2.x/core/kernel/utility/cache.php (revision 16772) @@ -1,1087 +1,1111 @@ maxCacheDuration = $max_cache_duration; $this->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; + $this->cachePrefix = $this->_cachePrefix(); 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 + * Stores new $value in cache with $name name. * - * @param string $name - * @param mixed $value - * @param int $expiration cache record expiration time in seconds - * @return bool + * @param string $name Key name to add to cache. + * @param mixed $value Value of cached record. + * @param integer|null $expiration When value expires (0 - doesn't expire). + * + * @return boolean */ - function setCache($name, $value, $expiration) + function setCache($name, $value, $expiration = null) { + if ( $expiration === null ) { + $expiration = $this->maxCacheDuration; + } + // 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) + * 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 + * @param string $name Key name to add to cache. + * @param mixed $value Value of cached record. + * @param integer|null $expiration When value expires (0 - doesn't expire). + * + * @return boolean */ - function addCache($name, $value, $expiration) + function addCache($name, $value, $expiration = null) { + if ( $expiration === null ) { + $expiration = $this->maxCacheDuration; + } + // 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); + $this->_handler->set($site_key, $this->_handler->get($site_key) + 1, 0); } /** * 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; + 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 + * @param string $name Name. + * @param mixed $value Value. + * @param integer|null $expiration Expiration. + * + * @return boolean */ - abstract public function set($name, $value, $expiration = 0); + abstract public function set($name, $value, $expiration = null); /** * Stores value in cache (only if it's not there already) * - * @param string $name - * @param mixed $value - * @param int $expiration - * @return bool - * @access public + * @param string $name Name. + * @param mixed $value Value. + * @param integer|null $expiration Expiration. + * + * @return boolean */ - abstract public function add($name, $value, $expiration = 0); + abstract public function add($name, $value, $expiration = null); /** * 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 + * @param string $name Name. + * @param mixed $value Value. + * @param integer|null $expiration Expiration. + * + * @return boolean */ - public function set($name, $value, $expiration = 0) + public function set($name, $value, $expiration = null) { 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 + * @param string $name Name. + * @param mixed $value Value. + * @param integer|null $expiration Expiration. + * + * @return boolean */ - public function add($name, $value, $expiration = 0) + public function add($name, $value, $expiration = null) { 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) ) { + // Verify, that memcache server is working. + if ( !$this->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 + * @param string $name Name. + * @param mixed $value Value. + * @param integer|null $expiration Expiration. + * + * @return boolean */ - public function set($name, $value, $expiration = 0) + public function set($name, $value, $expiration = null) { // 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 + * @param string $name Name. + * @param mixed $value Value. + * @param integer|null $expiration Expiration. + * + * @return boolean */ - public function add($name, $value, $expiration = 0) + public function add($name, $value, $expiration = null) { // 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) ) { + if ( !$this->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. + * @param string $name Name. + * @param mixed $value Value. + * @param integer|null $expiration Expiration. * * @return boolean - * @access public */ - public function set($name, $value, $expiration = 0) + public function set($name, $value, $expiration = null) { 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. + * @param string $name Name. + * @param mixed $value Value. + * @param integer|null $expiration Expiration. * * @return boolean - * @access public */ - public function add($name, $value, $expiration = 0) + public function add($name, $value, $expiration = null) { 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 + * @param string $name Name. + * @param mixed $value Value. + * @param integer|null $expiration Expiration. + * + * @return boolean */ - public function set($name, $value, $expiration = 0) + public function set($name, $value, $expiration = null) { 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 + * @param string $name Name. + * @param mixed $value Value. + * @param integer|null $expiration Expiration. + * + * @return boolean */ - public function add($name, $value, $expiration = 0) + public function add($name, $value, $expiration = null) { 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 + * @param string $name Name. + * @param mixed $value Value. + * @param integer|null $expiration Expiration. + * + * @return boolean */ - public function set($name, $value, $expiration = 0) + public function set($name, $value, $expiration = null) { 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 + * @param string $name Name. + * @param mixed $value Value. + * @param integer|null $expiration Expiration. + * + * @return boolean */ - public function add($name, $value, $expiration = 0) + public function add($name, $value, $expiration = null) { // 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); } } Index: branches/5.2.x/core/kernel/utility/system_config.php =================================================================== --- branches/5.2.x/core/kernel/utility/system_config.php (revision 16771) +++ branches/5.2.x/core/kernel/utility/system_config.php (revision 16772) @@ -1,289 +1,290 @@ parseSections = $parse_section; $this->strictMode = $strict; $this->file = FULL_PATH . DIRECTORY_SEPARATOR . 'system' . DIRECTORY_SEPARATOR . 'config.php'; } /** * Returns default config values. * * @return array */ protected function getDefaults() { $ret = array( 'AdminDirectory' => '/admin', 'AdminPresetsDirectory' => '/admin', 'ApplicationClass' => 'kApplication', 'ApplicationPath' => '/core/kernel/application.php', 'CacheHandler' => 'Fake', + 'MaxCacheDuration' => 30, 'CmsMenuRebuildTime' => 10, 'DomainsParsedRebuildTime' => 2, 'EditorPath' => '/core/ckeditor/', 'EnableSystemLog' => '0', 'MemcacheServers' => 'localhost:11211', 'CompressionEngine' => '', 'RestrictedPath' => DIRECTORY_SEPARATOR . 'system' . DIRECTORY_SEPARATOR . '.restricted', 'SectionsParsedRebuildTime' => 5, 'StructureTreeRebuildTime' => 10, 'SystemLogMaxLevel' => 5, 'TemplateMappingRebuildTime' => 5, 'TrustProxy' => '0', 'UnitCacheRebuildTime' => 10, 'WebsiteCharset' => 'utf-8', 'WebsitePath' => rtrim(preg_replace('/'.preg_quote(rtrim(defined('REL_PATH') ? REL_PATH : '', '/'), '/').'$/', '', str_replace('\\', '/', dirname($_SERVER['PHP_SELF']))), '/'), 'WriteablePath' => DIRECTORY_SEPARATOR . 'system', 'SecurityHmacKey' => '', 'SecurityEncryptionKey' => '', ); return $this->parseSections ? array('Misc' => $ret) : $ret; } /** * Parses "/system/config.php" file and writes the result to the data variable * * @return array * @throws kSystemConfigException When something goes wrong. */ public function parse() { if ( !$this->exists() ) { if ( $this->strictMode ) { throw new kSystemConfigException(sprintf('System config at "%s" not found', $this->file)); } return array(); } elseif ( !is_readable($this->file) ) { throw new kSystemConfigException(sprintf('System config at "%s" could not be opened', $this->file)); } $contents = file($this->file); if ( $contents && $contents[0] == '<' . '?' . 'php die() ?' . ">\n" ) { // format of "config.php" file before 5.1.0 version array_shift($contents); return parse_ini_string(implode('', $contents), $this->parseSections); } $_CONFIG = array(); require($this->file); if ( $this->parseSections ) { if ( isset($_CONFIG['Database']['LoadBalancing']) && $_CONFIG['Database']['LoadBalancing'] ) { require FULL_PATH . DIRECTORY_SEPARATOR . 'system' . DIRECTORY_SEPARATOR . 'db_servers.php'; } return $_CONFIG; } $ret = array(); foreach ($_CONFIG as $section_variables) { $ret = array_merge($ret, $section_variables); } if ( !count($ret) && $this->strictMode ) { throw new kSystemConfigException(sprintf('System config at "%s" could is empty', $this->file)); } return $ret; } /** * Returns parsed variables from "config.php" file * * @return array */ public function getData() { if ( !$this->data ) { $this->data = array_replace_recursive($this->getDefaults(), $this->parse()); } return $this->data; } /** * Checks if given section is present in config. * * @param string $section * * @return boolean */ function sectionFound($section) { return $this->parseSections ? array_key_exists($section, $this->getData()) : false; } /** * Returns config value * * @param string $key Key name. * @param string $section Section name. * @param mixed $default Default value. * * @return string */ public function get($key, $section = null, $default = false) { $data = $this->getData(); if ( $this->parseSections ) { return isset($data[$section][$key]) ? $data[$section][$key] : $default; } return isset($data[$key]) ? $data[$key] : (isset($section) ? $section : $default); } /** * Checks, if a configuration file exists on disk. * * @return boolean */ public function exists() { return file_exists($this->file); } /** * Returns config status - is changed or not changed * * @return bool */ public function isChanged() { return $this->isChanged; } /** * Sets value to system config (yet saveConfig must be called to write it to file) * * @param string $key Key name. * @param string $section Section name. * @param mixed $value Value. * * @return void */ public function set($key, $section, $value = null) { $this->getData(); $this->isChanged = true; if ( isset($value) ) { // create section, when missing if ( !array_key_exists($section, $this->data) ) { $this->data[$section] = array(); } // create key in section $this->data[$section][$key] = $value; return; } unset($this->data[$section][$key]); } /** * Saves config data to the file * * @param boolean $silent * * @return void * @throws Exception */ public function save($silent = false) { if ( !is_writable($this->file) && !is_writable(dirname($this->file)) ) { $error_msg = 'Cannot write to "' . $this->file . '" file'; if ( $silent ) { trigger_error($error_msg, E_USER_WARNING); return; } throw new Exception($error_msg); } $fp = fopen($this->file, 'w'); fwrite($fp, '<' . '?' . 'php' . "\n\n"); foreach ( $this->getData() as $section_name => $section_data ) { foreach ( $section_data as $key => $value ) { fwrite($fp, '$_CONFIG[\'' . $section_name . '\'][\'' . $key . '\'] = \'' . addslashes($value) . '\';' . "\n"); } fwrite($fp, "\n"); } fclose($fp); if ( function_exists('opcache_invalidate') ) { opcache_invalidate($this->file); } $this->isChanged = false; } } class kSystemConfigException extends Exception { } Index: branches/5.2.x/core/kernel/utility/unit_config_reader.php =================================================================== --- branches/5.2.x/core/kernel/utility/unit_config_reader.php (revision 16771) +++ branches/5.2.x/core/kernel/utility/unit_config_reader.php (revision 16772) @@ -1,1049 +1,1049 @@ _directorySeparator = preg_quote(DIRECTORY_SEPARATOR); $this->_skipFolders[] = basename(EDITOR_PATH); $this->_moduleFolderRegExp = '#' . $this->_directorySeparator . '(core|modules' . $this->_directorySeparator . '.*?)' . $this->_directorySeparator . '#'; } /** * Sets data from cache to object * * @param Array $data * @access public */ public function setFromCache(&$data) { $this->prefixFiles = $data['ConfigReader.prefixFiles']; } /** * Gets object data for caching * * @access public * @return Array */ public function getToCache() { return Array ( 'ConfigReader.prefixFiles' => $this->prefixFiles, ); } function scanModules($folderPath, $cache = true) { if (defined('IS_INSTALL') && IS_INSTALL && !defined('FORCE_CONFIG_CACHE')) { // disable config caching during installation $cache = false; } if ($cache) { $restored = $this->Application->cacheManager->LoadUnitCache(); if ($restored) { if ( defined('DEBUG_MODE') && DEBUG_MODE && $this->Application->isDebugMode() ) { $this->Application->Debugger->appendHTML('UnitConfigReader: Restoring Cache'); } return; } } if ( defined('DEBUG_MODE') && DEBUG_MODE && $this->Application->isDebugMode() ) { $this->Application->Debugger->appendHTML('UnitConfigReader: Generating Cache'); } $this->ProcessAllConfigs = true; $this->includeConfigFiles($folderPath, $cache); $this->ParseConfigs(); // tell AfterConfigRead to store cache if needed // can't store it here because AfterConfigRead needs ability to change config data $this->StoreCache = $cache; if ( !$this->Application->InitDone ) { // scanModules is called multiple times during installation process $this->Application->InitManagers(); // get build-in rewrite listeners ONLY to be able to parse mod-rewrite url when unit config cache is missing $this->retrieveCollections(); $this->_sortRewriteListeners(); } $this->Application->cacheManager->applyDelayedUnitProcessing(); if ( !$this->Application->InitDone && $cache ) { // Allow hooks to modify "m:QueryString" before URL parsing is started during cold start. $this->runAfterConfigRead('m'); } } function findConfigFiles($folderPath, $level = 0) { // If FULL_PATH = "/" ensure, that all "/" in $folderPath are not deleted. $reg_exp = '/^' . preg_quote(FULL_PATH, '/') . '/'; // This make sense, since $folder_path may NOT contain FULL_PATH. $folderPath = preg_replace($reg_exp, '', $folderPath, 1); $base_folder = FULL_PATH . $folderPath . DIRECTORY_SEPARATOR; $sub_folders = glob($base_folder . '*', GLOB_ONLYDIR); if (!$sub_folders) { return ; } foreach ($sub_folders as $full_path) { $sub_folder = substr($full_path, strlen($base_folder)); if (in_array($sub_folder, $this->_skipFolders)) { continue; } if (substr($sub_folder, 0, 1) == '.') { // Don't scan ".folders". continue; } $config_name = $this->getConfigName($folderPath . DIRECTORY_SEPARATOR . $sub_folder); if (file_exists(FULL_PATH . $config_name)) { $this->configFiles[] = $config_name; } $this->findConfigFiles($full_path, $level + 1); } } function includeConfigFiles($folderPath, $cache = true) { $data = false; $this->Application->refreshModuleInfo(); if ( $cache ) { if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) { $data = $this->Application->getCache( 'master:config_files', false, CacheSettings::$unitCacheRebuildTime ); } else { $data = $this->Application->getDBCache( 'config_files', CacheSettings::$unitCacheRebuildTime ); } } if ( $data ) { $this->configFiles = unserialize($data); if ( !(defined('DBG_VALIDATE_CONFIGS') && DBG_VALIDATE_CONFIGS) ) { shuffle($this->configFiles); } } else { /*if ( $this->Application->isDebugMode() ) { $this->Application->Debugger->profileStart('fcf'); }*/ $this->findConfigFiles(FULL_PATH . DIRECTORY_SEPARATOR . 'core'); // Search from core directory. $this->findConfigFiles($folderPath); // Search from modules directory. /*if ( $this->Application->isDebugMode() ) { $this->Application->Debugger->profileFinish( 'fcf', 'findConfigFiles [' . count($this->configFiles) . ']' ); }*/ if ( $cache ) { if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) { - $this->Application->setCache('master:config_files', serialize($this->configFiles)); + $this->Application->setCache('master:config_files', serialize($this->configFiles), 0); } else { - $this->Application->setDBCache('config_files', serialize($this->configFiles)); + $this->Application->setDBCache('config_files', serialize($this->configFiles), 0); } } } foreach ($this->configFiles as $filename) { $prefix = $this->PreloadConfigFile($filename); if (!$prefix) { throw new Exception('Prefix not defined in config file ' . $filename . ''); } } if ($cache) { unset($this->configFiles); } } /** * Process all read config files - called ONLY when there is no cache! * */ function ParseConfigs() { // 1. process normal configs $prioritized_configs = array(); foreach ($this->configData as $prefix => $config) { if (isset($config['ConfigPriority'])) { $prioritized_configs[$prefix] = $config['ConfigPriority']; continue; } $this->parseConfig($prefix); } foreach ($this->configData as $prefix => $config) { $this->postProcessConfig($prefix, 'AggregateConfigs', 'sub_prefix'); $clones = $this->postProcessConfig($prefix, 'Clones', 'prefix'); } // 2. process prioritized configs asort($prioritized_configs); foreach ($prioritized_configs as $prefix => $priority) { $this->parseConfig($prefix); } } function AfterConfigRead($store_cache = null) { // if (!$this->ProcessAllConfigs) return ; $this->FinalStage = true; foreach ($this->configData as $prefix => $config) { $this->runAfterConfigRead($prefix); } if ( !isset($store_cache) ) { // $store_cache not overridden -> use global setting $store_cache = $this->StoreCache; } if ($store_cache || (defined('IS_INSTALL') && IS_INSTALL)) { // cache is not stored during install, but dynamic clones should be processed in any case $this->processDynamicClones(); $this->retrieveCollections(); } if ($store_cache) { $this->_sortRewriteListeners(); $this->Application->HandleEvent(new kEvent('adm:OnAfterCacheRebuild')); $this->Application->cacheManager->UpdateUnitCache(); if (defined('DEBUG_MODE') && DEBUG_MODE && defined('DBG_VALIDATE_CONFIGS') && DBG_VALIDATE_CONFIGS) { // validate configs here to have changes from OnAfterConfigRead hooks to prefixes foreach ($this->configData as $prefix => $config) { if (!isset($config['TableName'])) continue; $this->ValidateConfig($prefix); } } } } /** * Sort rewrite listeners according to RewritePriority (non-prioritized listeners goes first) * */ function _sortRewriteListeners() { $listeners = Array (); $prioritized_listeners = Array (); // process non-prioritized listeners foreach ($this->Application->RewriteListeners as $prefix => $listener_data) { if ($listener_data['priority'] === false) { $listeners[$prefix] = $listener_data; } else { $prioritized_listeners[$prefix] = $listener_data['priority']; } } // process prioritized listeners asort($prioritized_listeners, SORT_NUMERIC); foreach ($prioritized_listeners as $prefix => $priority) { $listeners[$prefix] = $this->Application->RewriteListeners[$prefix]; } $this->Application->RewriteListeners = $listeners; } /** * Re-reads all configs * */ function ReReadConfigs() { // don't reset prefix file, since file scanning could slow down the process $prefix_files_backup = $this->prefixFiles; $this->Application->cacheManager->EmptyUnitCache(); $this->prefixFiles = $prefix_files_backup; // parse all configs $this->ProcessAllConfigs = true; $this->AfterConfigProcessed = Array (); $this->includeConfigFiles(MODULES_PATH, false); $this->ParseConfigs(); $this->AfterConfigRead(false); $this->processDynamicClones(); // don't call kUnitConfigReader::retrieveCollections since it // will overwrite what we already have in kApplication class instance } /** * Process clones, that were defined via OnAfterConfigRead event * */ function processDynamicClones() { $new_clones = Array(); foreach ($this->configData as $prefix => $config) { $clones = $this->postProcessConfig($prefix, 'Clones', 'prefix'); if ($clones) { $new_clones = array_merge($new_clones, $clones); } } // execute delayed methods for cloned unit configs $this->Application->cacheManager->applyDelayedUnitProcessing(); // call OnAfterConfigRead for cloned configs $new_clones = array_unique($new_clones); foreach ($new_clones as $prefix) { $this->runAfterConfigRead($prefix); } } /** * Process all collectible unit config options here to also catch ones, defined from OnAfterConfigRead events * */ function retrieveCollections() { foreach ($this->configData as $prefix => $config) { // collect replacement templates if (array_key_exists('ReplacementTemplates', $config) && $config['ReplacementTemplates']) { $this->Application->ReplacementTemplates = array_merge($this->Application->ReplacementTemplates, $config['ReplacementTemplates']); } // collect rewrite listeners if (array_key_exists('RewriteListener', $config) && $config['RewriteListener']) { $rewrite_listeners = $config['RewriteListener']; if (!is_array($rewrite_listeners)) { // when one method is used to build and parse url $rewrite_listeners = Array ($rewrite_listeners, $rewrite_listeners); } foreach ($rewrite_listeners as $index => $rewrite_listener) { if (strpos($rewrite_listener, ':') === false) { $rewrite_listeners[$index] = $prefix . '_EventHandler:' . $rewrite_listener; } } $rewrite_priority = array_key_exists('RewritePriority', $config) ? $config['RewritePriority'] : false; $this->Application->RewriteListeners[$prefix] = Array ('listener' => $rewrite_listeners, 'priority' => $rewrite_priority); } } } /** * Register nessasary classes * This method should only process the data which is cached! * * @param string $prefix * @access private */ function parseConfig($prefix) { $this->parseClasses($prefix); $this->parseScheduledTasks($prefix); $this->parseHooks($prefix); $this->parseAggregatedTags($prefix); } protected function parseClasses($prefix) { $config =& $this->configData[$prefix]; $register_classes = $this->getClasses($prefix); foreach ($register_classes as $class_info) { $this->Application->registerClass( $class_info['class'], $config['BasePath'] . DIRECTORY_SEPARATOR . $class_info['file'], $class_info['pseudo'] ); if ( isset($class_info['build_event']) && $class_info['build_event'] ) { $this->Application->delayUnitProcessing('registerBuildEvent', Array ($class_info['pseudo'], $class_info['build_event'])); } } } protected function parseScheduledTasks($prefix) { $config =& $this->configData[$prefix]; if ( !isset($config['ScheduledTasks']) || !$config['ScheduledTasks'] ) { return ; } $scheduled_tasks = $config['ScheduledTasks']; foreach ($scheduled_tasks as $short_name => $scheduled_task_info) { $event_status = array_key_exists('Status', $scheduled_task_info) ? $scheduled_task_info['Status'] : STATUS_ACTIVE; $this->Application->delayUnitProcessing('registerScheduledTask', Array ( $short_name, $config['Prefix'] . ':' . $scheduled_task_info['EventName'], $scheduled_task_info['RunSchedule'], $event_status )); } } protected function parseHooks($prefix) { $config =& $this->configData[$prefix]; if ( !isset($config['Hooks']) || !$config['Hooks'] ) { return ; } $hooks = $config['Hooks']; foreach ($hooks as $hook) { // Don't attempt to register a hook to a module isn't installed. if ( isset($hook['HookToModule']) && !isset($this->Application->ModuleInfo[$hook['HookToModule']]) ) { continue; } if ( isset($config['ParentPrefix']) && ($hook['HookToPrefix'] == $config['ParentPrefix']) ) { trigger_error('Depricated Hook Usage [prefix: ' . $config['Prefix'] . '; do_prefix: ' . $hook['DoPrefix'] . '] use #PARENT# as HookToPrefix value, where HookToPrefix is same as ParentPrefix', defined('E_USER_DEPRECATED') ? E_USER_DEPRECATED : E_USER_NOTICE); } if ($hook['HookToPrefix'] == '') { // new: set hooktoprefix to current prefix if not set $hook['HookToPrefix'] = $config['Prefix']; } if ( isset($config['ParentPrefix']) ) { // new: allow to set hook to parent prefix what ever it is if ($hook['HookToPrefix'] == '#PARENT#') { $hook['HookToPrefix'] = $config['ParentPrefix']; } if ($hook['DoPrefix'] == '#PARENT#') { $hook['DoPrefix'] = $config['ParentPrefix']; } } elseif ($hook['HookToPrefix'] == '#PARENT#' || $hook['DoPrefix'] == '#PARENT#') { // we need parent prefix but it's not set ! continue; } $hook_events = (array)$hook['HookToEvent']; $do_prefix = $hook['DoPrefix'] == '' ? $config['Prefix'] : $hook['DoPrefix']; foreach ($hook_events as $hook_event) { $hook_event = $hook['HookToPrefix'] . '.' . $hook['HookToSpecial'] . ':' . $hook_event; $do_event = $do_prefix . '.' . $hook['DoSpecial'] . ':' . $hook['DoEvent']; $this->Application->delayUnitProcessing('registerHook', Array ($hook_event, $do_event, $hook['Mode'], $hook['Conditional'])); } } } protected function parseAggregatedTags($prefix) { $config =& $this->configData[$prefix]; $aggregated_tags = isset($config['AggregateTags']) ? $config['AggregateTags'] : Array (); foreach ($aggregated_tags as $aggregate_tag) { if ( isset($config['ParentPrefix']) ) { if ($aggregate_tag['AggregateTo'] == $config['ParentPrefix']) { trigger_error('Depricated Aggregate Tag Usage [prefix: '.$config['Prefix'].'; AggregateTo: '.$aggregate_tag['AggregateTo'].'] use #PARENT# as AggregateTo value, where AggregateTo is same as ParentPrefix', defined('E_USER_DEPRECATED') ? E_USER_DEPRECATED : E_USER_NOTICE); } if ($aggregate_tag['AggregateTo'] == '#PARENT#') { $aggregate_tag['AggregateTo'] = $config['ParentPrefix']; } } $aggregate_tag['LocalPrefix'] = $config['Prefix']; $this->Application->delayUnitProcessing('registerAggregateTag', Array ($aggregate_tag)); } } function ValidateConfig($prefix) { global $debugger; $config =& $this->configData[$prefix]; $tablename = $config['TableName']; $float_types = Array ('float', 'double', 'numeric'); $table_found = $this->Conn->Query('SHOW TABLES LIKE "'.$tablename.'"'); if (!$table_found) { // config present, but table missing, strange kUtil::safeDefine('DBG_RAISE_ON_WARNINGS', 1); $debugger->appendHTML("Config Warning: Table $tablename missing, but prefix ".$config['Prefix']." requires it!"); $debugger->WarningCount++; return ; } $res = $this->Conn->Query('DESCRIBE '.$tablename); $config_link = $debugger->getFileLink(FULL_PATH.$this->prefixFiles[$config['Prefix']], 1, $config['Prefix']); $error_messages = Array ( 'field_not_found' => 'Field %s exists in the database, but is not defined in config', 'default_missing' => 'Default value for field %s not set in config', 'not_null_error1' => 'Field %s is NOT NULL in the database, but is not configured as not_null', // or required', 'not_null_error2' => 'Field %s is described as NOT NULL in config, but does not have DEFAULT value', 'not_null_error3' => 'Field %s is described as NOT NULL in config, but is NULL in db', 'invalid_default' => 'Default value for field %s%s not sync. to db (in config = %s, in db = %s)', 'date_column_not_null_error' => 'Field %s must be NULL in config and database, since it contains date', 'user_column_default_error' => 'Field %s must be have NULL as default value, since it holds user id', 'type_missing' => 'Type definition for field %s missing in config', 'virtual_type_missing' => 'Type definition for virtual field %s missing in config', 'virtual_default_missing' => 'Default value for virtual field %s not set in config', 'virtual_not_null_error' => 'Virtual field %s cannot be not null, since it doesn\'t exist in database', 'invalid_calculated_field' => 'Calculated field %s is missing corresponding virtual field', ); $config_errors = Array (); $tablename = preg_replace('/^'.preg_quote(TABLE_PREFIX, '/').'(.*)/', '\\1', $tablename); // remove table prefix // validate unit config field declaration in relation to database table structure foreach ($res as $field) { $f_name = $field['Field']; if (getArrayValue($config, 'Fields')) { if (preg_match('/l[\d]+_[\w]/', $f_name)) { // skip multilingual fields continue; } if (!array_key_exists ($f_name, $config['Fields'])) { $config_errors[] = sprintf($error_messages['field_not_found'], $f_name); } else { $db_default = $field['Default']; if (is_numeric($db_default)) { $db_default = preg_match('/[\.,]/', $db_default) ? (float)$db_default : (int)$db_default; } $default_missing = false; $options = $config['Fields'][$f_name]; $not_null = isset($options['not_null']) && $options['not_null']; $formatter = array_key_exists('formatter', $options) ? $options['formatter'] : false; if (!array_key_exists('default', $options)) { $config_errors[] = sprintf($error_messages['default_missing'], $f_name); $default_missing = true; } if ($field['Null'] != 'YES') { // field is NOT NULL in database (MySQL5 for null returns "NO", but MySQL4 returns "") if ( $f_name != $config['IDField'] && !isset($options['not_null']) /*&& !isset($options['required'])*/ ) { $config_errors[] = sprintf($error_messages['not_null_error1'], $f_name); } if ($not_null && !isset($options['default']) ) { $config_errors[] = sprintf($error_messages['not_null_error2'], $f_name); } } elseif ($not_null) { $config_errors[] = sprintf($error_messages['not_null_error3'], $f_name); } if (($formatter == 'kDateFormatter') && $not_null) { $config_errors[] = sprintf($error_messages['date_column_not_null_error'], $f_name); } // columns, holding userid should have NULL as default value if (array_key_exists('type', $options) && !$default_missing) { // both type and default value set if (preg_match('/ById$/', $f_name) && $options['default'] !== null) { $config_errors[] = sprintf($error_messages['user_column_default_error'], $f_name); } } if (!array_key_exists('type', $options)) { $config_errors[] = sprintf($error_messages['type_missing'], $f_name); } if (!$default_missing && ($field['Type'] != 'text')) { if ( is_null($db_default) && $not_null ) { $db_default = $options['type'] == 'string' ? '' : 0; } if ($f_name == $config['IDField'] && $options['type'] != 'string' && $options['default'] !== 0) { $config_errors[] = sprintf($error_messages['invalid_default'], 'IDField ', $f_name, $this->varDump($options['default']), $this->varDump($field['Default'])); } else if (((string)$options['default'] != '#NOW#') && ($db_default !== $options['default']) && !in_array($options['type'], $float_types)) { $config_errors[] = sprintf($error_messages['invalid_default'], '', $f_name, $this->varDump($options['default']), $this->varDump($db_default)); } } } } } // validate virtual fields if ( array_key_exists('VirtualFields', $config) ) { foreach ($config['VirtualFields'] as $f_name => $options) { if (!array_key_exists('type', $options)) { $config_errors[] = sprintf($error_messages['virtual_type_missing'], $f_name); } if (array_key_exists('not_null', $options)) { $config_errors[] = sprintf($error_messages['virtual_not_null_error'], $f_name); } if (!array_key_exists('default', $options)) { $config_errors[] = sprintf($error_messages['virtual_default_missing'], $f_name); } } } // validate calculated fields if ( array_key_exists('CalculatedFields', $config) ) { foreach ($config['CalculatedFields'] as $special => $calculated_fields) { foreach ($calculated_fields as $calculated_field => $calculated_field_expr) { if ( !isset($config['VirtualFields'][$calculated_field]) ) { $config_errors[] = sprintf($error_messages['invalid_calculated_field'], $calculated_field); } } } $config_errors = array_unique($config_errors); } if ($config_errors) { $error_prefix = 'Config Error'.(count($config_errors) > 1 ? 's' : '').': for prefix '.$config_link.' ('.$tablename.') in unit config:
'; $config_errors = $error_prefix.'   '.implode('
   ', $config_errors); kUtil::safeDefine('DBG_RAISE_ON_WARNINGS', 1); $debugger->appendHTML($config_errors); $debugger->WarningCount++; } } function varDump($value) { return ''.var_export($value, true).' of '.gettype($value); } function postProcessConfig($prefix, $config_key, $dst_prefix_var) { $main_config =& $this->configData[$prefix]; $sub_configs = isset($main_config[$config_key]) && $main_config[$config_key] ? $main_config[$config_key] : Array (); if ( !$sub_configs ) { return Array (); } unset($main_config[$config_key]); $processed = array(); foreach ($sub_configs as $sub_prefix => $sub_config) { if ($config_key == 'AggregateConfigs' && !isset($this->configData[$sub_prefix])) { $this->loadConfig($sub_prefix); } $sub_config['Prefix'] = $sub_prefix; $this->configData[$sub_prefix] = kUtil::array_merge_recursive($this->configData[$$dst_prefix_var], $sub_config); // when merging empty array to non-empty results non-empty array, but empty is required foreach ($sub_config as $sub_key => $sub_value) { if (!$sub_value) { unset($this->configData[$sub_prefix][$sub_key]); } } if ($config_key == 'Clones') { $this->prefixFiles[$sub_prefix] = $this->prefixFiles[$prefix]; } $this->postProcessConfig($sub_prefix, $config_key, $dst_prefix_var); if ($config_key == 'AggregateConfigs') { $processed = array_merge($this->postProcessConfig($sub_prefix, 'Clones', 'prefix'), $processed); } elseif ($this->ProcessAllConfigs) { $this->parseConfig($sub_prefix); } array_push($processed, $sub_prefix); } if (!$prefix) { // configs, that used only for cloning & not used ifself unset($this->configData[$prefix]); } return array_unique($processed); } function PreloadConfigFile($filename) { $config_found = file_exists(FULL_PATH . $filename) && $this->configAllowed($filename); if (defined('DEBUG_MODE') && DEBUG_MODE && defined('DBG_PROFILE_INCLUDES') && DBG_PROFILE_INCLUDES) { if ( in_array($filename, get_included_files()) ) { return ''; } global $debugger; if ($config_found) { $file = FULL_PATH . $filename; $file_crc = crc32($file); $debugger->ProfileStart('inc_' . $file_crc, $file); include_once($file); $debugger->ProfileFinish('inc_' . $file_crc); $debugger->profilerAddTotal('includes', 'inc_' . $file_crc); } } elseif ($config_found) { include_once(FULL_PATH . $filename); } if ($config_found) { if (isset($config) && $config) { // config file is included for 1st time -> save it's content for future processing $prefix = array_key_exists('Prefix', $config) ? $config['Prefix'] : ''; preg_match($this->_moduleFolderRegExp, $filename, $rets); $config['ModuleFolder'] = str_replace(DIRECTORY_SEPARATOR, '/', $rets[1]); $config['BasePath'] = dirname(FULL_PATH . $filename); if (array_key_exists('AdminTemplatePath', $config)) { // append template base folder for admin templates path of this prefix $module_templates = $rets[1] == 'core' ? '' : substr($rets[1], 8) . '/'; $config['AdminTemplatePath'] = $module_templates . $config['AdminTemplatePath']; } if (array_key_exists($prefix, $this->prefixFiles) && ($this->prefixFiles[$prefix] != $filename)) { trigger_error( 'Single unit config prefix "' . $prefix . '" ' . 'is used in multiple unit config files: ' . '"' . $this->prefixFiles[$prefix] . '", "' . $filename . '"', E_USER_WARNING ); } $this->configData[$prefix] = $config; $this->prefixFiles[$prefix] = $filename; return $prefix; } else { $prefix = array_search($filename, $this->prefixFiles); if ( $prefix ) { // attempt is made to include config file twice or more, but include_once prevents that, // but file exists on hdd, then it is already saved to all required arrays, just return it's prefix return $prefix; } } } return 'dummy'; } function loadConfig($prefix) { if ( !isset($this->prefixFiles[$prefix]) ) { throw new Exception('Configuration file for prefix "' . $prefix . '" is unknown'); return ; } $file = $this->prefixFiles[$prefix]; $prefix = $this->PreloadConfigFile($file); if ( $this->FinalStage || $prefix == 'm' ) { // Run prefix OnAfterConfigRead so all hooks to it can define their clones. // Allow hooks to modify "m:QueryString" before URL parsing is started during warm start. $this->runAfterConfigRead($prefix); } $clones = $this->postProcessConfig($prefix, 'AggregateConfigs', 'sub_prefix'); $clones = array_merge($this->postProcessConfig($prefix, 'Clones', 'prefix'), $clones); if ($this->FinalStage) { $clones = array_unique($clones); foreach ($clones as $a_prefix) { $this->runAfterConfigRead($a_prefix); } } } function runAfterConfigRead($prefix) { if (in_array($prefix, $this->AfterConfigProcessed)) { return ; } $this->Application->HandleEvent( new kEvent($prefix . ':OnAfterConfigRead') ); if (!(defined('IS_INSTALL') && IS_INSTALL)) { // allow to call OnAfterConfigRead multiple times during install array_push($this->AfterConfigProcessed, $prefix); } } /** * Reads unit (specified by $prefix) * option specified by $option * * @param string $prefix * @param string $name * @param mixed $default * @return string * @access public */ function getUnitOption($prefix, $name, $default = false) { if (preg_match('/(.*)\.(.*)/', $prefix, $rets)) { if (!isset($this->configData[$rets[1]])) { $this->loadConfig($rets[1]); } $ret = isset($this->configData[$rets[1]][$name][$rets[2]]) ? $this->configData[$rets[1]][$name][$rets[2]] : false; // $ret = getArrayValue($this->configData, $rets[1], $name, $rets[2]); } else { if (!isset($this->configData[$prefix])) { $this->loadConfig($prefix); } $ret = isset($this->configData[$prefix][$name]) ? $this->configData[$prefix][$name] : false; // $ret = getArrayValue($this->configData, $prefix, $name); } return $ret === false ? $default : $ret; } /** * Read all unit with $prefix options * * @param string $prefix * @return Array * @access public */ function getUnitOptions($prefix) { if (!isset($this->configData[$prefix])) { $this->loadConfig($prefix); } return $this->configData[$prefix]; } /** * Set's new unit option value * * @param string $prefix * @param string $name * @param string $value * @access public */ function setUnitOption($prefix, $name, $value) { if ( preg_match('/(.*)\.(.*)/', $prefix, $rets) ) { if ( !isset($this->configData[$rets[1]]) ) { $this->loadConfig($rets[1]); } $this->configData[$rets[1]][$name][$rets[2]] = $value; } else { if ( !isset($this->configData[$prefix]) ) { $this->loadConfig($prefix); } $this->configData[$prefix][$name] = $value; } } protected function getClasses($prefix) { $config =& $this->configData[$prefix]; $class_params = Array ('ItemClass', 'ListClass', 'EventHandlerClass', 'TagProcessorClass'); $register_classes = isset($config['RegisterClasses']) ? $config['RegisterClasses'] : Array (); foreach ($class_params as $param_name) { if ( !isset($config[$param_name]) ) { continue; } $config[$param_name]['pseudo'] = $this->getPseudoByOptionName($param_name, $prefix); $register_classes[] = $config[$param_name]; } return $register_classes; } protected function getPseudoByOptionName($option_name, $prefix) { $pseudo_class_map = Array ( 'ItemClass' => '%s', 'ListClass' => '%s_List', 'EventHandlerClass' => '%s_EventHandler', 'TagProcessorClass' => '%s_TagProcessor' ); return sprintf($pseudo_class_map[$option_name], $prefix); } /** * Get's config file name based * on folder name supplied * * @param string $folderPath * @return string * @access private */ function getConfigName($folderPath) { return $folderPath . DIRECTORY_SEPARATOR . basename($folderPath) . '_config.php'; } /** * Checks if config file is allowed for includion (if module of config is installed) * * @param string $config_path relative path from in-portal directory */ function configAllowed($config_path) { static $module_paths = null; if (defined('IS_INSTALL') && IS_INSTALL) { // at installation start no modules in db and kernel configs could not be read return true; } if (preg_match('#^' . $this->_directorySeparator . 'core#', $config_path)) { // always allow to include configs from "core" module's folder return true; } if (!$this->Application->ModuleInfo) { return false; } if (!isset($module_paths)) { $module_paths = Array (); foreach ($this->Application->ModuleInfo as $module_name => $module_info) { $module_paths[] = str_replace('/', DIRECTORY_SEPARATOR, rtrim($module_info['Path'], '/')); } $module_paths = array_unique($module_paths); } preg_match($this->_moduleFolderRegExp, $config_path, $rets); // config file path starts with module folder path return in_array($rets[1], $module_paths); } /** * Returns true if config exists and is allowed for reading * * @param string $prefix * @return bool */ function prefixRegistred($prefix) { return isset($this->prefixFiles[$prefix]) ? true : false; } /** * Returns config file for given prefix * * @param string $prefix * @return string */ function getPrefixFile($prefix) { return array_key_exists($prefix, $this->prefixFiles) ? $this->prefixFiles[$prefix] : false; } function iterateConfigs($callback_function, $params) { $this->includeConfigFiles(MODULES_PATH); //make sure to re-read all configs $this->AfterConfigRead(); foreach ($this->configData as $prefix => $config_data) { call_user_func_array( $callback_function, array($prefix, &$config_data, $params) ); } } } Index: branches/5.2.x/core/kernel/nparser/nparser.php =================================================================== --- branches/5.2.x/core/kernel/nparser/nparser.php (revision 16771) +++ branches/5.2.x/core/kernel/nparser/nparser.php (revision 16772) @@ -1,1247 +1,1252 @@ _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 Clear() { // Discard any half-parsed content (e.g. from nested RenderElements). $keep_buffering_levels = kUtil::constOn('SKIP_OUT_COMPRESSION') ? 1 : 2; while ( ob_get_level() > $keep_buffering_levels ) { ob_end_clean(); } $this->Stack = array(); $this->Level = 0; $this->Buffers = array(); $this->InsideComment = false; $this->SkipComments = true; $this->Params = array(); $this->ParamsStack = array(); $this->ParamsLevel = 0; $this->Definitions = ''; $this->Elements = array(); $this->ElementLocations = array(); $this->DataExists = false; $this->TemplateName = null; $this->TempalteFullPath = null; $this->CachePointers = array(); $this->Cachable = array(); $this->CacheLevel = 0; $this->FullCachePage = false; $this->PrefixesInUse = array(); $this->Captures = array(); } function Compile($pre_parsed, $template_name = 'unknown') { $data = file_get_contents($pre_parsed['tname']); if (!$this->CompileRaw($data, $pre_parsed['tname'], $template_name)) { // compilation failed during errors in template // trigger_error('Template "' . $template_name . '" not compiled because of errors', E_USER_WARNING); return false; } // saving compiled version (only when compilation was successful) $this->Application->TemplatesCache->saveTemplate($pre_parsed['fname'], $this->Buffers[0]); return true; } function Parse($raw_template, $name = null) { $this->CompileRaw($raw_template, $name); ob_start(); $_parser =& $this; eval('?'.'>'.$this->Buffers[0]); return ob_get_clean(); } function CompileRaw($data, $t_name, $template_name = 'unknown') { $code = "extract (\$_parser->Params);\n"; $code .= "\$_parser->ElementLocations['{$template_name}'] = Array('template' => '{$template_name}', 'start_pos' => 0, 'end_pos' => " . strlen($data) . ");\n"; // $code .= "__@@__DefinitionsMarker__@@__\n"; // $code .= "if (!\$this->CacheStart('".abs(crc32($t_name))."_0')) {\n"; $this->Buffers[0] = '\n"; $this->Cacheable[0] = true; $this->Definitions = ''; // finding all the tags $reg = '(.*?)(<[\\/]?)' . TAG_NAMESPACE . '([^>]*?)([\\/]?>)(\r\n){0,1}'; preg_match_all('/'.$reg.'/s', $data, $results, PREG_SET_ORDER + PREG_OFFSET_CAPTURE); $this->InsideComment = false; foreach ($results as $tag_data) { $tag = array( 'opening' => $tag_data[2][0], 'tag' => $tag_data[3][0], 'closing' => $tag_data[4][0], 'line' => substr_count(substr($data, 0, $tag_data[2][1]), "\n")+1, 'pos' => $tag_data[2][1], 'file' => $t_name, 'template' => $template_name, ); // the idea is to count number of comment openings and closings before current tag // if the numbers do not match we inverse the status of InsideComment if ($this->SkipComments && (substr_count($tag_data[1][0], ''))) { $this->InsideComment = !$this->InsideComment; } // appending any text/html data found before tag $this->Buffers[$this->Level] .= $tag_data[1][0]; if (!$this->InsideComment) { $tmp_tag = $this->Application->CurrentNTag; $this->Application->CurrentNTag = $tag; if ($this->ProcessTag($tag) === false) { $this->Application->CurrentNTag = $tmp_tag; return false; } $this->Application->CurrentNTag = $tmp_tag; } else { $this->Buffers[$this->Level] .= $tag_data[2][0] . TAG_NAMESPACE . $tag_data[3][0] . $tag_data[4][0]; } } if ($this->Level > 0) { $error_tag = Array ( 'file' => $this->Stack[$this->Level]->Tag['file'], 'line' => $this->Stack[$this->Level]->Tag['line'], ); throw new ParserException('Unclosed tag opened by ' . $this->TagInfo($this->Stack[$this->Level]->Tag), 0, null, $error_tag); return false; } // appending text data after last tag (after its closing pos), // if no tag was found at all ($tag_data is not set) - append the whole $data $this->Buffers[$this->Level] .= isset($tag_data) ? substr($data, $tag_data[4][1]+strlen($tag_data[4][0])) : $data; $this->Buffers[$this->Level] = preg_replace('//s', '', $this->Buffers[$this->Level]); // remove hidden comments IB#23065 // $this->Buffers[$this->Level] .= 'CacheEnd();\n}\n"." ?".">\n"; // $this->Buffers[$this->Level] = str_replace('__@@__DefinitionsMarker__@@__', $this->Definitions, $this->Buffers[$this->Level]); return true; } function SplitParamsStr($params_str) { preg_match_all('/([\${}a-zA-Z0-9_.\\-\\\\#\\[\\]]+)=(["\']{1,1})(.*?)(? $val){ $values[$val[1]] = str_replace('\\' . $val[2], $val[2], $val[3]); } return $values; } function SplitTag($tag) { if (!preg_match('/([^_ \t\r\n]*)[_]?([^ \t\r\n]*)[ \t\r\n]*(.*)$$/s', $tag['tag'], $parts)) { // this is virtually impossible, but just in case throw new ParserException('Incorrect tag format: ' . $tag['tag'], 0, null, $tag); return false; } $splited['prefix'] = $parts[2] ? $parts[1] : '__auto__'; $splited['name'] = $parts[2] ? $parts[2] : $parts[1]; $splited['attrs'] = $parts[3]; return $splited; } function ProcessTag($tag) { $splited = $this->SplitTag($tag); if ($splited === false) { return false; } $tag = array_merge($tag, $splited); $tag['processed'] = false; $tag['NP'] = $this->SplitParamsStr($tag['attrs']); $o = ''; $tag['is_closing'] = $tag['opening'] == ''; if (class_exists('_Tag_'.$tag['name'])) { // block tags should have special handling class if ($tag['opening'] == '<') { $class = '_Tag_'.$tag['name']; /** @var _BlockTag $instance */ $instance = new $class($tag); $instance->Parser =& $this; $this->Stack[++$this->Level] =& $instance; $this->Buffers[$this->Level] = ''; $this->Cachable[$this->Level] = true; $open_code = $instance->Open($tag); if ($open_code === false) { return false; } $o .= $open_code; } if ($tag['is_closing']) { // not ELSE here, because tag may be and still has a handler-class if ($this->Level == 0) { $dump = array(); foreach ($this->Stack as $instance) { $dump[] = $instance->Tag; } if ( $this->Application->isDebugMode() ) { $this->Application->Debugger->dumpVars($dump); } $error_msg = 'Closing tag without an opening: ' . $this->TagInfo($tag) . ' - probably opening tag was removed or nested tags error'; throw new ParserException($error_msg, 0, null, $tag); return false; } if ($this->Stack[$this->Level]->Tag['name'] != $tag['name']) { $opening_tag = $this->Stack[$this->Level]->Tag; $error_msg = ' Closing tag ' . $this->TagInfo($tag) . ' does not match opening tag at current nesting level (' . $this->TagInfo($opening_tag) . ' opened at line ' . $opening_tag['line'] . ')'; throw new ParserException($error_msg, 0, null, $tag); return false; } $o .= $this->Stack[$this->Level]->Close($tag); // DO NOT use $this->Level-- here because it's used inside Close $this->Level--; } } else { // regular tags - just compile if (!$tag['is_closing']) { $error_msg = 'Tag without a handler: ' . $this->TagInfo($tag) . ' - probably missing <empty /> tag closing'; throw new ParserException($error_msg, 0, null, $tag); return false; } if ($this->Level > 0) $o .= $this->Stack[$this->Level]->PassThrough($tag); if (!$tag['processed']) { $compiled = $this->CompileTag($tag); if ($compiled === false) return false; if (isset($tag['NP']['cachable']) && (!$tag['NP']['cachable'] || $tag['NP']['cachable'] == 'false')) { $this->Cachable[$this->Level] = false; } $o .= '\n"; // $o .= 'BreakCache($compiled, $this->GetPointer($tag)) : $compiled; // $o .= " ?".">\n"; } } $this->Buffers[$this->Level] .= $o; return true; } function GetPointer($tag) { return abs(crc32($tag['file'])).'_'.$tag['line']; } function BreakCache($code, $pointer, $condition='') { return "\$_parser->CacheEnd();\n}\n" . $code."\nif ( !\$_parser->CacheStart('{$pointer}'" . ($condition ? ", {$condition}" : '') . ") ) {\n"; } function TagInfo($tag, $with_params=false) { return "{$tag['prefix']}_{$tag['name']}".($with_params ? ' '.$tag['attrs'] : '').""; } function CompileParamsArray($arr) { $to_pass = 'Array('; foreach ($arr as $name => $val) { $to_pass .= '"'.$name.'" => "'.str_replace('"', '\"', $val).'",'; } $to_pass .= ')'; return $to_pass; } function CompileTag($tag) { $code = ''; $to_pass = $this->CompileParamsArray($tag['NP']); if ($tag['prefix'] == '__auto__') { $prefix = $this->GetParam('PrefixSpecial'); $code .= '$_p_ =& $_parser->GetProcessor($PrefixSpecial);'."\n"; $code .= 'echo $_p_->ProcessParsedTag("'.$tag['name'].'", '.$to_pass.', "$PrefixSpecial", \''.$tag['file'].'\', '.$tag['line'].');'."\n"; } else { $prefix = $tag['prefix']; $code .= '$_p_ =& $_parser->GetProcessor("'.$tag['prefix'].'");'."\n"; $code .= 'echo $_p_->ProcessParsedTag("'.$tag['name'].'", '.$to_pass.', "'.$tag['prefix'].'", \''.$tag['file'].'\', '.$tag['line'].');'."\n"; } if (array_key_exists('result_to_var', $tag['NP']) && $tag['NP']['result_to_var']) { $code .= "\$params['{$tag['NP']['result_to_var']}'] = \$_parser->GetParam('{$tag['NP']['result_to_var']}');\n"; $code .= "\${$tag['NP']['result_to_var']} = \$params['{$tag['NP']['result_to_var']}'];\n"; } if ($prefix && strpos($prefix, '$') === false) { $p =& $this->GetProcessor($prefix); if (!is_object($p) || !$p->CheckTag($tag['name'], $tag['prefix'])) { $error_msg = 'Unknown tag: ' . $this->TagInfo($tag) . ' - incorrect tag name or prefix'; throw new ParserException($error_msg, 0, null, $tag); return false; } } return $code; } function CheckTemplate($t, $silent = null) { $pre_parsed = $this->Application->TemplatesCache->GetPreParsed($t); if (!$pre_parsed) { if (!$silent) { throw new ParserException('Cannot include "' . $t . '" - file does not exist'); } return false; } $force_compile = defined('DBG_NPARSER_FORCE_COMPILE') && DBG_NPARSER_FORCE_COMPILE; if (!$pre_parsed || !$pre_parsed['active'] || $force_compile) { $inc_parser = new NParser(); if ($force_compile) { // remove Front-End theme markings during total compilation $t = preg_replace('/^theme:.*?\//', '', $t); } if (!$inc_parser->Compile($pre_parsed, $t)) { return false; } } return $pre_parsed; } function Run($t, $silent = null) { if ((strpos($t, '../') !== false) || (trim($t) !== $t)) { // when relative paths or special chars are found template names from url, then it's hacking attempt return false; } $pre_parsed = $this->CheckTemplate($t, $silent); if (!$pre_parsed) { return false; } $backup_template = $this->TemplateName; $backup_fullpath = $this->TempalteFullPath; $this->TemplateName = $t; $this->TempalteFullPath = $pre_parsed['tname']; if (!isset($backup_template) && $this->CachingEnabled && !$this->UserLoggedIn && !EDITING_MODE) { // this is main page template -> check for page-based aggressive caching settings $output =& $this->RunMainPage($pre_parsed); } else { $output =& $this->Application->TemplatesCache->runTemplate($this, $pre_parsed); } $this->TemplateName = $backup_template; $this->TempalteFullPath = $backup_fullpath; return $output; } function &RunMainPage($pre_parsed) { /** @var kDBItem $page */ $page = $this->Application->recallObject('st.-virtual'); 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 = ' . $this->Conn->qstr($page_cache_key) . ' WHERE ' . $page->IDField . ' = ' . $page->GetID(); $this->Conn->Query($sql); } } /** * Creates tag processor and stores it in local cache + factory * * @param string $prefix * @return kTagProcessor */ function &GetProcessor($prefix) { static $processors = Array (); if ( !isset($processors[$prefix]) ) { $processors[$prefix] = $this->Application->recallObject($prefix . '_TagProcessor'); } return $processors[$prefix]; } /** * Not tag. Method for parameter selection from list in this TagProcessor * * @param Array $params * @param Array $possible_names * * @return string * @access protected */ protected function SelectParam($params, $possible_names) { if ( !is_array($params) ) { return ''; } if ( !is_array($possible_names) ) { $possible_names = explode(',', $possible_names); } foreach ($possible_names as $name) { if ( isset($params[$name]) ) { return $params[$name]; } } return ''; } function SetParams($params) { $this->Params = $params; $keys = array_keys($this->Params); } function GetParam($name) { return isset($this->Params[$name]) ? $this->Params[$name] : false; } function SetParam($name, $value) { $this->Params[$name] = $value; } function PushParams($params) { $this->ParamsStack[$this->ParamsLevel++] = $this->Params; $this->Params = $params; } function PopParams() { $this->Params = $this->ParamsStack[--$this->ParamsLevel]; } function ParseBlock($params, $pass_params=false) { if (array_key_exists('cache_timeout', $params) && $params['cache_timeout']) { $ret = $this->getCache( $this->FormCacheKey('element_' . $params['name']) ); if ($ret) { return $ret; } } if (substr($params['name'], 0, 5) == 'html:') { return substr($params['name'], 5); } if (!array_key_exists($params['name'], $this->Elements) && array_key_exists('default_element', $params)) { // when given element not found, but default element name given, then render it instead $params['name'] = $params['default_element']; unset($params['default_element']); return $this->ParseBlock($params, $pass_params); } $original_params = $params; if ($pass_params || isset($params['pass_params'])) $params = array_merge($this->Params, $params); $this->PushParams($params); $data_exists_bak = $this->DataExists; // if we are parsing design block and we have block_no_data - we need to wrap block_no_data into design, // so we should set DataExists to true manually, otherwise the design block will be skipped because of data_exists in params (by Kostja) // // keep_data_exists is used by block RenderElement (always added in ntags.php), to keep the DataExists value // from inside-content block, otherwise when parsing the design block DataExists will be reset to false resulting missing design block (by Kostja) // // Inside-content block parsing result is given to design block in "content" parameter (ntags.php) and "keep_data_exists" // is only passed, when parsing design block. In case, when $this->DataExists is set to true, but // zero-length content (in 2 cases: method NParser::CheckNoData set it OR really empty block content) // is returned from inside-content block, then design block also should not be shown (by Alex) $this->DataExists = (isset($params['keep_data_exists']) && isset($params['content']) && $params['content'] != '' && $this->DataExists) || (isset($params['design']) && isset($params['block_no_data']) && $params['name'] == $params['design']); if (!array_key_exists($params['name'], $this->Elements)) { $pre_parsed = $this->Application->TemplatesCache->GetPreParsed($params['name']); if ($pre_parsed) { $ret = $this->IncludeTemplate($params); if (array_key_exists('no_editing', $params) && $params['no_editing']) { // when individual render element don't want to be edited return $ret; } return defined('EDITING_MODE') ? $this->DecorateBlock($ret, $params, true) : $ret; } $trace_results = debug_backtrace(); $error_tag = Array ( 'file' => $trace_results[0]['file'], 'line' => $trace_results[0]['line'], ); $error_msg = 'Rendering of undefined element ' . $params['name'] . ''; throw new ParserException($error_msg, 0, null, $error_tag); return false; } $m_processor =& $this->GetProcessor('m'); $flag_values = $m_processor->PreparePostProcess($params); /** @var Closure $f_name */ $f_name = $this->Elements[$params['name']]; $ret = $f_name($this, $params); $ret = $m_processor->PostProcess($ret, $flag_values); $block_params = $this->Params; // input parameters, but modified inside rendered block $this->PopParams(); if (array_key_exists('result_to_var', $flag_values) && $flag_values['result_to_var']) { // when "result_to_var" used inside ParseBlock, then $$result_to_var parameter is set inside ParseBlock, // but not outside it as expected and got lost at all after PopParams is called, so make it work by // setting it's value on current parameter deep level (from where ParseBlock was called) $this->SetParam($flag_values['result_to_var'], $block_params[ $flag_values['result_to_var'] ]); } $this->CheckNoData($ret, $params); $this->DataExists = $data_exists_bak || $this->DataExists; if (array_key_exists('cache_timeout', $original_params) && $original_params['cache_timeout']) { $cache_key = $this->FormCacheKey('element_' . $original_params['name']); $this->setCache($cache_key, $ret, (int)$original_params['cache_timeout']); } if (array_key_exists('no_editing', $block_params) && $block_params['no_editing']) { // when individual render element don't want to be edited return $ret; } return defined('EDITING_MODE') ? $this->DecorateBlock($ret, $params) : $ret; } /** * Checks, that given block is defined * * @param string $name * @return bool */ function blockFound($name) { return array_key_exists($name, $this->Elements); } function DecorateBlock($block_content, $block_params, $is_template = false) { static $used_ids = Array (), $base_url = null; if (!isset($base_url)) { $base_url = $this->Application->BaseURL(); } // $prepend = '[name: ' . $block_params['name'] . '] [params: ' . implode(', ', array_keys($block_params)) . ']'; $decorate = false; $design = false; if (EDITING_MODE == EDITING_MODE_DESIGN) { $decorate = true; if ($is_template) { // content inside pair RenderElement tag } else { if (strpos($block_params['name'], '__capture_') === 0) { // capture tag (usually inside pair RenderElement) $decorate = false; } elseif (array_key_exists('content', $block_params)) { // pair RenderElement (on template, were it's used) $design = true; } } } if (!$decorate) { return $block_content; } /*else { $block_content = $prepend . $block_content; }*/ $block_name = $block_params['name']; $function_name = $is_template ? $block_name : $this->Elements[$block_name]; $block_title = ''; if (array_key_exists($function_name, $this->Application->Parser->ElementLocations)) { $element_location = $this->Application->Parser->ElementLocations[$function_name]; $block_title .= $element_location['template'] . '.tpl'; $block_title .= ' (' . $element_location['start_pos'] . ' - ' . $element_location['end_pos'] . ')'; } // ensure unique id for every div (used from print lists) $container_num = 1; $container_id = 'parser_block[' . $function_name . ']'; while (in_array($container_id . '_' . $container_num, $used_ids)) { $container_num++; } $container_id .= '_' . $container_num; $used_ids[] = $container_id; // prepare parameter string $param_string = $block_name . ':' . $function_name; if ($design) { $btn_text = $this->_btnPhrases['design']; $btn_class = 'cms-edit-design-btn'; $btn_container_class = 'block-edit-design-btn-container'; $btn_name = 'design'; } else { $btn_text = $this->_btnPhrases['block']; $btn_class = 'cms-edit-block-btn'; $btn_container_class = 'block-edit-block-btn-container'; $btn_name = 'content'; } $icon_url = $base_url . 'core/admin_templates/img/top_frame/icons/' . $btn_name . '_mode.png'; $block_editor = '
%s
'; // 1 - text before, 2 - open tag, 3 - open tag attributes, 4 - content inside tag, 5 - closing tag, 6 - text after closing tag if (preg_match('/^(\s*)<(td|span)(.*?)>(.*)<\/(td|span)>(.*)$/is', $block_content, $regs)) { // div inside span -> put div outside span return $regs[1] . '<' . $regs[2] . ' ' . $regs[3] . '>' . str_replace('%s', $regs[4], $block_editor) . '' . $regs[6]; } return str_replace('%s', $block_content, $block_editor); } function IncludeTemplate($params, $silent=null) { $t = is_array($params) ? $this->SelectParam($params, 't,template,block,name') : $params; $cache_timeout = array_key_exists('cache_timeout', $params) ? $params['cache_timeout'] : false; if ($cache_timeout) { $cache_key = $this->FormCacheKey('template:' . $t); $ret = $this->getCache($cache_key); if ($ret !== false) { return $ret; } } $t = preg_replace('/\.tpl$/', '', $t); $data_exists_bak = $this->DataExists; $this->DataExists = false; if (!isset($silent) && array_key_exists('is_silent', $params)) { $silent = $params['is_silent']; } if (isset($params['pass_params'])) { // ability to pass params from block to template $params = array_merge($this->Params, $params); } $m_processor =& $this->GetProcessor('m'); $flag_values = $m_processor->PreparePostProcess($params); $this->PushParams($params); $ret = $this->Run($t, $silent); $this->PopParams(); $ret = $m_processor->PostProcess($ret, $flag_values); $this->CheckNoData($ret, $params); $this->DataExists = $data_exists_bak || $this->DataExists; if ($cache_timeout) { $this->setCache($cache_key, $ret, (int)$cache_timeout); } return $ret; } function CheckNoData(&$ret, $params) { if (array_key_exists('data_exists', $params) && $params['data_exists'] && !$this->DataExists) { $block_no_data = isset($params['BlockNoData']) ? $params['BlockNoData'] : (isset($params['block_no_data']) ? $params['block_no_data'] : false); if ($block_no_data) { $ret = $this->ParseBlock(array('name'=>$block_no_data)); } else { $ret = ''; } } } function getCache($name) { if (!$this->CachingEnabled) { return false; } $ret = $this->Application->getCache($name, false); if (preg_match('/^\[DE_MARK:(.*?)\]$/', substr($ret, -11), $regs)) { $this->DataExists = $regs[1] ? true : false; $ret = substr($ret, 0, -11); } return $ret; } - function setCache($name, $value, $expiration = 0) + function setCache($name, $value, $expiration = null) { if (!$this->CachingEnabled) { return false; } + // Don't allow creating a non-expiring caches from a template. + if ( (int)$expiration <= 0 ) { + $expiration = null; + } + // remeber DataExists in cache, because after cache will be restored // it will not be available naturally (no tags, that set it will be called) $value .= '[DE_MARK:' . (int)$this->DataExists . ']'; return $this->Application->setCache($name, $value, $expiration); } function FormCacheKey($element, $key_string = '') { if (strpos($key_string, 'guest_only') !== false && $this->UserLoggedIn) { // don't cache, when user is logged-in "guest_only" is specified in key return ''; } $parts = Array (); // 1. replace INLINE variable (from request) into key parts if (preg_match_all('/\(%(.*?)\)/', $key_string, $regs)) { // parts in form "(%variable_name)" were found foreach ($regs[1] as $variable_name) { $variable_value = $this->Application->GetVar($variable_name); $key_string = str_replace('(%' . $variable_name . ')', $variable_value, $key_string); } } // 2. replace INLINE serial numbers (they may not be related to any prefix at all) // Serial number also could be composed of inline variables! if (preg_match_all('/\[%(.*?)%\]/', $key_string, $regs)) { // format "[%LangSerial%]" - prefix-wide serial in case of any change in "lang" prefix // format "[%LangIDSerial:5%]" - one id-wide serial in case of data, associated with given id was changed // format "[%CiIDSerial:ItemResourceId:5%]" - foreign key-based serial in case of data, associated with given foreign key was changed foreach ($regs[1] as $serial_name) { $serial_value = $this->Application->getCache('[%' . $serial_name . '%]'); $key_string = str_replace('[%' . $serial_name . '%]', '[%' . $serial_name . '=' . $serial_value . '%]', $key_string); } } /* Always add: =========== * "var:m_lang" - show content on current language * "var:t" - template from url, used to differ multiple pages using same physical template (like as design) * "var:admin,editing_mode" - differ cached content when different editing modes are used * "var:m_cat_id,m_cat_page" - pass current category * "var:page,per_page,sort_by" - list pagination/sorting parameters * "prefix:theme-file" - to be able to reset all cached templated using "Rebuild Theme Files" function * "prefix:phrases" - use latest phrase translations * "prefix:conf" - output could slighly differ based on configuration settings */ $key_string = rtrim('var:m_lang,t,admin,editing_mode,m_cat_id,m_cat_page,page,per_page,sort_by;prefix:theme-file,phrases,conf;' . $key_string, ';'); $keys = explode(';', $key_string); /* Possible parts of a $key_string (all can have multiple occurencies): ==================================================================== * prefix:[,,] - 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 { throw new ParserException('Unknown key part "' . $key . '" used in "key" parameter of tag'); } } // 5. add unique given cache key identifier on this page $parts[] = $element; $key = implode(':', $parts); if (defined('DEBUG_MODE') && DEBUG_MODE && $this->Application->isDebugMode()) { $this->Application->Debugger->appendHTML('Parser Key: ' . $key); } return 'parser_' . crc32($key); } function PushPointer($pointer, $key) { $cache_key = $this->FullCachePage || !$this->CachingEnabled ? '' : $this->FormCacheKey('pointer:' . $pointer, $key); $this->CachePointers[++$this->CacheLevel] = $cache_key; return $this->CachePointers[$this->CacheLevel]; } function PopPointer() { return $this->CachePointers[$this->CacheLevel--]; } function CacheStart($pointer, $key) { $pointer = $this->PushPointer($pointer, $key); if ($pointer) { $ret = $this->getCache($pointer); $debug_mode = defined('DEBUG_MODE') && DEBUG_MODE && $this->Application->isDebugMode(); if ($ret !== false) { echo $debug_mode ? '' . $ret . '' : $ret; $this->PopPointer(); return true; } if ($debug_mode) { echo ''; } } ob_start(); return false; } function CacheEnd($expiration = 0) { $ret = ob_get_clean(); $pointer = $this->PopPointer(); if ($pointer) { $res = $this->setCache($pointer, $ret, $expiration); if (defined('DEBUG_MODE') && DEBUG_MODE && $this->Application->isDebugMode()) { echo ''; } } echo $ret; } /** * Performs compression of given files or text * * @param mixed $data * @param bool $raw_script * @param string $file_extension * @return string */ function CompressScript($data, $raw_script = false, $file_extension = '') { /** @var MinifyHelper $minify_helper */ $minify_helper = $this->Application->recallObject('MinifyHelper'); if ($raw_script) { $minify_helper->compressString($data, $file_extension); return $data; } return $minify_helper->CompressScriptTag($data); } } class ParserException extends Exception { public function __construct($message = null, $code = 0, $previous = null, $tag = null) { parent::__construct($message, $code, $previous); if ( isset($tag) ) { $this->file = $tag['file']; $this->line = $tag['line']; } } } Index: branches/5.2.x/core/units/helpers/category_helper.php =================================================================== --- branches/5.2.x/core/units/helpers/category_helper.php (revision 16771) +++ branches/5.2.x/core/units/helpers/category_helper.php (revision 16772) @@ -1,361 +1,361 @@ Application->findModule('Name', $params['module']); } // Get module by category path. if ( $category_path ) { foreach ( array_reverse($category_path) as $module_category_id ) { $module_info = $this->Application->findModule('RootCat', $module_category_id); if ( $module_info && $module_info['Var'] != 'adm' ) { return $module_info; } } } return false; } /** * Converts multi-dimensional category structure in one-dimensional option array (category_id=>category_name) * * @param Array $data * @param int $parent_category_id * @param int $language_id * @param int $theme_id * @param int $level * @return Array * @access protected */ protected function _printChildren(&$data, $parent_category_id, $language_id, $theme_id, $level = 0) { if ( $data['ThemeId'] != $theme_id && $data['ThemeId'] != 0 ) { // don't show system templates from different themes return Array (); } $category_language = $data['l' . $language_id . '_Name'] ? $language_id : $this->_primaryLanguageId; $ret = Array ($parent_category_id => str_repeat('—', $level) . ' ' . $data['l' . $category_language . '_Name']); if ( $data['children'] ) { $level++; foreach ($data['children'] as $category_id => $category_data) { // numeric keys $ret = kUtil::array_merge_recursive($ret, $this->_printChildren($data['children'][$category_id], $category_id, $language_id, $theme_id, $level)); } } return $ret; } /** * Returns information about children under parent path (recursive) * * @param int $parent_category_id * @param Array $languages * @return Array * @access protected */ protected function _getChildren($parent_category_id, $languages) { static $items_by_parent = null, $parent_mapping = null; if ( !isset($items_by_parent) ) { $fields = $items_by_parent = Array (); foreach ($languages as $language_id) { $fields[] = 'l' . $language_id . '_Name'; } $sql = 'SELECT CategoryId AS id, ' . implode(', ', $fields) . ', ParentId, Template, ThemeId FROM ' . TABLE_PREFIX . 'Categories ORDER BY Priority DESC'; $items = $this->Conn->Query($sql, 'id'); foreach ($items as $item_id => $item_data) { $item_parent_id = $item_data['ParentId']; unset($item_data['ParentId']); if ( !array_key_exists($item_parent_id, $items_by_parent) ) { $items_by_parent[$item_parent_id] = Array (); } $item_data['children'] = false; $parent_mapping[$item_id] = $item_parent_id; $items_by_parent[$item_parent_id][$item_id] = $item_data; } $base_category = $this->Application->getBaseCategory(); // "Content" category if ( isset($items_by_parent[$base_category]) ) { $index_category = $this->findIndexCategoryId($items_by_parent[$base_category]); // rename "Content" into "Home" keeping it's ID $items_by_parent[$parent_mapping[$base_category]][$base_category]['l1_Name'] = $this->Application->Phrase('la_rootcategory_name'); if ( $index_category !== false ) { // remove category of "index.tpl" template unset($items_by_parent[$base_category][$index_category]); unset($parent_mapping[$index_category]); } } } $data = $items_by_parent[$parent_mapping[$parent_category_id]][$parent_category_id]; $categories = array_key_exists($parent_category_id, $items_by_parent) ? $items_by_parent[$parent_category_id] : Array (); foreach ($categories as $category_id => $category_data) { if ( $category_id == $parent_category_id ) { // don't process myself - prevents recursion continue; } $data['children'][$category_id] = $this->_getChildren($category_id, $languages); } return $data; } /** * Finds "Home" category among given top level categories * * @param Array $top_categories * @return bool|int * @access protected */ protected function findIndexCategoryId($top_categories) { foreach ($top_categories as $category_id => $category_info) { if ($category_info['Template'] == 'index') { return $category_id; } } return false; } /** * Generates OR retrieves from cache structure tree * * @return Array * @access protected */ protected function &_getStructureTree() { // get cached version of structure tree if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) { $data = $this->Application->getCache('master:StructureTree', false, CacheSettings::$structureTreeRebuildTime); } else { $data = $this->Application->getDBCache('StructureTree', CacheSettings::$structureTreeRebuildTime); } if ( $data ) { $data = unserialize($data); return $data; } // generate structure tree from scratch /** @var kMultiLanguageHelper $ml_helper */ $ml_helper = $this->Application->recallObject('kMultiLanguageHelper'); $languages = $ml_helper->getLanguages(); $root_category = $this->Application->getBaseCategory(); $data = $this->_getChildren($root_category, $languages); if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) { - $this->Application->setCache('master:StructureTree', serialize($data)); + $this->Application->setCache('master:StructureTree', serialize($data), 0); } else { - $this->Application->setDBCache('StructureTree', serialize($data)); + $this->Application->setDBCache('StructureTree', serialize($data), 0); } return $data; } /** * Returns template mapping (between physical and virtual pages) * * @return Array * @access public */ public function getTemplateMapping() { if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) { $data = $this->Application->getCache('master:template_mapping', false, CacheSettings::$templateMappingRebuildTime); } else { $data = $this->Application->getDBCache('template_mapping', CacheSettings::$templateMappingRebuildTime); } if ( $data ) { return unserialize($data); } $sql = 'SELECT IF(c.`Type` = ' . PAGE_TYPE_TEMPLATE . ', CONCAT(c.Template, ":", c.ThemeId), CONCAT("id:", c.CategoryId)) AS SrcTemplate, LOWER( IF( c.SymLinkCategoryId IS NOT NULL, (SELECT cc.NamedParentPath FROM ' . TABLE_PREFIX . 'Categories AS cc WHERE cc.CategoryId = c.SymLinkCategoryId), c.NamedParentPath ) ) AS DstTemplate, c.UseExternalUrl, c.ExternalUrl FROM ' . TABLE_PREFIX . 'Categories 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)); + $data = $this->Application->setCache('master:template_mapping', serialize($mapping), 0); } else { - $this->Application->setDBCache('template_mapping', serialize($mapping)); + $this->Application->setDBCache('template_mapping', serialize($mapping), 0); } return $mapping; } /** * Returns category structure as field option list * * @return Array * @access public */ public function getStructureTreeAsOptions() { 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 (); } if ( isset($this->_structureTree) ) { return $this->_structureTree; } /** @var kThemesHelper $themes_helper */ $themes_helper = $this->Application->recallObject('ThemesHelper'); $data = $this->_getStructureTree(); $theme_id = (int)$themes_helper->getCurrentThemeId(); $this->_primaryLanguageId = $this->Application->GetDefaultLanguageId(); $this->_structureTree = $this->_printChildren($data, $data['id'], $this->Application->GetVar('m_lang'), $theme_id); return $this->_structureTree; } /** * Replace links like "@@ID@@" to actual template names in given text * * @param string $text * @return string * @access public */ public function replacePageIds($text) { if ( !preg_match_all('/@@(\\d+)@@/', $text, $regs) ) { return $text; } $page_ids = $regs[1]; $sql = 'SELECT NamedParentPath, CategoryId FROM ' . TABLE_PREFIX . 'Categories WHERE CategoryId IN (' . implode(',', $page_ids) . ')'; $templates = $this->Conn->GetCol($sql, 'CategoryId'); $base_category = $this->Application->getBaseCategory(); if ( isset($templates[$base_category]) ) { // "Content" category will act as "Home Page" $templates[$base_category] .= '/Index'; } foreach ($page_ids as $page_id) { if ( !isset($templates[$page_id]) ) { // internal page was deleted, but link to it was found in given content block data continue; } $url_params = Array ('m_cat_id' => $page_id == $base_category ? 0 : $page_id, 'pass' => 'm'); $page_url = $this->Application->HREF(strtolower($templates[$page_id]), '', $url_params); /*if ($this->Application->isAdmin) { $page_url = preg_replace('/&(admin|editing_mode)=[\d]/', '', $page_url); }*/ $text = str_replace('@@' . $page_id . '@@', $page_url, $text); } return $text; } } Index: branches/5.2.x/core/units/helpers/menu_helper.php =================================================================== --- branches/5.2.x/core/units/helpers/menu_helper.php (revision 16771) +++ branches/5.2.x/core/units/helpers/menu_helper.php (revision 16772) @@ -1,412 +1,412 @@ _prepareMenu(); $cat = $this->_getCategoryId($params); $parent_path = array_key_exists($cat, $this->parentPaths) ? $this->parentPaths[$cat] : ''; $parent_path = str_replace($root_path, '', $parent_path); // menu starts from module path $levels = explode('|', trim($parent_path, '|')); if ( $levels[0] === '' ) { $levels = Array (); } if ( array_key_exists('level', $params) && $params['level'] > count($levels) ) { // current level is deeper, then requested level return ''; } $level = max(array_key_exists('level', $params) ? $params['level'] - 1 : count($levels) - 1, 0); $parent = array_key_exists($level, $levels) ? $levels[$level] : 0; $cur_menu =& $menu; $menu_path = array_slice($levels, 0, $level + 1); foreach ($menu_path as $elem) { $cur_menu =& $cur_menu['c' . $elem]['sub_items']; } $block_params = $this->prepareTagParams($prefix_special, $params); $block_params['name'] = $params['render_as']; $this->Application->SetVar('cur_parent_path', $parent_path); $real_cat_id = $this->Application->GetVar('m_cat_id'); if ( !is_array($cur_menu) || !$cur_menu ) { // no menus on this level return ''; } $ret = ''; $cur_item = 1; $cur_menu = $this->_removeNonMenuItems($cur_menu); $block_params['total_items'] = count($cur_menu); foreach ($cur_menu as $page) { $block_params = array_merge($block_params, $this->_prepareMenuItem($page, $real_cat_id, $root_path)); $block_params['is_last'] = $cur_item == $block_params['total_items']; $block_params['is_first'] = $cur_item == 1; $ret .= $this->Application->ParseBlock($block_params); $cur_item++; } $this->Application->SetVar('m_cat_id', $real_cat_id); return $ret; } /** * Builds cached menu version * * @return Array * @access protected */ protected function _prepareMenu() { static $root_cat = NULL, $root_path = NULL; if ( !$root_cat ) { $root_cat = $this->Application->getBaseCategory(); $cache_key = 'parent_paths[%CIDSerial:' . $root_cat . '%]'; $root_path = $this->Application->getCache($cache_key); if ( $root_path === false ) { $this->Conn->nextQueryCachable = true; $sql = 'SELECT ParentPath FROM ' . TABLE_PREFIX . 'Categories WHERE CategoryId = ' . $root_cat; $root_path = $this->Conn->GetOne($sql); $this->Application->setCache($cache_key, $root_path); } } if ( !$this->Menu ) { if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) { $menu = $this->Application->getCache('master:cms_menu', false, CacheSettings::$cmsMenuRebuildTime); } else { $menu = $this->Application->getDBCache('cms_menu', CacheSettings::$cmsMenuRebuildTime); } if ( $menu ) { $menu = unserialize($menu); $this->parentPaths = $menu['parentPaths']; } else { $menu = $this->_buildMenuStructure($root_cat); $menu['parentPaths'] = $this->parentPaths; if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) { - $this->Application->setCache('master:cms_menu', serialize($menu)); + $this->Application->setCache('master:cms_menu', serialize($menu), 0); } else { - $this->Application->setDBCache('cms_menu', serialize($menu)); + $this->Application->setDBCache('cms_menu', serialize($menu), 0); } } unset($menu['parentPaths']); $this->Menu = $menu; } return Array ($this->Menu, $root_path); } /** * Returns category id based tag parameters * * @param Array $params * @return int */ function _getCategoryId($params) { $cat = isset($params['category_id']) && $params['category_id'] != '' ? $params['category_id'] : $this->Application->GetVar('m_cat_id'); if ( "$cat" == 'parent' ) { /** @var kDBItem $this_category */ $this_category = $this->Application->recallObject('c'); $cat = $this_category->GetDBField('ParentId'); } elseif ( $cat == 0 ) { $cat = $this->Application->getBaseCategory(); } return $cat; } /** * Prepares cms menu item block parameters * * @param Array $page * @param int $real_cat_id * @param string $root_path * @return Array * @access protected */ protected function _prepareMenuItem($page, $real_cat_id, $root_path) { static $language_id = NULL, $primary_language_id = NULL, $template = NULL; if ( !isset($language_id) ) { $language_id = $this->Application->GetVar('m_lang'); $primary_language_id = $this->Application->GetDefaultLanguageId(); $template = $this->Application->GetVar('t'); } $active = $category_active = false; $title = $page['l' . $language_id . '_ItemName'] ? $page['l' . $language_id . '_ItemName'] : $page['l' . $primary_language_id . '_ItemName']; if ( $page['ItemType'] == 'cat' ) { if ( array_key_exists($real_cat_id, $this->parentPaths) ) { $active = strpos($this->parentPaths[$real_cat_id], $page['ParentPath']) !== false; } elseif ( $page['ItemPath'] == $template ) { // physical template in menu $active = true; } $category_active = $page['CategoryId'] == $real_cat_id; } /*if ( $page['ItemType'] == 'cat_index' ) { $check_path = str_replace($root_path, '', $page['ParentPath']); $active = strpos($parent_path, $check_path) !== false; } if ( $page['ItemType'] == 'page' ) { $active = $page['ItemPath'] == preg_replace('/^Content\//i', '', $this->Application->GetVar('t')); }*/ if ( substr($page['ItemPath'], 0, 3) == 'id:' ) { // resolve ID path here, since it can be used directly without m_Link tag (that usually resolves it) $page['ItemPath'] = $this->Application->getVirtualPageTemplate(substr($page['ItemPath'], 3)); } $block_params = Array ( 'title' => $title, 'template' => $page['ItemPath'], 'active' => $active, 'category_active' => $category_active, // new 'parent_path' => $page['ParentPath'], 'parent_id' => $page['ParentId'], 'cat_id' => $page['CategoryId'], 'item_type' => $page['ItemType'], 'page_id' => $page['ItemId'], 'use_section' => ($page['Type'] == PAGE_TYPE_TEMPLATE) && ($page['ItemPath'] != 'index'), 'has_sub_menu' => isset($page['sub_items']) && count($this->_removeNonMenuItems($page['sub_items'])) > 0, 'external_url' => $page['UseExternalUrl'] ? $page['ExternalUrl'] : false, // for backward compatibility 'menu_icon' => $page['UseMenuIconUrl'] ? $page['MenuIconUrl'] : false, ); return $block_params; } /** * Returns only items, that are visible in menu * * @param Array $menu * @return Array * @access protected */ protected function _removeNonMenuItems($menu) { $theme_id = $this->Application->GetVar('m_theme'); foreach ($menu as $menu_index => $menu_item) { // $menu_index is in "cN" format, where N is category id if ( !$menu_item['IsMenu'] || $menu_item['Status'] != STATUS_ACTIVE || ($menu_item['ThemeId'] != $theme_id && $menu_item['ThemeId'] != 0) ) { // don't show sections, that are not from menu OR system templates from other themes unset($menu[$menu_index]); } } return $menu; } /** * Builds cache of all menu items and their parent categories * * @param int $top_category_id * @return Array * @access protected */ protected function _buildMenuStructure($top_category_id) { // 1. get parent paths of leaf categories, that are in menu (across all themes) $sql = 'SELECT ParentPath, CategoryId FROM ' . $this->Application->getUnitOption('c', 'TableName') . ' WHERE IsMenu = 1 AND Status = ' . STATUS_ACTIVE; $this->parentPaths = $this->Conn->GetCol($sql ,'CategoryId'); // 2. figure out parent paths of all categories in path to leaf categories foreach ($this->parentPaths as $leaf_parent_path) { $parent_categories = explode('|', substr($leaf_parent_path, 1, -1)); foreach ($parent_categories as $index => $parent_category_id) { if ( !isset($this->parentPaths[$parent_category_id]) ) { $parent_path = array_slice($parent_categories, 0, $index + 1); $this->parentPaths[$parent_category_id] = '|' . implode('|', $parent_path) . '|'; } } } return $this->_altBuildMenuStructure($top_category_id, implode(',', array_keys($this->parentPaths))); } /** * Builds cache for children of given category (no matter, what menu status is) * * @param int $parent_category_id * @param string $category_limit * @return Array * @access protected */ protected function _altBuildMenuStructure($parent_category_id, $category_limit = NULL) { // Sub-categories from current category $items = $this->_getSubCategories($parent_category_id, $category_limit); // sort menu items uasort($items, Array (&$this, '_menuSort')); // process sub-menus of each menu foreach ($items as $key => $menu_item) { if ( $menu_item['CategoryId'] == $parent_category_id ) { // don't process myself - prevents recursion continue; } $sub_items = $this->_altBuildMenuStructure($menu_item['CategoryId'], $category_limit); if ( $sub_items ) { $items[$key]['sub_items'] = $sub_items; } } return $items; } /** * Returns given category sub-categories * * @param int $parent_id * @param string $category_limit * @return Array * @access protected */ protected function _getSubCategories($parent_id, $category_limit = NULL) { static $items_by_parent = NULL, $lang_part = NULL; if ( !isset($lang_part) ) { /** @var kMultiLanguageHelper $ml_helper */ $ml_helper = $this->Application->recallObject('kMultiLanguageHelper'); $lang_part = ''; $languages = $ml_helper->getLanguages(); foreach ($languages as $language_id) { $lang_part .= 'c.l' . $language_id . '_MenuTitle AS l' . $language_id . '_ItemName,' . "\n"; } } if ( !isset($items_by_parent) ) { $items_by_parent = Array (); // Sub-categories from current category $sql = 'SELECT c.CategoryId AS CategoryId, CONCAT(\'c\', c.CategoryId) AS ItemId, c.Priority AS ItemPriority, ' . $lang_part . ' IF(c.`Type` = ' . PAGE_TYPE_TEMPLATE . ', c.Template, CONCAT("id:", c.CategoryId)) AS ItemPath, c.ParentPath AS ParentPath, c.ParentId As ParentId, \'cat\' AS ItemType, c.IsMenu, c.Type, c.ThemeId, c.UseExternalUrl, c.ExternalUrl, c.UseMenuIconUrl, c.MenuIconUrl, c.Status FROM ' . TABLE_PREFIX . 'Categories AS c'; if ( isset($category_limit) && $category_limit ) { $sql .= ' WHERE c.CategoryId IN (' . $category_limit . ')'; } $items = $this->Conn->Query($sql, 'ItemId'); foreach ($items as $item_id => $item_data) { $item_parent_id = $item_data['ParentId']; if ( !array_key_exists($item_parent_id, $items_by_parent) ) { $items_by_parent[$item_parent_id] = Array (); } $items_by_parent[$item_parent_id][$item_id] = $item_data; } } return array_key_exists($parent_id, $items_by_parent) ? $items_by_parent[$parent_id] : Array (); } /** * Method for sorting pages by priority in descending order * * @param Array $a * @param Array $b * @return int */ function _menuSort($a, $b) { if ( $a['ItemPriority'] == $b['ItemPriority'] ) { return 0; } return ($a['ItemPriority'] < $b['ItemPriority']) ? 1 : -1; // descending } } Index: branches/5.2.x/core/units/helpers/site_helper.php =================================================================== --- branches/5.2.x/core/units/helpers/site_helper.php (revision 16771) +++ branches/5.2.x/core/units/helpers/site_helper.php (revision 16772) @@ -1,147 +1,147 @@ Application->siteDomainField($country_prefix . 'Country'); if ($iso_format && !$country) { $country = 'USA'; } if (!$iso_format && strlen($country)) { return $this->getCountryId($country); } return $country; } /** * Returns country id based on it's ISO code * * @param string $iso_code * @return int */ function getCountryId($iso_code) { static $cache = null; if (!isset($cache)) { $sql = 'SELECT CountryStateId, IsoCode FROM ' . $this->Application->getUnitOption('country-state', 'TableName') . ' WHERE Type = ' . DESTINATION_TYPE_COUNTRY; $cache = $this->Conn->GetCol($sql, 'IsoCode'); } return $cache[$iso_code]; } /** * Gets site domains from cache * * @return Array */ function getSiteDomains() { static $cache = null; if ( !isset($cache) ) { if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) { $cache = $this->Application->getCache('master:domains_parsed', false, CacheSettings::$domainsParsedRebuildTime); } else { $cache = $this->Application->getDBCache('domains_parsed', CacheSettings::$domainsParsedRebuildTime); } if ($cache) { $cache = unserialize($cache); } else { $sql = 'SELECT * FROM ' . TABLE_PREFIX . 'SiteDomains ORDER BY Priority DESC'; $cache = $this->Conn->Query($sql, 'DomainId'); if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { - $this->Application->setCache('master:domains_parsed', serialize($cache)); + $this->Application->setCache('master:domains_parsed', serialize($cache), 0); } else { - $this->Application->setDBCache('domains_parsed', serialize($cache)); + $this->Application->setDBCache('domains_parsed', serialize($cache), 0); } } } return $cache; } /** * Try to match visited domain to any of existing * * @param string $field * @param string $value * @return int */ function getDomainByName($field, $value) { $site_domains = $this->getSiteDomains(); $name_fields = Array ('DomainName', 'SSLUrl'); foreach ($site_domains as $id => $site_domain) { if ( in_array($field, $name_fields) ) { if ( !$site_domain[$field . 'UsesRegExp'] ) { // not regular expression -> escape manually $site_domain[$field] = preg_quote($site_domain[$field], '/'); } if ( $site_domain[$field] && preg_match('/^' . $site_domain[$field] . ($field == 'DomainName' ? '$' : '') . '/', $value) ) { return $id; } } elseif ( $site_domain[$field] == $value ) { return $id; } } return false; } /** * Try to match domain settings based on visitor's IP address * * @return int */ function getDomainByIP() { $site_domains = $this->getSiteDomains(); foreach ($site_domains as $id => $site_domain) { if (kUtil::ipMatch($site_domain['DomainIPRange'], "\n")) { return $id; } } return false; } } Index: branches/5.2.x/core/units/helpers/sections_helper.php =================================================================== --- branches/5.2.x/core/units/helpers/sections_helper.php (revision 16771) +++ branches/5.2.x/core/units/helpers/sections_helper.php (revision 16772) @@ -1,381 +1,381 @@ debugMode = $this->Application->isDebugMode(); $this->superAdminMode = $this->Application->RecallVar('super_admin'); } /** * Set's prefix and special * * @param string $prefix * @param string $special * @access public */ public function Init($prefix, $special) { parent::Init($prefix, $special); $this->BuildTree(); } /** * Builds xml for tree in left frame in admin * * @return void * @access public */ public function BuildTree() { if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) { $data = $this->Application->getCache('master:sections_parsed', false, CacheSettings::$sectionsParsedRebuildTime); } else { $data = $this->Application->getDBCache('sections_parsed', CacheSettings::$sectionsParsedRebuildTime); } if ( $data ) { $this->Tree = unserialize($data); return ; } if ( !defined('IS_INSTALL') || !IS_INSTALL ) { // don't reread all configs during install, because they are reread on every install step $this->Application->UnitConfigReader->ReReadConfigs(); } $this->Tree = Array (); // 1. build base tree (don't update parent with children list yet) // 1.1. process prefixes without priority $prioritized_prefixes = Array (); $prefixes = array_keys($this->Application->UnitConfigReader->configData); foreach ($prefixes as $prefix) { $config =& $this->Application->UnitConfigReader->configData[$prefix]; if ( array_key_exists('ConfigPriority', $config) ) { $prioritized_prefixes[$prefix] = $config['ConfigPriority']; continue; } $this->_processPrefixSections($prefix); } // 2. process prefixes with priority asort($prioritized_prefixes); foreach ($prioritized_prefixes as $prefix => $priority) { $this->_processPrefixSections($prefix); } // 2. apply section adjustments foreach ($prefixes as $prefix) { $config =& $this->Application->UnitConfigReader->configData[$prefix]; /** @var Array $section_adjustments */ $section_adjustments = getArrayValue($config, 'SectionAdjustments'); if ( !$section_adjustments ) { continue; } foreach ($section_adjustments as $section_name => $adjustment_params) { if ( is_array($adjustment_params) ) { if ( !array_key_exists($section_name, $this->Tree) ) { // don't process adjustments for non-existing sections continue; } $this->Tree[$section_name] = kUtil::array_merge_recursive($this->Tree[$section_name], $adjustment_params); } else { // then remove section unset($this->Tree[$section_name]); } } } // 3. foreach ($this->Tree as $section_name => $section_params) { // 3.1. update parent -> children references $parent_section = $section_params['parent']; $section_order = "{$section_params['priority']}"; if ( !isset($parent_section) ) { // don't process parent section of "in-portal:root" section continue; } if ( !array_key_exists('children', $this->Tree[$parent_section]) ) { $this->Tree[$parent_section]['children'] = Array (); } if ( array_key_exists($section_order, $this->Tree[$parent_section]['children']) ) { trigger_error('Section "' . $section_name . '" has replaced section "' . $this->Tree[$parent_section]['children'][$section_order] . '" (parent section: "' . $parent_section . '"; duplicate priority: ' . $section_order . ')', E_USER_WARNING); } $this->Tree[$parent_section]['children'][$section_order] = $section_name; if ( $section_params['type'] == stTAB ) { // if this is tab, then mark parent section as TabOnly $this->Tree[$parent_section]['tabs_only'] = true; } // 3.2. process icons here, because they also can be adjusted if ( isset($section_params['icon']) && preg_match('/([^:]+):(.*)/', $section_params['icon'], $regs) ) { $this->Tree[$section_name]['icon'] = $regs[2]; $this->Tree[$section_name]['icon_module'] = $regs[1]; // set "icon_module" used in "combined_header" block $module_folder = trim($this->Application->findModule('Name', $regs[1], 'Path'), '/'); if ( $module_folder == '' ) { $module_folder = 'core'; } } else { $module_folder = $this->Application->getUnitOption($section_params['SectionPrefix'], 'ModuleFolder'); if ( !array_key_exists('icon_module', $section_params) ) { // set "icon_module" used in "combined_header" block $this->Tree[$section_name]['icon_module'] = $this->Application->findModule('Path', $module_folder . '/', 'Name'); } } // this is to display HELP icon instead of missing one.. can be replaced with some other icon to draw attention $icon_file = $module_folder . '/admin_templates/img/icons/icon24_' . $this->Tree[$section_name]['icon']; /*$core_file = FULL_PATH.'/core/admin_templates/img/icons/icon24_' . $this->Tree[$section_name]['icon'].'.png'; if ($module_folder != 'core' && file_exists($core_file) && file_exists(FULL_PATH.'/'.$icon_file.'.png')) { if (crc32(file_get_contents($core_file)) == crc32(file_get_contents(FULL_PATH.'/'.$icon_file.'.png'))) { trigger_error('Section "' . $section_name . '" uses icon copy from "Core" module', E_USER_NOTICE); } }*/ if ( !file_exists(FULL_PATH . '/' . $icon_file . '.png') ) { $this->Tree[$section_name]['icon'] = 'help'; $this->Tree[$section_name]['icon_module'] = 'core'; } } $this->Application->HandleEvent(new kEvent('adm:OnAfterBuildTree')); if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) { - $this->Application->setCache('master:sections_parsed', serialize($this->Tree)); + $this->Application->setCache('master:sections_parsed', serialize($this->Tree), 0); } else { - $this->Application->setDBCache('sections_parsed', serialize($this->Tree)); + $this->Application->setDBCache('sections_parsed', serialize($this->Tree), 0); } } function _processPrefixSections($prefix) { $config =& $this->Application->UnitConfigReader->configData[$prefix]; /** @var Array $sections */ $sections = getArrayValue($config, 'Sections'); if ( !$sections ) { return ; } foreach ($sections as $section_name => $section_params) { // we could also skip not allowed sections here in future if ( isset($section_params['SectionPrefix']) ) { $section_prefix = $section_params['SectionPrefix']; } elseif ( $this->Application->getUnitOption($prefix, 'SectionPrefix') ) { $section_prefix = $this->Application->getUnitOption($prefix, 'SectionPrefix'); } else { $section_prefix = $prefix; } if ( is_float($section_params['priority']) ) { $section_params['priority'] = (string)$section_params['priority']; } $section_params['SectionPrefix'] = $section_prefix; $section_params['url']['m_opener'] = 'r'; $section_params['url']['no_pass_through'] = 1; $pass_section = getArrayValue($section_params, 'url', 'pass_section'); if ( $pass_section ) { unset($section_params['url']['pass_section']); $section_params['url']['section'] = $section_name; if ( !isset($section_params['url']['module']) ) { $module_name = $this->Application->findModule('Path', $config['ModuleFolder'] . '/', 'Name'); $section_params['url']['module'] = $module_name; } } if ( !isset($section_params['url']['t']) ) { $section_params['url']['t'] = 'index'; } if ( !isset($section_params['onclick']) ) { $section_params['onclick'] = 'checkEditMode()'; } if ( !isset($section_params['container']) ) { $section_params['container'] = 0; // for js tree printing to xml } $current_data = isset($this->Tree[$section_name]) ? $this->Tree[$section_name] : Array (); if ( $current_data ) { trigger_error('Section "' . $section_name . '" declaration (originally defined in "' . $current_data['SectionPrefix'] . '") was overwriten from "' . $prefix . '"', E_USER_WARNING); } $this->Tree[$section_name] = kUtil::array_merge_recursive($current_data, $section_params); } } /** * Returns details information about section * * @param string $section_name * @return Array */ function &getSectionData($section_name) { if (isset($this->Tree[$section_name])) { $ret =& $this->Tree[$section_name]; } else { $ret = Array(); } return $ret; } /** * Returns first child, that is not a folder * * @param string $section_name * @param bool $check_permission * @return string * @access public */ public function getFirstChild($section_name, $check_permission = false) { $section_data =& $this->getSectionData($section_name); /** @var Array $children */ $children = isset($section_data['children']) && $section_data['children'] ? $section_data['children'] : false; if ( $children ) { // get 1st child ksort($children, SORT_NUMERIC); foreach ($children as $child_section) { if ( !$this->sectionVisible($child_section, $check_permission) ) { continue; } break; } return $this->getFirstChild($child_section, $check_permission); } return $section_name; } /** * Checks if given section is visible by it's definition and optionally by user permission * * @param string $section_name * @param bool $check_permission * @return bool */ function sectionVisible($section_name, $check_permission = false) { $section_data =& $this->getSectionData($section_name); if (isset($section_data['show_mode']) && is_numeric($section_data['show_mode'])) { $show_mode = $section_data['show_mode']; // if super admin section -> show in super admin mode & debug mode $show_section = $show_mode == smNORMAL || ((($show_mode & smSUPER_ADMIN) == smSUPER_ADMIN) && ($this->superAdminMode || $this->debugMode)); if (!$show_section) { // if section is in debug mode only && debug mode -> show $show_section = (($show_mode & smDEBUG) == smDEBUG) && $this->debugMode; } if (!$show_section) { // visibility by section definition return false; } } // visibility by section permission if ($check_permission) { $perm_section = $this->getPermSection($section_name); return $this->Application->CheckPermission($perm_section.'.view'); } return true; } /** * Returns section for permission checking based on given section * * @param string $section_name * @return string */ function getPermSection($section_name) { $ret = $section_name; $section_data =& $this->getSectionData($section_name); if ($section_data && isset($section_data['perm_prefix'])) { // this section uses other section permissions $ret = $this->Application->getUnitOption($section_data['perm_prefix'].'.main', 'PermSection'); } return $ret; } - } \ No newline at end of file + } Index: branches/5.2.x/core/install/step_templates/sys_config.tpl =================================================================== --- branches/5.2.x/core/install/step_templates/sys_config.tpl (revision 16771) +++ branches/5.2.x/core/install/step_templates/sys_config.tpl (revision 16772) @@ -1,77 +1,78 @@ Array ('type' => 'text', 'title' => 'Web Path to Installation', 'section' => 'Misc', 'required' => 1), 'WriteablePath' => Array ('type' => 'text', 'title' => 'Path to Writable folder', 'section' => 'Misc', 'required' => 1), 'RestrictedPath' => Array ('type' => 'text', 'title' => 'Path to Restricted folder', 'section' => 'Misc', 'required' => 1), 'AdminDirectory' => Array ('type' => 'text', 'title' => 'Path to Admin folder', 'section' => 'Misc'), 'AdminPresetsDirectory' => Array ('type' => 'text', 'title' => 'Path to Admin Interface Presets folder', 'section' => 'Misc'), 'ApplicationClass' => Array ('type' => 'text', 'title' => 'Name of Base Application Class', 'section' => 'Misc'), 'ApplicationPath' => Array ('type' => 'text', 'title' => 'Path to Base Application Class file', 'section' => 'Misc'), 'CacheHandler' => Array ('type' => 'select', 'title' => 'Output Caching Engine', 'section' => 'Misc'), + 'MaxCacheDuration' => Array ('type' => 'text', 'title' => 'Maximum Cache Duration (in days)', 'section' => 'Misc'), 'MemcacheServers' => Array ('type' => 'text', 'title' => 'Location of Memcache Servers', 'section' => 'Misc'), 'CompressionEngine' => Array ('type' => 'select', 'title' => 'CSS/JS Compression Engine', 'section' => 'Misc'), 'WebsiteCharset' => Array ('type' => 'text', 'title' => 'Website Charset', 'section' => 'Misc', 'required' => 1), 'EnableSystemLog' => Array ('type' => 'radio', 'title' => 'Enable "System Log"', 'section' => 'Misc', 'required' => 1), 'SystemLogMaxLevel' => Array ('type' => 'select', 'title' => 'Highest "Log Level", that will be saved in "System Log"', 'section' => 'Misc', 'required' => 1), 'TrustProxy' => Array ('type' => 'radio', 'title' => 'Trust Proxy', 'section' => 'Misc', 'required' => 1), ); $settings['CacheHandler']['options'] = $this->toolkit->getWorkingCacheHandlers(); $settings['CompressionEngine']['options'] = $this->toolkit->getWorkingCompressionEngines(); $settings['EnableSystemLog']['options'] = Array (1 => 'Enabled', 2 => 'User-only', 0 => 'Disabled'); $settings['SystemLogMaxLevel']['options'] = Array ( 0 => 'emergency', 1 => 'alert', 2 => 'critical', 3 => 'error', 4 => 'warning', 5 => 'notice', 6 => 'info', 7 => 'debug' ); $settings['TrustProxy']['options'] = Array (1 => 'Yes', 0 => 'No'); $row_class = 'table-color2'; foreach ($settings as $config_var => $output_params) { $row_class = $row_class == 'table-color1' ? 'table-color2' : 'table-color1'; ?> *' : ''); ?>: toolkit->systemConfig->get($config_var, $output_params['section'], ''); switch ( $output_params['type'] ) { case 'text': echo ''; break; case 'select': echo ''; break; case 'radio': foreach($output_params['options'] as $option_key => $option_value) { $selected = $option_key == $config_value ? ' checked' : ''; echo '' . $option_value; } break; } ?> Index: branches/5.2.x/core/install/install_toolkit.php =================================================================== --- branches/5.2.x/core/install/install_toolkit.php (revision 16771) +++ branches/5.2.x/core/install/install_toolkit.php (revision 16772) @@ -1,1030 +1,1031 @@ 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'); + $max_cache_duration = 60 * 60 * 24 * $this->systemConfig->get('MaxCacheDuration', 'Misc'); + $cache_handler = $this->Application->makeClass('kCache', array($max_cache_duration)); $cache_handlers = Array ( '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; } }