Added feature #13899: Add Viewport layout to BE (thanks to Stefan Galinski)
[Packages/TYPO3.CMS.git] / typo3 / classes / class.modulemenu.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2007-2009 Ingo Renner <ingo@typo3.org>
6 * All rights reserved
7 *
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 * A copy is found in the textfile GPL.txt and important notices to the license
17 * from the author is found in LICENSE.txt distributed with these scripts.
18 *
19 *
20 * This script is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
27
28
29 if(TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_AJAX) {
30 $GLOBALS['LANG']->includeLLFile('EXT:lang/locallang_misc.xml');
31 }
32
33 /**
34 * class to render the TYPO3 backend menu for the modules
35 *
36 * @author Ingo Renner <ingo@typo3.org>
37 * @package TYPO3
38 * @subpackage core
39 */
40 class ModuleMenu {
41
42 /**
43 * module loading object
44 *
45 * @var t3lib_loadModules
46 */
47 protected $moduleLoader;
48
49 protected $backPath;
50 protected $linkModules;
51 protected $loadedModules;
52 protected $fsMod; //TODO find a more descriptive name, left over from alt_menu_functions
53
54 /**
55 * constructor, initializes several variables
56 *
57 * @return void
58 */
59 public function __construct() {
60
61 $this->backPath = '';
62 $this->fsMod = array();
63 $this->linkModules = true;
64
65 // Loads the backend modules available for the logged in user.
66 $this->moduleLoader = t3lib_div::makeInstance('t3lib_loadModules');
67 $this->moduleLoader->observeWorkspaces = true;
68 $this->moduleLoader->load($GLOBALS['TBE_MODULES']);
69 $this->loadedModules = $this->moduleLoader->modules;
70
71 }
72
73 /**
74 * sets the path back to /typo3/
75 *
76 * @param string path back to /typo3/
77 * @return void
78 */
79 public function setBackPath($backPath) {
80 if(!is_string($backPath)) {
81 throw new InvalidArgumentException('parameter $backPath must be of type string', 1193315266);
82 }
83
84 $this->backPath = $backPath;
85 }
86
87 /**
88 * loads the collapse states for the main modules from user's configuration (uc)
89 *
90 * @return array collapse states
91 */
92 protected function getCollapsedStates() {
93
94 $collapsedStates = array();
95 if($GLOBALS['BE_USER']->uc['moduleData']['moduleMenu']) {
96 $collapsedStates = $GLOBALS['BE_USER']->uc['moduleData']['moduleMenu'];
97 }
98
99 return $collapsedStates;
100 }
101
102 /**
103 * returns the loaded modules
104 *
105 * @return array array of loaded modules
106 */
107 public function getLoadedModules() {
108 return $this->loadedModules;
109 }
110
111 /**
112 * saves the menu's toggle state in the backend user's uc
113 *
114 * @param array array of parameters from the AJAX interface, currently unused
115 * @param TYPO3AJAX object of type TYPO3AJAX
116 * @return void
117 */
118 public function saveMenuState($params, $ajaxObj) {
119 $menuItem = t3lib_div::_POST('menuid');
120 $state = t3lib_div::_POST('state') === 'true' ? 1 : 0;
121
122 $GLOBALS['BE_USER']->uc['moduleData']['menuState'][$menuItem] = $state;
123 $GLOBALS['BE_USER']->writeUC();
124 }
125
126 /**
127 * renders the backend menu as unordered list
128 *
129 * @param boolean optional parameter used to switch wrapping the menu in ul tags off for AJAX calls
130 * @return string menu html code to use in the backend
131 */
132 public function render($wrapInUl = true) {
133 $menu = '';
134 $onBlur = $GLOBALS['CLIENT']['FORMSTYLE'] ? 'this.blur();' : '';
135
136 $tsConfiguration = $GLOBALS['BE_USER']->getTSConfig('options.moduleMenuCollapsable');
137 $collapsable = (isset($tsConfiguration['value']) && $tsConfiguration['value'] == 0) ? 0 : 1;
138
139 $rawModuleData = $this->getRawModuleData();
140
141 foreach($rawModuleData as $moduleKey => $moduleData) {
142 if($moduleData['link'] != 'dummy.php' || ($moduleData['link'] == 'dummy.php' && is_array($moduleData['subitems'])) ) {
143 $menuState = $GLOBALS['BE_USER']->uc['moduleData']['menuState'][$moduleKey];
144 $moduleLabel = $moduleData['title'];
145
146 if($moduleData['link'] && $this->linkModules) {
147 $moduleLabel = '<a href="#" onclick="top.goToModule(\'' . $moduleData['name'] . '\');'.$onBlur . 'return false;">' . $moduleLabel . '</a>';
148 }
149
150 $menu .= '<li id="modmenu_' . $moduleData['name'] . '" '.
151 ($collapsable ? 'class="menuSection"' : '') .
152 ' title="' . $moduleData['description'] . '">
153 <div class="' . ($menuState ? 'collapsed' : 'expanded') . '">' .
154 $moduleData['icon']['html'] . ' ' . $moduleLabel . '</div>';
155
156 // traverse submodules
157 if (is_array($moduleData['subitems'])) {
158 $menu .= $this->renderSubModules($moduleData['subitems'], $menuState);
159 }
160
161 $menu .= '</li>' . "\n";
162 }
163 }
164
165 return ($wrapInUl ? '<ul id="typo3-menu">' . "\n".$menu.'</ul>' . "\n" : $menu);
166 }
167
168 /**
169 * renders the backend menu as unordered list as an AJAX response without
170 * the wrapping ul tags
171 *
172 * @param array array of parameters from the AJAX interface, currently unused
173 * @param TYPO3AJAX object of type TYPO3AJAX
174 * @return void
175 */
176 public function renderAjax($params = array(), TYPO3AJAX &$ajaxObj = null) {
177 $menu = $this->render(false);
178 $menuSwitch = $this->getGotoModuleJavascript();
179
180 // JS rocks: we can just overwrite a function with a new definition.
181 // and yes, we actually do that =)
182 $menuSwitchUpdate = '
183 <script type="text/javascript">
184 top.goToModule = '.$menuSwitch.';
185 </script>';
186
187 $ajaxObj->addContent('typo3-menu', $menu.$menuSwitchUpdate);
188 }
189
190 /**
191 * renders submodules
192 *
193 * @param array array of (sub)module data
194 * @param boolean collapse state of menu item, defaults to false
195 * @return string (sub)module html code
196 */
197 public function renderSubModules($modules, $menuState=false) {
198 $moduleMenu = '';
199 $onBlur = $GLOBALS['CLIENT']['FORMSTYLE'] ? 'this.blur();' : '';
200
201 foreach($modules as $moduleKey => $moduleData) {
202 // Setting additional JavaScript
203 $additionalJavascript = '';
204 if($moduleData['parentNavigationFrameScript']) {
205 $parentModuleName = substr($moduleData['name'], 0, strpos($moduleData['name'], '_'));
206 $additionalJavascript = "+'&id='+top.rawurlencodeAndRemoveSiteUrl(top.fsMod.recentIds['" . $parentModuleName . "'])";
207 }
208
209 if($moduleData['link'] && $this->linkModules) {
210
211 $onClickString = htmlspecialchars('top.goToModule(\''.$moduleData['name'].'\');'.$onBlur.'return false;');
212 $submoduleLink = '<a href="#" onclick="'.$onClickString.'" title="'.$moduleData['description'].'">'
213 //TODO make icon a background image using css
214 .'<span class="submodule-icon">'.$moduleData['icon']['html'].'</span>'
215 .'<span>'.htmlspecialchars($moduleData['title']).'</span>'
216 .'</a>';
217 }
218
219 $moduleMenu .= '<li id="modmenu_' . $moduleData['name'] . '">' . $submoduleLink . '</li>' . "\n";
220 }
221
222 return '<ul'.($menuState ? ' style="display:none;"' : '').'>'."\n".$moduleMenu.'</ul>'."\n";
223 }
224
225 /**
226 * gets the raw module data
227 *
228 * @return array multi dimension array with module data
229 */
230 public function getRawModuleData() {
231 $modules = array();
232
233 // Remove the 'doc' module?
234 if($GLOBALS['BE_USER']->getTSConfigVal('options.disableDocModuleInAB')) {
235 unset($this->loadedModules['doc']);
236 }
237
238 foreach($this->loadedModules as $moduleName => $moduleData) {
239 $moduleNavigationFramePrefix = $this->getNavigationFramePrefix($moduleData);
240
241 if($moduleNavigationFramePrefix) {
242 $this->fsMod[$moduleName] = 'fsMod.recentIds["'.$moduleName.'"]="";';
243 }
244
245 $moduleLink = '';
246 if(!is_array($moduleData['sub'])) {
247 $moduleLink = $moduleData['script'];
248 }
249 $moduleLink = t3lib_div::resolveBackPath($moduleLink);
250
251 $moduleKey = 'modmenu_' . $moduleName;
252 $moduleIcon = $this->getModuleIcon($moduleKey);
253
254 if($moduleLink && $moduleNavigationFramePrefix) {
255 $moduleLink = $moduleNavigationFramePrefix.rawurlencode($moduleLink);
256 }
257
258 $modules[$moduleKey] = array(
259 'name' => $moduleName,
260 'title' => $GLOBALS['LANG']->moduleLabels['tabs'][$moduleName . '_tab'],
261 'onclick' => 'top.goToModule(\''.$moduleName.'\');',
262 'icon' => $moduleIcon,
263 'link' => $moduleLink,
264 'prefix' => $moduleNavigationFramePrefix,
265 'description' => $GLOBALS['LANG']->moduleLabels['labels'][$moduleKey.'label']
266 );
267
268 if(is_array($moduleData['sub'])) {
269
270 foreach($moduleData['sub'] as $submoduleName => $submoduleData) {
271 $submoduleLink = t3lib_div::resolveBackPath($submoduleData['script']);
272 $submoduleNavigationFramePrefix = $this->getNavigationFramePrefix($moduleData, $submoduleData);
273
274 $submoduleKey = $moduleName.'_'.$submoduleName.'_tab';
275 $submoduleIcon = $this->getModuleIcon($submoduleKey);
276 $submoduleDescription = $GLOBALS['LANG']->moduleLabels['labels'][$submoduleKey.'label'];
277
278 $originalLink = $submoduleLink;
279 if($submoduleLink && $submoduleNavigationFramePrefix) {
280 $submoduleLink = $submoduleNavigationFramePrefix.rawurlencode($submoduleLink);
281 }
282
283 $modules[$moduleKey]['subitems'][$submoduleKey] = array(
284 'name' => $moduleName.'_'.$submoduleName,
285 'title' => $GLOBALS['LANG']->moduleLabels['tabs'][$submoduleKey],
286 'onclick' => 'top.goToModule(\''.$moduleName.'_'.$submoduleName.'\');',
287 'icon' => $submoduleIcon,
288 'link' => $submoduleLink,
289 'originalLink' => $originalLink,
290 'prefix' => $submoduleNavigationFramePrefix,
291 'description' => $submoduleDescription,
292 'navigationFrameScript' => $submoduleData['navFrameScript'],
293 );
294
295 if($moduleData['navFrameScript']) {
296 $modules[$moduleKey]['subitems'][$submoduleKey]['parentNavigationFrameScript'] = $moduleData['navFrameScript'];
297 }
298 }
299 }
300 }
301
302 return $modules;
303 }
304
305 /**
306 * gets the module icon and its size
307 *
308 * @param string module key
309 * @return array icon data array with 'filename', 'size', and 'html'
310 */
311 protected function getModuleIcon($moduleKey) {
312 $icon = array(
313 'filename' => '',
314 'size' => '',
315 'title' => '',
316 'html' => ''
317 );
318
319 $iconFileRelative = $this->getModuleIconRelative($GLOBALS['LANG']->moduleLabels['tabs_images'][$moduleKey]);
320 $iconFileAbsolute = $this->getModuleIconAbsolute($GLOBALS['LANG']->moduleLabels['tabs_images'][$moduleKey]);
321 $iconSizes = @getimagesize($iconFileAbsolute);
322 $iconTitle = $GLOBALS['LANG']->moduleLabels['tabs'][$moduleKey];
323
324 if(!empty($iconFileRelative)) {
325 $icon['filename'] = $iconFileRelative;
326 $icon['size'] = $iconSizes[3];
327 $icon['title'] = htmlspecialchars($iconTitle);
328 $icon['html'] = '<img src="'.$iconFileRelative.'" '.$iconSizes[3].' title="'.htmlspecialchars($iconTitle).'" alt="'.htmlspecialchars($iconTitle).'" />';
329 }
330
331 return $icon;
332 }
333
334 /**
335 * Returns the filename readable for the script from PATH_typo3.
336 * That means absolute names are just returned while relative names are
337 * prepended with the path pointing back to typo3/ dir
338 *
339 * @param string icon filename
340 * @return string icon filename with absolute path
341 * @see getModuleIconRelative()
342 */
343 protected function getModuleIconAbsolute($iconFilename) {
344
345 if(!t3lib_div::isAbsPath($iconFilename)) {
346 $iconFilename = $this->backPath . $iconFilename;
347 }
348
349 return $iconFilename;
350 }
351
352 /**
353 * Returns relative path to the icon filename for use in img-tags
354 *
355 * @param string icon filename
356 * @return string icon filename with relative path
357 * @see getModuleIconAbsolute()
358 */
359 protected function getModuleIconRelative($iconFilename) {
360 if (t3lib_div::isAbsPath($iconFilename)) {
361 $iconFilename = '../' . substr($iconFilename, strlen(PATH_site));
362 }
363 return $this->backPath.$iconFilename;
364 }
365
366 /**
367 * Returns a prefix used to call the navigation frame with parameters which then will call the scripts defined in the modules info array.
368 *
369 * @param array module data array
370 * @param array submodule data array
371 * @return string result URL string
372 */
373 protected function getNavigationFramePrefix($moduleData, $subModuleData = array()) {
374 $prefix = '';
375
376 $navigationFrameScript = $moduleData['navFrameScript'];
377 if($subModuleData['navFrameScript']) {
378 $navigationFrameScript = $subModuleData['navFrameScript'];
379 }
380
381 $navigationFrameParameter = $moduleData['navFrameScriptParam'];
382 if($subModuleData['navFrameScriptParam']) {
383 $navigationFrameParameter = $subModuleData['navFrameScriptParam'];
384 }
385
386 if($navigationFrameScript) {
387 $navigationFrameScript = t3lib_div::resolveBackPath($navigationFrameScript);
388 $navigationFrameScript = $this->appendQuestionmarkToLink($navigationFrameScript);
389
390 if($GLOBALS['BE_USER']->uc['condensedMode']) {
391 $prefix = $navigationFrameScript.$navigationFrameParameter.'&currentSubScript=';
392 } else {
393 $prefix = 'alt_mod_frameset.php?'
394 .'fW="+top.TS.navFrameWidth+"'
395 .'&nav="+top.TS.PATH_typo3+"'
396 .rawurlencode($navigationFrameScript.$navigationFrameParameter)
397 .'&script=';
398 }
399 }
400
401 return $prefix;
402 }
403
404 /**
405 * generates javascript code to switch between modules
406 *
407 * @return string javascript code snippet to switch modules
408 */
409 public function getGotoModuleJavascript() {
410
411 $moduleJavascriptCommands = array();
412 $rawModuleData = $this->getRawModuleData();
413 $navFrameScripts = array();
414
415 foreach($rawModuleData as $mainModuleKey => $mainModuleData) {
416 if ($mainModuleData['subitems']) {
417 foreach ($mainModuleData['subitems'] as $subModuleKey => $subModuleData) {
418
419 $parentModuleName = substr($subModuleData['name'], 0, strpos($subModuleData['name'], '_'));
420 $javascriptCommand = '';
421
422 // Setting additional JavaScript if frameset script:
423 $additionalJavascript = '';
424 if($subModuleData['parentNavigationFrameScript']) {
425 $additionalJavascript = "+'&id='+top.rawurlencodeAndRemoveSiteUrl(top.fsMod.recentIds['" . $parentModuleName . "'])";
426 }
427
428 if ($subModuleData['link'] && $this->linkModules) {
429 // For condensed mode, send &cMR parameter to frameset script.
430 if ($additionalJavascript && $GLOBALS['BE_USER']->uc['condensedMode']) {
431 $additionalJavascript .= "+(cMR ? '&cMR=1' : '')";
432 }
433
434 $javascriptCommand = '
435 modScriptURL = "'.$this->appendQuestionmarkToLink($subModuleData['link']).'"'.$additionalJavascript.';';
436
437 if ($subModuleData['navFrameScript']) {
438 $javascriptCommand .= '
439 top.currentSubScript="'.$subModuleData['originalLink'].'";';
440 }
441
442 if (!$GLOBALS['BE_USER']->uc['condensedMode'] && $subModuleData['parentNavigationFrameScript']) {
443 $additionalJavascript = "+'&id='+top.rawurlencodeAndRemoveSiteUrl(top.fsMod.recentIds['" . $parentModuleName . "'])";
444
445 $submoduleNavigationFrameScript = $subModuleData['navigationFrameScript'] ? $subModuleData['navigationFrameScript'] : $subModuleData['parentNavigationFrameScript'];
446 $submoduleNavigationFrameScript = t3lib_div::resolveBackPath($submoduleNavigationFrameScript);
447
448 // add GET parameters for sub module to the navigation script
449 $submoduleNavigationFrameScript = $this->appendQuestionmarkToLink($submoduleNavigationFrameScript).$subModuleData['navigationFrameScript'];
450 $navFrameScripts[$parentModuleName] = $submoduleNavigationFrameScript;
451 $javascriptCommand = '
452 top.currentSubScript = "'.$subModuleData['originalLink'].'";
453 if (top.content.list_frame && top.fsMod.currentMainLoaded == mainModName) {
454 modScriptURL = "'.$this->appendQuestionmarkToLink($subModuleData['originalLink']).'"'.$additionalJavascript.';
455 ';
456 // Change link to navigation frame if submodule has it's own navigation
457 if ($submoduleNavigationFrameScript) {
458 $javascriptCommand .= 'navFrames["' . $parentModuleName . '"] = "'. $submoduleNavigationFrameScript . '";';
459 }
460 $javascriptCommand .= '
461 } else if (top.nextLoadModuleUrl) {
462 modScriptURL = "'.($subModuleData['prefix'] ? $this->appendQuestionmarkToLink($subModuleData['link']) . '&exScript=' : '') . 'listframe_loader.php";
463 } else {
464 modScriptURL = "'.$this->appendQuestionmarkToLink($subModuleData['link']).'"'.$additionalJavascript.' + additionalGetVariables;
465 }';
466 }
467 }
468 $moduleJavascriptCommands[] = "
469 case '".$subModuleData['name']."':".$javascriptCommand."
470 break;";
471 }
472 } elseif(!$mainModuleData['subitems'] && !empty($mainModuleData['link'])) {
473 // main module has no sub modules but instead is linked itself (doc module f.e.)
474 $javascriptCommand = '
475 modScriptURL = "'.$this->appendQuestionmarkToLink($mainModuleData['link']).'";';
476 $moduleJavascriptCommands[] = "
477 case '".$mainModuleData['name']."':".$javascriptCommand."
478 break;";
479 }
480 }
481
482 $javascriptCode = 'function(modName, cMR_flag, addGetVars) {
483 var useCondensedMode = '.($GLOBALS['BE_USER']->uc['condensedMode'] ? 'true' : 'false').';
484 var mainModName = (modName.slice(0, modName.indexOf("_")) || modName);
485
486 var additionalGetVariables = "";
487 if (addGetVars) {
488 additionalGetVariables = addGetVars;
489 }';
490
491 $javascriptCode .= '
492 var navFrames = {};';
493 foreach ($navFrameScripts as $mainMod => $frameScript) {
494 $javascriptCode .= '
495 navFrames["'.$mainMod.'"] = "'.$frameScript.'";';
496 }
497
498 $javascriptCode .= '
499
500 var cMR = (cMR_flag ? 1 : 0);
501 var modScriptURL = "";
502
503 switch(modName) {'
504 ."\n".implode("\n", $moduleJavascriptCommands)."\n".'
505 }
506 ';
507
508 $javascriptCode .= '
509
510 if (!useCondensedMode && navFrames[mainModName]) {
511 if (top.content.list_frame && top.fsMod.currentMainLoaded == mainModName) {
512 top.content.list_frame.location = top.getModuleUrl(top.TS.PATH_typo3 + modScriptURL + additionalGetVariables);
513 if (top.currentSubNavScript != navFrames[mainModName]) {
514 top.currentSubNavScript = navFrames[mainModName];
515 top.content.nav_frame.location = top.getModuleUrl(top.TS.PATH_typo3 + navFrames[mainModName]);
516 }
517 } else {
518 TYPO3.Backend.loadModule(mainModName, modName, top.getModuleUrl(modScriptURL));
519 }
520 } else if (modScriptURL) {
521 TYPO3.Backend.loadModule(mainModName, modName, top.getModuleUrl(modScriptURL + additionalGetVariables));
522 }
523 currentModuleLoaded = modName;
524 top.fsMod.currentMainLoaded = mainModName;
525 TYPO3ModuleMenu.highlightModule("modmenu_" + modName, (modName == mainModName ? 1 : 0));
526 }';
527
528 return $javascriptCode;
529 }
530
531 /**
532 * Appends a '?' if there is none in the string already
533 *
534 * @param string Link URL
535 * @return string link URl appended with ? if there wasn't one
536 */
537 protected function appendQuestionmarkToLink($link) {
538 if(!strstr($link, '?')) {
539 $link .= '?';
540 }
541
542 return $link;
543 }
544
545 /**
546 * renders the logout button form
547 *
548 * @return string html code snippet displaying the logout button
549 */
550 public function renderLogoutButton() {
551 $buttonLabel = $GLOBALS['BE_USER']->user['ses_backuserid'] ? 'LLL:EXT:lang/locallang_core.php:buttons.exit' : 'LLL:EXT:lang/locallang_core.php:buttons.logout';
552
553 $buttonForm = '
554 <form action="logout.php" target="_top">
555 <input type="submit" value="&nbsp;' . $GLOBALS['LANG']->sL($buttonLabel, 1) . '&nbsp;" />
556 </form>';
557
558 return $buttonForm;
559 }
560
561 /**
562 * turns linking of modules on or off
563 *
564 * @param boolean status for linking modules with a-tags, set to false to turn lining off
565 */
566 public function setLinkModules($linkModules) {
567 if(!is_bool($linkModules)) {
568 throw new InvalidArgumentException('parameter $linkModules must be of type bool', 1193326558);
569 }
570
571 $this->linkModules = $linkModules;
572 }
573
574 /**
575 * gets the frameset (leftover) helper
576 *
577 * @return array array of javascript snippets
578 */
579 public function getFsMod() {
580 return $this->fsMod;
581 }
582 }
583
584
585 if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['typo3/classes/class.modulemenu.php']) {
586 include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['typo3/classes/class.modulemenu.php']);
587 }
588
589 ?>