<?php
/**
* @version	$Id: category_helper.php 16559 2017-07-16 12:59:08Z 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;

		/**
		 * Returns module information based on given module name or current category
		 * (relative to module root categories).
		 *
		 * @param array $params        Tag params.
		 * @param array $category_path Category parent path.
		 *
		 * @return array|false
		 */
		public function getCategoryModule(array $params, array $category_path)
		{
			// Get module by name.
			if ( isset($params['module']) ) {
				return $this->Application->findModule('Name', $params['module']);
			}

			// Get module by category path.
			if ( $category_path ) {
				foreach ( array_reverse($category_path) as $module_category_id ) {
					$module_info = $this->Application->findModule('RootCat', $module_category_id);

					if ( $module_info && $module_info['Var'] != 'adm' ) {
						return $module_info;
					}
				}
			}

			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 $language_id
		 * @param int $theme_id
		 * @param int $level
		 * @return Array
		 * @access protected
		 */
		protected 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('&mdash;', $level) . ' ' . $data['l' . $category_language . '_Name']);

			if ( $data['children'] ) {
				$level++;
				foreach ($data['children'] as $category_id => $category_data) {
					// numeric keys
					$ret = kUtil::array_merge_recursive($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 Array $languages
		 * @return Array
		 * @access protected
		 */
		protected function _getChildren($parent_category_id, $languages)
		{
			static $items_by_parent = null, $parent_mapping = null;

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

				foreach ($languages as $language_id) {
					$fields[] = 'l' . $language_id . '_Name';
				}

				$sql = 'SELECT CategoryId AS id, ' . implode(', ', $fields) . ', ParentId, Template, ThemeId
						FROM ' . TABLE_PREFIX . 'Categories
						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;
				}

				$base_category = $this->Application->getBaseCategory(); // "Content" category

				if ( isset($items_by_parent[$base_category]) ) {
					$index_category = $this->findIndexCategoryId($items_by_parent[$base_category]);

					// rename "Content" into "Home" keeping it's ID
					$items_by_parent[$parent_mapping[$base_category]][$base_category]['l1_Name'] = $this->Application->Phrase('la_rootcategory_name');

					if ( $index_category !== false ) {
						// remove category of "index.tpl" template
						unset($items_by_parent[$base_category][$index_category]);
						unset($parent_mapping[$index_category]);
					}
				}
			}

			$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, $languages);
			}

			return $data;
		}

		/**
		 * Finds "Home" category among given top level categories
		 *
		 * @param Array $top_categories
		 * @return bool|int
		 * @access protected
		 */
		protected function findIndexCategoryId($top_categories)
		{
			foreach ($top_categories as $category_id => $category_info) {
				if ($category_info['Template'] == 'index') {
					return $category_id;
				}
			}

			return false;
		}

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

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

				return $data;
			}

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

			$languages = $ml_helper->getLanguages();
			$root_category = $this->Application->getBaseCategory();
			$data = $this->_getChildren($root_category, $languages);

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

			return $data;
		}

		/**
		 * Returns template mapping (between physical and virtual pages)
		 *
		 * @return Array
		 * @access public
		 */
		public function getTemplateMapping()
		{
			if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) {
				$data = $this->Application->getCache('master:template_mapping', false, CacheSettings::$templateMappingRebuildTime);
			}
			else {
				$data = $this->Application->getDBCache('template_mapping', CacheSettings::$templateMappingRebuildTime);
			}

			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 . 'Categories AS cc WHERE cc.CategoryId = c.SymLinkCategoryId),
							 	c.NamedParentPath
							 )
						) AS DstTemplate,
						c.UseExternalUrl, c.ExternalUrl
					FROM ' . TABLE_PREFIX . 'Categories 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
		 * @access public
		 */
		public 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;
			}

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

			$data = $this->_getStructureTree();

			$theme_id = (int)$themes_helper->getCurrentThemeId();

			$this->_primaryLanguageId = $this->Application->GetDefaultLanguageId();
			$this->_structureTree = $this->_printChildren($data, $data['id'], $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
		 * @access public
		 */
		public function replacePageIds($text)
		{
			if ( !preg_match_all('/@@(\\d+)@@/', $text, $regs) ) {
				return $text;
			}

			$page_ids = $regs[1];

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

			$base_category = $this->Application->getBaseCategory();

			if ( isset($templates[$base_category]) ) {
				// "Content" category will act as "Home Page"
				$templates[$base_category] .= '/Index';
			}

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

				$url_params = Array ('m_cat_id' => $page_id == $base_category ? 0 : $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;
		}
	}
