b6f9eba5acb08b576a9737f601b2b504fc882c9c
[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 $rawModuleData = $this->getRawModuleData();
137
138 foreach($rawModuleData as $moduleKey => $moduleData) {
139 $menuState = $GLOBALS['BE_USER']->uc['moduleData']['menuState'][$moduleKey];
140 $moduleLabel = $moduleData['title'];
141
142 if($moduleData['link'] && $this->linkModules) {
143 $moduleLabel = '<a href="#" onclick="top.goToModule(\''.$moduleData['name'].'\');'.$onBlur.'return false;">'.$moduleLabel.'</a>';
144 }
145
146 $menu .= '<li id="modmenu_' . $moduleData['name'] . '" class="menuSection" title="' . $moduleData['description'] . '"><div class="' . ($menuState ? 'collapsed' : 'expanded') . '">' . $moduleData['icon']['html'] . ' ' . $moduleLabel . '</div>';
147
148 // traverse submodules
149 if(is_array($moduleData['subitems'])) {
150 $menu .= $this->renderSubModules($moduleData['subitems'], $menuState);
151 }
152
153 $menu .= '</li>'."\n";
154 }
155
156 return ($wrapInUl ? '<ul id="typo3-menu">'."\n".$menu.'</ul>'."\n" : $menu);
157 }
158
159 /**
160 * renders the backend menu as unordered list as an AJAX response without
161 * the wrapping ul tags
162 *
163 * @param array array of parameters from the AJAX interface, currently unused
164 * @param TYPO3AJAX object of type TYPO3AJAX
165 * @return void
166 */
167 public function renderAjax($params = array(), TYPO3AJAX &$ajaxObj = null) {
168 $menu = $this->render(false);
169 $menuSwitch = $this->getGotoModuleJavascript();
170
171 // JS rocks: we can just overwrite a function with a new definition.
172 // and yes, we actually do that =)
173 $menuSwitchUpdate = '
174 <script type="text/javascript">
175 top.goToModule = '.$menuSwitch.';
176 </script>';
177
178 $ajaxObj->addContent('typo3-menu', $menu.$menuSwitchUpdate);
179 }
180
181 /**
182 * renders submodules
183 *
184 * @param array array of (sub)module data
185 * @param boolean collapse state of menu item, defaults to false
186 * @return string (sub)module html code
187 */
188 public function renderSubModules($modules, $menuState=false) {
189 $moduleMenu = '';
190 $onBlur = $GLOBALS['CLIENT']['FORMSTYLE'] ? 'this.blur();' : '';
191
192 foreach($modules as $moduleKey => $moduleData) {
193 // Setting additional JavaScript
194 $additionalJavascript = '';
195 if($moduleData['parentNavigationFrameScript']) {
196 $parentModuleName = substr($moduleData['name'], 0, strpos($moduleData['name'], '_'));
197 $additionalJavascript = "+'&id='+top.rawurlencodeAndRemoveSiteUrl(top.fsMod.recentIds['" . $parentModuleName . "'])";
198 }
199
200 if($moduleData['link'] && $this->linkModules) {
201
202 $onClickString = htmlspecialchars('top.goToModule(\''.$moduleData['name'].'\');'.$onBlur.'return false;');
203 $submoduleLink = '<a href="#" onclick="'.$onClickString.'" title="'.$moduleData['description'].'">'
204 //TODO make icon a background image using css
205 .'<span class="submodule-icon">'.$moduleData['icon']['html'].'</span>'
206 .'<span>'.htmlspecialchars($moduleData['title']).'</span>'
207 .'</a>';
208 }
209
210 $moduleMenu .= '<li id="modmenu_' . $moduleData['name'] . '">' . $submoduleLink . '</li>' . "\n";
211 }
212
213 return '<ul'.($menuState ? ' style="display:none;"' : '').'>'."\n".$moduleMenu.'</ul>'."\n";
214 }
215
216 /**
217 * gets the raw module data
218 *
219 * @return array multi dimension array with module data
220 */
221 public function getRawModuleData() {
222 $modules = array();
223
224 // Remove the 'doc' module?
225 if($GLOBALS['BE_USER']->getTSConfigVal('options.disableDocModuleInAB')) {
226 unset($this->loadedModules['doc']);
227 }
228
229 foreach($this->loadedModules as $moduleName => $moduleData) {
230 $moduleNavigationFramePrefix = $this->getNavigationFramePrefix($moduleData);
231
232 if($moduleNavigationFramePrefix) {
233 $this->fsMod[$moduleName] = 'fsMod.recentIds["'.$moduleName.'"]="";';
234 }
235
236 $moduleLink = '';
237 if(!is_array($moduleData['sub'])) {
238 $moduleLink = $moduleData['script'];
239 }
240 $moduleLink = t3lib_div::resolveBackPath($moduleLink);
241
242 $moduleKey = 'modmenu_' . $moduleName;
243 $moduleIcon = $this->getModuleIcon($moduleKey);
244
245 if($moduleLink && $moduleNavigationFramePrefix) {
246 $moduleLink = $moduleNavigationFramePrefix.rawurlencode($moduleLink);
247 }
248
249 $modules[$moduleKey] = array(
250 'name' => $moduleName,
251 'title' => $GLOBALS['LANG']->moduleLabels['tabs'][$moduleName . '_tab'],
252 'onclick' => 'top.goToModule(\''.$moduleName.'\');',
253 'icon' => $moduleIcon,
254 'link' => $moduleLink,
255 'prefix' => $moduleNavigationFramePrefix,
256 'description' => $GLOBALS['LANG']->moduleLabels['labels'][$moduleKey.'label']
257 );
258
259 if(is_array($moduleData['sub'])) {
260
261 foreach($moduleData['sub'] as $submoduleName => $submoduleData) {
262 $submoduleLink = t3lib_div::resolveBackPath($submoduleData['script']);
263 $submoduleNavigationFramePrefix = $this->getNavigationFramePrefix($moduleData, $submoduleData);
264
265 $submoduleKey = $moduleName.'_'.$submoduleName.'_tab';
266 $submoduleIcon = $this->getModuleIcon($submoduleKey);
267 $submoduleDescription = $GLOBALS['LANG']->moduleLabels['labels'][$submoduleKey.'label'];
268
269 $originalLink = $submoduleLink;
270 if($submoduleLink && $submoduleNavigationFramePrefix) {
271 $submoduleLink = $submoduleNavigationFramePrefix.rawurlencode($submoduleLink);
272 }
273
274 $modules[$moduleKey]['subitems'][$submoduleKey] = array(
275 'name' => $moduleName.'_'.$submoduleName,
276 'title' => $GLOBALS['LANG']->moduleLabels['tabs'][$submoduleKey],
277 'onclick' => 'top.goToModule(\''.$moduleName.'_'.$submoduleName.'\');',
278 'icon' => $submoduleIcon,
279 'link' => $submoduleLink,
280 'originalLink' => $originalLink,
281 'prefix' => $submoduleNavigationFramePrefix,
282 'description' => $submoduleDescription,
283 'navigationFrameScript' => $submoduleData['navFrameScript'],
284 );
285
286 if($moduleData['navFrameScript']) {
287 $modules[$moduleKey]['subitems'][$submoduleKey]['parentNavigationFrameScript'] = $moduleData['navFrameScript'];
288 }
289 }
290 }
291 }
292
293 return $modules;
294 }
295
296 /**
297 * gets the module icon and its size
298 *
299 * @param string module key
300 * @return array icon data array with 'filename', 'size', and 'html'
301 */
302 protected function getModuleIcon($moduleKey) {
303 $icon = array();
304 $iconFileRelative = $this->getModuleIconRelative($GLOBALS['LANG']->moduleLabels['tabs_images'][$moduleKey]);
305 $iconFileAbsolute = $this->getModuleIconAbsolute($GLOBALS['LANG']->moduleLabels['tabs_images'][$moduleKey]);
306 $iconSizes = @getimagesize($iconFileAbsolute);
307 $iconTitle = $GLOBALS['LANG']->moduleLabels['tabs'][$moduleKey];
308
309 $icon['filename'] = $iconFileRelative;
310 $icon['size'] = $iconSizes[3];
311 $icon['title'] = htmlspecialchars($iconTitle);
312 $icon['html'] = '<img src="'.$iconFileRelative.'" '.$iconSizes[3].' title="'.htmlspecialchars($iconTitle).'" alt="'.htmlspecialchars($iconTitle).'" />';
313
314 return $icon;
315 }
316
317 /**
318 * Returns the filename readable for the script from PATH_typo3.
319 * That means absolute names are just returned while relative names are
320 * prepended with the path pointing back to typo3/ dir
321 *
322 * @param string icon filename
323 * @return string icon filename with absolute path
324 * @see getModuleIconRelative()
325 */
326 protected function getModuleIconAbsolute($iconFilename) {
327
328 if(!t3lib_div::isAbsPath($iconFilename)) {
329 $iconFilename = $this->backPath . $iconFilename;
330 }
331
332 return $iconFilename;
333 }
334
335 /**
336 * Returns relative path to the icon filename for use in img-tags
337 *
338 * @param string icon filename
339 * @return string icon filename with relative path
340 * @see getModuleIconAbsolute()
341 */
342 protected function getModuleIconRelative($iconFilename) {
343 if (t3lib_div::isAbsPath($iconFilename)) {
344 $iconFilename = '../' . substr($iconFilename, strlen(PATH_site));
345 }
346 return $this->backPath.$iconFilename;
347 }
348
349 /**
350 * Returns a prefix used to call the navigation frame with parameters which then will call the scripts defined in the modules info array.
351 *
352 * @param array module data array
353 * @param array submodule data array
354 * @return string result URL string
355 */
356 protected function getNavigationFramePrefix($moduleData, $subModuleData = array()) {
357 $prefix = '';
358
359 $navigationFrameScript = $moduleData['navFrameScript'];
360 if($subModuleData['navFrameScript']) {
361 $navigationFrameScript = $subModuleData['navFrameScript'];
362 }
363
364 $navigationFrameParameter = $moduleData['navFrameScriptParam'];
365 if($subModuleData['navFrameScriptParam']) {
366 $navigationFrameParameter = $subModuleData['navFrameScriptParam'];
367 }
368
369 if($navigationFrameScript) {
370 $navigationFrameScript = t3lib_div::resolveBackPath($navigationFrameScript);
371 $navigationFrameScript = $this->appendQuestionmarkToLink($navigationFrameScript);
372
373 if($GLOBALS['BE_USER']->uc['condensedMode']) {
374 $prefix = $navigationFrameScript.$navigationFrameParameter.'&currentSubScript=';
375 } else {
376 $prefix = 'alt_mod_frameset.php?'
377 .'fW="+top.TS.navFrameWidth+"'
378 .'&nav="+top.TS.PATH_typo3+"'
379 .rawurlencode($navigationFrameScript.$navigationFrameParameter)
380 .'&script=';
381 }
382 }
383
384 return $prefix;
385 }
386
387 /**
388 * generates javascript code to switch between modules
389 *
390 * @return string javascript code snippet to switch modules
391 */
392 public function getGotoModuleJavascript() {
393
394 $moduleJavascriptCommands = array();
395 $rawModuleData = $this->getRawModuleData();
396 $navFrameScripts = array();
397
398 foreach($rawModuleData as $mainModuleKey => $mainModuleData) {
399 if ($mainModuleData['subitems']) {
400 foreach ($mainModuleData['subitems'] as $subModuleKey => $subModuleData) {
401
402 $parentModuleName = substr($subModuleData['name'], 0, strpos($subModuleData['name'], '_'));
403 $javascriptCommand = '';
404
405 // Setting additional JavaScript if frameset script:
406 $additionalJavascript = '';
407 if($subModuleData['parentNavigationFrameScript']) {
408 $additionalJavascript = "+'&id='+top.rawurlencodeAndRemoveSiteUrl(top.fsMod.recentIds['" . $parentModuleName . "'])";
409 }
410
411 if ($subModuleData['link'] && $this->linkModules) {
412 // For condensed mode, send &cMR parameter to frameset script.
413 if ($additionalJavascript && $GLOBALS['BE_USER']->uc['condensedMode']) {
414 $additionalJavascript .= "+(cMR ? '&cMR=1' : '')";
415 }
416
417 $javascriptCommand = '
418 modScriptURL = "'.$this->appendQuestionmarkToLink($subModuleData['link']).'"'.$additionalJavascript.';';
419
420 if ($subModuleData['navFrameScript']) {
421 $javascriptCommand .= '
422 top.currentSubScript="'.$subModuleData['originalLink'].'";';
423 }
424
425 if (!$GLOBALS['BE_USER']->uc['condensedMode'] && $subModuleData['parentNavigationFrameScript']) {
426 $additionalJavascript = "+'&id='+top.rawurlencodeAndRemoveSiteUrl(top.fsMod.recentIds['" . $parentModuleName . "'])";
427
428 $submoduleNavigationFrameScript = $subModuleData['navigationFrameScript'] ? $subModuleData['navigationFrameScript'] : $subModuleData['parentNavigationFrameScript'];
429 $submoduleNavigationFrameScript = t3lib_div::resolveBackPath($submoduleNavigationFrameScript);
430
431 // add GET parameters for sub module to the navigation script
432 $submoduleNavigationFrameScript = $this->appendQuestionmarkToLink($submoduleNavigationFrameScript).$subModuleData['navigationFrameScript'];
433 $navFrameScripts[$parentModuleName] = $submoduleNavigationFrameScript;
434 $javascriptCommand = '
435 top.currentSubScript = "'.$subModuleData['originalLink'].'";
436 if (top.content.list_frame && top.fsMod.currentMainLoaded == mainModName) {
437 modScriptURL = "'.$this->appendQuestionmarkToLink($subModuleData['originalLink']).'"'.$additionalJavascript.';
438 } else if (top.nextLoadModuleUrl) {
439 modScriptURL = "'.($subModuleData['prefix'] ? $this->appendQuestionmarkToLink($subModuleData['link']) . '&exScript=' : '') . 'listframe_loader.php";
440 } else {
441 modScriptURL = "'.$this->appendQuestionmarkToLink($subModuleData['link']).'"'.$additionalJavascript.' + additionalGetVariables;
442 }';
443 }
444 }
445 $moduleJavascriptCommands[] = "
446 case '".$subModuleData['name']."':".$javascriptCommand."
447 break;";
448 }
449 } elseif(!$mainModuleData['subitems'] && !empty($mainModuleData['link'])) {
450 // main module has no sub modules but instead is linked itself (doc module f.e.)
451 $javascriptCommand = '
452 modScriptURL = "'.$this->appendQuestionmarkToLink($mainModuleData['link']).'";';
453 $moduleJavascriptCommands[] = "
454 case '".$mainModuleData['name']."':".$javascriptCommand."
455 break;";
456 }
457 }
458
459 $javascriptCode = 'function(modName, cMR_flag, addGetVars) {
460 var useCondensedMode = '.($GLOBALS['BE_USER']->uc['condensedMode'] ? 'true' : 'false').';
461 var mainModName = (modName.slice(0, modName.indexOf("_")) || modName);
462
463 var additionalGetVariables = "";
464 if (addGetVars) {
465 additionalGetVariables = addGetVars;
466 }
467
468 var cMR = (cMR_flag ? 1 : 0);
469 var modScriptURL = "";
470
471 switch(modName) {'
472 ."\n".implode("\n", $moduleJavascriptCommands)."\n".'
473 }
474
475 var navFrames = {};';
476 foreach ($navFrameScripts as $mainMod => $frameScript) {
477 $javascriptCode .= '
478 navFrames["'.$mainMod.'"] = "'.$frameScript.'";';
479 }
480 $javascriptCode .= '
481
482 if (!useCondensedMode && navFrames[mainModName]) {
483 if (top.content.list_frame && top.fsMod.currentMainLoaded == mainModName) {
484 top.content.list_frame.location = top.getModuleUrl(top.TS.PATH_typo3 + modScriptURL + additionalGetVariables);
485 if (top.currentSubNavScript != navFrames[mainModName]) {
486 top.currentSubNavScript = navFrames[mainModName];
487 top.content.nav_frame.location = top.getModuleUrl(top.TS.PATH_typo3 + navFrames[mainModName]);
488 }
489 } else {
490 $("content").src = top.TS.PATH_typo3 + modScriptURL;
491 }
492 } else if (modScriptURL) {
493 $("content").src = top.getModuleUrl(top.TS.PATH_typo3 + modScriptURL + additionalGetVariables);
494 }
495 currentModuleLoaded = modName;
496 top.fsMod.currentMainLoaded = mainModName;
497 TYPO3ModuleMenu.highlightModule("modmenu_" + modName, (modName == mainModName ? 1 : 0));
498 }';
499
500 return $javascriptCode;
501 }
502
503 /**
504 * Appends a '?' if there is none in the string already
505 *
506 * @param string Link URL
507 * @return string link URl appended with ? if there wasn't one
508 */
509 protected function appendQuestionmarkToLink($link) {
510 if(!strstr($link, '?')) {
511 $link .= '?';
512 }
513
514 return $link;
515 }
516
517 /**
518 * renders the logout button form
519 *
520 * @return string html code snippet displaying the logout button
521 */
522 public function renderLogoutButton() {
523 $buttonLabel = $GLOBALS['BE_USER']->user['ses_backuserid'] ? 'LLL:EXT:lang/locallang_core.php:buttons.exit' : 'LLL:EXT:lang/locallang_core.php:buttons.logout';
524
525 $buttonForm = '
526 <form action="logout.php" target="_top">
527 <input type="submit" value="&nbsp;' . $GLOBALS['LANG']->sL($buttonLabel, 1) . '&nbsp;" />
528 </form>';
529
530 return $buttonForm;
531 }
532
533 /**
534 * turns linking of modules on or off
535 *
536 * @param boolean status for linking modules with a-tags, set to false to turn lining off
537 */
538 public function setLinkModules($linkModules) {
539 if(!is_bool($linkModules)) {
540 throw new InvalidArgumentException('parameter $linkModules must be of type bool', 1193326558);
541 }
542
543 $this->linkModules = $linkModules;
544 }
545
546 /**
547 * gets the frameset (leftover) helper
548 *
549 * @return array array of javascript snippets
550 */
551 public function getFsMod() {
552 return $this->fsMod;
553 }
554 }
555
556
557 if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['typo3/classes/class.modulemenu.php']) {
558 include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['typo3/classes/class.modulemenu.php']);
559 }
560
561 ?>