Index: branches/5.2.x/core/kernel/application.php =================================================================== --- branches/5.2.x/core/kernel/application.php (revision 14786) +++ branches/5.2.x/core/kernel/application.php (revision 14787) @@ -1,2766 +1,2783 @@ <?php /** * @version $Id$ * @package In-Portal * @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved. * @license GNU/GPL * In-Portal is Open Source software. * This means that this software may have been modified pursuant * the GNU General Public License, and as distributed it includes * or is derivative of works licensed under the GNU General Public License * or other free or open source software licenses. * See http://www.in-portal.org/license for copyright notices and details. */ defined('FULL_PATH') or die('restricted access!'); /** * Basic class for Kernel4-based Application * * This class is a Facade for any other class which needs to deal with Kernel4 framework.<br> * The class incapsulates the main run-cycle of the script, provide access to all other objects in the framework.<br> * <br> * The class is a singleton, which means that there could be only one instance of kApplication in the script.<br> * 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.<br> * 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 */ var $InitDone = false; /** * Holds internal NParser object * @access private * @var NParser */ var $Parser; /** * Holds parser output buffer * @access private * @var string */ var $HTML; /** * The main Factory used to create * almost any class of kernel and * modules * * @access private * @var kFactory */ var $Factory; /** * Template names, that will be used instead of regular templates * * @var Array */ var $ReplacementTemplates = Array (); /** * Mod-Rewrite listeners used during url building and parsing * * @var Array */ var $RewriteListeners = Array (); /** * Reference to debugger * * @var Debugger */ var $Debugger = null; /** * Holds all phrases used * in code and template * * @var PhrasesCache */ var $Phrases; /** * Modules table content, key - module name * * @var Array */ var $ModuleInfo = Array(); /** * Holds DBConnection * * @var kDBConnection */ var $Conn = null; /** * Maintains list of user-defined error handlers * * @var Array */ var $errorHandlers = Array(); /** * Maintains list of user-defined exception handlers * * @var Array */ var $exceptionHandlers = Array(); // performance needs: /** * Holds a reference to httpquery * * @var kHttpQuery */ var $HttpQuery = null; /** * Holds a reference to UnitConfigReader * * @var kUnitConfigReader */ var $UnitConfigReader = null; /** * Holds a reference to Session * * @var Session */ var $Session = null; /** * Holds a ref to kEventManager * * @var kEventManager */ var $EventManager = null; /** * Holds a ref to kUrlManager * * @var kUrlManager * @access public */ public $UrlManager = null; /** * Ref for TemplatesCache * * @var TemplatesCache */ var $TemplatesCache = null; var $CompilationCache = array(); //used when compiling templates var $CachedProcessors = array(); //used when running compiled templates var $LambdaElements = 1; // for autonumbering unnamed RenderElements [any better place for this prop? KT] /** * Holds current NParser tag while parsing, can be used in error messages to display template file and line * * @var _BlockTag */ var $CurrentNTag = null; /** * Object of unit caching class * * @var kCacheManager */ var $cacheManager = null; /** * Tells, that administrator has authenticated in administrative console * Should be used to manipulate data change OR data restrictions! * * @var bool */ var $isAdminUser = false; /** * Tells, that admin version of "index.php" was used, nothing more! * Should be used to manipulate data display! * * @var bool */ var $isAdmin = false; /** * Instance of site domain object * * @var kDBItem */ var $siteDomain = null; /** * Prevent kApplication class to be created directly, only via Instance method * */ protected function __construct() { } /** * 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: * <code> * $application =& kApplication::Instance(); * </code> * or in an object: * <code> * $this->Application =& kApplication::Instance(); * </code> * 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 * @access public * @return kApplication */ public static function &Instance() { static $instance = false; if (!$instance) { $class = defined('APPLICATION_CLASS') ? APPLICATION_CLASS : 'kApplication'; $instance = new $class(); $instance->Application =& $instance; } return $instance; } /** * Initializes the Application * * @access public * @see kHTTPQuery * @see Session * @see TemplatesCache * @return bool Was Init actually made now or before */ public function Init() { if ( $this->InitDone ) { return false; } $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:'); } if ( !$this->isDebugMode() && !kUtil::constOn('DBG_ZEND_PRESENT') ) { error_reporting(0); ini_set('display_errors', 0); } if ( !kUtil::constOn('DBG_ZEND_PRESENT') ) { $error_handler = set_error_handler(Array (&$this, 'handleError')); if ( $error_handler ) { // wrap around previous error handler, if any was set $this->errorHandlers[] = $error_handler; } $exception_handler = set_exception_handler(Array (&$this, 'handleException')); if ( $exception_handler ) { // wrap around previous exception handler, if any was set $this->exceptionHandlers[] = $exception_handler; } } $this->Factory = new kFactory(); $this->registerDefaultClasses(); $vars = kUtil::parseConfig(true); $db_class = isset($vars['Databases']) ? 'kDBLoadBalancer' : 'kDBConnection'; $this->Conn =& $this->Factory->makeClass($db_class, Array (SQL_TYPE, Array (&$this, 'handleSQLError'))); $this->Conn->setup($vars); $this->cacheManager =& $this->makeClass('kCacheManager'); $this->cacheManager->InitCache(); 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); + $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->AfterInit(); + $this->HttpQuery->AfterInit(); // TODO: only uses build-in rewrite listeners, when cache is build for the first time 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->UrlManager->LoadStructureTemplateMapping(); - $this->Session->ValidateExpired(); + $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 ) { putenv('TZ=' . $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(); + $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') ) { $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); } $language =& $this->recallObject('lang.current', null, Array ('live_table' => true)); /* @var $language LanguagesItem */ if ( preg_match('/utf-8/', $language->GetDBField('Charset')) ) { setlocale(LC_ALL, 'en_US.UTF-8'); mb_internal_encoding('UTF-8'); } if ( defined('DEBUG_MODE') && $this->isDebugMode() ) { $this->Debugger->profileFinish('kernel4_startup'); } $this->InitDone = true; $this->HandleEvent(new kEvent('adm:OnStartup')); return true; } 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 */ function findModule($field, $value, $return_field = null) { $found = false; foreach ($this->ModuleInfo as $module_name => $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')) { $this->registerModuleConstants(); return ; } // use makeClass over recallObject, since used before kApplication initialization during installation $modules_helper =& $this->makeClass('ModulesHelper'); /* @var $modules_helper kModulesHelper */ $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 * */ function VerifyLanguageId() { $language_id = $this->GetVar('m_lang'); if (!$language_id) { $language_id = 'default'; } $this->SetVar('lang.current_id', $language_id); $this->SetVar('m_lang', $language_id); $lang_mode = $this->GetVar('lang_mode'); $this->SetVar('lang_mode', ''); $lang =& $this->recallObject('lang.current'); /* @var $lang kDBItem */ if (!$lang->isLoaded() || (!$this->isAdmin && !$lang->GetDBField('Enabled'))) { if (!defined('IS_INSTALL')) { $this->ApplicationDie('Unknown or disabled language'); } } $this->SetVar('lang_mode',$lang_mode); } /** * Checks if passed theme id if valid and sets it to primary otherwise * */ 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); } function GetFrontThemePath($force=0) { static $path = null; if ( !$force && isset($path) ) { return $path; } $theme_id = $this->GetVar('m_theme'); if ( !$theme_id ) { // $theme_id = $this->GetDefaultThemeId(1); //1 to force front-end mode! $theme_id = 'default'; } $this->SetVar('m_theme', $theme_id); $this->SetVar('theme.current_id', $theme_id); // KOSTJA: this is to fool theme' getPassedId $theme =& $this->recallObject('theme.current'); /* @var $theme ThemeItem */ if ( !$theme->isLoaded() || !$theme->GetDBField('Enabled') ) { return false; } // assign & then return, since it's static variable $path = '/themes/' . $theme->GetDBField('Name'); return $path; } 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; } function GetDefaultThemeId($force_front=0) { static $theme_id = 0; if ($theme_id > 0) { return $theme_id; } if (kUtil::constOn('DBG_FORCE_THEME')) { $theme_id = DBG_FORCE_THEME; } elseif (!$force_front && $this->isAdmin) { $theme_id = 999; } else { $cache_key = 'primary_theme[%ThemeSerial%]'; $theme_id = $this->getCache($cache_key); if ($theme_id === false) { $this->Conn->nextQueryCachable = true; $sql = 'SELECT ' . $this->getUnitOption('theme', 'IDField') . ' FROM ' . $this->getUnitOption('theme', 'TableName') . ' WHERE (PrimaryTheme = 1) AND (Enabled = 1)'; $theme_id = $this->Conn->GetOne($sql); if ($theme_id !== false) { $this->setCache($cache_key, $theme_id); } } } return $theme_id; } /** * Returns site primary currency ISO code * * @return string */ function GetPrimaryCurrency() { $cache_key = 'primary_currency[%CurrSerial%][%SiteDomainSerial%]:' . $this->siteDomainField('DomainId'); $currency_iso = $this->getCache($cache_key); if ($currency_iso === false) { if ($this->isModuleEnabled('In-Commerce')) { $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 */ 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'); /* @var $site_domain kDBItem */ } if ($this->siteDomain->isLoaded()) { return $formatted ? $this->siteDomain->GetField($field, $format) : $this->siteDomain->GetDBField($field); } return false; } /** * Registers default classes such as ItemController, GridController and LoginController * * Called automatically while initializing Application * @access private * @return void */ 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', 'kiCacheable'); $this->registerClass('kHookManager', KERNEL_PATH . '/managers/hook_manager.php', null, 'kiCacheable'); $this->registerClass('kAgentManager', KERNEL_PATH . '/managers/agent_manager.php', null, 'kiCacheable'); $this->registerClass('kRequestManager', KERNEL_PATH . '/managers/request_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', null, 'kUrlProcessor'); $this->registerClass('kRewriteUrlProcessor', KERNEL_PATH . '/managers/rewrite_url_processor.php', null, 'kUrlProcessor'); $this->registerClass('kCacheManager', KERNEL_PATH . '/managers/cache_manager.php', null, 'kiCacheable'); $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('kUnitConfigReader', KERNEL_PATH . '/utility/unit_config_reader.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', 'Params'); // 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', 'kTagProcessor'); $this->registerClass('kDBTagProcessor', KERNEL_PATH . '/db/db_tag_processor.php', null, 'kTagProcessor'); $this->registerClass('kCatDBTagProcessor', KERNEL_PATH . '/db/cat_tag_processor.php', null, 'kDBTagProcessor'); $this->registerClass('NParser', KERNEL_PATH . '/nparser/nparser.php'); $this->registerClass('TemplatesCache', KERNEL_PATH . '/nparser/template_cache.php', null, Array ('kHelper', 'kDBTagProcessor')); // database $this->registerClass('kDBConnection', 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', null, 'kDBItem'); $this->registerClass('kDBList', KERNEL_PATH . '/db/dblist.php'); $this->registerClass('kCatDBList', KERNEL_PATH . '/db/cat_dblist.php', null, 'kDBList'); $this->registerClass('kDBEventHandler', KERNEL_PATH . '/db/db_event_handler.php'); $this->registerClass('kCatDBEventHandler', KERNEL_PATH . '/db/cat_event_handler.php', null, 'kDBEventHandler'); // email sending $this->registerClass('kEmailSendingHelper', KERNEL_PATH . '/utility/email_send.php', 'EmailSender', 'kHelper'); $this->registerClass('kSocket', KERNEL_PATH . '/utility/socket.php', 'Socket'); // do not move to config - this helper is used before configs are read $this->registerClass('kModulesHelper', KERNEL_PATH . self::MODULE_HELPER_PATH, 'ModulesHelper'); } function RegisterDefaultBuildEvents() { $this->EventManager->registerBuildEvent('kTempTablesHandler', 'OnTempHandlerBuild'); } /** * 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) { 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 * @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); } /** * Adds new value to cache $cache_name and identified by key $key * * @param int $key key name to add to cache * @param mixed $value value of chached record * @param int $expiration when value expires (0 - doesn't expire) * @access public */ public function setCache($key, $value, $expiration = 0) { return $this->cacheManager->setCache($key, $value, $expiration); } /** * Sets rebuilding mode for given cache * * @param string $name * @param int $mode * @param int $max_rebuilding_time */ public function rebuildCache($name, $mode = null, $max_rebuilding_time = 0) { $this->cacheManager->rebuildCache($name, $mode, $max_rebuilding_time); } /** * Deletes key from cache * * @param string $key * @access public */ public function deleteCache($key) { $this->cacheManager->deleteCache($key); } /** * Reset's all memory cache at once * * @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 * * @param string $name * @param mixed $value * @param int|bool $expiration * @access public */ public function setDBCache($name, $value, $expiration = false) { $this->cacheManager->setDBCache($name, $value, $expiration); } /** * Sets rebuilding mode for given cache * * @param string $name * @param int $mode * @param int $max_rebuilding_time */ public function rebuildDBCache($name, $mode = null, $max_rebuilding_time = 0) { $this->cacheManager->rebuildDBCache($name, $mode, $max_rebuilding_time); } /** * Deletes key from database cache * * @param string $name * @access public */ public function deleteDBCache($name) { $this->cacheManager->deleteDBCache($name); } /** * Registers each module specific constants if any found * * @return bool */ 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; } /** * 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. * @access public * @return void */ function Run() { 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 } } elseif ( $this->GetVar('admin') ) { // viewing front-end through admin's frame $admin_session =& $this->recallObject('Session.admin'); /* @var $admin_session Session */ $user = (int)$admin_session->RecallVar('user_id'); // in case, when no valid admin session found $perm_helper =& $this->recallObject('PermissionsHelper'); /* @var $perm_helper kPermissionsHelper */ if ( $perm_helper->CheckUserPermission($user, 'CATEGORY.MODIFY', 0, $this->getBaseCategory()) ) { // user can edit cms blocks $editing_mode = $this->GetVar('editing_mode'); define('EDITING_MODE', $editing_mode ? $editing_mode : EDITING_MODE_BROWSE); } } kUtil::safeDefine('EDITING_MODE', ''); // user can't edit anything $this->Phrases->setPhraseEditing(); $this->EventManager->ProcessRequest(); $this->InitParser(); $t = $this->GetVar('t'); if ( !$this->TemplatesCache->TemplateExists($t) && !$this->isAdmin ) { $cms_handler =& $this->recallObject('st_EventHandler'); /* @var $cms_handler CategoriesEventHandler */ $t = ltrim($cms_handler->GetDesignTemplate(), '/'); if ( defined('DEBUG_MODE') && $this->isDebugMode() ) { $this->Debugger->appendHTML('<strong>Design Template</strong>: ' . $t . '; <strong>CategoryID</strong>: ' . $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:'); } } 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. * @access public * @return void */ function Done() { $this->HandleEvent( new kEvent('adm:OnBeforeShutdown') ); $debug_mode = defined('DEBUG_MODE') && $this->isDebugMode(); if ($debug_mode && kUtil::constOn('DBG_PROFILE_MEMORY')) { $this->Debugger->appendMemoryUsage('Application before Done:'); } if ($debug_mode) { $this->EventManager->runAgents(reAFTER); $this->Session->SaveData(); if (kUtil::constOn('DBG_CACHE')) { $this->cacheManager->printStatistics(); } $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; } 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 { echo $this->HTML; } $this->cacheManager->UpdateApplicationCache(); flush(); if (!$debug_mode) { $this->EventManager->runAgents(reAFTER); $this->Session->SaveData(); } if (defined('DBG_CAPTURE_STATISTICS') && DBG_CAPTURE_STATISTICS && !$this->isAdmin) { $this->_storeStatistics(); } } /** * Stores script execution statistics to database * */ function _storeStatistics() { global $start; $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['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'); } } /** * Calculates average time for statistics * * @param Array $data * @param string $field_prefix * @param float $current_value */ 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; } } function logSlowQuery($slow_sql, $time) { $query_crc = crc32($slow_sql); $sql = 'SELECT * FROM ' . TABLE_PREFIX . 'SlowSqlCapture WHERE QueryCrc = ' . $query_crc; $data = $this->Conn->Query($sql, null, true); if ($data) { $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['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'); } } /** * Checks if output compression options is available * * @return string */ function UseOutputCompression() { if (kUtil::constOn('IS_INSTALL') || kUtil::constOn('DBG_ZEND_PRESENT') || kUtil::constOn('SKIP_OUT_COMPRESSION')) { return false; } return $this->ConfigValue('UseOutputCompression') && function_exists('gzencode') && strstr($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip'); } // Facade /** * Returns current session id (SID) * @access public * @return int */ function GetSID() { $session =& $this->recallObject('Session'); /* @var $session Session */ return $session->GetID(); } function DestroySession() { $session =& $this->recallObject('Session'); /* @var $session 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; } /** * 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); return isset($this->HttpQuery->$type[$name]) ? $this->HttpQuery->$type[$name] : $default; } /** * Returns ALL variables passed to the script as GET/POST/COOKIE * * @access public * @return array */ 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.<br> * * This method is formerly known as $this->Session->SetProperty. * @param string $var Variable name to set * @param mixed $val Variable value * @access public * @return void */ public function SetVar($var,$val) { $this->HttpQuery->Set($var, $val); } /** * Deletes kHTTPQuery variable * * @param string $var * @todo think about method name */ function DeleteVar($var) { return $this->HttpQuery->Remove($var); } /** * Deletes Session variable * * @param string $var */ function RemoveVar($var) { return $this->Session->RemoveVar($var); } function RemovePersistentVar($var) { return $this->Session->RemovePersistentVar($var); } /** * Restores Session variable to it's db version * * @param string $var */ function RestoreVar($var) { return $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. * * @see SimpleSession * @access public * @param string $var Variable name * @param mixed $default Default value to return if no $var variable found in session * @return mixed */ function RecallVar($var,$default=false) { return $this->Session->RecallVar($var,$default); } 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 * @see kApplication::RecallVar() * @access public */ function StoreVar($var, $val, $optional = false) { $session =& $this->recallObject('Session'); $this->Session->StoreVar($var, $val, $optional); } function StorePersistentVar($var, $val, $optional = false) { $this->Session->StorePersistentVar($var, $val, $optional); } function StoreVarDefault($var, $val, $optional=false) { $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).<br> * 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 * @access public * @param string $var HTTP Query (GPC) variable name * @param mixed $ses_var Session variable name * @param mixed $default Default variable value * @param bool $optional */ 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 * * @see LinkVar * @access public * @param string $var HTTP Query (GPC) variable name * @param mixed $ses_var Session variable name * @param mixed $default Default variable value * @return mixed */ function GetLinkedVar($var, $ses_var = null, $default = '') { $this->LinkVar($var, $ses_var, $default); return $this->GetVar($var); } function ProcessParsedTag($prefix, $tag, $params) { $processor = $this->Parser->GetProcessor($prefix); /* @var $processor kDBTagProcessor */ return $processor->ProcessParsedTag($tag, $params, $prefix); } /** * Return ADODB Connection object * * Returns ADODB Connection object already connected to the project database, configurable in config.php * @access public * @return kDBConnection */ 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 */ 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 */ 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 */ 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 = null, $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 variables with values that should be passed through with this link + variable list * * @param Array $params * @return Array */ function getPassThroughVariables(&$params) { return $this->UrlManager->getPassThroughVariables($params); } 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); } 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 . '/'; } 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'; } $params['__URLENCODE__'] = 1; $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 '<strong>Debug output above !!!</strong><br/>' . "\n"; if ( array_key_exists('HTTP_REFERER', $_SERVER) ) { echo 'Referer: <strong>' . $_SERVER['HTTP_REFERER'] . '</strong><br/>' . "\n"; } echo "Proceed to redirect: <a href=\"{$location}\">{$location}</a><br/>\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 true; } else { if ($this->GetVar('ajax') == 'yes' && $t != $this->GetVar('t')) { // redirection to other then current template during ajax request kUtil::safeDefine('DBG_SKIP_REPORTING', 1); echo '#redirect#' . $location; } elseif (headers_sent() != '') { // some output occured -> redirect using javascript echo '<script type="text/javascript">window.location.href = \'' . $location . '\';</script>'; } 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 $session =& $this->recallObject('Session'); /* @var $session Session */ $this->HandleEvent( new kEvent('adm:OnBeforeShutdown') ); $session->SaveData(); ob_end_flush(); exit; } 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 */ 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. * * @access private */ function ValidateLogin() { $session =& $this->recallObject('Session'); /* @var $session 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 $user =& $this->recallObject('u.login-admin', null, Array ('form_name' => 'login')); /* @var $user UsersItem */ $user->SetError('UserLogin', 'session_expired', 'la_text_sess_expired'); } if ( ($user_id != USER_GUEST) && defined('DBG_REQUREST_LOG') && DBG_REQUREST_LOG ) { $this->HttpQuery->writeRequestLog(DBG_REQUREST_LOG); } if ( $user_id != USER_GUEST ) { // normal users + root $this->LoadPersistentVars(); } } /** * Loads current user persistent session data * */ function LoadPersistentVars() { $this->Session->LoadPersistentVars(); } /** * Returns configuration option value by name * * @param string $name * @return string */ function ConfigValue($name) { return $this->cacheManager->ConfigValue($name); } function SetConfigValue($name, $value) { return $this->cacheManager->SetConfigValue($name, $value); } /** * Allows to process any type of event * * @param kEvent $event * @param Array $params * @param Array $specific_params * @access public * @author Alex */ function HandleEvent(&$event, $params = null, $specific_params = null) { if ( isset($params) ) { $event = new kEvent($params, $specific_params); } $this->EventManager->HandleEvent($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 * @param Array $dependecies List of classes required for this class functioning * @access public * @author Alex */ function registerClass($real_class, $file, $pseudo_class = null, $dependecies = Array() ) { $this->Factory->registerClass($real_class, $file, $pseudo_class, $dependecies); } /** * 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 */ function unregisterClass($real_class, $pseudo_class = null) { $this->Factory->unregisterClass($real_class, $pseudo_class); } /** * Add $class_name to required classes list for $depended_class class. * All required class files are included before $depended_class file is included * * @param string $depended_class * @param string $class_name * @author Alex */ function registerDependency($depended_class, $class_name) { $this->Factory->registerDependency($depended_class, $class_name); } /** * Add new agent * * @param string $short_name name to be used to store last maintenace run info * @param string $event_name * @param int $run_interval run interval in seconds * @param int $type before or after agent * @param int $status * @access public */ public function registerAgent($short_name, $event_name, $run_interval, $type = reBEFORE, $status = STATUS_ACTIVE) { $this->EventManager->registerAgent($short_name, $event_name, $run_interval, $type, $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 * @author Kostja */ function registerAggregateTag($tag_info) { $aggregator =& $this->recallObject('TagsAggregator', 'kArray'); /* @var $aggregator 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, $event_params = 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(); }*/ $result =& $this->Factory->getObject($name, $pseudo_class, $event_params, $arguments); return $result; } /** * Returns tag processor for prefix specified * * @param string $prefix * @return kDBTagProcessor */ function &recallTagProcessor($prefix) { $this->InitParser(); // because kDBTagProcesor is in NParser dependencies $result =& $this->recallObject($prefix . '_TagProcessor'); return $result; } /** * Checks if object with prefix passes was already created in factory * * @param string $name object presudo_class, prefix * @return bool * @author Kostja */ function hasObject($name) { return isset($this->Factory->Storage[$name]); } /** * Removes object from storage by given name * * @param string $name Object's name in the Storage * @author Kostja */ 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, $arguments = Array ()) { $result =& $this->Factory->makeClass($pseudo_class, $arguments); return $result; } /** * 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 */ 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 * @access 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 private * @author Alex */ function handleSQLError($code, $msg, $sql) { if ( isset($this->Debugger) ) { $long_error_msg = '<span class="debug_error">' . $msg . ' (' . $code . ')</span><br/><a href="javascript:$Debugger.SetClipboard(\'' . htmlspecialchars($sql) . '\');"><strong>SQL</strong></a>: ' . $this->Debugger->formatSQL($sql); $long_id = $this->Debugger->mapLongError($long_error_msg); $error_msg = mb_substr($msg . ' (' . $code . ') [' . $sql . ']', 0, 1000) . ' #' . $long_id; if ( kUtil::constOn('DBG_SQL_FAILURE') && !defined('IS_INSTALL') ) { throw new Exception($error_msg); } else { $this->Debugger->appendTrace(); } } else { // when not debug mode, then fatal database query won't break anything $error_msg = '<strong>SQL Error</strong> in sql: ' . $sql . ', code <strong>' . $code . '</strong> (' . $msg . ')'; } trigger_error($error_msg, E_USER_WARNING); return true; } /** * Default error handler * * @param int $errno * @param string $errstr * @param string $errfile * @param int $errline * @param Array $errcontext * @return bool * @access public */ public function handleError($errno, $errstr, $errfile = null, $errline = null, $errcontext = Array ()) { $this->errorLogSilent($errno, $errstr, $errfile, $errline); $debug_mode = defined('DEBUG_MODE') && DEBUG_MODE; $skip_reporting = defined('DBG_SKIP_REPORTING') && DBG_SKIP_REPORTING; if ( !$this->errorHandlers || ($debug_mode && $skip_reporting) ) { // when debugger absent OR it's present, but we actually can't see it's error report (e.g. during ajax request) if ( $errno == E_USER_ERROR ) { $this->errorDisplayFatal('<strong>Fatal Error: </strong>' . "{$errstr} in {$errfile} on line {$errline}"); } if ( !$this->errorHandlers ) { return true; } } $res = false; /* @var $handler Closure */ foreach ($this->errorHandlers as $handler) { if ( is_array($handler) ) { $object =& $handler[0]; $method = $handler[1]; $res = $object->$method($errno, $errstr, $errfile, $errline, $errcontext); } else { $res = $handler($errno, $errstr, $errfile, $errline, $errcontext); } } return $res; } /** * Handles exception * * @param Exception $exception * @return bool * @access public */ public function handleException($exception) { // transform exception to regular error (no need to rewrite existing error handlers) $errno = $exception->getCode(); $errstr = $exception->getMessage(); $errfile = $exception->getFile(); $errline = $exception->getLine(); $this->errorLogSilent($errno, $errstr, $errfile, $errline); $debug_mode = defined('DEBUG_MODE') && DEBUG_MODE; $skip_reporting = defined('DBG_SKIP_REPORTING') && DBG_SKIP_REPORTING; if ( !$this->exceptionHandlers || ($debug_mode && $skip_reporting) ) { // when debugger absent OR it's present, but we actually can't see it's error report (e.g. during ajax request) $this->errorDisplayFatal('<strong>' . get_class($exception) . ': </strong>' . "{$errstr} in {$errfile} on line {$errline}"); if ( !$this->exceptionHandlers ) { return true; } } $res = false; /* @var $handler Closure */ foreach ($this->exceptionHandlers as $handler) { if ( is_array($handler) ) { $object =& $handler[0]; $method = $handler[1]; $res = $object->$method($exception); } else { $res = $handler($exception); } } return $res; } /** * Silently saves each given error message to "silent_log.txt" file, when silent log mode is enabled * @param int $errno * @param string $errstr * @param string $errfile * @param int $errline * @return void * @access protected */ protected function errorLogSilent($errno, $errstr = '', $errfile = '', $errline = null) { if ( !defined('SILENT_LOG') || !SILENT_LOG ) { return; } if ( !(defined('DBG_IGNORE_STRICT_ERRORS') && DBG_IGNORE_STRICT_ERRORS && defined('E_STRICT') && ($errno == E_STRICT)) ) { $time = adodb_date('d/m/Y H:i:s'); $fp = fopen((defined('RESTRICTED') ? RESTRICTED : FULL_PATH) . '/silent_log.txt', 'a'); fwrite($fp, '[' . $time . '] #' . $errno . ': ' . strip_tags($errstr) . ' in [' . $errfile . '] on line ' . $errline . "\n"); fclose($fp); } } /** * Displays div with given error message * * @param string $msg * @return void * @access protected */ protected function errorDisplayFatal($msg) { $margin = $this->isAdmin ? '8px' : 'auto'; echo '<div style="background-color: #FEFFBF; margin: ' . $margin . '; padding: 10px; border: 2px solid red; text-align: center">' . $msg . '</div>'; exit; } /** * Prints trace, when debug mode is not available * * @param bool $return_result * @param int $skip_levels * @return string * @access public */ public function printTrace($return_result = false, $skip_levels = 1) { $ret = Array (); $trace = debug_backtrace(false); for ($i = 0; $i < $skip_levels; $i++) { array_shift($trace); } foreach ($trace as $level => $trace_info) { if ( isset($trace_info['class']) ) { $object = $trace_info['class']; } elseif ( isset($trace_info['object']) ) { $object = get_class($trace_info['object']); } else { $object = ''; } $args = ''; $type = isset($trace_info['type']) ? $trace_info['type'] : ''; if ( isset($trace_info['args']) ) { foreach ($trace_info['args'] as $argument) { if ( is_object($argument) ) { $args .= get_class($argument) . ' instance, '; } else { $args .= is_array($argument) ? 'Array' : substr($argument, 0, 10) . ' ..., '; } } $args = substr($args, 0, -2); } $ret[] = '#' . $level . ' ' . $object . $type . $trace_info['function'] . '(' . $args . ') called at [' . $trace_info['file'] . ':' . $trace_info['line'] . ']'; } if ( $return_result ) { return implode("\n", $ret); } echo implode("\n", $ret); return ''; } /** * Returns & blocks next ResourceId available in system * * @return int * @access public * @author Alex */ 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 */ 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_event_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 */ function &EmailEventAdmin($email_event_name, $to_user_id = null, $send_params = Array ()) { $event =& $this->EmailEvent($email_event_name, EmailEvent::EVENT_TYPE_ADMIN, $to_user_id, $send_params); return $event; } /** * Triggers email event of type User * * @param string $email_event_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 */ function &EmailEventUser($email_event_name, $to_user_id = null, $send_params = Array ()) { $event =& $this->EmailEvent($email_event_name, EmailEvent::EVENT_TYPE_FRONTEND, $to_user_id, $send_params); return $event; } /** * Triggers general email event * * @param string $email_event_name * @param int $email_event_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 */ function &EmailEvent($email_event_name, $email_event_type, $to_user_id = null, $send_params = Array ()) { $params = Array ( 'EmailEventName' => $email_event_name, 'EmailEventToUserId' => $to_user_id, 'EmailEventType' => $email_event_type, 'DirectSendParams' => $send_params, ); if (array_key_exists('use_special', $send_params)) { $event_str = 'emailevents.' . $send_params['use_special'] . ':OnEmailEvent'; } else { $event_str = 'emailevents:OnEmailEvent'; } $this->HandleEvent($event, $event_str, $params); return $event; } /** * Allows to check if user in this session is logged in or not * * @return bool */ function LoggedIn() { // no session during expiration process return is_null($this->Session) ? false : $this->Session->LoggedIn(); } /** * 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 */ function CheckPermission($name, $type = 1, $cat_id = null) { $perm_helper =& $this->recallObject('PermissionsHelper'); /* @var $perm_helper kPermissionsHelper */ return $perm_helper->CheckPermission($name, $type, $cat_id); } /** * Set's any field of current visit * * @param string $field * @param mixed $value */ function setVisitField($field, $value) { if ($this->isAdmin || !$this->ConfigValue('UseVisitorTracking')) { // admin logins are not registred in visits list return ; } $visit =& $this->recallObject('visits', null, Array ('raise_warnings' => 0)); /* @var $visit kDBItem */ if ($visit->isLoaded()) { $visit->SetDBField($field, $value); $visit->Update(); } } /** * Allows to check if in-portal is installed * * @return bool */ function isInstalled() { return $this->InitDone && (count($this->ModuleInfo) > 0); } /** * Allows to determine if module is installed & enabled * * @param string $module_name * @return bool */ 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 mixed */ 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 */ function GetTempName($table, $wid = '') { return $this->GetTempTablePrefix($wid) . $table; } function GetTempTablePrefix($wid = '') { if (preg_match('/prefix:(.*)/', $wid, $regs)) { $wid = $this->GetTopmostWid($regs[1]); } return TABLE_PREFIX . 'ses_' . $this->GetSID() . ($wid ? '_' . $wid : '') . '_edit_'; } 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 */ 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 */ 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; } } function CheckProcessors($processors) { foreach ($processors as $a_processor) { if (!isset($this->CachedProcessors[$a_processor])) { $this->CachedProcessors[$a_processor] =& $this->recallObject($a_processor.'_TagProcessor'); } } } function ApplicationDie($message = '') { $message = ob_get_clean().$message; if ($this->isDebugMode()) { $message .= $this->Debugger->printReport(true); } echo $this->UseOutputCompression() ? gzencode($message, DBG_COMPRESSION_LEVEL) : $message; exit; } /* moved from MyApplication */ 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 . 'UserGroup 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 agents supported) * * @return bool */ 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 presense in database * * @param string $table_name * @return bool */ function TableFound($table_name) { return $this->Conn->TableFound($table_name); } /** * 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 parmeters) * @param bool $multiple_results * @return mixed */ function getCounter($name, $params = Array (), $query_name = null, $multiple_results = false) { $count_helper =& $this->recallObject('CountHelper'); /* @var $count_helper kCountHelper */ 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; } $count_helper =& $this->recallObject('CountHelper'); /* @var $count_helper kCountHelper */ $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) { $lang =& $this->recallObject('lang.current'); /* @var $lang LanguagesItem */ header('Content-type: text/xml; charset=' . $lang->GetDBField('Charset')); return $xml_version ? '<?xml version="' . $xml_version . '" encoding="' . $lang->GetDBField('Charset') . '"?>' : ''; } /** * Returns category tree * * @param int $category_id * @return Array */ 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 */ 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']; } - function DeleteUnitCache($include_sections = false) + /** + * 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->DeleteUnitCache($include_sections); + $this->cacheManager->DeleteSectionCache(); } /** * Sets data from cache to object * * @param Array $data * @access public */ public function 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 ( 'Application.ReplacementTemplates' => $this->ReplacementTemplates, 'Application.RewriteListeners' => $this->RewriteListeners, 'Application.ModuleInfo' => $this->ModuleInfo, ); } public function delayUnitProcessing($method, $params) { $this->cacheManager->delayUnitProcessing($method, $params); } } \ No newline at end of file Index: branches/5.2.x/core/kernel/managers/cache_manager.php =================================================================== --- branches/5.2.x/core/kernel/managers/cache_manager.php (revision 14786) +++ branches/5.2.x/core/kernel/managers/cache_manager.php (revision 14787) @@ -1,742 +1,752 @@ <?php /** * @version $Id$ * @package In-Portal * @copyright Copyright (C) 1997 - 2011 Intechnic. All rights reserved. * @license GNU/GPL * In-Portal is Open Source software. * This means that this software may have been modified pursuant * the GNU General Public License, and as distributed it includes * or is derivative of works licensed under the GNU General Public License * or other free or open source software licenses. * See http://www.in-portal.org/license for copyright notices and details. */ defined('FULL_PATH') or die('restricted access!'); class kCacheManager extends kBase implements kiCacheable { /** * Used variables from ConfigurationValues table * * @var Array * @access protected */ protected $configVariables = Array(); /** + * Used variables from ConfigurationValues table retrieved from unit cache + * + * @var Array + * @access protected + */ + protected $originalConfigVariables = Array (); + + /** * IDs of config variables used in current run (for caching) * * @var Array * @access protected */ protected $configIDs = Array (); /** - * IDs of config variables retirieved from cache + * IDs of config variables retrieved from unit cache * * @var Array * @access protected */ protected $originalConfigIDs = Array (); /** * Object of memory caching class * * @var kCache * @access protected */ protected $cacheHandler = null; protected $temporaryCache = Array ( 'registerAggregateTag' => Array (), 'registerAgent' => Array (), 'registerHook' => Array (), 'registerBuildEvent' => Array (), 'registerAggregateTag' => Array (), ); /** * Creates caching manager instance * * @access public */ public function InitCache() { $this->cacheHandler =& $this->Application->makeClass('kCache'); } /** * 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 ' . TABLE_PREFIX . 'ConfigurationValues 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) { if ($name == 'Smtp_AdminMailFrom') { $res = $this->Application->siteDomainField('AdminEmail'); if ($res) { return $res; } } if ( array_key_exists($name, $this->configVariables) ) { return $this->configVariables[$name]; } if ( defined('IS_INSTALL') && IS_INSTALL && !$this->Application->TableFound('ConfigurationValues') ) { return false; } $this->Conn->nextQueryCachable = true; $sql = 'SELECT VariableId, VariableValue FROM ' . TABLE_PREFIX . 'ConfigurationValues 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 "<strong>' . $name . '</strong>"', E_USER_NOTICE); return false; } function SetConfigValue($name, $value) { $this->configVariables[$name] = $value; $fields_hash = Array ('VariableValue' => $value); $this->Conn->doUpdate($fields_hash, TABLE_PREFIX . 'ConfigurationValues', 'VariableName = ' . $this->Conn->qstr($name)); - $this->DeleteUnitCache(); + 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->Factory->setFromCache($cache); $this->Application->UnitConfigReader->setFromCache($cache); $this->Application->EventManager->setFromCache($cache); $aggregator =& $this->Application->recallObject('TagsAggregator', 'kArray'); /* @var $aggregator kArray */ $aggregator->setFromCache($cache); $this->setFromCache($cache); $this->Application->setFromCache($cache); unset($cache); return true; } if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) { $this->Application->rebuildCache('master:configs_parsed', kCache::REBUILD_NOW, CacheSettings::$unitCacheRebuildTime); } else { $this->Application->rebuildDBCache('configs_parsed', kCache::REBUILD_NOW, CacheSettings::$unitCacheRebuildTime); } return false; } /** * Empties factory and event manager cache (without storing changes) */ public function EmptyUnitCache() { $cache_keys = Array ( 'Factory.Files', 'Factory.realClasses', 'Factory.Dependencies', 'EventManager.buildEvents', 'EventManager.beforeHooks', 'EventManager.afterHooks', 'EventManager.beforeRegularEvents', 'EventManager.afterRegularEvents' ); $empty_cache = Array (); foreach ($cache_keys as $cache_key) { $empty_cache[$cache_key] = Array (); } $this->Application->Factory->setFromCache($empty_cache); $this->Application->EventManager->setFromCache($empty_cache); // otherwise ModulesHelper indirectly used from includeConfigFiles won't work $this->Application->RegisterDefaultClasses(); } /** * Updates data, that was parsed from unit configs this time * * @access public */ public function UpdateUnitCache() { $aggregator =& $this->Application->recallObject('TagsAggregator', 'kArray'); /* @var $aggregator kArray */ $this->preloadConfigVars(); // preloading will put to cache $cache = array_merge( $this->Application->Factory->getToCache(), $this->Application->UnitConfigReader->getToCache(), $this->Application->EventManager->getToCache(), $aggregator->getToCache(), $this->getToCache(), $this->Application->getToCache() ); $cache_rebuild_by = SERVER_NAME . ' (' . getenv('REMOTE_ADDR') . ') - ' . 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); } else { $this->Application->setDBCache('configs_parsed', serialize($cache)); $this->Application->setDBCache('last_cache_rebuild', $cache_rebuild_by); } } 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 (including unit config locations) + * Deletes all data, that was cached during unit config parsing (excluding unit config locations) * - * @param bool $include_sections + * @param Array $config_variables * @access public */ - public function DeleteUnitCache($include_sections = false) + public function DeleteUnitCache($config_variables = null) { - if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { + 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->Application->rebuildDBCache('configs_parsed', kCache::REBUILD_LATER, CacheSettings::$unitCacheRebuildTime); } + } - if ($include_sections) { - if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { - $this->Application->rebuildCache('master:sections_parsed', kCache::REBUILD_LATER, CacheSettings::$sectionsParsedRebuildTime); - } - else { - $this->Application->rebuildDBCache('sections_parsed', kCache::REBUILD_LATER, CacheSettings::$sectionsParsedRebuildTime); - } + /** + * 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->Application->rebuildDBCache('sections_parsed', kCache::REBUILD_LATER, CacheSettings::$sectionsParsedRebuildTime); } } /** - * Preloads widely used configuration variables, so they will get to cache for sure + * 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', + 'SessionTimeout', 'SessionCookieName', 'SessionCookieDomains', 'SessionBrowserSignatureCheck', + 'SessionIPAddressCheck', 'CookieSessions', 'KeepSessionOnBrowserClose', 'User_GuestGroup', + 'User_LoggedInGroup', 'RegistrationUsernameRequired', // output related - 'UseModRewrite', - 'UseContentLanguageNegotiation', - 'UseOutputCompression', - 'OutputCompressionLevel', - 'Config_Site_Time', - 'SystemTagCache', + 'UseModRewrite', 'UseContentLanguageNegotiation', 'UseOutputCompression', 'OutputCompressionLevel', + 'Config_Site_Time', 'SystemTagCache', // tracking related - 'UseChangeLog', - 'UseVisitorTracking', - 'ModRewriteUrlEnding', - 'ForceModRewriteUrlEnding', + 'UseChangeLog', 'UseVisitorTracking', 'ModRewriteUrlEnding', 'ForceModRewriteUrlEnding', 'UseCronForRegularEvent', ); $escaped_config_vars = array_map(Array (&$this->Conn, 'qstr'), $config_vars); $sql = 'SELECT VariableId, VariableName, VariableValue FROM ' . TABLE_PREFIX . 'ConfigurationValues 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 = $data['Application.ConfigHash']; + $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; } /** * Prints caching statistics * * @access public */ public function printStatistics() { $this->cacheHandler->printStatistics(); } /** * 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); } /** * Adds new value to cache $cache_name and identified by key $key * * @param int $key key name to add to cache * @param mixed $value value of chached record * @param int $expiration when value expires (0 - doesn't expire) * @return bool * @access public */ public function setCache($key, $value, $expiration = 0) { return $this->cacheHandler->setCache($key, $value, $expiration); } /** * Sets rebuilding mode for given cache * * @param string $name * @param int $mode * @param int $max_rebuilding_time */ public function rebuildCache($name, $mode = null, $max_rebuilding_time = 0) { $this->cacheHandler->rebuildCache($name, $mode, $max_rebuilding_time); } /** * Deletes key from cache * * @param string $key * @access public */ public function deleteCache($key) { $this->cacheHandler->delete($key); } /** * Reset's all memory cache at once * * @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) { if ( $this->_getDBCache($name . '_rebuild') ) { // cache rebuild requested -> rebuild now $this->deleteDBCache($name . '_rebuild'); return false; } // no serials in cache key OR cache is outdated $wait_seconds = $max_rebuild_seconds; while (true) { $cache = $this->_getDBCache($name); $rebuilding = $this->_getDBCache($name . '_rebuilding'); if ( ($cache === false) && (!$rebuilding || $wait_seconds == 0) ) { // cache missing and nobody rebuilding it -> rebuild; enough waiting for cache to be ready return false; } elseif ( $cache !== false ) { // cache present -> return it return $cache; } $wait_seconds -= kCache::WAIT_STEP; sleep(kCache::WAIT_STEP); } return false; } /** * Returns value from database cache * * @param string $name key name * @return mixed * @access protected */ protected function _getDBCache($name) { $this->Conn->nextQueryCachable = true; $sql = 'SELECT Data, Cached, LifeTime FROM ' . TABLE_PREFIX . 'Cache WHERE VarName = ' . $this->Conn->qstr($name); $data = $this->Conn->GetRow($sql); if ($data) { $lifetime = (int)$data['LifeTime']; // in seconds if (($lifetime > 0) && ($data['Cached'] + $lifetime < adodb_mktime())) { // delete expired $this->Conn->nextQueryCachable = true; $sql = 'DELETE FROM ' . TABLE_PREFIX . 'Cache WHERE VarName = ' . $this->Conn->qstr($name); $this->Conn->Query($sql); return false; } return $data['Data']; } return false; } /** * Sets value to database cache * * @param string $name * @param mixed $value * @param int|bool $expiration * @access public */ public function setDBCache($name, $value, $expiration = false) { $this->deleteDBCache($name . '_rebuilding'); $this->_setDBCache($name, $value, $expiration); } /** * Sets value to database cache * * @param string $name * @param mixed $value * @param int|bool $expiration * @access protected */ protected function _setDBCache($name, $value, $expiration = false) { if ((int)$expiration <= 0) { $expiration = -1; } $fields_hash = Array ( 'VarName' => $name, 'Data' => &$value, 'Cached' => adodb_mktime(), 'LifeTime' => (int)$expiration, ); $this->Conn->nextQueryCachable = true; $this->Conn->doInsert($fields_hash, TABLE_PREFIX . 'Cache', 'REPLACE'); } /** * Sets rebuilding mode for given cache * * @param string $name * @param int $mode * @param int $max_rebuilding_time */ public function rebuildDBCache($name, $mode = null, $max_rebuilding_time = 0) { if ( !isset($mode) || $mode == kCache::REBUILD_NOW ) { $this->_setDBCache($name . '_rebuilding', 1, $max_rebuilding_time); $this->deleteDBCache($name . '_rebuild'); } elseif ( $mode == kCache::REBUILD_LATER ) { $this->_setDBCache($name . '_rebuild', 1, 0); $this->deleteDBCache($name . '_rebuilding'); } } /** * Deletes key from database cache * * @param string $name * @access public */ public function deleteDBCache($name) { $sql = 'DELETE FROM ' . TABLE_PREFIX . 'Cache 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 * @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: <strong>' . $serial_name . '</strong>.'); } $this->setCache($serial_name, (int)$this->getCache($serial_name) + 1); if (!defined('IS_INSTALL') || !IS_INSTALL) { // 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 . 'Category WHERE CategoryId = ' . (int)$category_id; $category_data = $this->Conn->GetRow($sql); if ($category_data !== false) { // only direct links to category pages work (symlinks, container pages and so on won't work) $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); } } \ No newline at end of file Index: branches/5.2.x/core/kernel/managers/rewrite_url_processor.php =================================================================== --- branches/5.2.x/core/kernel/managers/rewrite_url_processor.php (revision 14786) +++ branches/5.2.x/core/kernel/managers/rewrite_url_processor.php (revision 14787) @@ -1,965 +1,976 @@ <?php /** * @version $Id$ * @package In-Portal * @copyright Copyright (C) 1997 - 2011 Intechnic. All rights reserved. * @license GNU/GPL * In-Portal is Open Source software. * This means that this software may have been modified pursuant * the GNU General Public License, and as distributed it includes * or is derivative of works licensed under the GNU General Public License * or other free or open source software licenses. * See http://www.in-portal.org/license for copyright notices and details. */ defined('FULL_PATH') or die('restricted access!'); class kRewriteUrlProcessor extends kUrlProcessor { /** * Holds a reference to httpquery * * @var kHttpQuery * @access protected */ protected $HTTPQuery = null; /** * Urls parts, that needs to be matched by rewrite listeners * * @var Array * @access protected */ protected $_partsToParse = Array (); /** * Category item prefix, that was found * * @var string|bool * @access public */ public $modulePrefix = false; /** * Template aliases for current theme * * @var Array * @access protected */ protected $_templateAliases = null; /** * Domain-based primary language id * * @var int * @access public */ public $primaryLanguageId = false; /** * Domain-based primary theme id * * @var int * @access public */ public $primaryThemeId = false; /** * Possible url endings from ModRewriteUrlEnding configuration variable * * @var Array + * @access protected */ protected $_urlEndings = Array ('.html', '/', ''); /** + * Factory storage sub-set, containing mod-rewrite listeners, used during url building and parsing + * + * @var Array + * @access protected + */ + protected $rewriteListeners = Array (); + + /** * Constructor of kRewriteUrlProcessor class * * @param $manager * @return kRewriteUrlProcessor */ public function __construct(&$manager) { parent::__construct($manager); $this->HTTPQuery =& $this->Application->recallObject('HTTPQuery'); // domain based primary language $this->primaryLanguageId = $this->Application->siteDomainField('PrimaryLanguageId'); if (!$this->primaryLanguageId) { // when domain-based language not found -> use site-wide language $this->primaryLanguageId = $this->Application->GetDefaultLanguageId(); } // domain based primary theme $this->primaryThemeId = $this->Application->siteDomainField('PrimaryThemeId'); if (!$this->primaryThemeId) { // when domain-based theme not found -> use site-wide theme $this->primaryThemeId = $this->Application->GetDefaultThemeId(true); } $this->_initRewriteListeners(); } /** * Parses url * * @return void */ public function parseRewriteURL() { $url = $this->Application->GetVar('_mod_rw_url_'); if ($url) { foreach ($this->_urlEndings as $url_ending) { if (substr($url, strlen($url) - strlen($url_ending)) == $url_ending) { $url = substr($url, 0, strlen($url) - strlen($url_ending)); $default_ending = $this->Application->ConfigValue('ModRewriteUrlEnding'); // user manually typed url with different url ending -> redirect to same url with default url ending if (($url_ending != $default_ending) && $this->Application->ConfigValue('ForceModRewriteUrlEnding')) { $target_url = $this->Application->BaseURL() . $url . $default_ending; $this->Application->Redirect('external:' . $target_url, Array ('response_code' => 301)); } break; } } } $cached = $this->_getCachedUrl($url); if ( $cached !== false ) { $vars = $cached['vars']; $passed = $cached['passed']; } else { $vars = $this->parse($url); $passed = $vars['pass']; // also used in bottom of this method unset($vars['pass']); if ( !$this->_partsToParse ) { // don't cache 404 Not Found $this->_setCachedUrl($url, Array ('vars' => $vars, 'passed' => $passed)); } if ( $this->Application->GetVarDirect('t', 'Post') ) { // template from POST overrides template from URL. $vars['t'] = $this->Application->GetVarDirect('t', 'Post'); if ( isset($vars['is_virtual']) && $vars['is_virtual'] ) { $vars['m_cat_id'] = 0; // this is virtual template category (for Proj-CMS) } } unset($vars['is_virtual']); } foreach ($vars as $name => $value) { $this->HTTPQuery->Set($name, $value); } $this->_initAll(); // also will use parsed language to load phrases from it $this->HTTPQuery->finalizeParsing($passed); } /** * Returns url parsing result from cache or false, when not yet parsed * * @param $url * @return Array|bool * @access protected */ protected function _getCachedUrl($url) { if (!$url) { return false; } $sql = 'SELECT * FROM ' . TABLE_PREFIX . 'CachedUrls WHERE Hash = ' . crc32($url) . ' AND DomainId = ' . (int)$this->Application->siteDomainField('DomainId'); $data = $this->Conn->GetRow($sql); if ($data) { $lifetime = (int)$data['LifeTime']; // in seconds if (($lifetime > 0) && ($data['Cached'] + $lifetime < adodb_mktime())) { // delete expired $sql = 'DELETE FROM ' . TABLE_PREFIX . 'CachedUrls WHERE UrlId = ' . $data['UrlId']; $this->Conn->Query($sql); return false; } return unserialize($data['ParsedVars']); } return false; } /** * Caches url * * @param string $url * @param Array $data * @return void * @access protected */ protected function _setCachedUrl($url, $data) { if (!$url) { return ; } $vars = $data['vars']; $passed = $data['passed']; sort($passed); // get expiration if ($vars['m_cat_id'] > 0) { $sql = 'SELECT PageExpiration FROM ' . TABLE_PREFIX . 'Category WHERE CategoryId = ' . $vars['m_cat_id']; $expiration = $this->Conn->GetOne($sql); } // get prefixes $prefixes = Array (); $m_index = array_search('m', $passed); if ($m_index !== false) { unset($passed[$m_index]); if ($vars['m_cat_id'] > 0) { $prefixes[] = 'c:' . $vars['m_cat_id']; } $prefixes[] = 'lang:' . $vars['m_lang']; $prefixes[] = 'theme:' . $vars['m_theme']; } foreach ($passed as $prefix) { if (array_key_exists($prefix . '_id', $vars) && is_numeric($vars[$prefix . '_id'])) { $prefixes[] = $prefix . ':' . $vars[$prefix . '_id']; } else { $prefixes[] = $prefix; } } $fields_hash = Array ( 'Url' => $url, 'Hash' => crc32($url), 'DomainId' => (int)$this->Application->siteDomainField('DomainId'), 'Prefixes' => $prefixes ? '|' . implode('|', $prefixes) . '|' : '', 'ParsedVars' => serialize($data), 'Cached' => adodb_mktime(), 'LifeTime' => isset($expiration) && is_numeric($expiration) ? $expiration : -1 ); $this->Conn->doInsert($fields_hash, TABLE_PREFIX . 'CachedUrls'); } /** * Loads all registered rewrite listeners, so they could be quickly accessed later * * @access protected */ protected function _initRewriteListeners() { static $init_done = false; if ($init_done || count($this->Application->RewriteListeners) == 0) { // not initialized OR mod-rewrite url with missing config cache return ; } foreach ($this->Application->RewriteListeners as $prefix => $listener_data) { foreach ($listener_data['listener'] as $index => $rewrite_listener) { list ($listener_prefix, $listener_method) = explode(':', $rewrite_listener); - $listener =& $this->Application->recallObject($listener_prefix); - $this->Application->RewriteListeners[$prefix][$index] = Array (&$listener, $listener_method); + // don't use temp variable, since it will swap objects in Factory in PHP5 + $this->rewriteListeners[$prefix][$index] = Array (); + $this->rewriteListeners[$prefix][$index][0] =& $this->Application->recallObject($listener_prefix); + $this->rewriteListeners[$prefix][$index][1] = $listener_method; } } define('MOD_REWRITE_URL_ENDING', $this->Application->ConfigValue('ModRewriteUrlEnding')); $init_done = true; } /** * Parses given string into a set of variables (url in this case) * * @param string $string * @param string $pass_name * @return Array * @access public */ public function parse($string, $pass_name = 'pass') { $vars = Array ($pass_name => Array ('m')); $url_parts = $string ? explode('/', trim(mb_strtolower($string, 'UTF-8'), '/')) : Array (); $this->_partsToParse = $url_parts; if ( ($this->HTTPQuery->Get('rewrite') == 'on') || !$url_parts ) { $this->_setDefaultValues($vars); } if ( !$url_parts ) { $this->_initAll(); $vars['t'] = $this->Application->UrlManager->getTemplateName(); return $vars; } $this->_parseLanguage($url_parts, $vars); $this->_parseTheme($url_parts, $vars); // http://site-url/<language>/<theme>/<category>[_<category_page>]/<template>/<module_page> // http://site-url/<language>/<theme>/<category>[_<category_page>]/<module_page> (category-based section template) // http://site-url/<language>/<theme>/<category>[_<category_page>]/<template>/<module_item> // http://site-url/<language>/<theme>/<category>[_<category_page>]/<module_item> (category-based detail template) // http://site-url/<language>/<theme>/<rl_injections>/<category>[_<category_page>]/<rl_part> (customized url) if ( $this->_processRewriteListeners($url_parts, $vars) ) { return $vars; } $this->_parsePhysicalTemplate($url_parts, $vars); if ( ($this->modulePrefix === false) && $vars['m_cat_id'] && !$this->_partsToParse ) { // no category item found, but category found and all url matched -> module index page return $vars; } if ( $this->_partsToParse ) { $not_found = $this->Application->ConfigValue('ErrorTemplate'); $vars['t'] = $not_found ? $not_found : 'error_notfound'; $themes_helper =& $this->Application->recallObject('ThemesHelper'); /* @var $themes_helper kThemesHelper */ $vars['m_cat_id'] = $themes_helper->getPageByTemplate($vars['t'], $vars['m_theme']); header('HTTP/1.0 404 Not Found'); } return $vars; } /** * Initializes theme & language based on parse results * * @return void * @access protected */ protected function _initAll() { $this->Application->VerifyThemeId(); $this->Application->VerifyLanguageId(); // no need, since we don't have any cached phrase IDs + nobody will use PhrasesCache::LanguageId soon // $this->Application->Phrases->Init('phrases'); } /** * Sets default parsed values before actual url parsing (only, for empty url) * * @param Array $vars * @access protected */ protected function _setDefaultValues(&$vars) { $defaults = Array ( 'm_cat_id' => 0, // no category 'm_cat_page' => 1, // first category page 'm_opener' => 's', // stay on same page 't' => 'index' // main site page ); if ($this->primaryLanguageId) { // domain-based primary language $defaults['m_lang'] = $this->primaryLanguageId; } if ($this->primaryThemeId) { // domain-based primary theme $defaults['m_theme'] = $this->primaryThemeId; } foreach ($defaults as $default_key => $default_value) { if ($this->HTTPQuery->Get($default_key) === false) { $vars[$default_key] = $default_value; } } } /** * Processes url using rewrite listeners * * Pattern: Chain of Command * * @param Array $url_parts * @param Array $vars * @return bool * @access protected */ protected function _processRewriteListeners(&$url_parts, &$vars) { $this->_initRewriteListeners(); $page_number = $this->_parsePage($url_parts, $vars); - foreach ($this->Application->RewriteListeners as $prefix => $listeners) { + foreach ($this->rewriteListeners as $prefix => $listeners) { // set default page // $vars[$prefix . '_Page'] = 1; // will override page in session in case, when none is given in url if ($page_number) { // page given in url - use it $vars[$prefix . '_id'] = 0; $vars[$prefix . '_Page'] = $page_number; } // $listeners[1] - listener, used for parsing $listener_result = $listeners[1][0]->$listeners[1][1](REWRITE_MODE_PARSE, $prefix, $vars, $url_parts); if ($listener_result === false) { // will not proceed to other methods return true; } } // will proceed to other methods return false; } /** * Set's page (when found) to all modules * * @param Array $url_parts * @param Array $vars * @return string * @access protected * * @todo Should find a way, how to determine what rewrite listerner page is it */ protected function _parsePage(&$url_parts, &$vars) { if (!$url_parts) { return false; } $page_number = end($url_parts); if (!is_numeric($page_number)) { return false; } array_pop($url_parts); $this->partParsed($page_number, 'rtl'); return $page_number; } /** * Gets language part from url * * @param Array $url_parts * @param Array $vars * @return bool * @access protected */ protected function _parseLanguage(&$url_parts, &$vars) { if (!$url_parts) { return false; } $url_part = reset($url_parts); $sql = 'SELECT LanguageId, IF(LOWER(PackName) = ' . $this->Conn->qstr($url_part) . ', 2, PrimaryLang) AS SortKey FROM ' . TABLE_PREFIX . 'Language WHERE Enabled = 1 ORDER BY SortKey DESC'; $language_info = $this->Conn->GetRow($sql); if ($language_info && $language_info['LanguageId'] && $language_info['SortKey']) { // primary language will be selected in case, when $url_part doesn't match to other's language pack name // don't use next enabled language, when primary language is disabled $vars['m_lang'] = $language_info['LanguageId']; if ($language_info['SortKey'] == 2) { // language was found by pack name array_shift($url_parts); $this->partParsed($url_part); } elseif ($this->primaryLanguageId) { // use domain-based primary language instead of site-wide primary language $vars['m_lang'] = $this->primaryLanguageId; } return true; } return false; } /** * Gets theme part from url * * @param Array $url_parts * @param Array $vars * @return bool */ protected function _parseTheme(&$url_parts, &$vars) { if (!$url_parts) { return false; } $url_part = reset($url_parts); $sql = 'SELECT ThemeId, IF(LOWER(Name) = ' . $this->Conn->qstr($url_part) . ', 2, PrimaryTheme) AS SortKey, TemplateAliases FROM ' . TABLE_PREFIX . 'Theme WHERE Enabled = 1 ORDER BY SortKey DESC'; $theme_info = $this->Conn->GetRow($sql); if ($theme_info && $theme_info['ThemeId'] && $theme_info['SortKey']) { // primary theme will be selected in case, when $url_part doesn't match to other's theme name // don't use next enabled theme, when primary theme is disabled $vars['m_theme'] = $theme_info['ThemeId']; if ($theme_info['TemplateAliases']) { $this->_templateAliases = unserialize($theme_info['TemplateAliases']); } else { $this->_templateAliases = Array (); } if ($theme_info['SortKey'] == 2) { // theme was found by name array_shift($url_parts); $this->partParsed($url_part); } elseif ($this->primaryThemeId) { // use domain-based primary theme instead of site-wide primary theme $vars['m_theme'] = $this->primaryThemeId; } return true; } $vars['m_theme'] = 0; // required, because used later for category/template detection return false; } /** * Parses real template name from url * * @param Array $url_parts * @param Array $vars * @return bool */ protected function _parsePhysicalTemplate($url_parts, &$vars) { if ( !$url_parts ) { return false; } $themes_helper =& $this->Application->recallObject('ThemesHelper'); /* @var $themes_helper kThemesHelper */ do { $template_path = implode('/', $url_parts); $template_found = $themes_helper->getTemplateId($template_path, $vars['m_theme']); if ( !$template_found ) { array_shift($url_parts); } } while ( !$template_found && $url_parts ); if ( $template_found ) { $vars['t'] = $template_path; $template_parts = explode('/', $template_path); while ( $template_parts ) { $this->partParsed(array_pop($template_parts), 'rtl'); } // 1. will damage actual category during category item review add process // 2. will use "use_section" parameter of "m_Link" tag to gain same effect // $vars['m_cat_id'] = $themes_helper->getPageByTemplate($template_path, $vars['m_theme']); return true; } return false; } /** * Returns environment variable values for given prefix (uses directly given params, when available) * * @param string $prefix_special * @param Array $params * @param bool $keep_events * @return Array * @access public */ public function getProcessedParams($prefix_special, &$params, $keep_events) { list ($prefix) = explode('.', $prefix_special); $query_vars = $this->Application->getUnitOption($prefix, 'QueryString', Array ()); /* @var $query_vars Array */ if ( !$query_vars ) { // given prefix doesn't use "env" variable to pass it's data return false; } $event_key = array_search('event', $query_vars); if ( $event_key ) { // pass through event of this prefix unset($query_vars[$event_key]); } if ( array_key_exists($prefix_special . '_event', $params) && !$params[$prefix_special . '_event'] ) { // if empty event, then remove it from url unset($params[$prefix_special . '_event']); } // if pass events is off and event is not implicity passed if ( !$keep_events && !array_key_exists($prefix_special . '_event', $params) ) { unset($params[$prefix_special . '_event']); // remove event from url if requested //otherwise it will use value from get_var } $processed_params = Array (); foreach ($query_vars as $var_name) { // if value passed in params use it, otherwise use current from application $var_name = $prefix_special . '_' . $var_name; $processed_params[$var_name] = array_key_exists($var_name, $params) ? $params[$var_name] : $this->Application->GetVar($var_name); if ( array_key_exists($var_name, $params) ) { unset($params[$var_name]); } } return $processed_params; } /** * Returns module item details template specified in given category custom field for given module prefix * * @param int|Array $category * @param string $module_prefix * @return string * @access public * @todo Move to kPlainUrlProcessor */ public function GetItemTemplate($category, $module_prefix) { $category_id = is_array($category) ? $category['CategoryId'] : $category; $cache_key = __CLASS__ . '::' . __FUNCTION__ . '[%CIDSerial:' . $category_id . '%]:' . $module_prefix; $cached_value = $this->Application->getCache($cache_key); if ( $cached_value !== false ) { return $cached_value; } if ( !is_array($category) ) { if ( $category == 0 ) { $category = $this->Application->findModule('Var', $module_prefix, 'RootCat'); } $sql = 'SELECT c.ParentPath, c.CategoryId FROM ' . TABLE_PREFIX . 'Category AS c WHERE c.CategoryId = ' . $category; $category = $this->Conn->GetRow($sql); } $parent_path = implode(',', explode('|', substr($category['ParentPath'], 1, -1))); // item template is stored in module' system custom field - need to get that field Id $primary_lang = $this->Application->GetDefaultLanguageId(); $item_template_field_id = $this->getItemTemplateCustomField($module_prefix); // looking for item template through cats hierarchy sorted by parent path $query = ' SELECT ccd.l' . $primary_lang . '_cust_' . $item_template_field_id . ', FIND_IN_SET(c.CategoryId, ' . $this->Conn->qstr($parent_path) . ') AS Ord1, c.CategoryId, c.Name, ccd.l' . $primary_lang . '_cust_' . $item_template_field_id . ' FROM ' . TABLE_PREFIX . 'Category AS c LEFT JOIN ' . TABLE_PREFIX . 'CategoryCustomData AS ccd ON ccd.ResourceId = c.ResourceId WHERE c.CategoryId IN (' . $parent_path . ') AND ccd.l' . $primary_lang . '_cust_' . $item_template_field_id . ' != \'\' ORDER BY FIND_IN_SET(c.CategoryId, ' . $this->Conn->qstr($parent_path) . ') DESC'; $item_template = $this->Conn->GetOne($query); if ( !isset($this->_templateAliases) ) { // when empty url OR mod-rewrite disabled $themes_helper =& $this->Application->recallObject('ThemesHelper'); /* @var $themes_helper kThemesHelper */ $sql = 'SELECT TemplateAliases FROM ' . TABLE_PREFIX . 'Theme WHERE ThemeId = ' . (int)$themes_helper->getCurrentThemeId(); $template_aliases = $this->Conn->GetOne($sql); $this->_templateAliases = $template_aliases ? unserialize($template_aliases) : Array (); } if ( substr($item_template, 0, 1) == '#' ) { // it's template alias + "#" isn't allowed in filenames $item_template = (string)getArrayValue($this->_templateAliases, $item_template); } $this->Application->setCache($cache_key, $item_template); return $item_template; } /** * Returns category custom field id, where given module prefix item template name is stored * * @param string $module_prefix * @return int * @access public * @todo Move to kPlainUrlProcessor; decrease visibility, since used only during upgrade */ public function getItemTemplateCustomField($module_prefix) { $cache_key = __CLASS__ . '::' . __FUNCTION__ . '[%CfSerial%]:' . $module_prefix; $cached_value = $this->Application->getCache($cache_key); if ($cached_value !== false) { return $cached_value; } $sql = 'SELECT CustomFieldId FROM ' . TABLE_PREFIX . 'CustomField WHERE FieldName = ' . $this->Conn->qstr($module_prefix . '_ItemTemplate'); $item_template_field_id = $this->Conn->GetOne($sql); $this->Application->setCache($cache_key, $item_template_field_id); return $item_template_field_id; } /** * Marks url part as parsed * * @param string $url_part * @param string $parse_direction * @access public */ public function partParsed($url_part, $parse_direction = 'ltr') { if ( !$this->_partsToParse ) { return ; } if ( $parse_direction == 'ltr' ) { $expected_url_part = reset($this->_partsToParse); if ( $url_part == $expected_url_part ) { array_shift($this->_partsToParse); } } else { $expected_url_part = end($this->_partsToParse); if ( $url_part == $expected_url_part ) { array_pop($this->_partsToParse); } } if ( $url_part != $expected_url_part ) { trigger_error('partParsed: expected URL part "<strong>' . $expected_url_part . '</strong>", received URL part "<strong>' . $url_part . '</strong>"', E_USER_NOTICE); } } /** * 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 build($t, $params, $pass = 'all', $pass_events = false, $env_var = false) { if ( $this->Application->GetVar('admin') || (array_key_exists('admin', $params) && $params['admin']) ) { $params['admin'] = 1; if ( !array_key_exists('editing_mode', $params) ) { $params['editing_mode'] = EDITING_MODE; } } $ret = ''; $env = ''; $encode = false; if ( isset($params['__URLENCODE__']) ) { $encode = $params['__URLENCODE__']; unset($params['__URLENCODE__']); } if ( isset($params['__SSL__']) ) { unset($params['__SSL__']); } $catalog_item_found = false; $pass_info = $this->getPassInfo($pass); if ( $pass_info ) { if ( $pass_info[0] == 'm' ) { array_shift($pass_info); } $inject_parts = Array (); // url parts for beginning of url $params['t'] = $t; // make template available for rewrite listeners $params['pass_template'] = true; // by default we keep given template in resulting url if ( !array_key_exists('pass_category', $params) ) { $params['pass_category'] = false; // by default we don't keep categories in url } foreach ($pass_info as $pass_index => $pass_element) { list ($prefix) = explode('.', $pass_element); $catalog_item = $this->Application->findModule('Var', $prefix) && $this->Application->getUnitOption($prefix, 'CatalogItem'); - if ( array_key_exists($prefix, $this->Application->RewriteListeners) ) { + if ( array_key_exists($prefix, $this->rewriteListeners) ) { // if next prefix is same as current, but with special => exclude current prefix from url $next_prefix = array_key_exists($pass_index + 1, $pass_info) ? $pass_info[$pass_index + 1] : false; if ( $next_prefix ) { $next_prefix = substr($next_prefix, 0, strlen($prefix) + 1); if ( $prefix . '.' == $next_prefix ) { continue; } } // rewritten url part $url_part = $this->BuildModuleEnv($pass_element, $params, $pass_events); if ( is_string($url_part) && $url_part ) { $ret .= $url_part . '/'; if ( $catalog_item ) { // pass category later only for catalog items $catalog_item_found = true; } } elseif ( is_array($url_part) ) { // rewrite listener want to insert something at the beginning of url too if ( $url_part[0] ) { $inject_parts[] = $url_part[0]; } if ( $url_part[1] ) { $ret .= $url_part[1] . '/'; } if ( $catalog_item ) { // pass category later only for catalog items $catalog_item_found = true; } } elseif ( $url_part === false ) { // rewrite listener decided not to rewrite given $pass_element $env .= ':' . $this->manager->plain->BuildModuleEnv($pass_element, $params, $pass_events); } } else { $env .= ':' . $this->manager->plain->BuildModuleEnv($pass_element, $params, $pass_events); } } if ( $catalog_item_found || preg_match('/c\.[-\d]*/', implode(',', $pass_info)) ) { // "c" prefix is present -> keep category $params['pass_category'] = true; } $params['inject_parts'] = $inject_parts; $ret = $this->BuildModuleEnv('m', $params, $pass_events) . '/' . $ret; $cat_processed = array_key_exists('category_processed', $params) && $params['category_processed']; // remove temporary parameters used by listeners unset($params['t'], $params['inject_parts'], $params['pass_template'], $params['pass_category'], $params['category_processed']); $ret = trim($ret, '/'); if ( isset($params['url_ending']) ) { if ( $ret ) { $ret .= $params['url_ending']; } unset($params['url_ending']); } elseif ( $ret ) { $ret .= MOD_REWRITE_URL_ENDING; } if ( $env ) { $params[ENV_VAR_NAME] = ltrim($env, ':'); } } unset($params['pass'], $params['opener'], $params['m_event']); if ( array_key_exists('escape', $params) && $params['escape'] ) { $ret = addslashes($ret); unset($params['escape']); } $ret = str_replace('%2F', '/', urlencode($ret)); if ( $params ) { $params_str = ''; $join_string = $encode ? '&' : '&'; foreach ($params as $param => $value) { $params_str .= $join_string . $param . '=' . $value; } $ret .= '?' . substr($params_str, strlen($join_string)); } if ( $encode ) { $ret = str_replace('\\', '%5C', $ret); } return $ret; } /** * Builds env part that corresponds prefix passed * * @param string $prefix_special item's prefix & [special] * @param Array $params url params * @param bool $pass_events * @return string * @access protected */ protected function BuildModuleEnv($prefix_special, &$params, $pass_events = false) { list ($prefix) = explode('.', $prefix_special); $url_parts = Array (); - $listener = $this->Application->RewriteListeners[$prefix][0]; + $listener = $this->rewriteListeners[$prefix][0]; $ret = $listener[0]->$listener[1](REWRITE_MODE_BUILD, $prefix_special, $params, $url_parts, $pass_events); return $ret; } } \ No newline at end of file Index: branches/5.2.x/core/kernel/utility/unit_config_reader.php =================================================================== --- branches/5.2.x/core/kernel/utility/unit_config_reader.php (revision 14786) +++ branches/5.2.x/core/kernel/utility/unit_config_reader.php (revision 14787) @@ -1,1061 +1,1065 @@ <?php /** * @version $Id$ * @package In-Portal * @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved. * @license GNU/GPL * In-Portal is Open Source software. * This means that this software may have been modified pursuant * the GNU General Public License, and as distributed it includes * or is derivative of works licensed under the GNU General Public License * or other free or open source software licenses. * See http://www.in-portal.org/license for copyright notices and details. */ defined('FULL_PATH') or die('restricted access!'); class kUnitConfigReader extends kBase implements kiCacheable { /** * Configs reader * * @var Array * @access private */ var $configData = Array(); var $configFiles = Array(); var $CacheExpired = false; var $prefixFiles = array(); var $ProcessAllConfigs = false; var $FinalStage = false; var $StoreCache = false; var $AfterConfigProcessed = array(); /** * Escaped directory separator for using in regular expressions * * @var string */ var $_directorySeparator = ''; /** * Regular expression for detecting module folder * * @var string */ var $_moduleFolderRegExp = ''; /** * Folders to skip during unit config search * * @var Array */ var $_skipFolders = Array ('CVS', '.svn', 'admin_templates', 'libchart'); /** * Creates instance of unit config reader * */ public function __construct() { parent::__construct(); $this->_directorySeparator = preg_quote(DIRECTORY_SEPARATOR); $editor_path = explode('/', trim(EDITOR_PATH, '/')); $this->_skipFolders[] = array_pop($editor_path); // last of cmseditor folders $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) { + 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(); } function findConfigFiles($folderPath, $level = 0) { // if FULL_PATH = "/" ensure, that all "/" in $folderPath are not deleted $reg_exp = '/^' . preg_quote(FULL_PATH, '/') . '/'; $folderPath = preg_replace($reg_exp, '', $folderPath, 1); // this make sense, since $folderPath may NOT contain FULL_PATH $base_folder = FULL_PATH . $folderPath . DIRECTORY_SEPARATOR; $sub_folders = glob($base_folder . '*', GLOB_ONLYDIR); if (!$sub_folders) { return ; } if ($level == 0) { // don't scan Front-End themes because of extensive directory structure $sub_folders = array_diff($sub_folders, Array ($base_folder . 'themes', $base_folder . 'tools')); } foreach ($sub_folders as $full_path) { $sub_folder = substr($full_path, strlen($base_folder)); if (in_array($sub_folder, $this->_skipFolders)) { continue; } if (preg_match('/^\./', $sub_folder)) { // 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) { $this->Application->refreshModuleInfo(); 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 ( $cache ) { if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) { $this->Application->rebuildCache('master:config_files', kCache::REBUILD_NOW, CacheSettings::$unitCacheRebuildTime); } else { $this->Application->rebuildDBCache('config_files', kCache::REBUILD_NOW, CacheSettings::$unitCacheRebuildTime); } } $this->findConfigFiles(FULL_PATH . DIRECTORY_SEPARATOR . 'core'); // search from core directory $this->findConfigFiles($folderPath); // search from modules directory if ( $cache ) { if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) { $this->Application->setCache('master:config_files', serialize($this->configFiles)); } else { $this->Application->setDBCache('config_files', serialize($this->configFiles)); } } } foreach ($this->configFiles as $filename) { $prefix = $this->PreloadConfigFile($filename); if (!$prefix) { throw new Exception('Prefix not defined in config file <strong>' . $filename . '</strong>'); } } if ($cache) { unset($this->configFiles); } } /** * Process all read config files - called ONLY when there is no cache! * */ function ParseConfigs() { // 1. process normal configs and their dependencies $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->ProcessDependencies($prefix); $this->postProcessConfig($prefix, 'AggregateConfigs', 'sub_prefix'); $clones = $this->postProcessConfig($prefix, 'Clones', 'prefix'); } // 2. process prioritized configs and their dependencies asort($prioritized_configs); foreach ($prioritized_configs as $prefix => $priority) { $this->parseConfig($prefix); } foreach ($prioritized_configs as $prefix => $priority) { $this->ProcessDependencies($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(); $after_event = new kEvent('adm:OnAfterCacheRebuild'); $this->Application->HandleEvent($after_event); $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() { $this->Application->cacheManager->EmptyUnitCache(); // 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 collectable unit config options here to also catch ones, defined from OnAfterConfigRead events + * 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->parseAgents($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) { // remember class dependencies $class_name = $class_info['class']; $require_classes = isset($class_info['require_classes']) ? $class_info['require_classes'] : Array (); if ($require_classes) { $require_classes = (array)$require_classes; if ( !isset($config['_Dependencies'][$class_name]) ) { $config['_Dependencies'][$class_name] = Array (); } $config['_Dependencies'][$class_name] = array_merge($config['_Dependencies'][$class_name], $require_classes); } // register class $this->Application->registerClass( $class_name, $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 parseAgents($prefix) { $config =& $this->configData[$prefix]; if ( !isset($config['RegularEvents']) || !$config['RegularEvents'] ) { return ; } $regular_events = $config['RegularEvents']; foreach ($regular_events as $short_name => $regular_event_info) { $event_status = array_key_exists('Status', $regular_event_info) ? $regular_event_info['Status'] : STATUS_ACTIVE; $this->Application->delayUnitProcessing('registerAgent', Array ( $short_name, $config['Prefix'] . ':' . $regular_event_info['EventName'], $regular_event_info['RunInterval'], $regular_event_info['Type'], $event_status )); } } protected function parseHooks($prefix) { $config =& $this->configData[$prefix]; if ( !isset($config['Hooks']) || !$config['Hooks'] ) { return ; } $hooks = $config['Hooks']; foreach ($hooks as $hook) { if ( isset($config['ParentPrefix']) && ($hook['HookToPrefix'] == $config['ParentPrefix']) ) { trigger_error('Depricated Hook Usage [prefix: <strong>' . $config['Prefix'] . '</strong>; do_prefix: <strong>' . $hook['DoPrefix'] . '</strong>] use <strong>#PARENT#</strong> as <strong>HookToPrefix</strong> 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: <b>'.$config['Prefix'].'</b>; AggregateTo: <b>'.$aggregate_tag['AggregateTo'].'</b>] use <b>#PARENT#</b> as <b>AggregateTo</b> 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("<b class='debug_error'>Config Warning: </b>Table <strong>$tablename</strong> missing, but prefix <b>".$config['Prefix']."</b> 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 <strong>%s</strong> exists in the database, but <strong>is not defined</strong> in config', 'default_missing' => 'Default value for field <strong>%s</strong> not set in config', 'not_null_error1' => 'Field <strong>%s</strong> is NOT NULL in the database, but is not configured as not_null', // or required', 'not_null_error2' => 'Field <strong>%s</strong> is described as NOT NULL in config, but <strong>does not have DEFAULT value</strong>', 'not_null_error3' => 'Field <strong>%s</strong> is described as <strong>NOT NULL in config</strong>, but is <strong>NULL in db</strong>', 'invalid_default' => '<strong>Default value</strong> for field %s<strong>%s</strong> not sync. to db (in config = %s, in db = %s)', 'date_column_not_null_error' => 'Field <strong>%s</strong> must be NULL in config and database, since it contains date', 'user_column_default_error' => 'Field <strong>%s</strong> must be have NULL as default value, since it holds user id', 'type_missing' => '<strong>Type definition</strong> for field <strong>%s</strong> missing in config', 'virtual_type_missing' => '<strong>Type definition</strong> for virtual field <strong>%s</strong> missing in config', 'virtual_default_missing' => 'Default value for virtual field <strong>%s</strong> not set in config', 'virtual_not_null_error' => 'Virtual field <strong>%s</strong> cannot be not null, since it doesn\'t exist in database', 'invalid_calculated_field' => 'Calculated field <strong>%s</strong> 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'], '<span class="debug_error">IDField</span> ', $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 = '<strong class="debug_error">Config Error'.(count($config_errors) > 1 ? 's' : '').': </strong> for prefix <strong>'.$config_link.'</strong> ('.$tablename.') in unit config:<br />'; $config_errors = $error_prefix.' '.implode('<br /> ', $config_errors); kUtil::safeDefine('DBG_RAISE_ON_WARNINGS', 1); $debugger->appendHTML($config_errors); $debugger->WarningCount++; } } function varDump($value) { return '<strong>'.var_export($value, true).'</strong> of '.gettype($value); } protected function ProcessDependencies($prefix) { $config =& $this->configData[$prefix]; $dependencies = getArrayValue($config, '_Dependencies'); /* @var $dependencies Array */ if ( !$dependencies ) { return ; } foreach ($dependencies as $real_class => $requires) { foreach ($requires as $class) { $this->Application->registerDependency($real_class, $class); } } unset($config['_Dependencies']); } 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 "<strong>' . $prefix . '</strong>" ' . 'is used in multiple unit config files: ' . '"<strong>' . $this->prefixFiles[$prefix] . '</strong>", "<strong>' . $filename . '</strong>"', 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 <strong>' . $prefix . '</strong> is unknown'); return ; } $file = $this->prefixFiles[$prefix]; $prefix = $this->PreloadConfigFile($file); if ($this->FinalStage) { // run prefix OnAfterConfigRead so all // hooks to it can define their clonses $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) { $callback_function[0]->$callback_function[1]($prefix, $config_data, $params); } } } \ No newline at end of file Index: branches/5.2.x/core/units/configuration/configuration_event_handler.php =================================================================== --- branches/5.2.x/core/units/configuration/configuration_event_handler.php (revision 14786) +++ branches/5.2.x/core/units/configuration/configuration_event_handler.php (revision 14787) @@ -1,299 +1,292 @@ <?php /** * @version $Id$ * @package In-Portal * @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved. * @license GNU/GPL * In-Portal is Open Source software. * This means that this software may have been modified pursuant * the GNU General Public License, and as distributed it includes * or is derivative of works licensed under the GNU General Public License * or other free or open source software licenses. * See http://www.in-portal.org/license for copyright notices and details. */ defined('FULL_PATH') or die('restricted access!'); class ConfigurationEventHandler extends kDBEventHandler { /** * Changes permission section to one from REQUEST, not from config * * @param kEvent $event * @return bool * @access public */ public function CheckPermission(&$event) { $event->setEventParam('PermSection', $this->Application->GetVar('section')); return parent::CheckPermission($event); } /** * Apply any custom changes to list's sql query * * @param kEvent $event * @return void * @access protected * @see kDBEventHandler::OnListBuild() */ protected function SetCustomQuery(&$event) { $object =& $event->getObject(); /* @var $object kDBList */ $module = $this->Application->GetVar('module'); $section = $this->Application->GetVar('section'); $object->addFilter('module_filter', '%1$s.ModuleOwner = '.$this->Conn->qstr($module)); $object->addFilter('section_filter', '%1$s.Section = '.$this->Conn->qstr($section)); if (!$this->Application->ConfigValue('AllowAdminConsoleInterfaceChange')) { $object->addFilter('interface_change_filter', '%1$s.VariableName <> "AdminConsoleInterface"'); } if (defined('IS_INSTALL') && IS_INSTALL) { $object->addFilter('install_filter', '%1$s.Install = 1'); } $object->addFilter('visible_filter', '%1$s.Heading <> ""'); } /** * Performs validation of configuration variable value * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemUpdate(&$event) { static $default_field_options = null; parent::OnBeforeItemUpdate($event); $object =& $event->getObject(); /* @var $object kDBItem */ // ability to validate each configuration variable separately if ( !isset($default_field_options) ) { $default_field_options = $object->GetFieldOptions('VariableValue'); } $new_field_options = $default_field_options; $validation = $object->GetDBField('Validation'); if ( $validation ) { $new_field_options = array_merge($new_field_options, unserialize($validation)); } $object->SetFieldOptions('VariableValue', $new_field_options); // if password field is empty, then don't update if ( $object->GetDBField('ElementType') == 'password' ) { if ( trim($object->GetDBField('VariableValue')) == '' ) { $field_options = $object->GetFieldOptions('VariableValue'); $field_options['skip_empty'] = 1; $object->SetFieldOptions('VariableValue', $field_options); } else { $password_formatter =& $this->Application->recallObject('kPasswordFormatter'); /* @var $password_formatter kPasswordFormatter */ $object->SetDBField('VariableValue', $password_formatter->EncryptPassword($object->GetDBField('VariableValue'), 'b38')); } } $field_name = $object->GetDBField('VariableName'); $field_values = $this->Application->GetVar($event->getPrefixSpecial(true)); $state_country_hash = Array ('Comm_State' => 'Comm_Country', 'Comm_Shipping_State' => 'Comm_Shipping_Country'); if ( array_key_exists($field_name, $state_country_hash) ) { // if this is state field $sql = 'SELECT VariableId FROM ' . $this->Application->getUnitOption('conf', 'TableName') . ' WHERE VariableName = "' . $state_country_hash[$field_name] . '"'; $country_variable_id = $this->Conn->GetOne($sql); $check_state = $object->GetDBField('VariableValue'); $check_country = $field_values[$country_variable_id]['VariableValue']; if ( !$check_country || !$check_state ) { return; } $cs_helper =& $this->Application->recallObject('CountryStatesHelper'); /* @var $cs_helper kCountryStatesHelper */ $state_iso = $cs_helper->getStateIso($check_state, $check_country); if ( $state_iso !== false ) { $object->SetDBField('VariableValue', $state_iso); } else { // selected state doesn't belong to selected country $object->SetError('VariableValue', 'invalid_state', 'la_InvalidState'); } } if ( $object->GetDBField('VariableName') == 'AdminConsoleInterface' ) { $can_change = $this->Application->ConfigValue('AllowAdminConsoleInterfaceChange'); if ( ($object->GetDBField('VariableValue') != $object->GetOriginalField('VariableValue')) && !$can_change ) { $object->SetError('VariableValue', 'not_allowed', 'la_error_OperationNotAllowed'); } } } /** * Enter description here... * * @param kEvent $event */ function OnAfterItemUpdate(&$event) { + static $skin_deleted = false; + $object =& $event->getObject(); /* @var $object kDBItem */ - if ($object->GetDBField('ElementType') == 'password') { - if (trim($object->GetDBField('VariableValue')) == '') { + if ( $object->GetDBField('ElementType') == 'password' ) { + if ( trim($object->GetDBField('VariableValue')) == '' ) { $field_options = $object->GetFieldOptions('VariableValue'); unset($field_options['skip_empty']); $object->SetFieldOptions('VariableValue', $field_options); } } // allows to check if variable's value was changed now $variable_name = $object->GetDBField('VariableName'); - $variable_value = $object->GetDBField('VariableValue'); - $watch_variables = Array ( - 'Require_AdminSSL', 'AdminSSL_URL', 'AdvancedUserManagement', - 'Site_Name', 'AdminConsoleInterface', 'UsePopups' - ); + $changed = $this->Application->GetVar($event->getPrefixSpecial() . '_changed', Array ()); - if (in_array($variable_name, $watch_variables)) { - $changed = $this->Application->GetVar($event->getPrefixSpecial() . '_changed', Array ()); + if ( $object->GetDBField('VariableValue') != $object->GetOriginalField('VariableValue') ) { + $changed[] = $variable_name; + $this->Application->SetVar($event->getPrefixSpecial() . '_changed', $changed); + } + + if ( $variable_name == 'Require_AdminSSL' || $variable_name == 'AdminSSL_URL' ) { + // when administrative console is moved to SSL mode, then delete skin + if ( in_array($variable_name, $changed) && !$skin_deleted ) { + $skin_helper =& $this->Application->recallObject('SkinHelper'); + /* @var $skin_helper SkinHelper */ + + $skin_file = $skin_helper->getSkinPath(); + if ( file_exists($skin_file) ) { + unlink($skin_file); + } - if ($variable_value != $object->GetOriginalField('VariableValue')) { - $changed[] = $variable_name; - $this->Application->SetVar($event->getPrefixSpecial() . '_changed', $changed); - } - - switch ($variable_name) { - case 'Require_AdminSSL': - case 'AdminSSL_URL': - static $skin_deleted = false; - - if (in_array($variable_name, $changed) && !$skin_deleted) { - // when administrative console is moved to SSL mode, then delete skin - $skin_helper =& $this->Application->recallObject('SkinHelper'); - /* @var $skin_helper SkinHelper */ - - $skin_file = $skin_helper->getSkinPath(); - if (file_exists($skin_file)) { - unlink($skin_file); - } - - $skin_deleted = true; - } - break; + $skin_deleted = true; } } $this->Application->StoreVar('config_was_updated', 1); } /** * Enter description here... * * @param kEvent $event */ function OnUpdate(&$event) { - if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) { + if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) { $event->status = kEvent::erFAIL; - return ; + return; } - $items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) ); + $items_info = $this->Application->GetVar($event->getPrefixSpecial(true)); // 1. save user selected module root category $new_category_id = getArrayValue($items_info, 'ModuleRootCategory', 'VariableValue'); - if ($new_category_id !== false) { + if ( $new_category_id !== false ) { unset($items_info['ModuleRootCategory']); } $object =& $event->getObject( Array('skip_autoload' => true) ); /* @var $object kDBItem */ - if ($items_info) { + if ( $items_info ) { $has_error = false; + foreach ($items_info as $id => $field_values) { $object->Clear(); // clear validation errors from previous variable $object->Load($id); - $object->SetFieldsFromHash($field_values); + $object->SetFieldsFromHash($field_values); - if (!$object->Update($id)) { + if ( !$object->Update($id) ) { // don't stop when error found ! $has_error = true; } } $event->status = $has_error ? kEvent::erFAIL : kEvent::erSUCCESS; } - if ($event->status == kEvent::erSUCCESS) { - if ($new_category_id !== false) { + if ( $event->status == kEvent::erSUCCESS ) { + if ( $new_category_id !== false ) { // root category was submitted $module = $this->Application->GetVar('module'); $root_category_id = $this->Application->findModule('Name', $module, 'RootCat'); - if ($root_category_id != $new_category_id) { + if ( $root_category_id != $new_category_id ) { // root category differs from one in db - $fields_hash = Array('RootCat' => $new_category_id); - $this->Conn->doUpdate($fields_hash, TABLE_PREFIX.'Modules', 'Name = '.$this->Conn->qstr($module)); + $fields_hash = Array ('RootCat' => $new_category_id); + $this->Conn->doUpdate($fields_hash, TABLE_PREFIX . 'Modules', 'Name = ' . $this->Conn->qstr($module)); } } // reset cache $changed = $this->Application->GetVar($event->getPrefixSpecial() . '_changed', Array ()); - $require_refresh = Array ( - 'AdvancedUserManagement', 'Site_Name', 'AdminConsoleInterface', 'UsePopups' - ); + $require_refresh = Array ('AdvancedUserManagement', 'Site_Name', 'AdminConsoleInterface', 'UsePopups'); $refresh_sections = array_intersect($require_refresh, $changed); $require_full_refresh = Array ('Site_Name', 'AdminConsoleInterface'); - if (array_intersect($require_full_refresh, $changed)) { + if ( array_intersect($require_full_refresh, $changed) ) { $event->SetRedirectParam('refresh_all', 1); - } elseif ($refresh_sections) { - // reset sections too, because of AdvancedUserManagement + } + elseif ( $refresh_sections ) { $event->SetRedirectParam('refresh_tree', 1); } - $this->Application->DeleteUnitCache($refresh_sections ? true : false); + if ( $refresh_sections ) { + // reset sections too, because of AdvancedUserManagement + $this->Application->DeleteSectionCache(); + } + + $this->Application->DeleteUnitCache($changed); } - elseif ($this->Application->GetVar('errors_' . $event->getPrefixSpecial())) { + elseif ( $this->Application->GetVar('errors_' . $event->getPrefixSpecial()) ) { // because we have list out there, and this is item - $this->Application->removeObject( $event->getPrefixSpecial() ); + $this->Application->removeObject($event->getPrefixSpecial()); } // keeps module and section in REQUEST to ensure, that last admin template will work $event->SetRedirectParam('module', $this->Application->GetVar('module')); $event->SetRedirectParam('section', $this->Application->GetVar('section')); } /** * Process items from selector (selected_ids var, key - prefix, value - comma separated ids) * * @param kEvent $event */ function OnProcessSelected(&$event) { $selected_ids = $this->Application->GetVar('selected_ids'); $this->Application->StoreVar('ModuleRootCategory', $selected_ids['c']); $event->SetRedirectParam('opener', 'u'); } } \ No newline at end of file Index: branches/5.2.x/core/units/modules/modules_event_handler.php =================================================================== --- branches/5.2.x/core/units/modules/modules_event_handler.php (revision 14786) +++ branches/5.2.x/core/units/modules/modules_event_handler.php (revision 14787) @@ -1,161 +1,163 @@ <?php /** * @version $Id$ * @package In-Portal * @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved. * @license GNU/GPL * In-Portal is Open Source software. * This means that this software may have been modified pursuant * the GNU General Public License, and as distributed it includes * or is derivative of works licensed under the GNU General Public License * or other free or open source software licenses. * See http://www.in-portal.org/license for copyright notices and details. */ defined('FULL_PATH') or die('restricted access!'); class ModulesEventHandler extends kDBEventHandler { /** * Builds item * * @param kEvent $event * @access protected */ function OnItemBuild(&$event) { $this->Application->SetVar($event->getPrefixSpecial(true).'_id', $event->Special); parent::OnItemBuild($event); } /** * Apply any custom changes to list's sql query * * @param kEvent $event * @return void * @access protected * @see kDBEventHandler::OnListBuild() */ protected function SetCustomQuery(&$event) { $object =& $event->getObject(); /* @var $object kDBList */ if ($event->Special) { $object->addFilter('current_module', '%1$s.Name = '.$event->Special); } $object->addFilter('not_core', '%1$s.Name <> "Core"'); } function mapEvents() { parent::mapEvents(); $this->eventMethods['OnMassApprove'] = 'moduleAction'; $this->eventMethods['OnMassDecline'] = 'moduleAction'; } /** * Disabled modules, but not In-Portal * * @param kEvent $event */ function moduleAction(&$event) { if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) { $event->status = kEvent::erFAIL; return ; } $object =& $event->getObject( Array('skip_autoload' => true) ); /* @var $object kDBItem */ $ids = $this->StoreSelectedIDs($event); if (!$ids) { return ; } $updated = 0; $status_field = $this->Application->getUnitOption($event->Prefix, 'StatusField'); $status_field = array_shift($status_field); foreach ($ids as $id) { $object->Load($id); if (in_array($id, Array ('In-Portal', 'Core')) || !$object->isLoaded()) { // don't allow any kind of manupulations with kernel // approve/decline on not installed module continue; } $enabled = $event->Name == 'OnMassApprove' ? 1 : 0; $object->SetDBField($status_field, $enabled); if (!$object->GetChangedFields()) { // no changes -> skip continue; } if ($object->Update()) { $updated++; $sql = 'UPDATE ' . TABLE_PREFIX . 'ImportScripts SET Status = ' . $enabled . ' WHERE Module = "' . $object->GetDBField('Name') . '"'; $this->Conn->Query($sql); } else { $event->status = kEvent::erFAIL; $event->redirect = false; break; } } - if ($updated) { + if ( $updated ) { $event->status = kEvent::erSUCCESS; $event->setRedirectParams(Array ('opener' => 's'), true); - $this->Application->DeleteUnitCache(true); //true to reset sections cache also + $this->Application->DeleteUnitCache(); + $this->Application->DeleteSectionCache(); + $event->SetRedirectParam('RefreshTree', 1); } } /** - * Occures after list is queried + * Occurs after list is queried * * @param kEvent $event */ function OnAfterListQuery(&$event) { parent::OnAfterListQuery($event); $modules_helper =& $this->Application->recallObject('ModulesHelper'); /* @var $modules_helper kModulesHelper */ $new_modules = $modules_helper->getModules(kModulesHelper::NOT_INSTALLED); if (!$new_modules || $this->Application->RecallVar('user_id') != USER_ROOT) { return ; } require_once FULL_PATH . '/core/install/install_toolkit.php'; $toolkit = new kInstallToolkit(); $object =& $event->getObject(); /* @var $object kDBList */ foreach ($new_modules as $module) { $module_record = Array ( 'Name' => $toolkit->getModuleName($module), 'Path' => 'modules/' . $module . '/', 'Version' => $toolkit->GetMaxModuleVersion('modules/' . $module . '/'), 'Loaded' => 0, 'BuildDate' => null, ); $object->addRecord($module_record); } } } \ No newline at end of file