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