ModuleLoader.php 21.3 KB
Newer Older
1
2
3
<?php
namespace TYPO3\CMS\Backend\Module;

4
/*
5
 * This file is part of the TYPO3 CMS project.
6
 *
7
8
9
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
10
 *
11
12
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
13
 *
14
15
 * The TYPO3 project - inspiring people to share!
 */
Christian Kuhn's avatar
Christian Kuhn committed
16

17
use TYPO3\CMS\Backend\Utility\BackendUtility;
18
19
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
Nicole Cordes's avatar
Nicole Cordes committed
20
use TYPO3\CMS\Core\Utility\GeneralUtility;
21
22
use TYPO3\CMS\Core\Utility\PathUtility;
use TYPO3\CMS\Lang\LanguageService;
Nicole Cordes's avatar
Nicole Cordes committed
23

24
25
26
27
28
29
/**
 * This document provides a class that loads the modules for the TYPO3 interface.
 *
 * Load Backend Interface modules
 *
 * Typically instantiated like this:
30
 * $this->loadModules = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Module\ModuleLoader::class);
31
 * $this->loadModules->load($TBE_MODULES);
Christian Kuhn's avatar
Christian Kuhn committed
32
 * @internal
33
34
35
36
 */
class ModuleLoader {

	/**
37
38
	 * After the init() function this array will contain the structure of available modules for the backend user.
	 *
39
	 * @var array
40
41
42
43
	 */
	public $modules = array();

	/**
44
45
	 * Array with paths pointing to the location of modules from extensions
	 *
46
	 * @var array
47
48
49
50
	 */
	public $absPathArray = array();

	/**
51
52
	 * This array will hold the elements that should go into the select-list of modules for groups...
	 *
53
	 * @var array
54
55
56
57
	 */
	public $modListGroup = array();

	/**
58
59
	 * This array will hold the elements that should go into the select-list of modules for users...
	 *
60
	 * @var array
61
62
63
64
65
66
67
68
69
70
71
	 */
	public $modListUser = array();

	/**
	 * The backend user for use internally
	 *
	 * @var \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
	 */
	public $BE_USER;

	/**
72
73
	 * If set TRUE, workspace "permissions" will be observed so non-allowed modules will not be included in the array of modules.
	 *
74
	 * @var bool
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
	 */
	public $observeWorkspaces = FALSE;

	/**
	 * Contains the registered navigation components
	 *
	 * @var array
	 */
	protected $navigationComponents = array();

	/**
	 * Init.
	 * The outcome of the load() function will be a $this->modules array populated with the backend module structure available to the BE_USER
	 * Further the global var $LANG will have labels and images for the modules loaded in an internal array.
	 *
	 * @param array $modulesArray Should be the global var $TBE_MODULES, $BE_USER can optionally be set to an alternative Backend user object than the global var $BE_USER (which is the currently logged in user)
91
	 * @param BackendUserAuthentication $beUser Optional backend user object to use. If not set, the global BE_USER object is used.
92
93
	 * @return void
	 */
94
	public function load($modulesArray, BackendUserAuthentication $beUser = NULL) {
95
		// Setting the backend user for use internally
96
97
		$this->BE_USER = $beUser ?: $GLOBALS['BE_USER'];

98
99
100
101
102
103
104
105
106
107
		/*$modulesArray might look like this when entering this function.
		Notice the two modules added by extensions - they have a path attachedArray
		(
		[web] => list,info,perm,func
		[file] => list
		[user] =>
		[tools] => em,install,txphpmyadmin
		[help] => about
		[_PATHS] => Array
		(
108
		[system_install] => /www/htdocs/typo3/32/coreinstall/typo3/ext/install/mod/
109
110
111
112
113
		[tools_txphpmyadmin] => /www/htdocs/typo3/32/coreinstall/typo3/ext/phpmyadmin/modsub/
		))
		 */
		$this->absPathArray = $modulesArray['_PATHS'];
		unset($modulesArray['_PATHS']);
114
		// Unset the array for calling external backend module dispatchers in typo3/index.php
115
		unset($modulesArray['_dispatcher']);
116
		// Unset the array for calling backend modules based on external backend module dispatchers in typo3/index.php
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
		unset($modulesArray['_configuration']);
		$this->navigationComponents = $modulesArray['_navigationComponents'];
		unset($modulesArray['_navigationComponents']);
		$theMods = $this->parseModulesArray($modulesArray);
		// Originally modules were found in typo3/mod/
		// User defined modules were found in ../typo3conf/
		// Today almost all modules reside in extensions and they are found by the _PATHS array of the incoming $TBE_MODULES array
		// Setting paths for 1) core modules (old concept from mod/) and 2) user-defined modules (from ../typo3conf)
		$paths = array();
		// Path of static modules
		$paths['defMods'] = PATH_typo3 . 'mod/';
		// Local modules (maybe frontend specific)
		$paths['userMods'] = PATH_typo3 . '../typo3conf/';
		// Traverses the module setup and creates the internal array $this->modules
		foreach ($theMods as $mods => $subMod) {
			$path = NULL;
			$extModRelPath = $this->checkExtensionModule($mods);
			// EXTENSION module:
			if ($extModRelPath) {
				$theMainMod = $this->checkMod($mods, PATH_site . $extModRelPath);
				if (is_array($theMainMod) || $theMainMod != 'notFound') {
					// ... just so it goes on... submodules cannot be within this path!
					$path = 1;
				}
			} else {
				// 'CLASSIC' module
				// Checking for typo3/mod/ module existence...
				$theMainMod = $this->checkMod($mods, $paths['defMods'] . $mods);
				if (is_array($theMainMod) || $theMainMod != 'notFound') {
					$path = $paths['defMods'];
				} else {
					// If not typo3/mod/ then it could be user-defined in typo3conf/ ...?
					$theMainMod = $this->checkMod($mods, $paths['userMods'] . $mods);
					if (is_array($theMainMod) || $theMainMod != 'notFound') {
						$path = $paths['userMods'];
					}
				}
			}
			// If $theMainMod is not set (FALSE) there is no access to the module !(?)
			if ($theMainMod && !is_null($path)) {
				$this->modules[$mods] = $theMainMod;
				// SUBMODULES - if any - are loaded
				if (is_array($subMod)) {
					foreach ($subMod as $valsub) {
161
						$extModRelPath = $this->checkExtensionModule($mods . '_' . $valsub);
162
163
						if ($extModRelPath) {
							// EXTENSION submodule:
164
							$theTempSubMod = $this->checkMod($mods . '_' . $valsub, PATH_site . $extModRelPath);
165
166
167
168
169
170
171
							// Default sub-module in either main-module-path, be it the default or the userdefined.
							if (is_array($theTempSubMod)) {
								$this->modules[$mods]['sub'][$valsub] = $theTempSubMod;
							}
						} else {
							// 'CLASSIC' submodule
							// Checking for typo3/mod/xxx/ module existence...
172
							// @todo what about $path = 1; from above and using $path as string here?
173
							$theTempSubMod = $this->checkMod($mods . '_' . $valsub, $path . $mods . '/' . $valsub);
174
175
176
177
178
							// Default sub-module in either main-module-path, be it the default or the userdefined.
							if (is_array($theTempSubMod)) {
								$this->modules[$mods]['sub'][$valsub] = $theTempSubMod;
							} elseif ($path == $paths['defMods']) {
								// If the submodule did not exist in the default module path, then check if there is a submodule in the submodule path!
179
								$theTempSubMod = $this->checkMod($mods . '_' . $valsub, $paths['userMods'] . $mods . '/' . $valsub);
180
181
182
183
184
185
186
187
188
189
190
								if (is_array($theTempSubMod)) {
									$this->modules[$mods]['sub'][$valsub] = $theTempSubMod;
								}
							}
						}
					}
				}
			} else {
				// This must be done in order to fill out the select-lists for modules correctly!!
				if (is_array($subMod)) {
					foreach ($subMod as $valsub) {
191
						// @todo path can only be NULL here, or not?
192
						$this->checkMod($mods . '_' . $valsub, $path . $mods . '/' . $valsub);
193
194
195
196
197
198
199
					}
				}
			}
		}
	}

	/**
200
201
	 * If the module name ($name) is a module from an extension (has path in $this->absPathArray)
	 * then that path is returned relative to PATH_site
202
203
204
205
206
207
	 *
	 * @param string $name Module name
	 * @return string If found, the relative path from PATH_site
	 */
	public function checkExtensionModule($name) {
		if (isset($this->absPathArray[$name])) {
208
			return rtrim(PathUtility::stripPathSitePrefix($this->absPathArray[$name]), '/');
209
		}
210
		return '';
211
212
213
214
	}

	/**
	 * Here we check for the module.
215
	 *
216
217
218
	 * Return values:
	 * 'notFound':	If the module was not found in the path (no "conf.php" file)
	 * FALSE:		If no access to the module (access check failed)
219
	 * array():	    Configuration array, in case a valid module where access IS granted exists.
220
221
	 *
	 * @param string $name Module name
222
223
	 * @param string $fullPath Absolute path to module
	 * @return string|bool|array See description of function
224
	 */
225
226
	public function checkMod($name, $fullPath) {
		if ($name === 'user_ws' && !ExtensionManagementUtility::isLoaded('version')) {
227
228
229
230
231
232
			return FALSE;
		}
		// Check for own way of configuring module
		if (is_array($GLOBALS['TBE_MODULES']['_configuration'][$name]['configureModuleFunction'])) {
			$obj = $GLOBALS['TBE_MODULES']['_configuration'][$name]['configureModuleFunction'];
			if (is_callable($obj)) {
233
				$MCONF = call_user_func($obj, $name, $fullPath);
234
235
236
237
238
239
				if ($this->checkModAccess($name, $MCONF) !== TRUE) {
					return FALSE;
				}
				return $MCONF;
			}
		}
240
241

		// merges $MCONF and $MLANG from conf.php and the additional configuration of the module
242
		$setupInformation = $this->getModuleSetupInformation($name, $fullPath);
243

244
		// Because 'path/../path' does not work
245
		// clean up the configuration part
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
		if (empty($setupInformation['configuration'])) {
			return 'notFound';
		}
		if (
			$setupInformation['configuration']['shy']
			|| !$this->checkModAccess($name, $setupInformation['configuration'])
			|| !$this->checkModWorkspace($name, $setupInformation['configuration'])
		) {
			return FALSE;
		}
		$finalModuleConfiguration = $setupInformation['configuration'];
		$finalModuleConfiguration['name'] = $name;
		// Language processing. This will add module labels and image reference to the internal ->moduleLabels array of the LANG object.
		$lang = $this->getLanguageService();
		if (is_object($lang)) {
			// $setupInformation['labels']['default']['tabs_images']['tab'] is for modules the reference
			// to the module icon.
			$defaultLabels = $setupInformation['labels']['default'];

			// Here the path is transformed to an absolute reference.
			if ($defaultLabels['tabs_images']['tab']) {
				// Initializing search for alternative icon:
				// Alternative icon key (might have an alternative set in $TBE_STYLES['skinImg']
				$altIconKey = 'MOD:' . $name . '/' . $defaultLabels['tabs_images']['tab'];
				$altIconAbsPath = is_array($GLOBALS['TBE_STYLES']['skinImg'][$altIconKey]) ? GeneralUtility::resolveBackPath(PATH_typo3 . $GLOBALS['TBE_STYLES']['skinImg'][$altIconKey][0]) : '';
				// Setting icon, either default or alternative:
				if ($altIconAbsPath && @is_file($altIconAbsPath)) {
					$defaultLabels['tabs_images']['tab'] = $this->getRelativePath(PATH_typo3, $altIconAbsPath);
274
				} else {
275
276
					// Setting default icon:
					$defaultLabels['tabs_images']['tab'] = $this->getRelativePath(PATH_typo3, $fullPath . '/' . $defaultLabels['tabs_images']['tab']);
277
				}
278

279
280
281
282
283
				// Finally, setting the icon with correct path:
				if (substr($defaultLabels['tabs_images']['tab'], 0, 3) === '../') {
					$defaultLabels['tabs_images']['tab'] = PATH_site . substr($defaultLabels['tabs_images']['tab'], 3);
				} else {
					$defaultLabels['tabs_images']['tab'] = PATH_typo3 . $defaultLabels['tabs_images']['tab'];
284
				}
285
			}
286

287
288
289
290
291
292
293
294
295
296
297
298
299
			// If LOCAL_LANG references are used for labels of the module:
			if ($defaultLabels['ll_ref']) {
				// Now the 'default' key is loaded with the CURRENT language - not the english translation...
				$defaultLabels['labels']['tablabel'] = $lang->sL($defaultLabels['ll_ref'] . ':mlang_labels_tablabel');
				$defaultLabels['labels']['tabdescr'] = $lang->sL($defaultLabels['ll_ref'] . ':mlang_labels_tabdescr');
				$defaultLabels['tabs']['tab'] = $lang->sL($defaultLabels['ll_ref'] . ':mlang_tabs_tab');
				$lang->addModuleLabels($defaultLabels, $name . '_');
			} else {
				// ... otherwise use the old way:
				$lang->addModuleLabels($defaultLabels, $name . '_');
				$lang->addModuleLabels($setupInformation['labels'][$lang->lang], $name . '_');
			}
		}
300

301
302
303
304
		// Default script setup
		if ($setupInformation['configuration']['script'] === '_DISPATCH') {
			if ($setupInformation['configuration']['extbase']) {
				$finalModuleConfiguration['script'] = BackendUtility::getModuleUrl('Tx_' . $name);
305
			} else {
306
				$finalModuleConfiguration['script'] = BackendUtility::getModuleUrl($name);
307
			}
308
309
		} elseif ($setupInformation['configuration']['script'] && file_exists($setupInformation['path'] . '/' . $setupInformation['configuration']['script'])) {
			$finalModuleConfiguration['script'] = $this->getRelativePath(PATH_typo3, $fullPath . '/' . $setupInformation['configuration']['script']);
310
		} else {
311
			$finalModuleConfiguration['script'] = BackendUtility::getModuleUrl('dummy');
312
313
		}

314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
		// Navigation Frame Script (GET params could be added)
		if ($setupInformation['configuration']['navFrameScript']) {
			$navFrameScript = explode('?', $setupInformation['configuration']['navFrameScript']);
			$navFrameScript = $navFrameScript[0];
			if (file_exists($setupInformation['path'] . '/' . $navFrameScript)) {
				$finalModuleConfiguration['navFrameScript'] = $this->getRelativePath(PATH_typo3, $fullPath . '/' . $setupInformation['configuration']['navFrameScript']);
			}
		}

		// additional params for Navigation Frame Script: "&anyParam=value&moreParam=1"
		if ($setupInformation['configuration']['navFrameScriptParam']) {
			$finalModuleConfiguration['navFrameScriptParam'] = $setupInformation['configuration']['navFrameScriptParam'];
		}

		// Check if this is a submodule
		$mainModule = '';
		if (strpos($name, '_') !== FALSE) {
			list($mainModule, ) = explode('_', $name, 2);
		}

		// check if there is a navigation component (like the pagetree)
		if (is_array($this->navigationComponents[$name])) {
336
337
338
339
340
341
			// the navigation component is a module, so the module URL is taken
			if (isset($GLOBALS['TBE_MODULES']['_PATHS'][$this->navigationComponents[$name]['componentId']])) {
				$finalModuleConfiguration['navFrameScript'] = BackendUtility::getModuleUrl($this->navigationComponents[$name]['componentId']);
			} else {
				$finalModuleConfiguration['navigationComponentId'] = $this->navigationComponents[$name]['componentId'];
			}
342
343
		// navigation component can be overriden by the main module component
		} elseif ($mainModule && is_array($this->navigationComponents[$mainModule]) && $setupInformation['configuration']['inheritNavigationComponentFromMainModule'] !== FALSE) {
344
345
346
347
348
349
			// the navigation component is a module, so the module URL is taken
			if (isset($GLOBALS['TBE_MODULES']['_PATHS'][$this->navigationComponents[$mainModule]['componentId']])) {
				$finalModuleConfiguration['navFrameScript'] = BackendUtility::getModuleUrl($this->navigationComponents[$mainModule]['componentId']);
			} else {
				$finalModuleConfiguration['navigationComponentId'] = $this->navigationComponents[$mainModule]['componentId'];
			}
350
		}
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
		return $finalModuleConfiguration;
	}

	/**
	 * fetches the conf.php file of a certain module, and also merges that with
	 * some additional configuration
	 *
	 * @param \string $moduleName the combined name of the module, can be "web", "web_info", or "tools_log"
	 * @param \string $pathToModuleDirectory the path where the module data is put, used for the conf.php or the modScript
	 * @return array an array with subarrays, named "configuration" (aka $MCONF), "labels" (previously known as $MLANG) and the stripped path
	 */
	protected function getModuleSetupInformation($moduleName, $pathToModuleDirectory) {

		// Because 'path/../path' does not work
		$path = preg_replace('/\\/[^\\/.]+\\/\\.\\.\\//', '/', $pathToModuleDirectory);

		$moduleSetupInformation = array(
			'configuration' => array(),
			'labels' => array(),
			'path' => $path
		);

		if (@is_dir($path) && file_exists($path . '/conf.php')) {
			$MCONF = array();
			$MLANG = array();

			// The conf-file is included. This must be valid PHP.
			include $path . '/conf.php';

380
			// Move the global variables defined in conf.php into the local method
381
382
383
384
385
386
			if (is_array($MCONF)) {
				$moduleSetupInformation['configuration'] = $MCONF;
			} else {
				$moduleSetupInformation['configuration'] = array();
			}
			$moduleSetupInformation['labels'] = $MLANG;
387
		}
388

389
390
391
		$moduleConfiguration = !empty($GLOBALS['TBE_MODULES']['_configuration'][$moduleName])
			? $GLOBALS['TBE_MODULES']['_configuration'][$moduleName]
			: NULL;
392
393
394
395
396
397
398
399
400
401
402
403
404
405
		if ($moduleConfiguration !== NULL) {
			// Overlay setup with additional labels
			if (!empty($moduleConfiguration['labels']) && is_array($moduleConfiguration['labels'])) {
				if (empty($moduleSetupInformation['labels']['default']) || !is_array($moduleSetupInformation['labels']['default'])) {
					$moduleSetupInformation['labels']['default'] = $moduleConfiguration['labels'];
				} else {
					$moduleSetupInformation['labels']['default'] = array_replace_recursive($moduleSetupInformation['labels']['default'], $moduleConfiguration['labels']);
				}
				unset($moduleConfiguration['labels']);
			}
			// Overlay setup with additional configuration
			if (is_array($moduleConfiguration)) {
				$moduleSetupInformation['configuration'] = array_replace_recursive($moduleSetupInformation['configuration'], $moduleConfiguration);
			}
406
407
		}

408
		// Add some default configuration
409
410
411
412
413
		if (!isset($moduleSetupInformation['configuration']['inheritNavigationComponentFromMainModule'])) {
			$moduleSetupInformation['configuration']['inheritNavigationComponentFromMainModule'] = TRUE;
		}

		return $moduleSetupInformation;
414
415
416
417
418
419
420
	}

	/**
	 * Returns TRUE if the internal BE_USER has access to the module $name with $MCONF (based on security level set for that module)
	 *
	 * @param string $name Module name
	 * @param array $MCONF MCONF array (module configuration array) from the modules conf.php file (contains settings about what access level the module has)
421
	 * @return bool TRUE if access is granted for $this->BE_USER
422
423
	 */
	public function checkModAccess($name, $MCONF) {
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
		if (empty($MCONF['access'])) {
			return TRUE;
		}
		$access = strtolower($MCONF['access']);
		// Checking if admin-access is required
		// If admin-permissions is required then return TRUE if user is admin
		if (strpos($access, 'admin') !== FALSE && $this->BE_USER->isAdmin()) {
			return TRUE;
		}
		// This will add modules to the select-lists of user and groups
		if (strpos($access, 'user') !== FALSE) {
			$this->modListUser[] = $name;
		}
		if (strpos($access, 'group') !== FALSE) {
			$this->modListGroup[] = $name;
		}
		// This checks if a user is permitted to access the module
		if ($this->BE_USER->isAdmin() || $this->BE_USER->check('modules', $name)) {
442
443
			return TRUE;
		}
444
		return FALSE;
445
446
447
448
449
450
	}

	/**
	 * Check if a module is allowed inside the current workspace for be user
	 * Processing happens only if $this->observeWorkspaces is TRUE
	 *
451
	 * @param string $name Module name (unused)
452
	 * @param array $MCONF MCONF array (module configuration array) from the modules conf.php file (contains settings about workspace restrictions)
453
	 * @return bool TRUE if access is granted for $this->BE_USER
454
455
	 */
	public function checkModWorkspace($name, $MCONF) {
456
		if (!$this->observeWorkspaces) {
457
458
			return TRUE;
		}
459
460
461
462
463
464
465
466
467
		$status = TRUE;
		if (!empty($MCONF['workspaces'])) {
			$status = $this->BE_USER->workspace === 0 && GeneralUtility::inList($MCONF['workspaces'], 'online')
				|| $this->BE_USER->workspace === -1 && GeneralUtility::inList($MCONF['workspaces'], 'offline')
				|| $this->BE_USER->workspace > 0 && GeneralUtility::inList($MCONF['workspaces'], 'custom');
		} elseif ($this->BE_USER->workspace === -99) {
			$status = FALSE;
		}
		return $status;
468
469
470
	}

	/**
471
	 * Parses the moduleArray ($TBE_MODULES) into an internally useful structure.
472
473
474
475
476
477
478
479
480
481
482
483
484
	 * Returns an array where the keys are names of the module and the values may be TRUE (only module) or an array (of submodules)
	 *
	 * @param array $arr ModuleArray ($TBE_MODULES)
	 * @return array Output structure with available modules
	 */
	public function parseModulesArray($arr) {
		$theMods = array();
		if (is_array($arr)) {
			foreach ($arr as $mod => $subs) {
				// Clean module name to alphanum
				$mod = $this->cleanName($mod);
				if ($mod) {
					if ($subs) {
Nicole Cordes's avatar
Nicole Cordes committed
485
						$subsArr = GeneralUtility::trimExplode(',', $subs);
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
						foreach ($subsArr as $subMod) {
							$subMod = $this->cleanName($subMod);
							if ($subMod) {
								$theMods[$mod][] = $subMod;
							}
						}
					} else {
						$theMods[$mod] = 1;
					}
				}
			}
		}
		return $theMods;
	}

	/**
502
503
	 * The $str is cleaned so that it contains alphanumerical characters only.
	 * Module names must only consist of these characters
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
	 *
	 * @param string $str String to clean up
	 * @return string
	 */
	public function cleanName($str) {
		return preg_replace('/[^a-z0-9]/i', '', $str);
	}

	/**
	 * Get relative path for $destDir compared to $baseDir
	 *
	 * @param string $baseDir Base directory
	 * @param string $destDir Destination directory
	 * @return string The relative path of destination compared to base.
	 */
	public function getRelativePath($baseDir, $destDir) {
520
521
		// A special case, the dirs are equal
		if ($baseDir === $destDir) {
522
523
524
525
526
527
528
529
			return './';
		}
		// Remove beginning
		$baseDir = ltrim($baseDir, '/');
		$destDir = ltrim($destDir, '/');
		$found = TRUE;
		do {
			$slash_pos = strpos($destDir, '/');
530
			if ($slash_pos !== FALSE && substr($destDir, 0, $slash_pos) == substr($baseDir, 0, $slash_pos)) {
531
532
533
534
535
				$baseDir = substr($baseDir, $slash_pos + 1);
				$destDir = substr($destDir, $slash_pos + 1);
			} else {
				$found = FALSE;
			}
536
		} while ($found);
537
538
539
540
		$slashes = strlen($baseDir) - strlen(str_replace('/', '', $baseDir));
		for ($i = 0; $i < $slashes; $i++) {
			$destDir = '../' . $destDir;
		}
Nicole Cordes's avatar
Nicole Cordes committed
541
		return GeneralUtility::resolveBackPath($destDir);
542
543
	}

544
545
546
547
548
549
550
	/**
	 * @return LanguageService
	 */
	protected function getLanguageService() {
		return $GLOBALS['LANG'];
	}

551
}