<?php
namespace TYPO3\CMS\Backend\View;

/**
 * This file is part of the TYPO3 CMS project.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;

/**
 * class to render the TYPO3 backend menu for the modules
 *
 * @author Ingo Renner <ingo@typo3.org>
 */
class ModuleMenuView {

	/**
	 * Module loading object
	 *
	 * @var \TYPO3\CMS\Backend\Module\ModuleLoader
	 */
	protected $moduleLoader;

	protected $backPath;

	protected $linkModules;

	protected $loadedModules;

	/**
	 * Constructor, initializes several variables
	 */
	public function __construct() {
		if (TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_AJAX) {
			$GLOBALS['LANG']->includeLLFile('EXT:lang/locallang_misc.xlf');
		}
		$this->backPath = '';
		$this->linkModules = TRUE;
		// Loads the backend modules available for the logged in user.
		$this->moduleLoader = GeneralUtility::makeInstance('TYPO3\\CMS\\Backend\\Module\\ModuleLoader');
		$this->moduleLoader->observeWorkspaces = TRUE;
		$this->moduleLoader->load($GLOBALS['TBE_MODULES']);
		$this->loadedModules = $this->moduleLoader->modules;
	}

	/**
	 * sets the path back to /typo3/
	 *
	 * @param string $backPath Path back to /typo3/
	 * @throws \InvalidArgumentException
	 * @return void
	 */
	public function setBackPath($backPath) {
		if (!is_string($backPath)) {
			throw new \InvalidArgumentException('parameter $backPath must be of type string', 1193315266);
		}
		$this->backPath = $backPath;
	}

	/**
	 * loads the collapse states for the main modules from user's configuration (uc)
	 *
	 * @return array Collapse states
	 */
	protected function getCollapsedStates() {
		$collapsedStates = array();
		if ($GLOBALS['BE_USER']->uc['moduleData']['moduleMenu']) {
			$collapsedStates = $GLOBALS['BE_USER']->uc['moduleData']['moduleMenu'];
		}
		return $collapsedStates;
	}

	/**
	 * ModuleMenu Store loading data
	 *
	 * @param array $params
	 * @param \TYPO3\CMS\Core\Http\AjaxRequestHandler $ajaxObj
	 * @return array
	 */
	public function getModuleData($params, $ajaxObj) {
		$data = array('success' => TRUE, 'root' => array());
		$rawModuleData = $this->getRawModuleData();
		$index = 0;
		foreach ($rawModuleData as $moduleKey => $moduleData) {
			$key = substr($moduleKey, 8);
			$num = count($data['root']);
			if ($moduleData['link'] != 'dummy.php' || $moduleData['link'] == 'dummy.php' && is_array($moduleData['subitems'])) {
				$data['root'][$num]['key'] = $key;
				$data['root'][$num]['menuState'] = $GLOBALS['BE_USER']->uc['moduleData']['menuState'][$moduleKey];
				$data['root'][$num]['label'] = $moduleData['title'];
				$data['root'][$num]['subitems'] = is_array($moduleData['subitems']) ? count($moduleData['subitems']) : 0;
				if ($moduleData['link'] && $this->linkModules) {
					$data['root'][$num]['link'] = 'top.goToModule(\'' . $moduleData['name'] . '\')';
				}
				// Traverse submodules
				if (is_array($moduleData['subitems'])) {
					foreach ($moduleData['subitems'] as $subKey => $subData) {
						$data['root'][$num]['sub'][] = array(
							'name' => $subData['name'],
							'description' => $subData['description'],
							'label' => $subData['title'],
							'icon' => $subData['icon']['filename'],
							'navframe' => $subData['parentNavigationFrameScript'],
							'link' => $subData['link'],
							'originalLink' => $subData['originalLink'],
							'index' => $index++,
							'navigationFrameScript' => $subData['navigationFrameScript'],
							'navigationFrameScriptParam' => $subData['navigationFrameScriptParam'],
							'navigationComponentId' => $subData['navigationComponentId']
						);
					}
				}
			}
		}
		if ($ajaxObj) {
			$ajaxObj->setContent($data);
			$ajaxObj->setContentFormat('jsonbody');
		} else {
			return $data;
		}
	}

	/**
	 * Returns the loaded modules
	 *
	 * @return array Array of loaded modules
	 */
	public function getLoadedModules() {
		return $this->loadedModules;
	}

	/**
	 * saves the menu's toggle state in the backend user's uc
	 *
	 * @param array $params Array of parameters from the AJAX interface, currently unused
	 * @param \TYPO3\CMS\Core\Http\AjaxRequestHandler $ajaxObj Object of type AjaxRequestHandler
	 * @return void
	 */
	public function saveMenuState($params, $ajaxObj) {
		$menuItem = GeneralUtility::_POST('menuid');
		$state = GeneralUtility::_POST('state') === 'true' ? 1 : 0;
		$GLOBALS['BE_USER']->uc['moduleData']['menuState'][$menuItem] = $state;
		$GLOBALS['BE_USER']->writeUC();
	}

	/**
	 * Reads User configuration from options.hideModules and removes
	 * modules from $this->loadedModules accordingly.
	 *
	 * @return void
	 */
	protected function unsetHiddenModules() {
		// Hide modules if set in userTS.
		$hiddenModules = $GLOBALS['BE_USER']->getTSConfig('options.hideModules');
		if (!empty($hiddenModules['value'])) {
			$hiddenMainModules = GeneralUtility::trimExplode(',', $hiddenModules['value'], TRUE);
			foreach ($hiddenMainModules as $hiddenMainModule) {
				unset($this->loadedModules[$hiddenMainModule]);
			}
		}

		// Hide sub-modules if set in userTS.
		if (!empty($hiddenModules['properties']) && is_array($hiddenModules['properties'])) {
			foreach ($hiddenModules['properties'] as $mainModuleName => $subModules) {
				$hiddenSubModules = GeneralUtility::trimExplode(',', $subModules, TRUE);
				foreach ($hiddenSubModules as $hiddenSubModule) {
					unset($this->loadedModules[$mainModuleName]['sub'][$hiddenSubModule]);
				}
			}
		}
	}

	/**
	 * gets the raw module data
	 *
	 * @return array Multi dimension array with module data
	 */
	public function getRawModuleData() {
		$modules = array();

		// Unset modules that are meant to be hidden from the menu.
		$this->unsetHiddenModules();

		foreach ($this->loadedModules as $moduleName => $moduleData) {
			$moduleLink = '';
			if (!is_array($moduleData['sub'])) {
				$moduleLink = $moduleData['script'];
			}
			$moduleLink = GeneralUtility::resolveBackPath($moduleLink);
			$moduleKey = 'modmenu_' . $moduleName;
			$moduleIcon = $this->getModuleIcon($moduleKey);
			$modules[$moduleKey] = array(
				'name' => $moduleName,
				'title' => $GLOBALS['LANG']->moduleLabels['tabs'][$moduleName . '_tab'],
				'onclick' => 'top.goToModule(\'' . $moduleName . '\');',
				'icon' => $moduleIcon,
				'link' => $moduleLink,
				'description' => $GLOBALS['LANG']->moduleLabels['labels'][$moduleKey . 'label']
			);
			if (!is_array($moduleData['sub']) && $moduleData['script'] != 'dummy.php') {
				// Work around for modules with own main entry, but being self the only submodule
				$modules[$moduleKey]['subitems'][$moduleKey] = array(
					'name' => $moduleName,
					'title' => $GLOBALS['LANG']->moduleLabels['tabs'][$moduleName . '_tab'],
					'onclick' => 'top.goToModule(\'' . $moduleName . '\');',
					'icon' => $this->getModuleIcon($moduleName . '_tab'),
					'link' => $moduleLink,
					'originalLink' => $moduleLink,
					'description' => $GLOBALS['LANG']->moduleLabels['labels'][$moduleKey . 'label'],
					'navigationFrameScript' => NULL,
					'navigationFrameScriptParam' => NULL,
					'navigationComponentId' => NULL
				);
			} elseif (is_array($moduleData['sub'])) {
				foreach ($moduleData['sub'] as $submoduleName => $submoduleData) {
					if (isset($submoduleData['script'])) {
						$submoduleLink = GeneralUtility::resolveBackPath($submoduleData['script']);
					} else {
						$submoduleLink = BackendUtility::getModuleUrl($submoduleData['name']);
					}
					$submoduleKey = $moduleName . '_' . $submoduleName . '_tab';
					$submoduleIcon = $this->getModuleIcon($submoduleKey);
					$submoduleDescription = $GLOBALS['LANG']->moduleLabels['labels'][$submoduleKey . 'label'];
					$originalLink = $submoduleLink;
					if (isset($submoduleData['navigationFrameModule'])) {
						$navigationFrameScript = BackendUtility::getModuleUrl(
							$submoduleData['navigationFrameModule'],
							isset($submoduleData['navigationFrameModuleParameters'])
								? $submoduleData['navigationFrameModuleParameters']
								: array()
						);
					} else {
						$navigationFrameScript = $submoduleData['navFrameScript'];
					}
					$modules[$moduleKey]['subitems'][$submoduleKey] = array(
						'name' => $moduleName . '_' . $submoduleName,
						'title' => $GLOBALS['LANG']->moduleLabels['tabs'][$submoduleKey],
						'onclick' => 'top.goToModule(\'' . $moduleName . '_' . $submoduleName . '\');',
						'icon' => $submoduleIcon,
						'link' => $submoduleLink,
						'originalLink' => $originalLink,
						'description' => $submoduleDescription,
						'navigationFrameScript' => $navigationFrameScript,
						'navigationFrameScriptParam' => $submoduleData['navFrameScriptParam'],
						'navigationComponentId' => $submoduleData['navigationComponentId']
					);
					// if the main module has a navframe script, inherit to the submodule,
					// but only if it is not disabled explicitly (option is set to FALSE)
					if ($moduleData['navFrameScript'] && $submoduleData['inheritNavigationComponentFromMainModule'] !== FALSE) {
						$modules[$moduleKey]['subitems'][$submoduleKey]['parentNavigationFrameScript'] = $moduleData['navFrameScript'];
					}
				}
			}
		}
		return $modules;
	}

	/**
	 * gets the module icon and its size
	 *
	 * @param string $moduleKey Module key
	 * @return array Icon data array with 'filename', 'size', and 'html'
	 */
	protected function getModuleIcon($moduleKey) {
		$icon = array(
			'filename' => '',
			'size' => '',
			'title' => '',
			'html' => ''
		);

		if (!empty($GLOBALS['LANG']->moduleLabels['tabs_images'][$moduleKey])) {
			$imageReference = $GLOBALS['LANG']->moduleLabels['tabs_images'][$moduleKey];
			$iconFileRelative = $this->getModuleIconRelative($imageReference);
			if (!empty($iconFileRelative)) {
				$iconTitle = $GLOBALS['LANG']->moduleLabels['tabs'][$moduleKey];
				$iconFileAbsolute = $this->getModuleIconAbsolute($imageReference);
				$iconSizes = @getimagesize($iconFileAbsolute);
				$icon['filename'] = $iconFileRelative;
				$icon['size'] = $iconSizes[3];
				$icon['title'] = htmlspecialchars($iconTitle);
				$icon['html'] = '<img src="' . $iconFileRelative . '" ' . $iconSizes[3] . ' title="' . htmlspecialchars($iconTitle) . '" alt="' . htmlspecialchars($iconTitle) . '" />';
			}
		}
		return $icon;
	}

	/**
	 * Returns the filename readable for the script from PATH_typo3.
	 * That means absolute names are just returned while relative names are
	 * prepended with the path pointing back to typo3/ dir
	 *
	 * @param string $iconFilename Icon filename
	 * @return string Icon filename with absolute path
	 * @see getModuleIconRelative()
	 */
	protected function getModuleIconAbsolute($iconFilename) {
		if (!GeneralUtility::isAbsPath($iconFilename)) {
			$iconFilename = $this->backPath . $iconFilename;
		}
		return $iconFilename;
	}

	/**
	 * Returns relative path to the icon filename for use in img-tags
	 *
	 * @param string $iconFilename Icon filename
	 * @return string Icon filename with relative path
	 * @see getModuleIconAbsolute()
	 */
	protected function getModuleIconRelative($iconFilename) {
		if (GeneralUtility::isAbsPath($iconFilename)) {
			$iconFilename = '../' . \TYPO3\CMS\Core\Utility\PathUtility::stripPathSitePrefix($iconFilename);
		}
		return $this->backPath . $iconFilename;
	}

	/**
	 * Appends a '?' if there is none in the string already
	 *
	 * @param string $link Link URL
	 * @return string Link URl appended with ? if there wasn't one
	 */
	protected function appendQuestionmarkToLink($link) {
		if (!strstr($link, '?')) {
			$link .= '?';
		}
		return $link;
	}

	/**
	 * renders the logout button form
	 *
	 * @return string Html code snippet displaying the logout button
	 */
	public function renderLogoutButton() {
		$buttonLabel = $GLOBALS['BE_USER']->user['ses_backuserid'] ? 'LLL:EXT:lang/locallang_core.xlf:buttons.exit' : 'LLL:EXT:lang/locallang_core.xlf:buttons.logout';
		$buttonForm = '
		<form action="logout.php" target="_top">
			<input type="submit" id="logout-submit-button" value="' . $GLOBALS['LANG']->sL($buttonLabel, TRUE) . '" />
		</form>';
		return $buttonForm;
	}

	/**
	 * turns linking of modules on or off
	 *
	 * @param boolean $linkModules Status for linking modules with a-tags, set to FALSE to turn lining off
	 * @throws \InvalidArgumentException
	 * @return void
	 */
	public function setLinkModules($linkModules) {
		if (!is_bool($linkModules)) {
			throw new \InvalidArgumentException('parameter $linkModules must be of type bool', 1193326558);
		}
		$this->linkModules = $linkModules;
	}

}
