<?php
/**
* @version	$Id: category_helper.php 14241 2011-03-16 20:24:35Z 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 CategoryHelper extends kHelper {

		/**
		 * Structure tree for ParentId field in category or category items
		 *
		 * @var Array
		 */
		var $_structureTree = null;

		/**
		 * ID of primary language (only for caching)
		 *
		 * @var int
		 */
		var $_primaryLanguageId = false;

		/**
		 * Prints category path using given blocks. Also supports used defined path elements at the end.
		 *
		 * @param Array $params
		 * @return string
		 */
		function NavigationBar($params)
		{
			$params['is_first'] = 1;
			$main_category_id = isset($params['cat_id']) ? $params['cat_id'] : $this->Application->GetVar('m_cat_id');

			if (array_key_exists('shift', $params) && $params['shift']) {
				$home_element = '';
				$params['shift']--;
			}
			else {
				$home_element = $this->getHomeCategoryPath($params, $main_category_id);
				unset($params['is_first']);
			}

			if (!getArrayValue($params, 'titles') && !getArrayValue($params, 'templates')) {
				// no static templates given, show only category path
				return $home_element . $this->getCategoryPath($main_category_id, $params);
			}

			$navigation_parts = $this->getNavigationParts($params['titles'], $params['templates']);

			$ret = '';
			$block_params = Array (); //$params; // sort of TagProcessor:prepareTagParams
			$block_params['no_editing'] = 1;
			$block_params['category'] = 0;
			$block_params['separator'] = $params['separator'];
			$show_category = getArrayValue($params, 'show_category');

			$current_template = $this->Application->GetVar('t');
			$physical_template = array_search($current_template, $this->Application->structureTemplateMapping);

			if ($physical_template !== false) {
				// replace menu template name with it's actual template name on disk
				list ($current_template) = explode(':', $physical_template, 2);
			}

			foreach ($navigation_parts as $template => $title) {
				$block_params['template'] = $template;

				if ($title == '__item__') {
					if ($show_category) {
						$ret .= $this->getCategoryPath($main_category_id, $params);
						$show_category = false;
					}

					$category_path = $this->getCategoryParentPath($main_category_id);
					$module_info = $this->getCategoryModule($params, array_keys($category_path));
					if (!$module_info) {
						continue;
					}

					$module_prefix = $module_info['Var'];
					$object =& $this->Application->recallObject($module_prefix);
					/* @var $object kCatDBItem */

					$title_field = $this->Application->getUnitOption($module_prefix, 'TitleField');
					$block_params['title'] = $object->GetField($title_field);
					$block_params['prefix'] = $module_prefix;
					$block_params['current'] = 0;

					$block_params['name'] = $this->SelectParam($params, 'module_item_render_as,render_as');
				}
				else {
					$block_params['current'] = ($template == $current_template);
					$block_params['title'] = $this->Application->Phrase($title);

					$block_params['name'] = $template == $current_template ? $params['current_render_as'] : $params['render_as'];
				}

				$ret .= $this->Application->ParseBlock($block_params);
			}

			if ($show_category) {
				$params['no_current'] = true;
				return $home_element . ($show_category ? $this->getCategoryPath($main_category_id, $params) : '') . $ret;
			}

			return $home_element . $ret;
		}

		/**
		 * Get navigation parts
		 *
		 * @param Array $titles
		 * @param Array $templates
		 * @return Array
		 */
		function getNavigationParts($titles, $templates)
		{
			$titles = explode(',', $titles);
			$templates = explode(',', $templates);

			$ret = Array ();
			foreach ($templates as $template_pos => $template) {
				$ret[$template] = $titles[$template_pos];
			}

			return $ret;
		}

		/**
		 * Renders path to given category using given blocks.
		 *
		 * @param int $main_category_id
		 * @param Array $params
		 * @return string
		 */
		function getCategoryPath($main_category_id, $params)
		{
			$category_path = $this->getCategoryParentPath($main_category_id);
			if (!$category_path) {
				// in "Home" category
				return '';
			}

			if (array_key_exists('shift', $params) && $params['shift']) {
				array_splice($category_path, 0, $params['shift']);
			}

			$module_info = $this->getCategoryModule($params, array_keys($category_path));

			$module_category_id = $module_info['RootCat'];
			$module_item_id = $this->Application->GetVar($module_info['Var'].'_id');

			$ret = '';
			$block_params['category'] = 1;
			$block_params['no_editing'] = 1;

			if (array_key_exists('is_first', $params)) {
				$block_params['is_first'] = $params['is_first'];
			}

			$block_params['separator'] = $params['separator'];
			$no_current = isset($params['no_current']) && $params['no_current'];

			$backup_category_id = $this->Application->GetVar('c_id');
			foreach ($category_path as $category_id => $category_name) {
				$block_params['cat_id'] = $category_id;
				$block_params['cat_name'] = $block_params['title'] = $category_name;

				if ($no_current) {
					$block_params['current'] = 0;
				}
				else {
					$block_params['current'] = ($main_category_id == $category_id) && !$module_item_id ? 1 : 0;
				}

				$block_params['is_module_root'] = $category_id == $module_category_id ? 1 : 0;
				$block_params['name'] = $this->SelectParam($params, 'render_as,block');

				// which block to parse as current ?
				if ($block_params['is_module_root']) {
					$block_params['name'] = $this->SelectParam($params, 'module_root_render_as,render_as');
					$block_params['module_index'] = $module_info['TemplatePath'].'index';
				}

				if ($block_params['current']) {
					$block_params['name'] = $this->SelectParam($params, 'current_render_as,render_as');
				}

				$this->Application->SetVar('c_id', $category_id);
				$ret .= $this->Application->ParseBlock($block_params);

				if (array_key_exists('is_first', $block_params)) {
					unset($block_params['is_first']);
				}
			}

			$this->Application->SetVar('c_id', $backup_category_id);

			return $ret;
		}

		/**
		 * Returns module information based on given module name or current category (relative to module root categories)
		 *
		 * @param Array $params
		 * @param Array $category_ids category parent path (already as array)
		 * @return Array
		 */
		function getCategoryModule($params, $category_ids)
		{
			if (isset($params['module'])) {
				// get module by name specified
				$module_info = $this->Application->findModule('Name', $params['module']);

			}
			elseif ($category_ids) {
				// get module by category path
				$module_root_categories = $this->getModuleRootCategories();
				$common_categories = array_intersect($category_ids, $module_root_categories);
				$module_category_id = array_shift($common_categories); // get 1st common category
				$module_info = $this->Application->findModule('RootCat', $module_category_id);
			}

			return $module_info;
		}

		/**
		 * Renders path to top catalog category
		 *
		 * @param Array $params
		 * @param int $current_category
		 * @return string
		 */
		function getHomeCategoryPath($params, $current_category)
		{
			$block_params['cat_id'] = $this->Application->getBaseCategory();
			$block_params['no_editing'] = 1;
			$block_params['current'] = $current_category == $block_params['cat_id'] ? 1 : 0;
			$block_params['separator'] = $params['separator'];
			$block_params['is_first'] = $params['is_first'];
			$block_params['cat_name'] = $this->Application->Phrase(($this->Application->isAdmin ? 'la_' : 'lu_') . 'rootcategory_name');
			$block_params['name'] = $this->SelectParam($params, 'root_cat_render_as,render_as');

			return $this->Application->ParseBlock($block_params);
		}

		/**
		 * Returns root categories from all modules
		 *
		 * @return Array
		 */
		function getModuleRootCategories()
		{
			static $root_categories = null;

			if (!isset($root_categories)) {
				$root_categories = Array ();
				foreach ($this->Application->ModuleInfo as $module_name => $module_info) {
					array_push($root_categories, $module_info['RootCat']);
				}

				$root_categories = array_unique($root_categories);
			}

			return $root_categories;
		}

		/**
		 * Returns given category's parent path as array of id=>name elements
		 *
		 * @param int $main_category_id
		 * @return Array
		 */
		function getCategoryParentPath($main_category_id)
		{
			if ($main_category_id == 0) {
				// don't query path for "Home" category
				return Array ();
			}

			$cache_key = 'parent_paths_named[%CIDSerial:' . $main_category_id . '%]';
			$cached_path = $this->Application->getCache($cache_key);

			if ($cached_path === false) {
				$ml_formatter =& $this->Application->recallObject('kMultiLanguage');
				$navbar_field = $ml_formatter->LangFieldName('CachedNavBar');

				$id_field = $this->Application->getUnitOption('c', 'IDField');
				$table_name = $this->Application->getUnitOption('c', 'TableName');

				$this->Conn->nextQueryCachable = true;
				$sql = 'SELECT '.$navbar_field.', ParentPath
						FROM '.$table_name.'
						WHERE '.$id_field.' = '.$main_category_id;
				$category_data = $this->Conn->GetRow($sql);

				$cached_path = Array ();
				$skip_category = $this->Application->getBaseCategory();

				if ($category_data) {
					$category_names = explode('&|&', $category_data[$navbar_field]);
					$category_ids = explode('|', substr($category_data['ParentPath'], 1, -1));

					foreach ($category_ids as $category_index => $category_id) {
						if ($category_id == $skip_category) {
							continue;
						}

						$cached_path[$category_id] = $category_names[$category_index];
					}
				}

				$this->Application->setCache($cache_key, $cached_path);
			}

			return $cached_path;
		}

		 /**
		 * Not tag, method for parameter
		 * selection from list in this TagProcessor
		 *
		 * @param Array $params
		 * @param string $possible_names
		 * @return string
		 * @access public
		 */
		function SelectParam($params, $possible_names)
		{
			if (!is_array($params)) return;
			if (!is_array($possible_names))

			$possible_names = explode(',', $possible_names);
			foreach ($possible_names as $name)
			{
				if( isset($params[$name]) ) return $params[$name];
			}
			return false;
		}

		/**
		 * Converts multi-dimensional category structure in one-dimensional option array (category_id=>category_name)
		 *
		 * @param Array $data
		 * @param int $parent_category_id
		 * @param int_type $language_id
		 * @param int $theme_id
		 * @param int $level
		 * @return Array
		 */
		function _printChildren(&$data, $parent_category_id, $language_id, $theme_id, $level = 0)
		{
			if ($data['ThemeId'] != $theme_id && $data['ThemeId'] != 0) {
				// don't show system templates from different themes
				return Array ();
			}

			$category_language = $data['l' . $language_id . '_Name'] ? $language_id : $this->_primaryLanguageId;
			$ret = Array($parent_category_id => str_repeat('-', $level).' '.$data['l' . $category_language . '_Name']);

			if ($data['children']) {
				$level++;
				foreach ($data['children'] as $category_id => $category_data) {
					$ret = array_merge_recursive2($ret, $this->_printChildren($data['children'][$category_id], $category_id, $language_id, $theme_id, $level));
				}
			}

			return $ret;
		}

		/**
		 * Returns information about children under parent path (recursive)
		 *
		 * @param int $parent_category_id
		 * @param int $language_count
		 * @return Array
		 */
		function _getChildren($parent_category_id, $language_count)
		{
			static $items_by_parent = null, $parent_mapping = null;

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

				for ($i = 1; $i <= $language_count; $i++) {
					$fields[] = 'l' . $i . '_Name';
				}

				$sql = 'SELECT CategoryId AS id, ' . implode(', ', $fields) . ', ParentId, ThemeId
						FROM ' . $this->Application->getUnitOption('c', 'TableName') . '
						ORDER BY Priority DESC';
				$items = $this->Conn->Query($sql, 'id');

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

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

					$item_data['children'] = false;
					$parent_mapping[$item_id] = $item_parent_id;
					$items_by_parent[$item_parent_id][$item_id] = $item_data;
				}
			}

			$data = $items_by_parent[ $parent_mapping[$parent_category_id] ][$parent_category_id];
			$categories = array_key_exists($parent_category_id, $items_by_parent) ? $items_by_parent[$parent_category_id] : Array ();

			foreach ($categories as $category_id => $category_data) {
				if ($category_id == $parent_category_id) {
					// don't process myself - prevents recursion
					continue;
				}

				$data['children'][$category_id] = $this->_getChildren($category_id, $language_count);
			}

			return $data;
		}

		/**
		 * Generates OR retrieves from cache structure tree
		 *
		 * @return Array
		 */
		function &_getStructureTree()
		{
			// get cached version of structure tree
			if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) {
				$data = $this->Application->getCache('master:StructureTree', false);
			}
			else {
				$data = $this->Application->getDBCache('StructureTree');
			}

			if ($data) {
				$data = unserialize($data);

				return $data;
			}

			// generate structure tree from scratch
			$ml_helper =& $this->Application->recallObject('kMultiLanguageHelper');
			/* @var $ml_helper kMultiLanguageHelper */

			$language_count = $ml_helper->getLanguageCount();

			$root_category = $this->Application->getBaseCategory();
			$data = $this->_getChildren($root_category, $language_count);

			if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) {
				$this->Application->setCache('master:StructureTree', serialize($data));
			}
			else {
				$this->Application->setDBCache('StructureTree', serialize($data));
			}

			return $data;
		}

		function getTemplateMapping()
		{
			if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) {
				$data = $this->Application->getCache('master:template_mapping', false);
			}
			else {
				$data = $this->Application->getDBCache('template_mapping');
			}

			if ($data) {
				return unserialize($data);
			}

			$sql = 'SELECT
						IF(c.`Type` = ' . PAGE_TYPE_TEMPLATE . ', CONCAT(c.Template, ":", c.ThemeId), CONCAT("id:", c.CategoryId)) AS SrcTemplate,
						LOWER(
							IF(
								c.SymLinkCategoryId IS NOT NULL,
								(SELECT cc.NamedParentPath FROM ' . TABLE_PREFIX . 'Category AS cc WHERE cc.CategoryId = c.SymLinkCategoryId),
							 	c.NamedParentPath
							 )
						) AS DstTemplate,
						c.UseExternalUrl, c.ExternalUrl
					FROM ' . TABLE_PREFIX . 'Category AS c
					WHERE c.Status = ' . STATUS_ACTIVE;
			$pages = $this->Conn->Query($sql, 'SrcTemplate');

			$mapping = Array ();
			$base_url = $this->Application->BaseURL();

			foreach ($pages as $src_template => $page) {
				// process external url, before placing in cache
				if ($page['UseExternalUrl']) {
					$external_url = $page['ExternalUrl'];

					if (!preg_match('/^(.*?):\/\/(.*)$/', $external_url)) {
						// url without protocol will be relative url to our site
						$external_url = $base_url . $external_url;
					}

					$dst_template = 'external:' . $external_url;
				}
				else {
					$dst_template = preg_replace('/^Content\//i', '', $page['DstTemplate']);
				}

				$mapping[$src_template] = $dst_template;
			}

			if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) {
				$data = $this->Application->setCache('master:template_mapping', serialize($mapping));
			}
			else {
				$this->Application->setDBCache('template_mapping', serialize($mapping));
			}

			return $mapping;
		}

		/**
		 * Returns category structure as field option list
		 *
		 * @return Array
		 */
		function getStructureTreeAsOptions()
		{
			if ((defined('IS_INSTALL') && IS_INSTALL) || !$this->Application->isAdmin) {
				// no need to create category structure during install
				// OR on Front-End, because it's not used there
				return Array ();
			}

			if (isset($this->_structureTree)) {
				return $this->_structureTree;
			}

			$themes_helper =& $this->Application->recallObject('ThemesHelper');
			/* @var $themes_helper kThemesHelper */

			$data = $this->_getStructureTree();

			$theme_id = (int)$themes_helper->getCurrentThemeId();
			$root_category = $this->Application->getBaseCategory();

			$this->_primaryLanguageId = $this->Application->GetDefaultLanguageId();
			$this->_structureTree = $this->_printChildren($data, $root_category, $this->Application->GetVar('m_lang'), $theme_id);

			return $this->_structureTree;
		}

		/**
		 * Replace links like "@@ID@@" to actual template names in given text
		 *
		 * @param string $text
		 * @return string
		 */
		function replacePageIds($text)
		{
			if (!preg_match_all('/@@(\\d+)@@/', $text, $regs)) {
				return $text;
			}

			$page_ids = $regs[1];

			$sql = 'SELECT NamedParentPath, CategoryId
					FROM ' . TABLE_PREFIX . 'Category
					WHERE CategoryId IN (' . implode(',', $page_ids) . ')';
			$templates = $this->Conn->GetCol($sql, 'CategoryId');

			foreach ($page_ids as $page_id) {
				if (!array_key_exists($page_id, $templates)) {
					// internal page was deleted, but link to it was found in given content block data
					continue;
				}

				$url_params = Array ('m_cat_id' => $page_id, 'pass' => 'm');
				$page_url = $this->Application->HREF(strtolower($templates[$page_id]), '', $url_params);
				/*if ($this->Application->isAdmin) {
					$page_url = preg_replace('/&(admin|editing_mode)=[\d]/', '', $page_url);
				}*/

				$text = str_replace('@@' . $page_id . '@@', $page_url, $text);
			}

			return $text;
		}
	}