[FEATURE] Control visibility of modules in the BE
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / View / ModuleMenuView.php
1 <?php
2 namespace TYPO3\CMS\Backend\View;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 2007-2013 Ingo Renner <ingo@typo3.org>
8 * All rights reserved
9 *
10 * This script is part of the TYPO3 project. The TYPO3 project is
11 * free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * The GNU General Public License can be found at
17 * http://www.gnu.org/copyleft/gpl.html.
18 * A copy is found in the textfile GPL.txt and important notices to the license
19 * from the author is found in LICENSE.txt distributed with these scripts.
20 *
21 *
22 * This script is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * This copyright notice MUST APPEAR in all copies of the script!
28 ***************************************************************/
29
30 use TYPO3\CMS\Core\Utility\GeneralUtility;
31
32 /**
33 * class to render the TYPO3 backend menu for the modules
34 *
35 * @author Ingo Renner <ingo@typo3.org>
36 */
37 class ModuleMenuView {
38
39 /**
40 * Module loading object
41 *
42 * @var \TYPO3\CMS\Backend\Module\ModuleLoader
43 */
44 protected $moduleLoader;
45
46 protected $backPath;
47
48 protected $linkModules;
49
50 protected $loadedModules;
51
52 /**
53 * Constructor, initializes several variables
54 */
55 public function __construct() {
56 if (TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_AJAX) {
57 $GLOBALS['LANG']->includeLLFile('EXT:lang/locallang_misc.xlf');
58 }
59 $this->backPath = '';
60 $this->linkModules = TRUE;
61 // Loads the backend modules available for the logged in user.
62 $this->moduleLoader = GeneralUtility::makeInstance('TYPO3\\CMS\\Backend\\Module\\ModuleLoader');
63 $this->moduleLoader->observeWorkspaces = TRUE;
64 $this->moduleLoader->load($GLOBALS['TBE_MODULES']);
65 $this->loadedModules = $this->moduleLoader->modules;
66 }
67
68 /**
69 * sets the path back to /typo3/
70 *
71 * @param string $backPath Path back to /typo3/
72 * @throws \InvalidArgumentException
73 * @return void
74 */
75 public function setBackPath($backPath) {
76 if (!is_string($backPath)) {
77 throw new \InvalidArgumentException('parameter $backPath must be of type string', 1193315266);
78 }
79 $this->backPath = $backPath;
80 }
81
82 /**
83 * loads the collapse states for the main modules from user's configuration (uc)
84 *
85 * @return array Collapse states
86 */
87 protected function getCollapsedStates() {
88 $collapsedStates = array();
89 if ($GLOBALS['BE_USER']->uc['moduleData']['moduleMenu']) {
90 $collapsedStates = $GLOBALS['BE_USER']->uc['moduleData']['moduleMenu'];
91 }
92 return $collapsedStates;
93 }
94
95 /**
96 * ModuleMenu Store loading data
97 *
98 * @param array $params
99 * @param \TYPO3\CMS\Core\Http\AjaxRequestHandler $ajaxObj
100 * @return array
101 */
102 public function getModuleData($params, $ajaxObj) {
103 $data = array('success' => TRUE, 'root' => array());
104 $rawModuleData = $this->getRawModuleData();
105 $index = 0;
106 foreach ($rawModuleData as $moduleKey => $moduleData) {
107 $key = substr($moduleKey, 8);
108 $num = count($data['root']);
109 if ($moduleData['link'] != 'dummy.php' || $moduleData['link'] == 'dummy.php' && is_array($moduleData['subitems'])) {
110 $data['root'][$num]['key'] = $key;
111 $data['root'][$num]['menuState'] = $GLOBALS['BE_USER']->uc['moduleData']['menuState'][$moduleKey];
112 $data['root'][$num]['label'] = $moduleData['title'];
113 $data['root'][$num]['subitems'] = is_array($moduleData['subitems']) ? count($moduleData['subitems']) : 0;
114 if ($moduleData['link'] && $this->linkModules) {
115 $data['root'][$num]['link'] = 'top.goToModule(\'' . $moduleData['name'] . '\')';
116 }
117 // Traverse submodules
118 if (is_array($moduleData['subitems'])) {
119 foreach ($moduleData['subitems'] as $subKey => $subData) {
120 $data['root'][$num]['sub'][] = array(
121 'name' => $subData['name'],
122 'description' => $subData['description'],
123 'label' => $subData['title'],
124 'icon' => $subData['icon']['filename'],
125 'navframe' => $subData['parentNavigationFrameScript'],
126 'link' => $subData['link'],
127 'originalLink' => $subData['originalLink'],
128 'index' => $index++,
129 'navigationFrameScript' => $subData['navigationFrameScript'],
130 'navigationFrameScriptParam' => $subData['navigationFrameScriptParam'],
131 'navigationComponentId' => $subData['navigationComponentId']
132 );
133 }
134 }
135 }
136 }
137 if ($ajaxObj) {
138 $ajaxObj->setContent($data);
139 $ajaxObj->setContentFormat('jsonbody');
140 } else {
141 return $data;
142 }
143 }
144
145 /**
146 * Returns the loaded modules
147 *
148 * @return array Array of loaded modules
149 */
150 public function getLoadedModules() {
151 return $this->loadedModules;
152 }
153
154 /**
155 * saves the menu's toggle state in the backend user's uc
156 *
157 * @param array $params Array of parameters from the AJAX interface, currently unused
158 * @param \TYPO3\CMS\Core\Http\AjaxRequestHandler $ajaxObj Object of type AjaxRequestHandler
159 * @return void
160 */
161 public function saveMenuState($params, $ajaxObj) {
162 $menuItem = GeneralUtility::_POST('menuid');
163 $state = GeneralUtility::_POST('state') === 'true' ? 1 : 0;
164 $GLOBALS['BE_USER']->uc['moduleData']['menuState'][$menuItem] = $state;
165 $GLOBALS['BE_USER']->writeUC();
166 }
167
168 /**
169 * Reads User configuration from options.hideModules and removes
170 * modules from $this->loadedModules accordingly.
171 *
172 * @return void
173 */
174 protected function unsetHiddenModules() {
175 // Hide modules if set in userTS.
176 $hiddenModules = $GLOBALS['BE_USER']->getTSConfig('options.hideModules');
177 if (!empty($hiddenModules['value'])) {
178 $hiddenMainModules = GeneralUtility::trimExplode(',', $hiddenModules['value'], TRUE);
179 foreach ($hiddenMainModules as $hiddenMainModule) {
180 unset($this->loadedModules[$hiddenMainModule]);
181 }
182 }
183
184 // Hide sub-modules if set in userTS.
185 if (!empty($hiddenModules['properties']) && is_array($hiddenModules['properties'])) {
186 foreach ($hiddenModules['properties'] as $mainModuleName => $subModules) {
187 $hiddenSubModules = GeneralUtility::trimExplode(',', $subModules, TRUE);
188 foreach ($hiddenSubModules as $hiddenSubModule) {
189 unset($this->loadedModules[$mainModuleName]['sub'][$hiddenSubModule]);
190 }
191 }
192 }
193 }
194
195 /**
196 * gets the raw module data
197 *
198 * @return array Multi dimension array with module data
199 */
200 public function getRawModuleData() {
201 $modules = array();
202 // Remove the 'doc' module?
203 if ($GLOBALS['BE_USER']->getTSConfigVal('options.disableDocModuleInAB')) {
204 unset($this->loadedModules['doc']);
205 }
206
207 // Unset modules that are meant to be hidden from the menu.
208 $this->unsetHiddenModules();
209
210 foreach ($this->loadedModules as $moduleName => $moduleData) {
211 $moduleLink = '';
212 if (!is_array($moduleData['sub'])) {
213 $moduleLink = $moduleData['script'];
214 }
215 $moduleLink = GeneralUtility::resolveBackPath($moduleLink);
216 $moduleKey = 'modmenu_' . $moduleName;
217 $moduleIcon = $this->getModuleIcon($moduleKey);
218 $modules[$moduleKey] = array(
219 'name' => $moduleName,
220 'title' => $GLOBALS['LANG']->moduleLabels['tabs'][$moduleName . '_tab'],
221 'onclick' => 'top.goToModule(\'' . $moduleName . '\');',
222 'icon' => $moduleIcon,
223 'link' => $moduleLink,
224 'description' => $GLOBALS['LANG']->moduleLabels['labels'][$moduleKey . 'label']
225 );
226 if (!is_array($moduleData['sub']) && $moduleData['script'] != 'dummy.php') {
227 // Work around for modules with own main entry, but being self the only submodule
228 $modules[$moduleKey]['subitems'][$moduleKey] = array(
229 'name' => $moduleName,
230 'title' => $GLOBALS['LANG']->moduleLabels['tabs'][$moduleName . '_tab'],
231 'onclick' => 'top.goToModule(\'' . $moduleName . '\');',
232 'icon' => $this->getModuleIcon($moduleName . '_tab'),
233 'link' => $moduleLink,
234 'originalLink' => $moduleLink,
235 'description' => $GLOBALS['LANG']->moduleLabels['labels'][$moduleKey . 'label'],
236 'navigationFrameScript' => NULL,
237 'navigationFrameScriptParam' => NULL,
238 'navigationComponentId' => NULL
239 );
240 } elseif (is_array($moduleData['sub'])) {
241 foreach ($moduleData['sub'] as $submoduleName => $submoduleData) {
242 $submoduleLink = GeneralUtility::resolveBackPath($submoduleData['script']);
243 $submoduleKey = $moduleName . '_' . $submoduleName . '_tab';
244 $submoduleIcon = $this->getModuleIcon($submoduleKey);
245 $submoduleDescription = $GLOBALS['LANG']->moduleLabels['labels'][$submoduleKey . 'label'];
246 $originalLink = $submoduleLink;
247 $modules[$moduleKey]['subitems'][$submoduleKey] = array(
248 'name' => $moduleName . '_' . $submoduleName,
249 'title' => $GLOBALS['LANG']->moduleLabels['tabs'][$submoduleKey],
250 'onclick' => 'top.goToModule(\'' . $moduleName . '_' . $submoduleName . '\');',
251 'icon' => $submoduleIcon,
252 'link' => $submoduleLink,
253 'originalLink' => $originalLink,
254 'description' => $submoduleDescription,
255 'navigationFrameScript' => $submoduleData['navFrameScript'],
256 'navigationFrameScriptParam' => $submoduleData['navFrameScriptParam'],
257 'navigationComponentId' => $submoduleData['navigationComponentId']
258 );
259 // if the main module has a navframe script, inherit to the submodule,
260 // but only if it is not disabled explicitly (option is set to FALSE)
261 if ($moduleData['navFrameScript'] && $submoduleData['inheritNavigationComponentFromMainModule'] !== FALSE) {
262 $modules[$moduleKey]['subitems'][$submoduleKey]['parentNavigationFrameScript'] = $moduleData['navFrameScript'];
263 }
264 }
265 }
266 }
267 return $modules;
268 }
269
270 /**
271 * gets the module icon and its size
272 *
273 * @param string $moduleKey Module key
274 * @return array Icon data array with 'filename', 'size', and 'html'
275 */
276 protected function getModuleIcon($moduleKey) {
277 $icon = array(
278 'filename' => '',
279 'size' => '',
280 'title' => '',
281 'html' => ''
282 );
283 $iconFileRelative = $this->getModuleIconRelative($GLOBALS['LANG']->moduleLabels['tabs_images'][$moduleKey]);
284 $iconFileAbsolute = $this->getModuleIconAbsolute($GLOBALS['LANG']->moduleLabels['tabs_images'][$moduleKey]);
285 $iconSizes = @getimagesize($iconFileAbsolute);
286 $iconTitle = $GLOBALS['LANG']->moduleLabels['tabs'][$moduleKey];
287 if (!empty($iconFileRelative)) {
288 $icon['filename'] = $iconFileRelative;
289 $icon['size'] = $iconSizes[3];
290 $icon['title'] = htmlspecialchars($iconTitle);
291 $icon['html'] = '<img src="' . $iconFileRelative . '" ' . $iconSizes[3] . ' title="' . htmlspecialchars($iconTitle) . '" alt="' . htmlspecialchars($iconTitle) . '" />';
292 }
293 return $icon;
294 }
295
296 /**
297 * Returns the filename readable for the script from PATH_typo3.
298 * That means absolute names are just returned while relative names are
299 * prepended with the path pointing back to typo3/ dir
300 *
301 * @param string $iconFilename Icon filename
302 * @return string Icon filename with absolute path
303 * @see getModuleIconRelative()
304 */
305 protected function getModuleIconAbsolute($iconFilename) {
306 if (!GeneralUtility::isAbsPath($iconFilename)) {
307 $iconFilename = $this->backPath . $iconFilename;
308 }
309 return $iconFilename;
310 }
311
312 /**
313 * Returns relative path to the icon filename for use in img-tags
314 *
315 * @param string $iconFilename Icon filename
316 * @return string Icon filename with relative path
317 * @see getModuleIconAbsolute()
318 */
319 protected function getModuleIconRelative($iconFilename) {
320 if (GeneralUtility::isAbsPath($iconFilename)) {
321 $iconFilename = '../' . substr($iconFilename, strlen(PATH_site));
322 }
323 return $this->backPath . $iconFilename;
324 }
325
326 /**
327 * Appends a '?' if there is none in the string already
328 *
329 * @param string $link Link URL
330 * @return string Link URl appended with ? if there wasn't one
331 */
332 protected function appendQuestionmarkToLink($link) {
333 if (!strstr($link, '?')) {
334 $link .= '?';
335 }
336 return $link;
337 }
338
339 /**
340 * renders the logout button form
341 *
342 * @return string Html code snippet displaying the logout button
343 */
344 public function renderLogoutButton() {
345 $buttonLabel = $GLOBALS['BE_USER']->user['ses_backuserid'] ? 'LLL:EXT:lang/locallang_core.xlf:buttons.exit' : 'LLL:EXT:lang/locallang_core.xlf:buttons.logout';
346 $buttonForm = '
347 <form action="logout.php" target="_top">
348 <input type="submit" id="logout-submit-button" value="' . $GLOBALS['LANG']->sL($buttonLabel, TRUE) . '" />
349 </form>';
350 return $buttonForm;
351 }
352
353 /**
354 * turns linking of modules on or off
355 *
356 * @param boolean $linkModules Status for linking modules with a-tags, set to FALSE to turn lining off
357 * @throws \InvalidArgumentException
358 * @return void
359 */
360 public function setLinkModules($linkModules) {
361 if (!is_bool($linkModules)) {
362 throw new \InvalidArgumentException('parameter $linkModules must be of type bool', 1193326558);
363 }
364 $this->linkModules = $linkModules;
365 }
366
367 }