<?php
/**
* @version	$Id: menu_helper.php 12951 2009-12-19 10:04:11Z 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
		 */
		var $Menu = null;

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

		/**
		 * Builds site menu
		 *
		 * @param Array $params
		 * @return string
		 */
		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_recursive2(
					$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);
				}
				else {
					$menu = $this->Application->getDBCache('cms_menu');
				}

				if ($menu) {
					$menu = unserialize($menu);
					$this->parentPaths = $menu['parentPaths'];
				}
				else {
					$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 {
						$to_cache = serialize($menu); // setDBCache's 2nd parameter passed by reference!
						$this->Application->setDBCache('cms_menu', $to_cache);
					}
				}

				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 = '';

				for ($i = 1; $i <= $ml_helper->languageCount; $i++) {
					$lang_part .= 'c.l' . $i . '_MenuTitle AS l' . $i . '_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 ();

				for ($i = 1; $i <= $ml_helper->languageCount; $i++) {
					$lang_part .= 'c.l' . $i . '_MenuTitle AS l' . $i . '_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
		}
	}
