Index: branches/5.3.x/core/install/cache/class_structure.php =================================================================== --- branches/5.3.x/core/install/cache/class_structure.php +++ branches/5.3.x/core/install/cache/class_structure.php @@ -7,6 +7,9 @@ return array( 'cache_format' => 2, 'classes' => array( + 'AbstractCategoryItemRouter' => '/core/kernel/utility/Router/AbstractCategoryItemRouter.php', + 'AbstractReviewRouter' => '/core/kernel/utility/Router/AbstractReviewRouter.php', + 'AbstractRouter' => '/core/kernel/utility/Router/AbstractRouter.php', 'AdminEventsHandler' => '/core/units/admin/admin_events_handler.php', 'AdminTagProcessor' => '/core/units/admin/admin_tag_processor.php', 'AjaxFormHelper' => '/core/units/helpers/ajax_form_helper.php', @@ -20,7 +23,6 @@ 'CategoriesItem' => '/core/units/categories/categories_item.php', 'CategoriesTagProcessor' => '/core/units/categories/categories_tag_processor.php', 'CategoryHelper' => '/core/units/helpers/category_helper.php', - 'CategoryItemRewrite' => '/core/units/helpers/mod_rewrite_helper.php', 'CategoryItemsEventHandler' => '/core/units/category_items/category_items_event_handler.php', 'CategoryItemsTagProcessor' => '/core/units/category_items/category_items_tag_processor.php', 'CategoryPermissionRebuild' => '/core/kernel/constants.php', @@ -101,6 +103,7 @@ 'MailingListEventHandler' => '/core/units/mailing_lists/mailing_list_eh.php', 'MailingListHelper' => '/core/units/helpers/mailing_list_helper.php', 'MailingListTagProcessor' => '/core/units/mailing_lists/mailing_list_tp.php', + 'MainRouter' => '/core/units/general/MainRouter.php', 'MaintenanceMode' => '/core/kernel/startup.php', 'MassImageResizer' => '/core/units/admin/admin_events_handler.php', 'MemcacheCacheHandler' => '/core/kernel/utility/cache.php', @@ -339,6 +342,27 @@ 'kiCacheable' => '/core/kernel/interfaces/cacheable.php', ), 'class_info' => array( + 'AbstractCategoryItemRouter' => array( + 'type' => 1, + 'modifiers' => 1, + 'extends' => array( + 0 => 'AbstractRouter', + ), + ), + 'AbstractReviewRouter' => array( + 'type' => 1, + 'modifiers' => 1, + 'extends' => array( + 0 => 'AbstractRouter', + ), + ), + 'AbstractRouter' => array( + 'type' => 1, + 'modifiers' => 1, + 'extends' => array( + 0 => 'kBase', + ), + ), 'AdminEventsHandler' => array( 'type' => 1, 'modifiers' => 0, @@ -427,13 +451,6 @@ 0 => 'kHelper', ), ), - 'CategoryItemRewrite' => array( - 'type' => 1, - 'modifiers' => 0, - 'extends' => array( - 0 => 'kHelper', - ), - ), 'CategoryItemsEventHandler' => array( 'type' => 1, 'modifiers' => 0, @@ -951,6 +968,13 @@ 0 => 'kDBTagProcessor', ), ), + 'MainRouter' => array( + 'type' => 1, + 'modifiers' => 0, + 'extends' => array( + 0 => 'AbstractRouter', + ), + ), 'MaintenanceMode' => array( 'type' => 1, 'modifiers' => 0, Index: branches/5.3.x/core/kernel/application.php =================================================================== --- branches/5.3.x/core/kernel/application.php +++ branches/5.3.x/core/kernel/application.php @@ -74,12 +74,11 @@ public $ReplacementTemplates = Array (); /** - * Mod-Rewrite listeners used during url building and parsing + * Registered routers, that are used during url building and parsing. * - * @var Array - * @access public + * @var array */ - public $RewriteListeners = Array (); + public $routers = array(); /** * Reference to debugger @@ -312,7 +311,7 @@ // init config reader and all managers $this->UnitConfigReader = $this->makeClass('kUnitConfigReader'); - $this->UnitConfigReader->scanModules(MODULES_PATH); // will also set RewriteListeners when existing cache is read + $this->UnitConfigReader->scanModules(MODULES_PATH); // Will also set routers. $this->registerModuleConstants(); @@ -356,7 +355,7 @@ $this->ValidateLogin(); // must be called before AfterConfigRead, because current user should be available there - $this->UnitConfigReader->AfterConfigRead(); // will set RewriteListeners when missing cache is built first time + $this->UnitConfigReader->AfterConfigRead(); if ( defined('DEBUG_MODE') && $this->isDebugMode() ) { $this->Debugger->appendTimestamp('Processed AfterConfigRead'); @@ -2935,7 +2934,7 @@ $this->EventManager->setFromCache($data); $this->ReplacementTemplates = $data['Application.ReplacementTemplates']; - $this->RewriteListeners = $data['Application.RewriteListeners']; + $this->routers = $data['Application.Routers']; $this->ModuleInfo = $data['Application.ModuleInfo']; } @@ -2954,7 +2953,7 @@ $this->EventManager->getToCache(), Array ( 'Application.ReplacementTemplates' => $this->ReplacementTemplates, - 'Application.RewriteListeners' => $this->RewriteListeners, + 'Application.Routers' => $this->routers, 'Application.ModuleInfo' => $this->ModuleInfo, ) ); Index: branches/5.3.x/core/kernel/managers/cache_manager.php =================================================================== --- branches/5.3.x/core/kernel/managers/cache_manager.php +++ branches/5.3.x/core/kernel/managers/cache_manager.php @@ -305,7 +305,7 @@ 'Factory.Files', 'Factory.ClassInfo', 'Factory.ClassTree', 'Factory.Namespaces', 'Factory.realClasses', 'ConfigReader.prefixFiles', 'ConfigCloner.clones', 'EventManager.beforeHooks', 'EventManager.afterHooks', 'EventManager.scheduledTasks', 'EventManager.buildEvents', - 'Application.ReplacementTemplates', 'Application.RewriteListeners', 'Application.ModuleInfo', + 'Application.ReplacementTemplates', 'Application.Routers', 'Application.ModuleInfo', 'Application.ConfigHash', 'Application.ConfigCacheIds', ); Index: branches/5.3.x/core/kernel/managers/rewrite_url_processor.php =================================================================== --- branches/5.3.x/core/kernel/managers/rewrite_url_processor.php +++ branches/5.3.x/core/kernel/managers/rewrite_url_processor.php @@ -25,12 +25,11 @@ protected $HTTPQuery = null; /** - * Urls parts, that needs to be matched by rewrite listeners + * Urls parts, that needs to be matched by routers. * - * @var Array - * @access protected + * @var array */ - protected $_partsToParse = Array (); + protected $_partsToParse = array(); /** * Category item prefix, that was found @@ -73,12 +72,11 @@ protected $_urlEndings = Array ('.html', '/', ''); /** - * Factory storage sub-set, containing mod-rewrite listeners, used during url building and parsing + * Factory storage sub-set, containing routers, used during url building and parsing. * - * @var Array - * @access protected + * @var array */ - protected $rewriteListeners = Array (); + protected $routers = array(); /** * Constructor of kRewriteUrlProcessor class @@ -108,7 +106,7 @@ $this->primaryThemeId = $this->Application->GetDefaultThemeId(true); } - $this->_initRewriteListeners(); + $this->initRouters(); } /** @@ -335,32 +333,24 @@ } /** - * Loads all registered rewrite listeners, so they could be quickly accessed later + * Loads all registered routers, so they could be quickly accessed later. * - * @access protected + * @return void */ - protected function _initRewriteListeners() + protected function initRouters() { static $init_done = false; - if ($init_done || count($this->Application->RewriteListeners) == 0) { - // not initialized OR mod-rewrite url with missing config cache - return ; + // Not initialized OR mod-rewrite url with missing config cache. + if ( $init_done || count($this->Application->routers) == 0 ) { + 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); - - // 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; - } + foreach ( $this->Application->routers as $prefix => $router_data ) { + $this->routers[$prefix] = $this->Application->makeClass($router_data['class']); } define('MOD_REWRITE_URL_ENDING', $this->Application->ConfigValue('ModRewriteUrlEnding')); - $init_done = true; } @@ -434,8 +424,8 @@ // http://site-url///[_]/ (category-based detail template) // http://site-url////[_]/ (customized url) - if ( !$this->_processRewriteListeners($url_parts, $vars) ) { - // rewrite listener wasn't able to determine template + if ( !$this->processRouters($url_parts, $vars) ) { + // Routers weren't able to determine template. $this->_parsePhysicalTemplate($url_parts, $vars); if ( ($this->modulePrefix === false) && $vars['m_cat_id'] && !$this->_partsToParse ) { @@ -517,39 +507,34 @@ } /** - * Processes url using rewrite listeners + * Processes url using routers. * * Pattern: Chain of Command * - * @param Array $url_parts - * @param Array $vars - * @return bool - * @access protected + * @param array $url_parts Url parts. + * @param array $vars Vars. + * + * @return boolean */ - protected function _processRewriteListeners(&$url_parts, &$vars) + protected function processRouters(array &$url_parts, array &$vars) { - $this->_initRewriteListeners(); + $this->initRouters(); $page_number = $this->_parsePage($url_parts, $vars); - 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 + foreach ( $this->routers as $prefix => $router ) { + 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 + if ( $router->parse($url_parts, $vars) === false ) { + // Will not proceed to other methods. return true; } } - // will proceed to other methods + // Will proceed to other methods. return false; } @@ -561,7 +546,7 @@ * @return string * @access protected * - * @todo Should find a way, how to determine what rewrite listener page is it + * @todo Should find a way, how to determine what router page is it. */ protected function _parsePage(&$url_parts, &$vars) { @@ -955,9 +940,9 @@ 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 + $inject_parts = array(); // Url parts for beginning of url. + $params['t'] = $t; // Make template available for routers. + $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 @@ -967,7 +952,7 @@ list ($prefix) = explode('.', $pass_element); $catalog_item = $this->Application->findModule('Var', $prefix) && $this->Application->getUnitConfig($prefix)->getCatalogItem(); - if ( array_key_exists($prefix, $this->rewriteListeners) ) { + if ( array_key_exists($prefix, $this->routers) ) { // 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 ) { @@ -989,7 +974,7 @@ } } elseif ( is_array($url_part) ) { - // rewrite listener want to insert something at the beginning of url too + // Router want to insert something at the beginning of url too. if ( $url_part[0] ) { $inject_parts[] = $url_part[0]; } @@ -1004,7 +989,7 @@ } } elseif ( $url_part === false ) { - // rewrite listener decided not to rewrite given $pass_element + // Router decided not to rewrite given $pass_element. $env .= ':' . $this->manager->plain->BuildModuleEnv($pass_element, $params, $pass_events); } } @@ -1023,7 +1008,7 @@ $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 + // Remove temporary parameters used by routers. unset($params['t'], $params['inject_parts'], $params['pass_template'], $params['pass_category'], $params['category_processed']); $ret = trim($ret, '/'); @@ -1070,11 +1055,6 @@ { list ($prefix) = explode('.', $prefix_special); - $url_parts = Array (); - $listener = $this->rewriteListeners[$prefix][0]; - - $ret = $listener[0]->$listener[1](REWRITE_MODE_BUILD, $prefix_special, $params, $url_parts, $pass_events); - - return $ret; + return $this->routers[$prefix]->buildWrapper($prefix_special, $params, $pass_events); } } Index: branches/5.3.x/core/kernel/managers/url_processor.php =================================================================== --- branches/5.3.x/core/kernel/managers/url_processor.php +++ branches/5.3.x/core/kernel/managers/url_processor.php @@ -74,7 +74,13 @@ foreach ($pass_info as $prefix) { list ($prefix_only,) = explode('.', $prefix, 2); - $sorted[$prefix] = $this->Application->getUnitConfig($prefix_only)->getRewritePriority(0); + + if ( isset($this->Application->routers[$prefix_only]) ) { + $sorted[$prefix] = (int)$this->Application->routers[$prefix_only]['priority']; + } + else { + $sorted[$prefix] = 0; + } } asort($sorted, SORT_NUMERIC); @@ -124,4 +130,4 @@ * @access protected */ abstract protected function BuildModuleEnv($prefix_special, &$params, $pass_events = false); -} \ No newline at end of file +} Index: branches/5.3.x/core/kernel/utility/Router/AbstractCategoryItemRouter.php =================================================================== --- branches/5.3.x/core/kernel/utility/Router/AbstractCategoryItemRouter.php +++ branches/5.3.x/core/kernel/utility/Router/AbstractCategoryItemRouter.php @@ -0,0 +1,266 @@ +extractBuildParams(); + + if ( $build_params === false ) { + return ''; + } + + $this->keepEvent(); + list ($prefix) = explode('.', $this->buildPrefix); + + if ( !array_key_exists($prefix, $default_per_page) ) { + $list_helper = $this->Application->recallObject('ListHelper'); + /* @var $list_helper ListHelper */ + + $default_per_page[$prefix] = $list_helper->getDefaultPerPage($prefix); + } + + if ( $build_params[$this->buildPrefix . '_id'] ) { + $category_id = $this->getBuildParam('m_cat_id'); + + // If template is also item template of category, then remove template. + $template = $this->getBuildParam('t', false); + $item_template = $this->getUrlProcessor()->GetItemTemplate($category_id, $prefix); + + if ( $template == $item_template || strtolower($template) == '__default__' ) { + // Given template is also default template for this category item or '__default__' given. + $this->setBuildParam('pass_template', false); + } + + // Get item's filename. + if ( $prefix == 'bb' ) { + $ret .= 'bb_' . $build_params[$this->buildPrefix . '_id'] . '/'; + } + else { + $filename = $this->getFilename($prefix, $build_params[$this->buildPrefix . '_id'], $category_id); + + if ( $filename !== false ) { + $ret .= $filename . '/'; + } + } + } + else { + if ( $build_params[$this->buildPrefix . '_Page'] == 1 ) { + // When printing category items and we are on the 1st page -> there is no information about + // category item prefix and $params['pass_category'] will not be added automatically. + $this->setBuildParam('pass_category', true); + } + elseif ( $build_params[$this->buildPrefix . '_Page'] > 1 ) { + $this->setBuildParam('page', $build_params[$this->buildPrefix . '_Page']); + } + + $per_page = $build_params[$this->buildPrefix . '_PerPage']; + + if ( $per_page && ($per_page != $default_per_page[$prefix]) ) { + $this->setBuildParam('per_page', $build_params[$this->buildPrefix . '_PerPage']); + } + } + + return mb_strtolower(rtrim($ret, '/')); + } + + /** + * Parses url part. + * + * @param array $url_parts Url parts to parse. + * @param array $params Parameters, that are used for url building or created during url parsing. + * + * @return boolean Return true to continue to next router; return false to stop processing at this router. + */ + public function parse(array &$url_parts, array &$params) + { + $module_prefix = $this->parseCategoryItemUrl($url_parts, $params); + + if ( $module_prefix !== false ) { + $params['pass'][] = $module_prefix; + $this->getUrlProcessor()->setModulePrefix($module_prefix); + } + + return true; + } + + /** + * Returns item's filename that corresponds id passed. If possible, then get it from cache. + * + * @param string $prefix Prefix. + * @param integer $id Id. + * @param integer $category_id Category Id. + * + * @return string + */ + protected function getFilename($prefix, $id, $category_id = null) + { + $category_id = isset($category_id) ? $category_id : $this->Application->GetVar('m_cat_id'); + + $serial_name = $this->Application->incrementCacheSerial($prefix, $id, false); + $cache_key = 'filenames[%' . $serial_name . '%]:' . (int)$category_id; + $filename = $this->Application->getCache($cache_key); + + if ( $filename === false ) { + $this->Conn->nextQueryCachable = true; + $config = $this->Application->getUnitConfig($prefix); + + $sql = 'SELECT ResourceId + FROM ' . $config->getTableName() . ' + WHERE ' . $config->getIDField() . ' = ' . $this->Conn->qstr($id); + $resource_id = $this->Conn->GetOne($sql); + + $this->Conn->nextQueryCachable = true; + $sql = 'SELECT Filename + FROM ' . TABLE_PREFIX . 'CategoryItems + WHERE (ItemResourceId = ' . $resource_id . ') AND (CategoryId = ' . (int)$category_id . ')'; + $filename = $this->Conn->GetOne($sql); + + if ( $filename !== false ) { + $this->Application->setCache($cache_key, $filename); + } + } + + return $filename; + } + + /** + * Sets template and id, corresponding to category item given in url + * + * @param array $url_parts Url parts. + * @param array $params Params. + * + * @return boolean|string + */ + protected function parseCategoryItemUrl(array &$url_parts, array &$params) + { + if ( !$url_parts ) { + return false; + } + + $item_filename = end($url_parts); + + if ( is_numeric($item_filename) ) { + // This page, don't process here. + return false; + } + + if ( $this->buildPrefix == 'bb' && preg_match('/^bb_([\d]+)/', $item_filename, $regs) ) { + // Process topics separately, because they don't use item filenames. + array_pop($url_parts); + $this->partParsed($item_filename, 'rtl'); + + return $this->parseTopicUrl($regs[1], $params); + } + + $cat_item = $this->findCategoryItem((int)$params['m_cat_id'], $item_filename); + + if ( $cat_item !== false ) { + // Item found. + $module_prefix = $cat_item['ItemPrefix']; + $item_template = $this->getUrlProcessor()->GetItemTemplate($cat_item, $module_prefix, $params['m_theme']); + + // Converting ResourceId to corresponding Item id. + $module_config = $this->Application->getUnitConfig($module_prefix); + + $sql = 'SELECT ' . $module_config->getIDField() . ' + FROM ' . $module_config->getTableName() . ' + WHERE ResourceId = ' . $cat_item['ItemResourceId']; + $item_id = $this->Conn->GetOne($sql); + + if ( $item_id ) { + array_pop($url_parts); + $this->partParsed($item_filename, 'rtl'); + + if ( $item_template ) { + // When template is found in category -> set it. + $params['t'] = $item_template; + } + + // We have category item id. + $params[$module_prefix . '_id'] = $item_id; + + return $module_prefix; + } + } + + return false; + } + + /** + * Set's template and topic id corresponding to topic given in url + * + * @param integer $topic_id Topic ID. + * @param array $params Params. + * + * @return string + */ + protected function parseTopicUrl($topic_id, array &$params) + { + $sql = 'SELECT c.ParentPath, c.CategoryId + FROM ' . TABLE_PREFIX . 'Categories AS c + WHERE c.CategoryId = ' . (int)$params['m_cat_id']; + $cat_item = $this->Conn->GetRow($sql); + + $item_template = $this->getUrlProcessor()->GetItemTemplate($cat_item, 'bb', $params['m_theme']); + + if ( $item_template ) { + $params['t'] = $item_template; + } + + $params['bb_id'] = $topic_id; + + return 'bb'; + } + + /** + * Locating the item in CategoryItems by filename to detect its ItemPrefix and its category ParentPath. + * + * @param integer $category_id Category. + * @param string $filename Filename. + * + * @return string + */ + protected function findCategoryItem($category_id, $filename) + { + static $cache = array(); + + $cache_key = $category_id . ':' . $filename; + + if ( !isset($cache[$cache_key]) ) { + $sql = 'SELECT ci.ItemResourceId, ci.ItemPrefix, c.ParentPath, ci.CategoryId + FROM ' . TABLE_PREFIX . 'CategoryItems AS ci + LEFT JOIN ' . TABLE_PREFIX . 'Categories AS c ON c.CategoryId = ci.CategoryId + WHERE (ci.CategoryId = ' . $category_id . ') AND (ci.Filename = ' . $this->Conn->qstr($filename) . ')'; + $cache[$cache_key] = $this->Conn->GetRow($sql); + } + + $category_item = $cache[$cache_key]; + + return $category_item && $category_item['ItemPrefix'] == $this->buildPrefix ? $category_item : false; + } + +} Index: branches/5.3.x/core/kernel/utility/Router/AbstractReviewRouter.php =================================================================== --- branches/5.3.x/core/kernel/utility/Router/AbstractReviewRouter.php +++ branches/5.3.x/core/kernel/utility/Router/AbstractReviewRouter.php @@ -0,0 +1,69 @@ +extractBuildParams(); + + if ( $build_params === false ) { + return ''; + } + + list ($prefix) = explode('.', $this->buildPrefix); + + if ( !array_key_exists($prefix, $default_per_page) ) { + $list_helper = $this->Application->recallObject('ListHelper'); + /* @var $list_helper ListHelper */ + + $default_per_page[$prefix] = $list_helper->getDefaultPerPage($prefix); + } + + if ( $build_params[$this->buildPrefix . '_id'] ) { + return false; + } + else { + if ( $build_params[$this->buildPrefix . '_Page'] == 1 ) { + // When printing category items and we are on the 1st page -> there is no information about + // category item prefix and $params['pass_category'] will not be added automatically. + $this->setBuildParam('pass_category', true); + } + elseif ( $build_params[$this->buildPrefix . '_Page'] > 1 ) { + $this->setBuildParam('page', $build_params[$this->buildPrefix . '_Page']); + } + + $per_page = $build_params[$this->buildPrefix . '_PerPage']; + + if ( $per_page && ($per_page != $default_per_page[$this->buildPrefix]) ) { + $this->setBuildParam('per_page', $build_params[$this->buildPrefix . '_PerPage']); + } + } + + return mb_strtolower(rtrim($ret, '/')); + } + + /** + * Parses url part. + * + * @param array $url_parts Url parts to parse. + * @param array $params Parameters, that are used for url building or created during url parsing. + * + * @return boolean Return true to continue to next router; return false to stop processing at this router. + */ + public function parse(array &$url_parts, array &$params) + { + // Don't parse anything. + return true; + } + +} Index: branches/5.3.x/core/kernel/utility/Router/AbstractRouter.php =================================================================== --- branches/5.3.x/core/kernel/utility/Router/AbstractRouter.php +++ branches/5.3.x/core/kernel/utility/Router/AbstractRouter.php @@ -0,0 +1,295 @@ +buildPrefix = $this->getPrefix(); + $this->debug = $this->Application->isDebugMode() && (defined('DBG_ROUTING') && DBG_ROUTING); + } + + /** + * Add debug information to every returned url part. + * + * @param string|array $url_part Url part. + * + * @return array|string + */ + protected function debug($url_part) + { + if ( is_array($url_part) ) { + return array_map(array($this, 'debug'), $url_part); + } + + return '{' . $this->buildPrefix . ':' . $url_part . '}'; + } + + /** + * Returns unit config's prefix, that is associated with this router. + * + * @return string + */ + abstract public function getPrefix(); + + /** + * Returns weight of this router among other routers. + * + * @return float|boolean Number - when order is important; false, when order isn't important. + */ + public function getWeight() + { + return false; + } + + /** + * Wraps building part to hide internal implementation details. + * + * @param string $prefix_special Prefix. + * @param array $params Params. + * @param boolean $keep_events Keep events in url. + * + * @return mixed + */ + public final function buildWrapper($prefix_special, array &$params, $keep_events = false) + { + $this->buildPrefix = $prefix_special; + $this->buildParams = $params; + $this->keepEventFromUrl = $keep_events; + + $url_part = $this->build(); + $params = $this->buildParams; + + return $this->debug ? $this->debug($url_part) : $url_part; + } + + /** + * Builds url part. + * + * @return boolean Return true to continue to next router; return false not to rewrite given prefix. + */ + abstract protected function build(); + + /** + * Parses url part. + * + * @param array $url_parts Url parts to parse. + * @param array $params Parameters, that are used for url building or created during url parsing. + * + * @return boolean Return true to continue to next router; return false to stop processing at this router. + */ + abstract public function parse(array &$url_parts, array &$params); + + /** + * Returns value of build parameter (would use global value, when not given directly). + * + * @param string $name Name of parameter. Symbol "@" would be replaced to "prefix_special_". + * @param mixed $default Default value. + * + * @return string|boolean + */ + protected function getBuildParam($name, $default = null) + { + $param_name = str_replace('@', $this->buildPrefix . '_', $name); + + if ( isset($this->buildParams[$param_name]) ) { + return $this->buildParams[$param_name]; + } + + return isset($default) ? $default : $this->Application->GetVar($param_name); + } + + /** + * Returns build template. + * + * @return string + */ + protected function getBuildTemplate() + { + return $this->Application->getPhysicalTemplate($this->getBuildParam('t')); + } + + /** + * Sets new value of build parameter. + * + * @param string $name Name of build parameter. + * @param string $value New build parameter value or NULL to remove it. + * + * @return void + */ + protected function setBuildParam($name, $value = null) + { + $param_name = str_replace('@', $this->buildPrefix . '_', $name); + + if ( isset($value) ) { + $this->buildParams[$param_name] = $value; + } + else { + unset($this->buildParams[$param_name]); + } + } + + /** + * Returns environment variable values for given prefix + * Uses directly given params, when available and removes them from build params afterwards. + * + * @return array + */ + protected function extractBuildParams() + { + list ($prefix) = explode('.', $this->buildPrefix); + + $query_vars = $this->Application->getUnitConfig($prefix)->getQueryString(); + + 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]); + } + + $event_name = $this->getBuildParam('@event', false); + + if ( ($event_name !== false && !$event_name) || ($event_name === false && !$this->keepEventFromUrl) ) { + // If empty event passed, then remove it from url. + // If keep event is off and event is not implicitly passed (to prevent global value). + $this->setBuildParam('@event'); + } + + $ret = array(); + + foreach ( $query_vars as $name ) { + $ret[$this->buildPrefix . '_' . $name] = $this->getBuildParam('@' . $name); + $this->setBuildParam('@' . $name); + } + + return $ret; + } + + /** + * Transforms event into form, that will survive in mod-rewritten url. + * + * @return void + */ + protected function keepEvent() + { + $event_name = $this->getBuildParam('@event'); + + if ( $event_name ) { + $this->setBuildParam('events[' . $this->buildPrefix . ']', $event_name); + $this->setBuildParam('@event'); + } + } + + /** + * Returns build parameters from given object (without changing it). + * + * @param kDBBase $object Object. + * @param array $params Parameter set to use as base. + * + * @return array + */ + public function getBuildParams(kDBBase $object, array $params = array()) + { + $params[$object->Prefix . '_id'] = $object->GetID(); + + if ( !isset($params['pass']) ) { + $params['pass'] = 'm,' . $object->Prefix; + } + + return $params; + } + + /** + * Marks url part as parsed. + * + * @param string $url_part Url part. + * @param string $parse_direction Parse direction. + * + * @return void + */ + protected function partParsed($url_part, $parse_direction = 'ltr') + { + $this->getUrlProcessor()->partParsed($url_part, $parse_direction); + } + + /** + * Determines if there is more to parse in url. + * + * @return boolean + */ + protected function moreToParse() + { + return $this->getUrlProcessor()->moreToParse(); + } + + /** + * Returns an instance of url processor in use. + * + * @return kRewriteUrlProcessor + */ + protected function getUrlProcessor() + { + return $this->Application->recallObject('kRewriteUrlProcessor'); + } + +} Index: branches/5.3.x/core/kernel/utility/http_query.php =================================================================== --- branches/5.3.x/core/kernel/utility/http_query.php +++ branches/5.3.x/core/kernel/utility/http_query.php @@ -324,11 +324,9 @@ } /** - * Finishes initialization of kHTTPQuery class + * Finishes initialization of kHTTPQuery class. * * @return void - * @access protected - * @todo: only uses build-in rewrite listeners, when cache is build for the first time */ protected function AfterInit() { Index: branches/5.3.x/core/kernel/utility/unit_config.php =================================================================== --- branches/5.3.x/core/kernel/utility/unit_config.php +++ branches/5.3.x/core/kernel/utility/unit_config.php @@ -144,12 +144,6 @@ * @method bool getPortalStyleEnv(mixed $default = false) * @method kUnitConfig setPortalStyleEnv(bool $value) * - * @method int getRewritePriority(mixed $default = false) - * @method kUnitConfig setRewritePriority(int $value) - * - * @method Array getRewriteListener(mixed $default = false) - * @method kUnitConfig setRewriteListener(Array $value) - * * @method bool getForceDontLogChanges(mixed $default = false) * @method kUnitConfig setForceDontLogChanges(bool $value) * Index: branches/5.3.x/core/kernel/utility/unit_config_reader.php =================================================================== --- branches/5.3.x/core/kernel/utility/unit_config_reader.php +++ branches/5.3.x/core/kernel/utility/unit_config_reader.php @@ -158,6 +158,7 @@ // no cache found -> include all unit configs to create it ! $this->includeConfigFiles($folder_path, $cache); $this->parseConfigs(); + $this->sortRouters(); // tell AfterConfigRead to store cache if needed // can't store it here because AfterConfigRead needs ability to change config data @@ -166,10 +167,6 @@ 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(); @@ -360,8 +357,6 @@ } if ( $store_cache ) { - $this->sortRewriteListeners(); - $this->Application->HandleEvent(new kEvent('adm:OnAfterCacheRebuild')); $this->Application->cacheManager->UpdateUnitCache(); @@ -379,33 +374,63 @@ } /** - * Sort rewrite listeners according to RewritePriority (non-prioritized listeners goes first) + * Sort routers according to their weight (non-prioritized routers goes first). * * @return void */ - protected function sortRewriteListeners() + protected function sortRouters() { - $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; + $sorted_routers = array(); + $prioritized_routers = array(); + $routers = $this->collectRouters(); + + // Process non-prioritized routers. + foreach ( $routers as $prefix => $router_data ) { + if ( $router_data['priority'] === false ) { + $sorted_routers[$prefix] = $router_data; } else { - $prioritized_listeners[$prefix] = $listener_data['priority']; + $prioritized_routers[$prefix] = $router_data['priority']; } } - // process prioritized listeners - asort($prioritized_listeners, SORT_NUMERIC); + // Process prioritized routers. + asort($prioritized_routers, SORT_NUMERIC); - foreach ( $prioritized_listeners as $prefix => $priority ) { - $listeners[$prefix] = $this->Application->RewriteListeners[$prefix]; + foreach ( $prioritized_routers as $prefix => $priority ) { + $sorted_routers[$prefix] = $routers[$prefix]; } - $this->Application->RewriteListeners = $listeners; + $this->Application->routers = $sorted_routers; + } + + /** + * Collects routers. + * + * @return array + */ + protected function collectRouters() + { + $routers = array(); + $router_classes = $this->Application->getSubClasses('AbstractRouter'); + + foreach ( $router_classes as $router_class ) { + if ( !class_exists($router_class) ) { + // This can happen, when: + // - router class (coming from cache) was renamed; + // - new cache is built based on outdated class map. + continue; + } + + /** @var AbstractRouter $router */ + $router = new $router_class(); + $routers[$router->getPrefix()] = array( + 'class' => $router_class, + 'priority' => $router->getWeight(), + ); + } + + return $routers; } /** @@ -424,6 +449,7 @@ $this->afterConfigProcessed = array(); $this->includeConfigFiles(MODULES_PATH, false); $this->parseConfigs(); + $this->sortRouters(); $this->AfterConfigRead(false); $this->cloner->processDynamicallyAdded(); $this->retrieveCollections(); @@ -440,26 +466,6 @@ if ( $config->getReplacementTemplates() ) { $this->Application->ReplacementTemplates = array_merge($this->Application->ReplacementTemplates, $config->getReplacementTemplates()); } - - // collect rewrite listeners - if ( $config->getRewriteListener() ) { - $rewrite_listeners = $config->getRewriteListener(); - - 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 = $config->getRewritePriority(); - - $this->Application->RewriteListeners[$prefix] = array('listener' => $rewrite_listeners, 'priority' => $rewrite_priority); - } } } @@ -720,4 +726,4 @@ return $this->prefixFiles[$prefix]; } -} \ No newline at end of file +} Index: branches/5.3.x/core/units/categories/categories_event_handler.php =================================================================== --- branches/5.3.x/core/units/categories/categories_event_handler.php +++ branches/5.3.x/core/units/categories/categories_event_handler.php @@ -2861,262 +2861,6 @@ } /** - * Parses category part of url, build main part of url - * - * @param int $rewrite_mode Mode in what rewrite listener was called. Possbile two modes: REWRITE_MODE_BUILD, REWRITE_MODE_PARSE. - * @param string $prefix Prefix, that listener uses for system integration - * @param Array $params Params, that are used for url building or created during url parsing. - * @param Array $url_parts Url parts to parse (only for parsing). - * @param bool $keep_events Keep event names in resulting url (only for building). - * @return bool|string|Array Return true to continue to next listener; return false (when building) not to rewrite given prefix; return false (when parsing) to stop processing at this listener. - */ - public function CategoryRewriteListener($rewrite_mode = REWRITE_MODE_BUILD, $prefix, &$params, &$url_parts, $keep_events = false) - { - if ($rewrite_mode == REWRITE_MODE_BUILD) { - return $this->_buildMainUrl($prefix, $params, $keep_events); - } - - if ( $this->_parseFriendlyUrl($url_parts, $params) ) { - // friendly urls work like exact match only! - return false; - } - - $this->_parseCategory($url_parts, $params); - - return true; - } - - /** - * Build main part of every url - * - * @param string $prefix_special - * @param Array $params - * @param bool $keep_events - * @return string - */ - protected function _buildMainUrl($prefix_special, &$params, $keep_events) - { - $ret = ''; - list ($prefix) = explode('.', $prefix_special); - - $rewrite_processor = $this->Application->recallObject('kRewriteUrlProcessor'); - /* @var $rewrite_processor kRewriteUrlProcessor */ - - $processed_params = $rewrite_processor->getProcessedParams($prefix_special, $params, $keep_events); - if ($processed_params === false) { - return ''; - } - - // add language - if ($processed_params['m_lang'] && ($processed_params['m_lang'] != $rewrite_processor->primaryLanguageId)) { - $language_name = $this->Application->getCache('language_names[%LangIDSerial:' . $processed_params['m_lang'] . '%]'); - if ($language_name === false) { - $sql = 'SELECT PackName - FROM ' . TABLE_PREFIX . 'Languages - WHERE LanguageId = ' . $processed_params['m_lang']; - $language_name = $this->Conn->GetOne($sql); - - $this->Application->setCache('language_names[%LangIDSerial:' . $processed_params['m_lang'] . '%]', $language_name); - } - - $ret .= $language_name . '/'; - } - - // add theme - if ($processed_params['m_theme'] && ($processed_params['m_theme'] != $rewrite_processor->primaryThemeId)) { - $theme_name = $this->Application->getCache('theme_names[%ThemeIDSerial:' . $processed_params['m_theme'] . '%]'); - if ($theme_name === false) { - $sql = 'SELECT Name - FROM ' . TABLE_PREFIX . 'Themes - WHERE ThemeId = ' . $processed_params['m_theme']; - $theme_name = $this->Conn->GetOne($sql); - - $this->Application->setCache('theme_names[%ThemeIDSerial:' . $processed_params['m_theme'] . '%]', $theme_name); - - } - - $ret .= $theme_name . '/'; - } - - // inject custom url parts made by other rewrite listeners just after language/theme url parts - if ($params['inject_parts']) { - $ret .= implode('/', $params['inject_parts']) . '/'; - } - - // add category - if ($processed_params['m_cat_id'] > 0 && $params['pass_category']) { - $category_filename = $this->Application->getCategoryCache($processed_params['m_cat_id'], 'filenames'); - - preg_match('/^Content\/(.*)/i', $category_filename, $regs); - - if ($regs) { - $template = array_key_exists('t', $params) ? $params['t'] : false; - - if (strtolower($regs[1]) == strtolower($template)) { - // we could have category path like "Content/" in this case remove template - $params['pass_template'] = false; - } - - $ret .= $regs[1] . '/'; - } - - $params['category_processed'] = true; - } - - // reset category page - $force_page_adding = false; - if (array_key_exists('reset', $params) && $params['reset']) { - unset($params['reset']); - - if ($processed_params['m_cat_id']) { - $processed_params['m_cat_page'] = 1; - $force_page_adding = true; - } - } - - if ((array_key_exists('category_processed', $params) && $params['category_processed'] && ($processed_params['m_cat_page'] > 1)) || $force_page_adding) { - // category name was added before AND category page number found - $ret = rtrim($ret, '/') . '_' . $processed_params['m_cat_page'] . '/'; - } - - $template = array_key_exists('t', $params) ? $params['t'] : false; - $category_template = ($processed_params['m_cat_id'] > 0) && $params['pass_category'] ? $this->Application->getCategoryCache($processed_params['m_cat_id'], 'category_designs') : ''; - - if ((strtolower($template) == '__default__') && ($processed_params['m_cat_id'] == 0)) { - // for "Home" category set template to index when not set - $template = 'index'; - } - - // remove template from url if it is category index cached template - if ( ($template == $category_template) || (mb_strtolower($template) == '__default__') ) { - // given template is also default template for this category OR '__default__' given - $params['pass_template'] = false; - } - - // remove template from url if it is site homepage on primary language & theme - if ( ($template == 'index') && $processed_params['m_lang'] == $rewrite_processor->primaryLanguageId && $processed_params['m_theme'] == $rewrite_processor->primaryThemeId ) { - // given template is site homepage on primary language & theme - $params['pass_template'] = false; - } - - if ($template && $params['pass_template']) { - $ret .= $template . '/'; - } - - return mb_strtolower( rtrim($ret, '/') ); - } - - /** - * Checks if whole url_parts matches a whole In-CMS page - * - * @param Array $url_parts - * @param Array $vars - * @return bool - */ - protected function _parseFriendlyUrl($url_parts, &$vars) - { - if (!$url_parts) { - return false; - } - - $sql = 'SELECT CategoryId, NamedParentPath - FROM ' . TABLE_PREFIX . 'Categories - WHERE FriendlyURL = ' . $this->Conn->qstr(implode('/', $url_parts)); - $friendly = $this->Conn->GetRow($sql); - - $rewrite_processor = $this->Application->recallObject('kRewriteUrlProcessor'); - /* @var $rewrite_processor kRewriteUrlProcessor */ - - if ($friendly) { - $vars['m_cat_id'] = $friendly['CategoryId']; - $vars['t'] = preg_replace('/^Content\//i', '', $friendly['NamedParentPath']); - - while ($url_parts) { - $rewrite_processor->partParsed( array_shift($url_parts) ); - } - - return true; - } - - return false; - } - - /** - * Extracts category part from url - * - * @param Array $url_parts - * @param Array $vars - * @return bool - */ - protected function _parseCategory($url_parts, &$vars) - { - if (!$url_parts) { - return false; - } - - $res = false; - $url_part = array_shift($url_parts); - - $category_id = 0; - $last_category_info = false; - $category_path = $url_part == 'content' ? '' : 'content'; - - $rewrite_processor = $this->Application->recallObject('kRewriteUrlProcessor'); - /* @var $rewrite_processor kRewriteUrlProcessor */ - - do { - $category_path = trim($category_path . '/' . $url_part, '/'); - // bb_ -> forums/bb_2 - if ( !preg_match('/^bb_[\d]+$/', $url_part) && preg_match('/(.*)_([\d]+)$/', $category_path, $rets) ) { - $category_path = $rets[1]; - $vars['m_cat_page'] = $rets[2]; - } - - $sql = 'SELECT CategoryId, SymLinkCategoryId, NamedParentPath - FROM ' . TABLE_PREFIX . 'Categories - WHERE (LOWER(NamedParentPath) = ' . $this->Conn->qstr($category_path) . ') AND (ThemeId = ' . $vars['m_theme'] . ' OR ThemeId = 0)'; - $category_info = $this->Conn->GetRow($sql); - - if ($category_info !== false) { - $last_category_info = $category_info; - $rewrite_processor->partParsed($url_part); - - $url_part = array_shift($url_parts); - $res = true; - } - } while ($category_info !== false && $url_part); - - if ($last_category_info) { - // this category is symlink to other category, so use it's url instead - // (used in case if url prior to symlink adding was indexed by spider or was bookmarked) - if ($last_category_info['SymLinkCategoryId']) { - $sql = 'SELECT CategoryId, NamedParentPath - FROM ' . TABLE_PREFIX . 'Categories - WHERE (CategoryId = ' . $last_category_info['SymLinkCategoryId'] . ')'; - $category_info = $this->Conn->GetRow($sql); - - if ($category_info) { - // web symlinked category was found use it - // TODO: maybe 302 redirect should be made to symlinked category url (all other url parts should stay) - $last_category_info = $category_info; - } - } - - // 1. Set virtual page as template, this will be replaced to physical template later in kApplication::Run. - // 2. Don't set CachedTemplate field as template here, because we will loose original page associated with it's cms blocks! - $vars['t'] = mb_strtolower( preg_replace('/^Content\//i', '', $last_category_info['NamedParentPath'])); - - $vars['m_cat_id'] = $last_category_info['CategoryId']; - $vars['is_virtual'] = true; // for template from POST, strange code there! - } - /*else { - $vars['m_cat_id'] = 0; - }*/ - - return $res; - } - - /** * Set's new unique resource id to user * * @param kEvent $event Index: branches/5.3.x/core/units/general/MainRouter.php =================================================================== --- branches/5.3.x/core/units/general/MainRouter.php +++ branches/5.3.x/core/units/general/MainRouter.php @@ -0,0 +1,305 @@ +getUrlProcessor(); + + $build_params = $this->extractBuildParams(); + + if ( $build_params === false ) { + return ''; + } + + // Add language. + if ( $build_params['m_lang'] && ($build_params['m_lang'] != $rewrite_processor->primaryLanguageId) ) { + $cache_key = 'language_names[%LangIDSerial:' . $build_params['m_lang'] . '%]'; + $language_name = $this->Application->getCache($cache_key); + + if ( $language_name === false ) { + $sql = 'SELECT PackName + FROM ' . TABLE_PREFIX . 'Languages + WHERE LanguageId = ' . $build_params['m_lang']; + $language_name = $this->Conn->GetOne($sql); + + $this->Application->setCache($cache_key, $language_name); + } + + $ret .= $language_name . '/'; + } + + // Add theme. + if ( $build_params['m_theme'] && ($build_params['m_theme'] != $rewrite_processor->primaryThemeId) ) { + $cache_key = 'theme_names[%ThemeIDSerial:' . $build_params['m_theme'] . '%]'; + $theme_name = $this->Application->getCache($cache_key); + + if ( $theme_name === false ) { + $sql = 'SELECT Name + FROM ' . TABLE_PREFIX . 'Themes + WHERE ThemeId = ' . $build_params['m_theme']; + $theme_name = $this->Conn->GetOne($sql); + + $this->Application->setCache($cache_key, $theme_name); + } + + $ret .= $theme_name . '/'; + } + + // Inject custom url parts made by other routers just after language/theme url parts. + $inject_parts = $this->getBuildParam('inject_parts', false); + + if ( $inject_parts ) { + $ret .= implode('/', $inject_parts) . '/'; + } + + // Add category. + if ( $build_params['m_cat_id'] > 0 && $this->getBuildParam('pass_category', false) ) { + $category_filename = $this->Application->getCategoryCache($build_params['m_cat_id'], 'filenames'); + + preg_match('/^Content\/(.*)/i', $category_filename, $regs); + + if ( $regs ) { + $template = $this->getBuildParam('t', false); + + if ( strtolower($regs[1]) == strtolower($template) ) { + // We could have category path like "Content/" in this case remove template. + $this->setBuildParam('pass_template', false); + } + + $ret .= $regs[1] . '/'; + } + + $this->setBuildParam('category_processed', true); + } + + // Reset category page. + $force_page_adding = false; + $reset_category_page = $this->getBuildParam('reset', false); + + if ( $reset_category_page ) { + $this->setBuildParam('reset'); + + if ( $build_params['m_cat_id'] ) { + $build_params['m_cat_page'] = 1; + $force_page_adding = true; + } + } + + $category_processed = $this->getBuildParam('category_processed', false); + + if ( ($category_processed && ($build_params['m_cat_page'] > 1)) || $force_page_adding ) { + // Category name was added before AND category page number found. + $ret = rtrim($ret, '/') . '_' . $build_params['m_cat_page'] . '/'; + } + + $template = $this->getBuildParam('t', false); + + if ( ($build_params['m_cat_id'] > 0) && $this->getBuildParam('pass_category', false) ) { + $category_template = $this->Application->getCategoryCache($build_params['m_cat_id'], 'category_designs'); + } + else { + $category_template = ''; + } + + if ( (strtolower($template) == '__default__') && ($build_params['m_cat_id'] == 0) ) { + // For "Home" category set template to index when not set. + $template = 'index'; + } + + // Remove template from url if it is category index cached template. + if ( ($template == $category_template) || (mb_strtolower($template) == '__default__') ) { + // Given template is also default template for this category OR '__default__' given. + $this->setBuildParam('pass_template', false); + } + + // Remove template from url if it is site homepage on primary language & theme. + if ( $template == 'index' + && $build_params['m_lang'] == $rewrite_processor->primaryLanguageId + && $build_params['m_theme'] == $rewrite_processor->primaryThemeId + ) { + // Given template is site homepage on primary language & theme. + $this->setBuildParam('pass_template', false); + } + + if ( $template && $this->getBuildParam('pass_template', false) ) { + $ret .= $template . '/'; + } + + return mb_strtolower(rtrim($ret, '/')); + } + + /** + * Parses url part. + * + * @param array $url_parts Url parts to parse. + * @param array $params Parameters, that are used for url building or created during url parsing. + * + * @return boolean Return true to continue to next router; return false to stop processing at this router. + */ + public function parse(array &$url_parts, array &$params) + { + if ( $this->parseFriendlyUrl($url_parts, $params) ) { + // Friendly urls work like exact match only! + return false; + } + + $this->parseCategory($url_parts, $params); + + return true; + } + + /** + * Checks if whole url_parts matches a whole In-CMS page + * + * @param array $url_parts Url parts. + * @param array $params Params. + * + * @return boolean + */ + protected function parseFriendlyUrl(array $url_parts, array &$params) + { + if ( !$url_parts ) { + return false; + } + + $sql = 'SELECT CategoryId, NamedParentPath + FROM ' . TABLE_PREFIX . 'Categories + WHERE FriendlyURL = ' . $this->Conn->qstr(implode('/', $url_parts)); + $friendly = $this->Conn->GetRow($sql); + + if ( $friendly ) { + $params['m_cat_id'] = $friendly['CategoryId']; + $params['t'] = preg_replace('/^Content\//i', '', $friendly['NamedParentPath']); + + while ( $url_parts ) { + $this->partParsed(array_shift($url_parts)); + } + + return true; + } + + return false; + } + + /** + * Extracts category part from url + * + * @param array $url_parts Url parts. + * @param array $params Params. + * + * @return boolean + */ + protected function parseCategory(array $url_parts, array &$params) + { + if ( !$url_parts ) { + return false; + } + + $res = false; + $url_part = array_shift($url_parts); + + $category_id = 0; + $last_category_info = false; + $category_path = $url_part == 'content' ? '' : 'content'; + + $rewrite_processor = $this->getUrlProcessor(); + + do { + $category_path = trim($category_path . '/' . $url_part, '/'); + + // Part: "bb_" matches "forums/bb_2". + if ( !preg_match('/^bb_[\d]+$/', $url_part) && preg_match('/(.*)_([\d]+)$/', $category_path, $regs) ) { + $category_path = $regs[1]; + $params['m_cat_page'] = $regs[2]; + } + + $sql = 'SELECT CategoryId, SymLinkCategoryId, NamedParentPath + FROM ' . TABLE_PREFIX . 'Categories + WHERE + (LOWER(NamedParentPath) = ' . $this->Conn->qstr($category_path) . ') + AND + (ThemeId = ' . $params['m_theme'] . ' OR ThemeId = 0)'; + $category_info = $this->Conn->GetRow($sql); + + if ( $category_info !== false ) { + $last_category_info = $category_info; + $rewrite_processor->partParsed($url_part); + + $url_part = array_shift($url_parts); + $res = true; + } + } while ( $category_info !== false && $url_part ); + + if ( $last_category_info ) { + // This category is symlink to other category, so use it's url instead + // (used in case if url prior to symlink adding was indexed by spider or was bookmarked). + if ( $last_category_info['SymLinkCategoryId'] ) { + $sql = 'SELECT CategoryId, NamedParentPath + FROM ' . TABLE_PREFIX . 'Categories + WHERE (CategoryId = ' . $last_category_info['SymLinkCategoryId'] . ')'; + $category_info = $this->Conn->GetRow($sql); + + if ( $category_info ) { + // Web symlinked category was found use it. + // TODO: maybe 302 redirect should be made to symlinked category url + // (all other url parts should stay). + $last_category_info = $category_info; + } + } + + // 1. Set virtual page as template, this will be replaced to physical template later in kApplication::Run. + // 2. Don't set CachedTemplate field as template here, because we will loose original + // page associated with it's cms blocks! + $params['t'] = mb_strtolower(preg_replace('/^Content\//i', '', $last_category_info['NamedParentPath'])); + + $params['m_cat_id'] = $last_category_info['CategoryId']; + $params['is_virtual'] = true; // For template from POST, strange code there! + } + + return $res; + } + +} Index: branches/5.3.x/core/units/general/general_config.php =================================================================== --- branches/5.3.x/core/units/general/general_config.php +++ branches/5.3.x/core/units/general/general_config.php @@ -36,8 +36,5 @@ $config->setCatalogItem(true); $config->setPortalStyleEnv(true); -$config->setRewritePriority(100); -$config->setRewriteListener('c_EventHandler:CategoryRewriteListener'); - $config->setPermTabText('In-Portal'); $config->setPermSection(Array ('search' => 'in-portal:configuration_search', 'custom' => 'in-portal:configuration_custom')); Index: branches/5.3.x/core/units/helpers/helpers_config.php =================================================================== --- branches/5.3.x/core/units/helpers/helpers_config.php +++ branches/5.3.x/core/units/helpers/helpers_config.php @@ -25,7 +25,6 @@ Array ('pseudo' => 'SectionsHelper', 'class' => 'kSectionsHelper', 'file' => 'sections_helper.php', 'build_event' => ''), Array ('pseudo' => 'PermissionsHelper', 'class' => 'kPermissionsHelper', 'file' => 'permissions_helper.php', 'build_event' => ''), Array ('pseudo' => 'kModulesHelper', 'class' => 'kModulesHelper', 'file' => 'modules_helper.php', 'build_event' => ''), - Array ('pseudo' => 'CategoryItemRewrite', 'class' => 'CategoryItemRewrite', 'file' => 'mod_rewrite_helper.php', 'build_event' => ''), Array ('pseudo' => 'RecursiveHelper', 'class' => 'kRecursiveHelper', 'file' => 'recursive_helper.php', 'build_event' => ''), Array ('pseudo' => 'FilenamesHelper', 'class' => 'kFilenamesHelper', 'file' => 'filenames_helper.php', 'build_event' => ''), Array ('pseudo' => 'ClipboardHelper', 'class' => 'kClipboardHelper', 'file' => 'clipboard_helper.php', 'build_event' => ''), Index: branches/5.3.x/core/units/helpers/mod_rewrite_helper.php =================================================================== --- branches/5.3.x/core/units/helpers/mod_rewrite_helper.php +++ branches/5.3.x/core/units/helpers/mod_rewrite_helper.php @@ -1,356 +0,0 @@ -_buildCategoryItemUrl($prefix, $params, $keep_events); - } - - $module_prefix = $this->_parseCategoryItemUrl($url_parts, $params, $prefix); - - if ($module_prefix !== false) { - $rewrite_processor = $this->Application->recallObject('kRewriteUrlProcessor'); - /* @var $rewrite_processor kRewriteUrlProcessor */ - - $params['pass'][] = $module_prefix; - $rewrite_processor->setModulePrefix($module_prefix); - } - - return true; - } - - /** - * Build category item part of url - * - * @param string $prefix_special - * @param Array $params - * @param bool $keep_events - * @return string - * @access protected - */ - protected function _buildCategoryItemUrl($prefix_special, &$params, $keep_events) - { - static $default_per_page = Array (); - - $rewrite_processor = $this->Application->recallObject('kRewriteUrlProcessor'); - /* @var $rewrite_processor kRewriteUrlProcessor */ - - $ret = ''; - list ($prefix) = explode('.', $prefix_special); - $processed_params = $rewrite_processor->getProcessedParams($prefix_special, $params, $keep_events); - - if ($processed_params === false) { - return ''; - } - - if ( isset($params[$prefix_special . '_event']) && $params[$prefix_special . '_event'] ) { - $params['events[' . $prefix_special . ']'] = $params[$prefix_special . '_event']; - unset($params[$prefix_special . '_event']); - } - - if (!array_key_exists($prefix, $default_per_page)) { - $list_helper = $this->Application->recallObject('ListHelper'); - /* @var $list_helper ListHelper */ - - $default_per_page[$prefix] = $list_helper->getDefaultPerPage($prefix); - } - - if ($processed_params[$prefix_special . '_id']) { - $category_id = array_key_exists('m_cat_id', $params) ? $params['m_cat_id'] : $this->Application->GetVar('m_cat_id'); - - // if template is also item template of category, then remove template - $template = array_key_exists('t', $params) ? $params['t'] : false; - $item_template = $rewrite_processor->GetItemTemplate($category_id, $prefix); - - if ($template == $item_template || strtolower($template) == '__default__') { - // given template is also default template for this category item or '__default__' given - $params['pass_template'] = false; - } - - // get item's filename - if ($prefix == 'bb') { - $ret .= 'bb_' . $processed_params[$prefix_special . '_id'] . '/'; - } - else { - $filename = $this->getFilename($prefix, $processed_params[$prefix_special . '_id'], $category_id); - if ($filename !== false) { - $ret .= $filename . '/'; - } - } - } else { - if ($processed_params[$prefix_special . '_Page'] == 1) { - // when printing category items and we are on the 1st page -> there is no information about - // category item prefix and $params['pass_category'] will not be added automatically - $params['pass_category'] = true; - } - elseif ($processed_params[$prefix_special . '_Page'] > 1) { - // $ret .= $processed_params[$prefix_special . '_Page'] . '/'; - $params['page'] = $processed_params[$prefix_special . '_Page']; - } - - $per_page = $processed_params[$prefix_special . '_PerPage']; - - if ($per_page && ($per_page != $default_per_page[$prefix])) { - $params['per_page'] = $processed_params[$prefix_special . '_PerPage']; - } - } - - return mb_strtolower( rtrim($ret, '/') ); - } - - /** - * Builds/parses review part of url - * - * @param int $rewrite_mode Mode in what rewrite listener was called. Possbile two modes: REWRITE_MODE_BUILD, REWRITE_MODE_PARSE. - * @param string $prefix_special Prefix, that listener uses for system integration - * @param Array $params Params, that are used for url building or created during url parsing. - * @param Array $url_parts Url parts to parse (only for parsing). - * @param bool $keep_events Keep event names in resulting url (only for building). - * @return bool Return true to continue to next listener; return false (when building) not to rewrite given prefix; return false (when parsing) to stop processing at this listener. - * @access public - */ - public function ReviewRewriteListener($rewrite_mode = REWRITE_MODE_BUILD, $prefix_special, &$params, &$url_parts, $keep_events = false) - { - static $default_per_page = Array (); - - if ( $rewrite_mode != REWRITE_MODE_BUILD ) { - // don't parse anything - return true; - } - - $rewrite_processor = $this->Application->recallObject('kRewriteUrlProcessor'); - /* @var $rewrite_processor kRewriteUrlProcessor */ - - $ret = ''; - list ($prefix) = explode('.', $prefix_special); - $processed_params = $rewrite_processor->getProcessedParams($prefix_special, $params, $keep_events); - - if ($processed_params === false) { - return ''; - } - - if (!array_key_exists($prefix, $default_per_page)) { - $list_helper = $this->Application->recallObject('ListHelper'); - /* @var $list_helper ListHelper */ - - $default_per_page[$prefix] = $list_helper->getDefaultPerPage($prefix); - } - - if ($processed_params[$prefix_special . '_id']) { - return false; - } - else { - if ($processed_params[$prefix_special . '_Page'] == 1) { - // when printing category items and we are on the 1st page -> there is no information about - // category item prefix and $params['pass_category'] will not be added automatically - $params['pass_category'] = true; - } - elseif ($processed_params[$prefix_special . '_Page'] > 1) { - // $ret .= $processed_params[$prefix_special . '_Page'] . '/'; - $params['page'] = $processed_params[$prefix_special . '_Page']; - } - - $per_page = $processed_params[$prefix_special . '_PerPage']; - - if ($per_page && ($per_page != $default_per_page[$prefix])) { - $params['per_page'] = $processed_params[$prefix_special . '_PerPage']; - } - } - - return mb_strtolower( rtrim($ret, '/') ); - } - - /** - * Returns item's filename that corresponds id passed. If possible, then get it from cache - * - * @param string $prefix - * @param int $id - * @param int $category_id - * @return string - * @access protected - */ - protected function getFilename($prefix, $id, $category_id = null) - { - if ($prefix == 'c') { - throw new Exception('Method "' . __FUNCTION__ . '" no longer work with "c" prefix. Please use "getCategoryCache" method instead'); - } - - $category_id = isset($category_id) ? $category_id : $this->Application->GetVar('m_cat_id'); - - $cache_key = 'filenames[%' . $this->Application->incrementCacheSerial($prefix, $id, false) . '%]:' . (int)$category_id; - $filename = $this->Application->getCache($cache_key); - - if ($filename === false) { - $this->Conn->nextQueryCachable = true; - $config = $this->Application->getUnitConfig($prefix); - - $sql = 'SELECT ResourceId - FROM ' . $config->getTableName() . ' - WHERE ' . $config->getIDField() . ' = ' . $this->Conn->qstr($id); - $resource_id = $this->Conn->GetOne($sql); - - $this->Conn->nextQueryCachable = true; - $sql = 'SELECT Filename - FROM ' . TABLE_PREFIX . 'CategoryItems - WHERE (ItemResourceId = ' . $resource_id . ') AND (CategoryId = ' . (int)$category_id . ')'; - $filename = $this->Conn->GetOne($sql); - - if ($filename !== false) { - $this->Application->setCache($cache_key, $filename); - } - } - - return $filename; - } - - /** - * Sets template and id, corresponding to category item given in url - * - * @param Array $url_parts - * @param Array $vars - * @param string $prefix Prefix, that listener uses for system integration - * - * @return boolean|string - * @access protected - */ - protected function _parseCategoryItemUrl(&$url_parts, &$vars, $prefix) - { - if ( !$url_parts ) { - return false; - } - - $item_filename = end($url_parts); - if ( is_numeric($item_filename) ) { - // this page, don't process here - return false; - } - - $rewrite_processor = $this->Application->recallObject('kRewriteUrlProcessor'); - /* @var $rewrite_processor kRewriteUrlProcessor */ - - if ( $prefix == 'bb' && preg_match('/^bb_([\d]+)/', $item_filename, $regs) ) { - // process topics separately, because they don't use item filenames - array_pop($url_parts); - $rewrite_processor->partParsed($item_filename, 'rtl'); - - return $this->_parseTopicUrl($regs[1], $vars); - } - - $cat_item = $this->findCategoryItem((int)$vars['m_cat_id'], $item_filename, $prefix); - - if ( $cat_item !== false ) { - // item found - $module_prefix = $cat_item['ItemPrefix']; - $item_template = $rewrite_processor->GetItemTemplate($cat_item, $module_prefix, $vars['m_theme']); - - // converting ResourceId to corresponding Item id - $module_config = $this->Application->getUnitConfig($module_prefix); - - $sql = 'SELECT ' . $module_config->getIDField() . ' - FROM ' . $module_config->getTableName() . ' - WHERE ResourceId = ' . $cat_item['ItemResourceId']; - $item_id = $this->Conn->GetOne($sql); - - if ( $item_id ) { - array_pop($url_parts); - $rewrite_processor->partParsed($item_filename, 'rtl'); - - if ( $item_template ) { - // when template is found in category -> set it - $vars['t'] = $item_template; - } - - // we have category item id - $vars[$module_prefix . '_id'] = $item_id; - - return $module_prefix; - } - } - - return false; - } - - /** - * Locating the item in CategoryItems by filename to detect its ItemPrefix and its category ParentPath. - * - * @param integer $category_id Category. - * @param string $filename Filename. - * @param string $prefix Prefix, that listener uses for system integration - * - * @return string - */ - protected function findCategoryItem($category_id, $filename, $prefix) - { - static $cache = array(); - - $cache_key = $category_id . ':' . $filename; - - if ( !isset($cache[$cache_key]) ) { - $sql = 'SELECT ci.ItemResourceId, ci.ItemPrefix, c.ParentPath, ci.CategoryId - FROM ' . TABLE_PREFIX . 'CategoryItems AS ci - LEFT JOIN ' . TABLE_PREFIX . 'Categories AS c ON c.CategoryId = ci.CategoryId - WHERE (ci.CategoryId = ' . $category_id . ') AND (ci.Filename = ' . $this->Conn->qstr($filename) . ')'; - $cache[$cache_key] = $this->Conn->GetRow($sql); - } - - $category_item = $cache[$cache_key]; - - return $category_item && $category_item['ItemPrefix'] == $prefix ? $category_item : false; - } - - /** - * Set's template and topic id corresponding to topic given in url - * - * @param int $topic_id - * @param Array $vars - * @return string - * @access protected - */ - protected function _parseTopicUrl($topic_id, &$vars) - { - $rewrite_processor = $this->Application->recallObject('kRewriteUrlProcessor'); - /* @var $rewrite_processor kRewriteUrlProcessor */ - - $sql = 'SELECT c.ParentPath, c.CategoryId - FROM ' . TABLE_PREFIX . 'Categories AS c - WHERE c.CategoryId = ' . (int)$vars['m_cat_id']; - $cat_item = $this->Conn->GetRow($sql); - - $item_template = $rewrite_processor->GetItemTemplate($cat_item, 'bb', $vars['m_theme']); - - if ($item_template) { - $vars['t'] = $item_template; - } - - $vars['bb_id'] = $topic_id; - - return 'bb'; - } - } Index: branches/5.3.x/core/units/reviews/reviews_config.php =================================================================== --- branches/5.3.x/core/units/reviews/reviews_config.php +++ branches/5.3.x/core/units/reviews/reviews_config.php @@ -31,8 +31,6 @@ 5 => 'mode', ), - 'RewriteListener' => 'CategoryItemRewrite:ReviewRewriteListener', - 'ParentPrefix' => 'p', // replace all usage of rev to "p-rev" and then remove this param from here and Prefix too 'ConfigMapping' => Array ( 'PerPage' => 'Comm_Perpage_Reviews', @@ -213,4 +211,4 @@ ), ), ), -); \ No newline at end of file +); Index: branches/5.3.x/tools/debug_sample.php =================================================================== --- branches/5.3.x/tools/debug_sample.php +++ branches/5.3.x/tools/debug_sample.php @@ -55,6 +55,6 @@ // 'DBG_IMAGE_RECOVERY' => 1, // Don't replace missing images with noimage.gif // 'DBG_SQL_MODE' => 'TRADITIONAL', // Extra control over sql syntax & data from MySQL server side // 'DBG_RESET_ROOT' => 1, // Shows "root" user password reset link on Admin Console login screen -// 'DBG_CACHE_URLS' => 0, // Cache urls, that are build by rewrite listeners +// 'DBG_CACHE_URLS' => 0, // Cache urls, that are build by routers // 'DBG_SHORTCUT' => 'F12' // Defines debugger activation shortcut (any symbols or Ctrl/Alt/Shift are allowed, e.g. Ctrl+Alt+F12) - ); \ No newline at end of file + );