TYPO3  7.6
ModuleLoader.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Backend\Module;
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 
23 
35 {
41  public $modules = array();
42 
48  public $absPathArray = array();
49 
55  public $modListGroup = array();
56 
62  public $modListUser = array();
63 
69  public $BE_USER;
70 
76  public $observeWorkspaces = false;
77 
83  protected $navigationComponents = array();
84 
94  public function load($modulesArray, BackendUserAuthentication $beUser = null)
95  {
96  // Setting the backend user for use internally
97  $this->BE_USER = $beUser ?: $GLOBALS['BE_USER'];
98 
99  /*$modulesArray might look like this when entering this function.
100  Notice the two modules added by extensions - they have a path attachedArray
101  (
102  [web] => list,info,perm,func
103  [file] => list
104  [user] =>
105  [tools] => em,install,txphpmyadmin
106  [help] => about
107  [_PATHS] => Array
108  (
109  [system_install] => /www/htdocs/typo3/32/coreinstall/typo3/ext/install/mod/
110  [tools_txphpmyadmin] => /www/htdocs/typo3/32/coreinstall/typo3/ext/phpmyadmin/modsub/
111  ))
112  */
113  $this->absPathArray = $modulesArray['_PATHS'];
114  unset($modulesArray['_PATHS']);
115  // Unset the array for calling external backend module dispatchers in typo3/index.php
116  // (unused in Core, but in case some extension still sets this, we unset that)
117  unset($modulesArray['_dispatcher']);
118  // Unset the array for calling backend modules based on external backend module dispatchers in typo3/index.php
119  unset($modulesArray['_configuration']);
120  $this->navigationComponents = $modulesArray['_navigationComponents'];
121  unset($modulesArray['_navigationComponents']);
122  $theMods = $this->parseModulesArray($modulesArray);
123  // Originally modules were found in typo3/mod/
124  // User defined modules were found in ../typo3conf/
125  // Today almost all modules reside in extensions and they are found by the _PATHS array of the incoming $TBE_MODULES array
126  // Setting paths for 1) core modules (old concept from mod/) and 2) user-defined modules (from ../typo3conf)
127  $paths = array();
128  // Path of static modules
129  $paths['defMods'] = PATH_typo3 . 'mod/';
130  // Local modules (maybe frontend specific)
131  $paths['userMods'] = PATH_typo3 . '../typo3conf/';
132  // Traverses the module setup and creates the internal array $this->modules
133  foreach ($theMods as $mods => $subMod) {
134  $path = null;
135  $extModRelPath = $this->checkExtensionModule($mods);
136  // EXTENSION module:
137  if ($extModRelPath) {
138  $theMainMod = $this->checkMod($mods, PATH_site . $extModRelPath);
139  if (is_array($theMainMod) || $theMainMod != 'notFound') {
140  // ... just so it goes on... submodules cannot be within this path!
141  $path = 1;
142  }
143  } else {
144  // 'CLASSIC' module
145  // Checking for typo3/mod/ module existence...
146  $theMainMod = $this->checkMod($mods, $paths['defMods'] . $mods);
147  if (is_array($theMainMod) || $theMainMod != 'notFound') {
148  $path = $paths['defMods'];
149  } else {
150  // If not typo3/mod/ then it could be user-defined in typo3conf/ ...?
151  $theMainMod = $this->checkMod($mods, $paths['userMods'] . $mods);
152  if (is_array($theMainMod) || $theMainMod != 'notFound') {
153  $path = $paths['userMods'];
154  }
155  }
156  }
157  // If $theMainMod is not set (FALSE) there is no access to the module !(?)
158  if ($theMainMod && !is_null($path)) {
159  $this->modules[$mods] = $theMainMod;
160  // SUBMODULES - if any - are loaded
161  if (is_array($subMod)) {
162  foreach ($subMod as $valsub) {
163  $extModRelPath = $this->checkExtensionModule($mods . '_' . $valsub);
164  if ($extModRelPath) {
165  // EXTENSION submodule:
166  $theTempSubMod = $this->checkMod($mods . '_' . $valsub, PATH_site . $extModRelPath);
167  // Default sub-module in either main-module-path, be it the default or the userdefined.
168  if (is_array($theTempSubMod)) {
169  $this->modules[$mods]['sub'][$valsub] = $theTempSubMod;
170  }
171  } else {
172  // 'CLASSIC' submodule
173  // Checking for typo3/mod/xxx/ module existence...
174  // @todo what about $path = 1; from above and using $path as string here?
175  $theTempSubMod = $this->checkMod($mods . '_' . $valsub, $path . $mods . '/' . $valsub);
176  // Default sub-module in either main-module-path, be it the default or the userdefined.
177  if (is_array($theTempSubMod)) {
178  $this->modules[$mods]['sub'][$valsub] = $theTempSubMod;
179  } elseif ($path == $paths['defMods']) {
180  // If the submodule did not exist in the default module path, then check if there is a submodule in the submodule path!
181  $theTempSubMod = $this->checkMod($mods . '_' . $valsub, $paths['userMods'] . $mods . '/' . $valsub);
182  if (is_array($theTempSubMod)) {
183  $this->modules[$mods]['sub'][$valsub] = $theTempSubMod;
184  }
185  }
186  }
187  }
188  }
189  } else {
190  // This must be done in order to fill out the select-lists for modules correctly!!
191  if (is_array($subMod)) {
192  foreach ($subMod as $valsub) {
193  // @todo path can only be NULL here, or not?
194  $this->checkMod($mods . '_' . $valsub, $path . $mods . '/' . $valsub);
195  }
196  }
197  }
198  }
199  }
200 
208  public function checkExtensionModule($name)
209  {
210  if (isset($this->absPathArray[$name])) {
211  return rtrim(PathUtility::stripPathSitePrefix($this->absPathArray[$name]), '/');
212  }
213  return '';
214  }
215 
228  public function checkMod($name, $fullPath)
229  {
230  if ($name === 'user_ws' && !ExtensionManagementUtility::isLoaded('version')) {
231  return false;
232  }
233  // Check for own way of configuring module
234  if (is_array($GLOBALS['TBE_MODULES']['_configuration'][$name]['configureModuleFunction'])) {
235  $obj = $GLOBALS['TBE_MODULES']['_configuration'][$name]['configureModuleFunction'];
236  if (is_callable($obj)) {
237  $MCONF = call_user_func($obj, $name, $fullPath);
238  if ($this->checkModAccess($name, $MCONF) !== true) {
239  return false;
240  }
241  return $MCONF;
242  }
243  }
244 
245  // merges $MCONF and $MLANG from conf.php and the additional configuration of the module
246  $setupInformation = $this->getModuleSetupInformation($name, $fullPath);
247 
248  // Because 'path/../path' does not work
249  // clean up the configuration part
250  if (empty($setupInformation['configuration'])) {
251  return 'notFound';
252  }
253  if (
254  $setupInformation['configuration']['shy']
255  || !$this->checkModAccess($name, $setupInformation['configuration'])
256  || !$this->checkModWorkspace($name, $setupInformation['configuration'])
257  ) {
258  return false;
259  }
260  $finalModuleConfiguration = $setupInformation['configuration'];
261  $finalModuleConfiguration['name'] = $name;
262  // Language processing. This will add module labels and image reference to the internal ->moduleLabels array of the LANG object.
263  $lang = $this->getLanguageService();
264  if (is_object($lang)) {
265  // $setupInformation['labels']['default']['tabs_images']['tab'] is for modules the reference
266  // to the module icon.
267  $defaultLabels = $setupInformation['labels']['default'];
268 
269  // Here the path is transformed to an absolute reference.
270  if ($defaultLabels['tabs_images']['tab']) {
271  // Initializing search for alternative icon:
272  // Alternative icon key (might have an alternative set in $TBE_STYLES['skinImg']
273  $altIconKey = 'MOD:' . $name . '/' . $defaultLabels['tabs_images']['tab'];
274  $altIconAbsPath = is_array($GLOBALS['TBE_STYLES']['skinImg'][$altIconKey]) ? GeneralUtility::resolveBackPath(PATH_typo3 . $GLOBALS['TBE_STYLES']['skinImg'][$altIconKey][0]) : '';
275  // Setting icon, either default or alternative:
276  if ($altIconAbsPath && @is_file($altIconAbsPath)) {
277  $defaultLabels['tabs_images']['tab'] = $altIconAbsPath;
278  } else {
279  if (\TYPO3\CMS\Core\Utility\StringUtility::beginsWith($defaultLabels['tabs_images']['tab'], 'EXT:')) {
280  list($extensionKey, $relativePath) = explode('/', substr($defaultLabels['tabs_images']['tab'], 4), 2);
281  $defaultLabels['tabs_images']['tab'] = ExtensionManagementUtility::extPath($extensionKey) . $relativePath;
282  } else {
283  $defaultLabels['tabs_images']['tab'] = $fullPath . '/' . $defaultLabels['tabs_images']['tab'];
284  }
285  }
286 
287  $defaultLabels['tabs_images']['tab'] = $this->getRelativePath(PATH_typo3, $defaultLabels['tabs_images']['tab']);
288 
289  // Finally, setting the icon with correct path:
290  if (substr($defaultLabels['tabs_images']['tab'], 0, 3) === '../') {
291  $defaultLabels['tabs_images']['tab'] = PATH_site . substr($defaultLabels['tabs_images']['tab'], 3);
292  } else {
293  $defaultLabels['tabs_images']['tab'] = PATH_typo3 . $defaultLabels['tabs_images']['tab'];
294  }
295  }
296 
297  // If LOCAL_LANG references are used for labels of the module:
298  if ($defaultLabels['ll_ref']) {
299  // Now the 'default' key is loaded with the CURRENT language - not the english translation...
300  $defaultLabels['labels']['tablabel'] = $lang->sL($defaultLabels['ll_ref'] . ':mlang_labels_tablabel');
301  $defaultLabels['labels']['tabdescr'] = $lang->sL($defaultLabels['ll_ref'] . ':mlang_labels_tabdescr');
302  $defaultLabels['tabs']['tab'] = $lang->sL($defaultLabels['ll_ref'] . ':mlang_tabs_tab');
303  $lang->addModuleLabels($defaultLabels, $name . '_');
304  } else {
305  // ... otherwise use the old way:
306  $lang->addModuleLabels($defaultLabels, $name . '_');
307  $lang->addModuleLabels($setupInformation['labels'][$lang->lang], $name . '_');
308  }
309  }
310 
311  // Default script setup
312  if ($setupInformation['configuration']['script'] === '_DISPATCH' || isset($setupInformation['configuration']['routeTarget'])) {
313  if ($setupInformation['configuration']['extbase']) {
314  $finalModuleConfiguration['script'] = BackendUtility::getModuleUrl('Tx_' . $name);
315  } else {
316  // just go through BackendModuleRequestHandler where the routeTarget is resolved
317  $finalModuleConfiguration['script'] = BackendUtility::getModuleUrl($name);
318  }
319  } elseif ($setupInformation['configuration']['script'] && file_exists($setupInformation['path'] . '/' . $setupInformation['configuration']['script'])) {
320  GeneralUtility::deprecationLog('Loading module "' . $name . '" as a standalone script. Script-based modules are deprecated since TYPO3 CMS 7. Support will be removed with TYPO3 CMS 8, use the "routeTarget" option or dispatched modules instead.');
321  $finalModuleConfiguration['script'] = $this->getRelativePath(PATH_typo3, $fullPath . '/' . $setupInformation['configuration']['script']);
322  } else {
323  $finalModuleConfiguration['script'] = BackendUtility::getModuleUrl('dummy');
324  }
325 
326  if (!empty($setupInformation['configuration']['navigationFrameModule'])) {
327  $finalModuleConfiguration['navFrameScript'] = BackendUtility::getModuleUrl(
328  $setupInformation['configuration']['navigationFrameModule'],
329  !empty($setupInformation['configuration']['navigationFrameModuleParameters'])
330  ? $setupInformation['configuration']['navigationFrameModuleParameters']
331  : array()
332  );
333  } elseif (!empty($setupInformation['configuration']['navFrameScript'])) {
334  GeneralUtility::deprecationLog('Loading navFrameScript "' . $setupInformation['configuration']['navFrameScript'] . '" as a standalone script. Script-based navigation frames are deprecated since TYPO3 CMS 7. Support will be removed with TYPO3 CMS 8, use "navigationFrameModule" option or the "navigationComponentId" option instead.');
335  // Navigation Frame Script (GET params could be added)
336  $navFrameScript = explode('?', $setupInformation['configuration']['navFrameScript']);
337  $navFrameScript = $navFrameScript[0];
338  if (file_exists($setupInformation['path'] . '/' . $navFrameScript)) {
339  $finalModuleConfiguration['navFrameScript'] = $this->getRelativePath(PATH_typo3, $fullPath . '/' . $setupInformation['configuration']['navFrameScript']);
340  }
341  // Additional params for Navigation Frame Script: "&anyParam=value&moreParam=1"
342  if ($setupInformation['configuration']['navFrameScriptParam']) {
343  $finalModuleConfiguration['navFrameScriptParam'] = $setupInformation['configuration']['navFrameScriptParam'];
344  }
345  }
346 
347  // Check if this is a submodule
348  $mainModule = '';
349  if (strpos($name, '_') !== false) {
350  list($mainModule, ) = explode('_', $name, 2);
351  }
352 
353  // check if there is a navigation component (like the pagetree)
354  if (is_array($this->navigationComponents[$name])) {
355  $finalModuleConfiguration['navigationComponentId'] = $this->navigationComponents[$name]['componentId'];
356  // navigation component can be overriden by the main module component
357  } elseif ($mainModule && is_array($this->navigationComponents[$mainModule]) && $setupInformation['configuration']['inheritNavigationComponentFromMainModule'] !== false) {
358  $finalModuleConfiguration['navigationComponentId'] = $this->navigationComponents[$mainModule]['componentId'];
359  }
360  return $finalModuleConfiguration;
361  }
362 
371  protected function getModuleSetupInformation($moduleName, $pathToModuleDirectory)
372  {
373 
374  // Because 'path/../path' does not work
375  $path = preg_replace('/\\/[^\\/.]+\\/\\.\\.\\//', '/', $pathToModuleDirectory);
376 
377  $moduleSetupInformation = array(
378  'configuration' => array(),
379  'labels' => array(),
380  'path' => $path
381  );
382 
383  if (@is_dir($path) && file_exists($path . '/conf.php')) {
384  $MCONF = array();
385  $MLANG = array();
386 
387  // The conf-file is included. This must be valid PHP.
388  include $path . '/conf.php';
389 
390  // Move the global variables defined in conf.php into the local method
391  if (is_array($MCONF)) {
392  $moduleSetupInformation['configuration'] = $MCONF;
393  } else {
394  $moduleSetupInformation['configuration'] = array();
395  }
396  $moduleSetupInformation['labels'] = $MLANG;
397  }
398 
399  $moduleConfiguration = !empty($GLOBALS['TBE_MODULES']['_configuration'][$moduleName])
400  ? $GLOBALS['TBE_MODULES']['_configuration'][$moduleName]
401  : null;
402  if ($moduleConfiguration !== null) {
403  // Overlay setup with additional labels
404  if (!empty($moduleConfiguration['labels']) && is_array($moduleConfiguration['labels'])) {
405  if (empty($moduleSetupInformation['labels']['default']) || !is_array($moduleSetupInformation['labels']['default'])) {
406  $moduleSetupInformation['labels']['default'] = $moduleConfiguration['labels'];
407  } else {
408  $moduleSetupInformation['labels']['default'] = array_replace_recursive($moduleSetupInformation['labels']['default'], $moduleConfiguration['labels']);
409  }
410  unset($moduleConfiguration['labels']);
411  }
412  // Overlay setup with additional configuration
413  if (is_array($moduleConfiguration)) {
414  $moduleSetupInformation['configuration'] = array_replace_recursive($moduleSetupInformation['configuration'], $moduleConfiguration);
415  }
416  }
417 
418  // Add some default configuration
419  if (!isset($moduleSetupInformation['configuration']['inheritNavigationComponentFromMainModule'])) {
420  $moduleSetupInformation['configuration']['inheritNavigationComponentFromMainModule'] = true;
421  }
422 
423  return $moduleSetupInformation;
424  }
425 
433  public function checkModAccess($name, $MCONF)
434  {
435  if (empty($MCONF['access'])) {
436  return true;
437  }
438  $access = strtolower($MCONF['access']);
439  // Checking if admin-access is required
440  // If admin-permissions is required then return TRUE if user is admin
441  if (strpos($access, 'admin') !== false && $this->BE_USER->isAdmin()) {
442  return true;
443  }
444  // This will add modules to the select-lists of user and groups
445  if (strpos($access, 'user') !== false) {
446  $this->modListUser[] = $name;
447  }
448  if (strpos($access, 'group') !== false) {
449  $this->modListGroup[] = $name;
450  }
451  // This checks if a user is permitted to access the module
452  if ($this->BE_USER->isAdmin() || $this->BE_USER->check('modules', $name)) {
453  return true;
454  }
455  return false;
456  }
457 
466  public function checkModWorkspace($name, $MCONF)
467  {
468  if (!$this->observeWorkspaces) {
469  return true;
470  }
471  $status = true;
472  if (!empty($MCONF['workspaces'])) {
473  $status = $this->BE_USER->workspace === 0 && GeneralUtility::inList($MCONF['workspaces'], 'online')
474  || $this->BE_USER->workspace === -1 && GeneralUtility::inList($MCONF['workspaces'], 'offline')
475  || $this->BE_USER->workspace > 0 && GeneralUtility::inList($MCONF['workspaces'], 'custom');
476  } elseif ($this->BE_USER->workspace === -99) {
477  $status = false;
478  }
479  return $status;
480  }
481 
489  public function parseModulesArray($arr)
490  {
491  $theMods = array();
492  if (is_array($arr)) {
493  foreach ($arr as $mod => $subs) {
494  // Clean module name to alphanum
495  $mod = $this->cleanName($mod);
496  if ($mod) {
497  if ($subs) {
498  $subsArr = GeneralUtility::trimExplode(',', $subs);
499  foreach ($subsArr as $subMod) {
500  $subMod = $this->cleanName($subMod);
501  if ($subMod) {
502  $theMods[$mod][] = $subMod;
503  }
504  }
505  } else {
506  $theMods[$mod] = 1;
507  }
508  }
509  }
510  }
511  return $theMods;
512  }
513 
521  public function cleanName($str)
522  {
523  return preg_replace('/[^a-z0-9]/i', '', $str);
524  }
525 
533  public function getRelativePath($baseDir, $destDir)
534  {
535  // A special case, the dirs are equal
536  if ($baseDir === $destDir) {
537  return './';
538  }
539  // Remove beginning
540  $baseDir = ltrim($baseDir, '/');
541  $destDir = ltrim($destDir, '/');
542  $found = true;
543  do {
544  $slash_pos = strpos($destDir, '/');
545  if ($slash_pos !== false && substr($destDir, 0, $slash_pos) == substr($baseDir, 0, $slash_pos)) {
546  $baseDir = substr($baseDir, $slash_pos + 1);
547  $destDir = substr($destDir, $slash_pos + 1);
548  } else {
549  $found = false;
550  }
551  } while ($found);
552  $slashes = strlen($baseDir) - strlen(str_replace('/', '', $baseDir));
553  for ($i = 0; $i < $slashes; $i++) {
554  $destDir = '../' . $destDir;
555  }
556  return GeneralUtility::resolveBackPath($destDir);
557  }
558 
562  protected function getLanguageService()
563  {
564  return $GLOBALS['LANG'];
565  }
566 }