Fixed bug #10954: Validation issue: Images with empty attributes in Module menu ...
[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 'filename' => '',
305 'size' => '',
306 'title' => '',
307 'html' => ''
308 );
309
310 $iconFileRelative = $this->getModuleIconRelative($GLOBALS['LANG']->moduleLabels['tabs_images'][$moduleKey]);
311 $iconFileAbsolute = $this->getModuleIconAbsolute($GLOBALS['LANG']->moduleLabels['tabs_images'][$moduleKey]);
312 $iconSizes = @getimagesize($iconFileAbsolute);
313 $iconTitle = $GLOBALS['LANG']->moduleLabels['tabs'][$moduleKey];
314
315 if(!empty($iconFileRelative)) {
316 $icon['filename'] = $iconFileRelative;
317 $icon['size'] = $iconSizes[3];
318 $icon['title'] = htmlspecialchars($iconTitle);
319 $icon['html'] = '<img src="'.$iconFileRelative.'" '.$iconSizes[3].' title="'.htmlspecialchars($iconTitle).'" alt="'.htmlspecialchars($iconTitle).'" />';
320 }
321
322 return $icon;
323 }
324
325 /**
326 * Returns the filename readable for the script from PATH_typo3.
327 * That means absolute names are just returned while relative names are
328 * prepended with the path pointing back to typo3/ dir
329 *
330 * @param string icon filename
331 * @return string icon filename with absolute path
332 * @see getModuleIconRelative()
333 */
334 protected function getModuleIconAbsolute($iconFilename) {
335
336 if(!t3lib_div::isAbsPath($iconFilename)) {
337 $iconFilename = $this->backPath . $iconFilename;
338 }
339
340 return $iconFilename;
341 }
342
343 /**
344 * Returns relative path to the icon filename for use in img-tags
345 *
346 * @param string icon filename
347 * @return string icon filename with relative path
348 * @see getModuleIconAbsolute()
349 */
350 protected function getModuleIconRelative($iconFilename) {
351 if (t3lib_div::isAbsPath($iconFilename)) {
352 $iconFilename = '../' . substr($iconFilename, strlen(PATH_site));
353 }
354 return $this->backPath.$iconFilename;
355 }
356
357 /**
358 * Returns a prefix used to call the navigation frame with parameters which then will call the scripts defined in the modules info array.
359 *
360 * @param array module data array
361 * @param array submodule data array
362 * @return string result URL string
363 */
364 protected function getNavigationFramePrefix($moduleData, $subModuleData = array()) {
365 $prefix = '';
366
367 $navigationFrameScript = $moduleData['navFrameScript'];
368 if($subModuleData['navFrameScript']) {
369 $navigationFrameScript = $subModuleData['navFrameScript'];
370 }
371
372 $navigationFrameParameter = $moduleData['navFrameScriptParam'];
373 if($subModuleData['navFrameScriptParam']) {
374 $navigationFrameParameter = $subModuleData['navFrameScriptParam'];
375 }
376
377 if($navigationFrameScript) {
378 $navigationFrameScript = t3lib_div::resolveBackPath($navigationFrameScript);
379 $navigationFrameScript = $this->appendQuestionmarkToLink($navigationFrameScript);
380
381 if($GLOBALS['BE_USER']->uc['condensedMode']) {
382 $prefix = $navigationFrameScript.$navigationFrameParameter.'&currentSubScript=';
383 } else {
384 $prefix = 'alt_mod_frameset.php?'
385 .'fW="+top.TS.navFrameWidth+"'
386 .'&nav="+top.TS.PATH_typo3+"'
387 .rawurlencode($navigationFrameScript.$navigationFrameParameter)
388 .'&script=';
389 }
390 }
391
392 return $prefix;
393 }
394
395 /**
396 * generates javascript code to switch between modules
397 *
398 * @return string javascript code snippet to switch modules
399 */
400 public function getGotoModuleJavascript() {
401
402 $moduleJavascriptCommands = array();
403 $rawModuleData = $this->getRawModuleData();
404 $navFrameScripts = array();
405
406 foreach($rawModuleData as $mainModuleKey => $mainModuleData) {
407 if ($mainModuleData['subitems']) {
408 foreach ($mainModuleData['subitems'] as $subModuleKey => $subModuleData) {
409
410 $parentModuleName = substr($subModuleData['name'], 0, strpos($subModuleData['name'], '_'));
411 $javascriptCommand = '';
412
413 // Setting additional JavaScript if frameset script:
414 $additionalJavascript = '';
415 if($subModuleData['parentNavigationFrameScript']) {
416 $additionalJavascript = "+'&id='+top.rawurlencodeAndRemoveSiteUrl(top.fsMod.recentIds['" . $parentModuleName . "'])";
417 }
418
419 if ($subModuleData['link'] && $this->linkModules) {
420 // For condensed mode, send &cMR parameter to frameset script.
421 if ($additionalJavascript && $GLOBALS['BE_USER']->uc['condensedMode']) {
422 $additionalJavascript .= "+(cMR ? '&cMR=1' : '')";
423 }
424
425 $javascriptCommand = '
426 modScriptURL = "'.$this->appendQuestionmarkToLink($subModuleData['link']).'"'.$additionalJavascript.';';
427
428 if ($subModuleData['navFrameScript']) {
429 $javascriptCommand .= '
430 top.currentSubScript="'.$subModuleData['originalLink'].'";';
431 }
432
433 if (!$GLOBALS['BE_USER']->uc['condensedMode'] && $subModuleData['parentNavigationFrameScript']) {
434 $additionalJavascript = "+'&id='+top.rawurlencodeAndRemoveSiteUrl(top.fsMod.recentIds['" . $parentModuleName . "'])";
435
436 $submoduleNavigationFrameScript = $subModuleData['navigationFrameScript'] ? $subModuleData['navigationFrameScript'] : $subModuleData['parentNavigationFrameScript'];
437 $submoduleNavigationFrameScript = t3lib_div::resolveBackPath($submoduleNavigationFrameScript);
438
439 // add GET parameters for sub module to the navigation script
440 $submoduleNavigationFrameScript = $this->appendQuestionmarkToLink($submoduleNavigationFrameScript).$subModuleData['navigationFrameScript'];
441 $navFrameScripts[$parentModuleName] = $submoduleNavigationFrameScript;
442 $javascriptCommand = '
443 top.currentSubScript = "'.$subModuleData['originalLink'].'";
444 if (top.content.list_frame && top.fsMod.currentMainLoaded == mainModName) {
445 modScriptURL = "'.$this->appendQuestionmarkToLink($subModuleData['originalLink']).'"'.$additionalJavascript.';
446 } else if (top.nextLoadModuleUrl) {
447 modScriptURL = "'.($subModuleData['prefix'] ? $this->appendQuestionmarkToLink($subModuleData['link']) . '&exScript=' : '') . 'listframe_loader.php";
448 } else {
449 modScriptURL = "'.$this->appendQuestionmarkToLink($subModuleData['link']).'"'.$additionalJavascript.' + additionalGetVariables;
450 }';
451 }
452 }
453 $moduleJavascriptCommands[] = "
454 case '".$subModuleData['name']."':".$javascriptCommand."
455 break;";
456 }
457 } elseif(!$mainModuleData['subitems'] && !empty($mainModuleData['link'])) {
458 // main module has no sub modules but instead is linked itself (doc module f.e.)
459 $javascriptCommand = '
460 modScriptURL = "'.$this->appendQuestionmarkToLink($mainModuleData['link']).'";';
461 $moduleJavascriptCommands[] = "
462 case '".$mainModuleData['name']."':".$javascriptCommand."
463 break;";
464 }
465 }
466
467 $javascriptCode = 'function(modName, cMR_flag, addGetVars) {
468 var useCondensedMode = '.($GLOBALS['BE_USER']->uc['condensedMode'] ? 'true' : 'false').';
469 var mainModName = (modName.slice(0, modName.indexOf("_")) || modName);
470
471 var additionalGetVariables = "";
472 if (addGetVars) {
473 additionalGetVariables = addGetVars;
474 }
475
476 var cMR = (cMR_flag ? 1 : 0);
477 var modScriptURL = "";
478
479 switch(modName) {'
480 ."\n".implode("\n", $moduleJavascriptCommands)."\n".'
481 }
482
483 var navFrames = {};';
484 foreach ($navFrameScripts as $mainMod => $frameScript) {
485 $javascriptCode .= '
486 navFrames["'.$mainMod.'"] = "'.$frameScript.'";';
487 }
488 $javascriptCode .= '
489
490 if (!useCondensedMode && navFrames[mainModName]) {
491 if (top.content.list_frame && top.fsMod.currentMainLoaded == mainModName) {
492 top.content.list_frame.location = top.getModuleUrl(top.TS.PATH_typo3 + modScriptURL + additionalGetVariables);
493 if (top.currentSubNavScript != navFrames[mainModName]) {
494 top.currentSubNavScript = navFrames[mainModName];
495 top.content.nav_frame.location = top.getModuleUrl(top.TS.PATH_typo3 + navFrames[mainModName]);
496 }
497 } else {
498 $("content").src = top.TS.PATH_typo3 + modScriptURL;
499 }
500 } else if (modScriptURL) {
501 $("content").src = top.getModuleUrl(top.TS.PATH_typo3 + modScriptURL + additionalGetVariables);
502 }
503 currentModuleLoaded = modName;
504 top.fsMod.currentMainLoaded = mainModName;
505 TYPO3ModuleMenu.highlightModule("modmenu_" + modName, (modName == mainModName ? 1 : 0));
506 }';
507
508 return $javascriptCode;
509 }
510
511 /**
512 * Appends a '?' if there is none in the string already
513 *
514 * @param string Link URL
515 * @return string link URl appended with ? if there wasn't one
516 */
517 protected function appendQuestionmarkToLink($link) {
518 if(!strstr($link, '?')) {
519 $link .= '?';
520 }
521
522 return $link;
523 }
524
525 /**
526 * renders the logout button form
527 *
528 * @return string html code snippet displaying the logout button
529 */
530 public function renderLogoutButton() {
531 $buttonLabel = $GLOBALS['BE_USER']->user['ses_backuserid'] ? 'LLL:EXT:lang/locallang_core.php:buttons.exit' : 'LLL:EXT:lang/locallang_core.php:buttons.logout';
532
533 $buttonForm = '
534 <form action="logout.php" target="_top">
535 <input type="submit" value="&nbsp;' . $GLOBALS['LANG']->sL($buttonLabel, 1) . '&nbsp;" />
536 </form>';
537
538 return $buttonForm;
539 }
540
541 /**
542 * turns linking of modules on or off
543 *
544 * @param boolean status for linking modules with a-tags, set to false to turn lining off
545 */
546 public function setLinkModules($linkModules) {
547 if(!is_bool($linkModules)) {
548 throw new InvalidArgumentException('parameter $linkModules must be of type bool', 1193326558);
549 }
550
551 $this->linkModules = $linkModules;
552 }
553
554 /**
555 * gets the frameset (leftover) helper
556 *
557 * @return array array of javascript snippets
558 */
559 public function getFsMod() {
560 return $this->fsMod;
561 }
562 }
563
564
565 if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['typo3/classes/class.modulemenu.php']) {
566 include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['typo3/classes/class.modulemenu.php']);
567 }
568
569 ?>