_prepareMenu(); $cat = $this->_getCategoryId($params); $parent_path = array_key_exists($cat, $this->parentPaths) ? $this->parentPaths[$cat] : ''; $parent_path = str_replace($root_path, '', $parent_path); // menu starts from module path $levels = explode('|', trim($parent_path, '|')); if ($levels[0] === '') { $levels = Array (); } if (array_key_exists('level', $params) && $params['level'] > count($levels)) { // current level is deeper, then requested level return ; } $level = max(array_key_exists('level', $params) ? $params['level'] - 1 : count($levels) - 1, 0); $parent = array_key_exists($level, $levels) ? $levels[$level] : 0; $cur_menu =& $menu; $menu_path = array_slice($levels, 0, $level + 1); foreach ($menu_path as $elem) { $cur_menu =& $cur_menu['c' . $elem]['sub_items']; } $block_params = $this->prepareTagParams($prefix_special, $params); $block_params['name'] = $params['render_as']; $this->Application->SetVar('cur_parent_path', $parent_path); $real_cat_id = $this->Application->GetVar('m_cat_id'); if (!is_array($cur_menu) || !$cur_menu) { // no menus on this level return ''; } $ret = ''; $cur_item = 1; $cur_menu = $this->_removeNonMenuItems($cur_menu); $block_params['total_items'] = count($cur_menu); foreach ($cur_menu as $page) { $block_params = array_merge( $block_params, $this->_prepareMenuItem($page, $real_cat_id, $root_path) ); $block_params['is_last'] = $cur_item == $block_params['total_items']; $block_params['is_first'] = $cur_item == 1; // bug #1: this breaks active section highlighting when 2 menu levels are printed on same page (both visible) // bug #2: people doesn't pass cat_id parameter to m_Link tags in their blocks, so this line helps them; when removed their links will lead to nowhere $this->Application->SetVar('m_cat_id', $page['CategoryId']); $ret .= $this->Application->ParseBlock($block_params); $cur_item++; } $this->Application->SetVar('m_cat_id', $real_cat_id); return $ret; } /** * Builds cached menu version * * @return Array */ function _prepareMenu() { static $root_cat = null; static $root_path = null; if (!$root_cat) { $root_cat = $this->Application->getBaseCategory(); $cache_key = 'parent_paths[%CIDSerial:' . $root_cat . '%]'; $root_path = $this->Application->getCache($cache_key); if ($root_path === false) { $this->Conn->nextQueryCachable = true; $sql = 'SELECT ParentPath FROM ' . TABLE_PREFIX . 'Category WHERE CategoryId = ' . $root_cat; $root_path = $this->Conn->GetOne($sql); $this->Application->setCache($cache_key, $root_path); } } if ( !$this->Menu ) { if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) { $menu = $this->Application->getCache('master:cms_menu', false, CacheSettings::$cmsMenuRebuildTime); } else { $menu = $this->Application->getDBCache('cms_menu', CacheSettings::$cmsMenuRebuildTime); } if ($menu) { $menu = unserialize($menu); $this->parentPaths = $menu['parentPaths']; } else { if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) { $this->Application->rebuildCache('master:cms_menu', kCache::REBUILD_NOW, CacheSettings::$cmsMenuRebuildTime); } else { $this->Application->rebuildDBCache('cms_menu', kCache::REBUILD_NOW, CacheSettings::$cmsMenuRebuildTime); } $menu = $this->_altBuildMenuStructure(Array ('CategoryId' => $root_cat, 'ParentPath' => $root_path)); $menu['parentPaths'] = $this->parentPaths; if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { $this->Application->setCache('master:cms_menu', serialize($menu)); } else { $this->Application->setDBCache('cms_menu', serialize($menu)); } } unset($menu['parentPaths']); $this->Menu = $menu; } return Array ($this->Menu, $root_path); } /** * Returns category id based tag parameters * * @param Array $params * @return int */ function _getCategoryId($params) { $cat = isset($params['category_id']) && $params['category_id'] != '' ? $params['category_id'] : $this->Application->GetVar('m_cat_id'); if ("$cat" == 'parent') { $this_category =& $this->Application->recallObject('c'); /* @var $this_category kDBItem */ $cat = $this_category->GetDBField('ParentId'); } elseif ($cat == 0) { $cat = $this->Application->getBaseCategory(); } return $cat; } /** * Prepares cms menu item block parameters * * @param Array $page * @param int $real_cat_id * @param string $root_path * @return Array */ function _prepareMenuItem($page, $real_cat_id, $root_path) { static $language_id = null; static $primary_language_id = null; static $template = null; if (!isset($language_id)) { $language_id = $this->Application->GetVar('m_lang'); $primary_language_id = $this->Application->GetDefaultLanguageId(); $template = $this->Application->GetVar('t'); } $active = $category_active = false; $title = $page['l' . $language_id . '_ItemName'] ? $page['l' . $language_id . '_ItemName'] : $page['l' . $primary_language_id . '_ItemName']; if ($page['ItemType'] == 'cat') { if (array_key_exists($real_cat_id, $this->parentPaths)) { $active = strpos($this->parentPaths[$real_cat_id], $page['ParentPath']) !== false; } elseif ($page['ItemPath'] == $template) { // physical template in menu $active = true; } $category_active = $page['CategoryId'] == $real_cat_id; } /*if ($page['ItemType'] == 'cat_index') { $check_path = str_replace($root_path, '', $page['ParentPath']); $active = strpos($parent_path, $check_path) !== false; } if ($page['ItemType'] == 'page') { $active = $page['ItemPath'] == preg_replace('/^Content\//i', '', $this->Application->GetVar('t')); }*/ $block_params = Array ( 'title' => $title, 'template' => $page['ItemPath'], 'active' => $active, 'category_active' => $category_active, // new 'parent_path' => $page['ParentPath'], 'parent_id' => $page['ParentId'], 'cat_id' => $page['CategoryId'], 'item_type' => $page['ItemType'], 'page_id' => $page['ItemId'], 'use_section' => ($page['Type'] == PAGE_TYPE_TEMPLATE) && ($page['ItemPath'] != 'index'), 'has_sub_menu' => isset($page['sub_items']) && count($page['sub_items']) > 0, 'external_url' => $page['UseExternalUrl'] ? $page['ExternalUrl'] : false, // for backward compatibility 'menu_icon' => $page['UseMenuIconUrl'] ? $page['MenuIconUrl'] : false, ); return $block_params; } /** * Returns only items, that are visible in menu * * @param Array $menu * @return Array */ function _removeNonMenuItems($menu) { $theme_id = $this->Application->GetVar('m_theme'); foreach ($menu as $menu_index => $menu_item) { // $menu_index is in "cN" format, where N is category id if (!$menu_item['IsMenu'] || ($menu_item['ThemeId'] != $theme_id && $menu_item['ThemeId'] != 0)) { // don't show sections, that are not from menu OR system templates from other themes unset($menu[$menu_index]); } } return $menu; } /** * Builds cache for children of given category (no matter, what menu status is) * * @param Array $parent * @return Array */ function _altBuildMenuStructure($parent) { static $lang_part = null; if (!isset($lang_part)) { $ml_helper =& $this->Application->recallObject('kMultiLanguageHelper'); /* @var $ml_helper kMultiLanguageHelper */ $lang_part = ''; $languages = $ml_helper->getLanguages(); foreach ($languages as $language_id) { $lang_part .= 'c.l' . $language_id . '_MenuTitle AS l' . $language_id . '_ItemName,' . "\n"; } } // Sub-categories from current category $items = $this->getSubCategories( $parent['CategoryId'] ); // sort menu items uasort($items, Array (&$this, '_menuSort')); // store menu items $the_items = Array(); foreach ($items as $index => $an_item) { $the_items[ $an_item['ItemId'] ] = $an_item; $this->parentPaths[ $an_item['CategoryId'] ] = $an_item['ParentPath']; } // process submenus of each menu $items = $the_items; foreach ($items as $key => $menu_item) { if ($menu_item['CategoryId'] == $parent['CategoryId']) { // don't process myself - prevents recursion continue; } $sub_items = $this->_altBuildMenuStructure($menu_item); if ($sub_items) { $items[$key]['sub_items'] = $sub_items; } } return $items; } function getSubCategories($parent_id) { static $items_by_parent = null; if (!isset($items_by_parent)) { $ml_helper =& $this->Application->recallObject('kMultiLanguageHelper'); /* @var $ml_helper kMultiLanguageHelper */ $lang_part = ''; $items_by_parent = Array (); $languages = $ml_helper->getLanguages(); foreach ($languages as $language_id) { $lang_part .= 'c.l' . $language_id . '_MenuTitle AS l' . $language_id . '_ItemName,' . "\n"; } // Sub-categories from current category $sql = 'SELECT c.CategoryId AS CategoryId, CONCAT(\'c\', c.CategoryId) AS ItemId, c.Priority AS ItemPriority, ' . $lang_part . ' IF(c.`Type` = ' . PAGE_TYPE_TEMPLATE . ', c.Template, CONCAT("id:", c.CategoryId)) AS ItemPath, c.ParentPath AS ParentPath, c.ParentId As ParentId, \'cat\' AS ItemType, c.IsMenu, c.Type, c.ThemeId, c.UseExternalUrl, c.ExternalUrl, c.UseMenuIconUrl, c.MenuIconUrl FROM ' . TABLE_PREFIX . 'Category AS c WHERE c.Status = ' . STATUS_ACTIVE; $items = $this->Conn->Query($sql, 'ItemId'); foreach ($items as $item_id => $item_data) { $item_parent_id = $item_data['ParentId']; if ( !array_key_exists($item_parent_id, $items_by_parent) ) { $items_by_parent[$item_parent_id] = Array (); } $items_by_parent[$item_parent_id][$item_id] = $item_data; } } return array_key_exists($parent_id, $items_by_parent) ? $items_by_parent[$parent_id] : Array (); } /** * Method for sorting pages by priority in decending order * * @param Array $a * @param Array $b * @return int */ function _menuSort($a, $b) { if ($a['ItemPriority'] == $b['ItemPriority']) { return 0; } return ($a['ItemPriority'] < $b['ItemPriority']) ? 1 : -1; //descending } }