array('Filename'), // 'created_on' => array('CreatedOn', '-- d/m/Y --'), ); /** * Category dummy for formatting purposes. * * @var CategoriesItem */ protected $categoryDummy; /** * Set's references to kApplication and DBConnection interface class instances * * @access public */ public function __construct() { parent::__construct(); if ( $this->menuItemsRequireFormatting() ) { $this->categoryDummy = $this->Application->recallObject( 'c.menu-item', null, array('skip_autoload' => true) ); } } /** * Builds site menu * * @param string $prefix_special * @param Array $params * * @return string * @access public */ public function menuTag($prefix_special, $params) { list ($menu, $root_path) = $this->_prepareMenu(); $cat = $this->_getCategoryId($params); $parent_path = array_key_exists($cat, $this->parentPaths) ? $this->parentPaths[$cat] : ''; $parent_path = str_replace($root_path, '', $parent_path); // menu starts from module path $levels = explode('|', trim($parent_path, '|')); if ( $levels[0] === '' ) { $levels = Array (); } if ( array_key_exists('level', $params) && $params['level'] > count($levels) ) { // current level is deeper, then requested level return ''; } $level = max(array_key_exists('level', $params) ? $params['level'] - 1 : count($levels) - 1, 0); $parent = array_key_exists($level, $levels) ? $levels[$level] : 0; $cur_menu =& $menu; $menu_path = array_slice($levels, 0, $level + 1); foreach ($menu_path as $elem) { $cur_menu =& $cur_menu['c' . $elem]['sub_items']; } $block_params = $this->prepareTagParams($prefix_special, $params); $block_params['name'] = $params['render_as']; $this->Application->SetVar('cur_parent_path', $parent_path); $real_cat_id = $this->Application->GetVar('m_cat_id'); if ( !is_array($cur_menu) || !$cur_menu ) { // no menus on this level return ''; } $ret = ''; $cur_item = 1; $cur_menu = $this->_removeNonMenuItems($cur_menu); $block_params['total_items'] = count($cur_menu); foreach ($cur_menu as $page) { $block_params = array_merge($block_params, $this->_prepareMenuItem($page, $real_cat_id, $root_path)); $block_params['is_last'] = $cur_item == $block_params['total_items']; $block_params['is_first'] = $cur_item == 1; $ret .= $this->Application->ParseBlock($block_params); $cur_item++; } $this->Application->SetVar('m_cat_id', $real_cat_id); return $ret; } /** * Builds cached menu version * * @return Array * @access protected */ protected function _prepareMenu() { static $root_cat = NULL, $root_path = NULL; if ( !$root_cat ) { $root_cat = $this->Application->getBaseCategory(); $cache_key = 'parent_paths[%CIDSerial:' . $root_cat . '%]'; $root_path = $this->Application->getCache($cache_key); if ( $root_path === false ) { $this->Conn->nextQueryCachable = true; $sql = 'SELECT ParentPath FROM ' . TABLE_PREFIX . 'Categories WHERE CategoryId = ' . $root_cat; $root_path = $this->Conn->GetOne($sql); $this->Application->setCache($cache_key, $root_path); } } if ( !$this->Menu ) { if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) { $menu = $this->Application->getCache('master:cms_menu', false, CacheSettings::$cmsMenuRebuildTime); } else { $menu = $this->Application->getDBCache('cms_menu', CacheSettings::$cmsMenuRebuildTime); } if ( $menu ) { $menu = unserialize($menu); $this->parentPaths = $menu['parentPaths']; } else { $menu = $this->_buildMenuStructure($root_cat); $menu['parentPaths'] = $this->parentPaths; if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) { $this->Application->setCache('master:cms_menu', serialize($menu)); } 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 * @access protected */ protected function _prepareMenuItem($page, $real_cat_id, $root_path) { static $language_id = NULL, $primary_language_id = NULL, $template = NULL; if ( !isset($language_id) ) { $language_id = $this->Application->GetVar('m_lang'); $primary_language_id = $this->Application->GetDefaultLanguageId(); $template = $this->Application->GetVar('t'); } $active = $category_active = false; $title = $page['l' . $language_id . '_ItemName'] ? $page['l' . $language_id . '_ItemName'] : $page['l' . $primary_language_id . '_ItemName']; if ( $page['ItemType'] == 'cat' ) { if ( array_key_exists($real_cat_id, $this->parentPaths) ) { $active = strpos($this->parentPaths[$real_cat_id], $page['ParentPath']) !== false; } elseif ( $page['ItemPath'] == $template ) { // physical template in menu $active = true; } $category_active = $page['CategoryId'] == $real_cat_id; } /*if ( $page['ItemType'] == 'cat_index' ) { $check_path = str_replace($root_path, '', $page['ParentPath']); $active = strpos($parent_path, $check_path) !== false; } if ( $page['ItemType'] == 'page' ) { $active = $page['ItemPath'] == preg_replace('/^Content\//i', '', $this->Application->GetVar('t')); }*/ if ( substr($page['ItemPath'], 0, 3) == 'id:' ) { // resolve ID path here, since it can be used directly without m_Link tag (that usually resolves it) $page['ItemPath'] = $this->Application->getVirtualPageTemplate(substr($page['ItemPath'], 3)); } $block_params = Array ( 'title' => $title, 'template' => $page['ItemPath'], 'active' => $active, 'category_active' => $category_active, // new 'parent_path' => $page['ParentPath'], 'parent_id' => $page['ParentId'], 'cat_id' => $page['CategoryId'], 'item_type' => $page['ItemType'], 'page_id' => $page['ItemId'], 'use_section' => ($page['Type'] == PAGE_TYPE_TEMPLATE) && ($page['ItemPath'] != 'index'), 'has_sub_menu' => isset($page['sub_items']) && count($this->_removeNonMenuItems($page['sub_items'])) > 0, 'external_url' => $page['UseExternalUrl'] ? $page['ExternalUrl'] : false, // for backward compatibility 'menu_icon' => $page['UseMenuIconUrl'] ? $page['MenuIconUrl'] : false, ); if ( isset($this->categoryDummy) ) { $this->categoryDummy->SetDBFieldsFromHash($page); } foreach ( $this->itemParams as $param_name => $category_field_data ) { $category_field_name = $category_field_data[0]; if ( array_key_exists(1, $category_field_data) ) { $block_params[$param_name] = $this->categoryDummy->GetField( $category_field_name, $category_field_data[1] ); } else { $block_params[$param_name] = $page[$category_field_name]; } } return $block_params; } /** * Returns only items, that are visible in menu * * @param Array $menu * @return Array * @access protected */ protected function _removeNonMenuItems($menu) { $theme_id = $this->Application->GetVar('m_theme'); foreach ($menu as $menu_index => $menu_item) { // $menu_index is in "cN" format, where N is category id if ( !$menu_item['IsMenu'] || $menu_item['Status'] != STATUS_ACTIVE || ($menu_item['ThemeId'] != $theme_id && $menu_item['ThemeId'] != 0) ) { // don't show sections, that are not from menu OR system templates from other themes unset($menu[$menu_index]); } } return $menu; } /** * Builds cache of all menu items and their parent categories * * @param int $top_category_id * @return Array * @access protected */ protected function _buildMenuStructure($top_category_id) { // 1. get parent paths of leaf categories, that are in menu (across all themes) $sql = 'SELECT ParentPath, CategoryId FROM ' . $this->Application->getUnitConfig('c')->getTableName() . ' WHERE IsMenu = 1 AND Status = ' . STATUS_ACTIVE; $this->parentPaths = $this->Conn->GetCol($sql ,'CategoryId'); // 2. figure out parent paths of all categories in path to leaf categories foreach ($this->parentPaths as $leaf_parent_path) { $parent_categories = explode('|', substr($leaf_parent_path, 1, -1)); foreach ($parent_categories as $index => $parent_category_id) { if ( !isset($this->parentPaths[$parent_category_id]) ) { $parent_path = array_slice($parent_categories, 0, $index + 1); $this->parentPaths[$parent_category_id] = '|' . implode('|', $parent_path) . '|'; } } } return $this->_altBuildMenuStructure($top_category_id, implode(',', array_keys($this->parentPaths))); } /** * Builds cache for children of given category (no matter, what menu status is) * * @param int $parent_category_id * @param string $category_limit * @return Array * @access protected */ protected function _altBuildMenuStructure($parent_category_id, $category_limit = NULL) { // Sub-categories from current category $items = $this->_getSubCategories($parent_category_id, $category_limit); // sort menu items uasort($items, Array (&$this, '_menuSort')); // process sub-menus of each menu foreach ($items as $key => $menu_item) { if ( $menu_item['CategoryId'] == $parent_category_id ) { // don't process myself - prevents recursion continue; } $sub_items = $this->_altBuildMenuStructure($menu_item['CategoryId'], $category_limit); if ( $sub_items ) { $items[$key]['sub_items'] = $sub_items; } } return $items; } /** * Returns given category sub-categories * * @param int $parent_id * @param string $category_limit * @return Array * @access protected */ protected function _getSubCategories($parent_id, $category_limit = NULL) { static $items_by_parent = NULL, $lang_part = NULL; if ( !isset($lang_part) ) { $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"; } } if ( !isset($items_by_parent) ) { $items_by_parent = Array (); $extra_select_clause = ''; foreach ( $this->itemParams as $category_field_data ) { $extra_select_clause .= ', c.' . $category_field_data[0]; } // Sub-categories from current category $sql = 'SELECT c.CategoryId AS CategoryId, CONCAT(\'c\', c.CategoryId) AS ItemId, c.Priority AS ItemPriority, ' . $lang_part . ' IF(c.`Type` = ' . PAGE_TYPE_TEMPLATE . ', c.Template, CONCAT("id:", c.CategoryId)) AS ItemPath, c.ParentPath AS ParentPath, c.ParentId As ParentId, \'cat\' AS ItemType, c.IsMenu, c.Type, c.ThemeId, c.UseExternalUrl, c.ExternalUrl, c.UseMenuIconUrl, c.MenuIconUrl, c.Status' . $extra_select_clause . ' FROM ' . TABLE_PREFIX . 'Categories AS c'; if ( isset($category_limit) && $category_limit ) { $sql .= ' WHERE c.CategoryId IN (' . $category_limit . ')'; } $items = $this->Conn->Query($sql, 'ItemId'); foreach ($items as $item_id => $item_data) { $item_parent_id = $item_data['ParentId']; if ( !array_key_exists($item_parent_id, $items_by_parent) ) { $items_by_parent[$item_parent_id] = Array (); } $items_by_parent[$item_parent_id][$item_id] = $item_data; } } return array_key_exists($parent_id, $items_by_parent) ? $items_by_parent[$parent_id] : Array (); } /** * Method for sorting pages by priority in descending order * * @param Array $a * @param Array $b * @return int */ function _menuSort($a, $b) { if ( $a['ItemPriority'] == $b['ItemPriority'] ) { return 0; } return ($a['ItemPriority'] < $b['ItemPriority']) ? 1 : -1; // descending } /** * Determines if menu item parameters require formatting. * * @return boolean */ protected function menuItemsRequireFormatting() { foreach ( $this->itemParams as $category_field_data ) { if ( array_key_exists(1, $category_field_data) ) { return true; } } return false; } }