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