<?php
/**
* @version	$Id: menu_helper.php 16513 2017-01-20 14:10:53Z alex $
* @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 MenuHelper extends kHelper {

		/**
		 * Cached version of site menu
		 *
		 * @var Array
		 * @access protected
		 */
		protected $Menu = NULL;

		/**
		 * Parent path mapping used in CachedMenu tag
		 *
		 * @var Array
		 * @access protected
		 */
		protected $parentPaths = Array ();

		/**
		 * 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' ) {
				/** @var kDBItem $this_category */
				$this_category = $this->Application->recallObject('c');

				$cat = $this_category->GetDBField('ParentId');
			}
			elseif ( $cat == 0 ) {
				$cat = $this->Application->getBaseCategory();
			}

			return $cat;
		}

		/**
		 * Prepares cms menu item block parameters
		 *
		 * @param Array $page
		 * @param int $real_cat_id
		 * @param string $root_path
		 * @return Array
		 * @access protected
		 */
		protected function _prepareMenuItem($page, $real_cat_id, $root_path)
		{
			static $language_id = NULL, $primary_language_id = NULL, $template = NULL;

			if ( !isset($language_id) ) {
				$language_id = $this->Application->GetVar('m_lang');
				$primary_language_id = $this->Application->GetDefaultLanguageId();
				$template = $this->Application->GetVar('t');
			}

			$active = $category_active = false;
			$title = $page['l' . $language_id . '_ItemName'] ? $page['l' . $language_id . '_ItemName'] : $page['l' . $primary_language_id . '_ItemName'];

			if ( $page['ItemType'] == 'cat' ) {
				if ( array_key_exists($real_cat_id, $this->parentPaths) ) {
					$active = strpos($this->parentPaths[$real_cat_id], $page['ParentPath']) !== false;
				}
				elseif ( $page['ItemPath'] == $template ) {
					// physical template in menu
					$active = true;
				}

				$category_active = $page['CategoryId'] == $real_cat_id;
			}

			/*if ( $page['ItemType'] == 'cat_index' ) {
				$check_path = str_replace($root_path, '', $page['ParentPath']);
				$active = strpos($parent_path, $check_path) !== false;
			}

			if ( $page['ItemType'] == 'page' ) {
				$active = $page['ItemPath'] == preg_replace('/^Content\//i', '', $this->Application->GetVar('t'));
			}*/

			if ( substr($page['ItemPath'], 0, 3) == 'id:' ) {
				// resolve ID path here, since it can be used directly without m_Link tag (that usually resolves it)
				$page['ItemPath'] = $this->Application->getVirtualPageTemplate(substr($page['ItemPath'], 3));
			}

			$block_params = Array (
				'title' => $title,
				'template' => $page['ItemPath'],
				'active' => $active,
				'category_active' => $category_active, // new
				'parent_path' => $page['ParentPath'],
				'parent_id' => $page['ParentId'],
				'cat_id' => $page['CategoryId'],
				'item_type' => $page['ItemType'],
				'page_id' => $page['ItemId'],
				'use_section' => ($page['Type'] == PAGE_TYPE_TEMPLATE) && ($page['ItemPath'] != 'index'),
				'has_sub_menu' => isset($page['sub_items']) && count($this->_removeNonMenuItems($page['sub_items'])) > 0,
				'external_url' => $page['UseExternalUrl'] ? $page['ExternalUrl'] : false, // for backward compatibility
				'menu_icon' => $page['UseMenuIconUrl'] ? $page['MenuIconUrl'] : false,
			);

			return $block_params;
		}

		/**
		 * Returns only items, that are visible in menu
		 *
		 * @param Array $menu
		 * @return Array
		 * @access protected
		 */
		protected function _removeNonMenuItems($menu)
		{
			$theme_id = $this->Application->GetVar('m_theme');

			foreach ($menu as $menu_index => $menu_item) {
				// $menu_index is in "cN" format, where N is category id
				if ( !$menu_item['IsMenu'] || $menu_item['Status'] != STATUS_ACTIVE || ($menu_item['ThemeId'] != $theme_id && $menu_item['ThemeId'] != 0) ) {
					// don't show sections, that are not from menu OR system templates from other themes
					unset($menu[$menu_index]);
				}
			}

			return $menu;
		}

		/**
		 * Builds cache of all menu items and their parent categories
		 *
		 * @param int $top_category_id
		 * @return Array
		 * @access protected
		 */
		protected function _buildMenuStructure($top_category_id)
		{
			// 1. get parent paths of leaf categories, that are in menu (across all themes)
			$sql = 'SELECT ParentPath, CategoryId
					FROM ' . $this->Application->getUnitOption('c', 'TableName') . '
					WHERE IsMenu = 1 AND Status = ' . STATUS_ACTIVE;
			$this->parentPaths = $this->Conn->GetCol($sql ,'CategoryId');

			// 2. figure out parent paths of all categories in path to leaf categories
			foreach ($this->parentPaths as $leaf_parent_path) {
				$parent_categories = explode('|', substr($leaf_parent_path, 1, -1));

				foreach ($parent_categories as $index => $parent_category_id) {
					if ( !isset($this->parentPaths[$parent_category_id]) ) {
						$parent_path = array_slice($parent_categories, 0, $index + 1);
						$this->parentPaths[$parent_category_id] = '|' . implode('|', $parent_path) . '|';
					}
				}
			}

			return $this->_altBuildMenuStructure($top_category_id, implode(',', array_keys($this->parentPaths)));
		}

		/**
		 * Builds cache for children of given category (no matter, what menu status is)
		 *
		 * @param int $parent_category_id
		 * @param string $category_limit
		 * @return Array
		 * @access protected
		 */
		protected function _altBuildMenuStructure($parent_category_id, $category_limit = NULL)
		{
			// Sub-categories from current category
			$items = $this->_getSubCategories($parent_category_id, $category_limit);

			// sort menu items
			uasort($items, Array (&$this, '_menuSort'));

			// process sub-menus of each menu
			foreach ($items as $key => $menu_item) {
				if ( $menu_item['CategoryId'] == $parent_category_id ) {
					// don't process myself - prevents recursion
					continue;
				}

				$sub_items = $this->_altBuildMenuStructure($menu_item['CategoryId'], $category_limit);

				if ( $sub_items ) {
					$items[$key]['sub_items'] = $sub_items;
				}
			}

			return $items;
		}

		/**
		 * Returns given category sub-categories
		 *
		 * @param int $parent_id
		 * @param string $category_limit
		 * @return Array
		 * @access protected
		 */
		protected function _getSubCategories($parent_id, $category_limit = NULL)
		{
			static $items_by_parent = NULL, $lang_part = NULL;

			if ( !isset($lang_part) ) {
				/** @var kMultiLanguageHelper $ml_helper */
				$ml_helper = $this->Application->recallObject('kMultiLanguageHelper');

				$lang_part = '';
				$languages = $ml_helper->getLanguages();

				foreach ($languages as $language_id) {
					$lang_part .= 'c.l' . $language_id . '_MenuTitle AS l' . $language_id . '_ItemName,' . "\n";
				}
			}

			if ( !isset($items_by_parent) ) {
				$items_by_parent = Array ();

				// Sub-categories from current category
				$sql = 'SELECT
							c.CategoryId AS CategoryId,
							CONCAT(\'c\', c.CategoryId) AS ItemId,
							c.Priority AS ItemPriority,
							' . $lang_part . '

							IF(c.`Type` = ' . PAGE_TYPE_TEMPLATE . ', c.Template, CONCAT("id:", c.CategoryId)) AS ItemPath,
							c.ParentPath AS ParentPath,
							c.ParentId As ParentId,
							\'cat\' AS ItemType,
							c.IsMenu, c.Type, c.ThemeId, c.UseExternalUrl, c.ExternalUrl, c.UseMenuIconUrl, c.MenuIconUrl,
							c.Status
						FROM ' . TABLE_PREFIX . 'Categories AS c';

				if ( isset($category_limit) && $category_limit ) {
					$sql .= ' WHERE c.CategoryId IN (' . $category_limit . ')';
				}

				$items = $this->Conn->Query($sql, 'ItemId');

				foreach ($items as $item_id => $item_data) {
					$item_parent_id = $item_data['ParentId'];

					if ( !array_key_exists($item_parent_id, $items_by_parent) ) {
						$items_by_parent[$item_parent_id] = Array ();
					}

					$items_by_parent[$item_parent_id][$item_id] = $item_data;
				}
			}

			return array_key_exists($parent_id, $items_by_parent) ? $items_by_parent[$parent_id] : Array ();
		}

		/**
		 * Method for sorting pages by priority in descending order
		 *
		 * @param Array $a
		 * @param Array $b
		 * @return int
		 */
		function _menuSort($a, $b)
		{
			if ( $a['ItemPriority'] == $b['ItemPriority'] ) {
				return 0;
			}

			return ($a['ItemPriority'] < $b['ItemPriority']) ? 1 : -1; // descending
		}
	}
