TYPO3 CMS  TYPO3_8-7
ExtensionManagementUtility.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Core\Utility;
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 
22 
30 {
34  protected static $extensionKeyMap;
35 
46  protected static $extTablesWasReadFromCacheOnce = false;
47 
51  protected static $packageManager;
52 
61  {
62  static::$packageManager = $packageManager;
63  }
64 
68  protected static $cacheManager;
69 
75  protected static function getCacheManager()
76  {
77  if (static::$cacheManager === null) {
78  static::$cacheManager = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Cache\CacheManager::class);
79  }
80  return static::$cacheManager;
81  }
82 
86  protected static $signalSlotDispatcher;
87 
93  protected static function getSignalSlotDispatcher()
94  {
95  if (static::$signalSlotDispatcher === null) {
96  static::$signalSlotDispatcher = GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher::class);
97  }
99  }
100 
101  /**************************************
102  *
103  * PATHS and other evaluation
104  *
105  ***************************************/
114  public static function isLoaded($key, $exitOnError = false)
115  {
116  $isLoaded = static::$packageManager->isPackageActive($key);
117  if ($exitOnError && !$isLoaded) {
118  throw new \BadFunctionCallException('TYPO3 Fatal Error: Extension "' . $key . '" is not loaded!', 1270853910);
119  }
120  return $isLoaded;
121  }
122 
131  public static function extPath($key, $script = '')
132  {
133  if (!static::$packageManager->isPackageActive($key)) {
134  throw new \BadFunctionCallException('TYPO3 Fatal Error: Extension key "' . $key . '" is NOT loaded!', 1365429656);
135  }
136  return static::$packageManager->getPackage($key)->getPackagePath() . $script;
137  }
138 
150  public static function extRelPath($key)
151  {
153  if (!static::$packageManager->isPackageActive($key)) {
154  throw new \BadFunctionCallException('TYPO3 Fatal Error: Extension key "' . $key . '" is NOT loaded!', 1365429673);
155  }
156  $relativePathToSiteRoot = self::siteRelPath($key);
157  $typo3MainDirLength = strlen(TYPO3_mainDir);
158  if (substr($relativePathToSiteRoot, 0, $typo3MainDirLength) === TYPO3_mainDir) {
159  $relativePathToSiteRoot = substr($relativePathToSiteRoot, $typo3MainDirLength);
160  } else {
161  $relativePathToSiteRoot = '../' . $relativePathToSiteRoot;
162  }
163  return $relativePathToSiteRoot;
164  }
165 
174  public static function siteRelPath($key)
175  {
176  return PathUtility::stripPathSitePrefix(self::extPath($key));
177  }
178 
186  public static function getCN($key)
187  {
188  return strpos($key, 'user_') === 0 ? 'user_' . str_replace('_', '', substr($key, 5)) : 'tx_' . str_replace('_', '', $key);
189  }
190 
197  public static function getExtensionKeyByPrefix($prefix)
198  {
199  $result = false;
200  // Build map of short keys referencing to real keys:
201  if (!isset(self::$extensionKeyMap)) {
202  self::$extensionKeyMap = [];
203  foreach (static::$packageManager->getActivePackages() as $package) {
204  $shortKey = str_replace('_', '', $package->getPackageKey());
205  self::$extensionKeyMap[$shortKey] = $package->getPackageKey();
206  }
207  }
208  // Lookup by the given short key:
209  $parts = explode('_', $prefix);
210  if (isset(self::$extensionKeyMap[$parts[1]])) {
211  $result = self::$extensionKeyMap[$parts[1]];
212  }
213  return $result;
214  }
215 
219  public static function clearExtensionKeyMap()
220  {
221  self::$extensionKeyMap = null;
222  }
223 
234  public static function getExtensionVersion($key)
235  {
236  if (!is_string($key) || empty($key)) {
237  throw new \InvalidArgumentException('Extension key must be a non-empty string.', 1294586096);
238  }
239  if (!static::isLoaded($key)) {
240  return '';
241  }
242  $version = static::$packageManager->getPackage($key)->getPackageMetaData()->getVersion();
243  if (empty($version)) {
244  throw new \TYPO3\CMS\Core\Package\Exception('Version number in composer manifest of package "' . $key . '" is missing or invalid', 1395614959);
245  }
246  return $version;
247  }
248 
249  /**************************************
250  *
251  * Adding BACKEND features
252  * (related to core features)
253  *
254  ***************************************/
265  public static function addTCAcolumns($table, $columnArray)
266  {
267  if (is_array($columnArray) && is_array($GLOBALS['TCA'][$table]) && is_array($GLOBALS['TCA'][$table]['columns'])) {
268  // Candidate for array_merge() if integer-keys will some day make trouble...
269  $GLOBALS['TCA'][$table]['columns'] = array_merge($GLOBALS['TCA'][$table]['columns'], $columnArray);
270  }
271  }
272 
286  public static function addToAllTCAtypes($table, $newFieldsString, $typeList = '', $position = '')
287  {
288  $newFieldsString = trim($newFieldsString);
289  if ($newFieldsString === '' || !is_array($GLOBALS['TCA'][$table]['types'])) {
290  return;
291  }
292  list($positionIdentifier, $entityName) = GeneralUtility::trimExplode(':', $position);
293  $palettesChanged = [];
294 
295  foreach ($GLOBALS['TCA'][$table]['types'] as $type => &$typeDetails) {
296  // skip if we don't want to add the field for this type
297  if ($typeList !== '' && !GeneralUtility::inList($typeList, $type)) {
298  continue;
299  }
300  // skip if fields were already added
301  if (!isset($typeDetails['showitem'])) {
302  continue;
303  }
304 
305  $fieldArray = GeneralUtility::trimExplode(',', $typeDetails['showitem'], true);
306  if (in_array($newFieldsString, $fieldArray, true)) {
307  continue;
308  }
309 
310  $fieldExists = false;
311  $newPosition = '';
312  if (is_array($GLOBALS['TCA'][$table]['palettes'] ?? false)) {
313  // Get the palette names used in current showitem
314  $paletteCount = preg_match_all('/(?:^|,) # Line start or a comma
315  (?:
316  \\s*\\-\\-palette\\-\\-;[^;]*;([^,$]*)| # --palette--;label;paletteName
317  \\s*\\b[^;,]+\\b(?:;[^;]*;([^;,]+))?[^,]* # field;label;paletteName
318  )/x', $typeDetails['showitem'], $paletteMatches);
319  if ($paletteCount > 0) {
320  $paletteNames = array_filter(array_merge($paletteMatches[1], $paletteMatches[2]));
321  if (!empty($paletteNames)) {
322  foreach ($paletteNames as $paletteName) {
323  if (!isset($GLOBALS['TCA'][$table]['palettes'][$paletteName])) {
324  continue;
325  }
326  $palette = $GLOBALS['TCA'][$table]['palettes'][$paletteName];
327  switch ($positionIdentifier) {
328  case 'after':
329  case 'before':
330  if (preg_match('/\\b' . $entityName . '\\b/', $palette['showitem']) > 0) {
331  $newPosition = $positionIdentifier . ':--palette--;;' . $paletteName;
332  }
333  break;
334  case 'replace':
335  // check if fields have been added to palette before
336  if (isset($palettesChanged[$paletteName])) {
337  $fieldExists = true;
338  continue 2;
339  }
340  if (preg_match('/\\b' . $entityName . '\\b/', $palette['showitem']) > 0) {
341  self::addFieldsToPalette($table, $paletteName, $newFieldsString, $position);
342  // Memorize that we already changed this palette, in case other types also use it
343  $palettesChanged[$paletteName] = true;
344  $fieldExists = true;
345  continue 2;
346  }
347  break;
348  default:
349  // Intentionally left blank
350  }
351  }
352  }
353  }
354  }
355  if ($fieldExists === false) {
356  $typeDetails['showitem'] = self::executePositionedStringInsertion(
357  $typeDetails['showitem'],
358  $newFieldsString,
359  $newPosition !== '' ? $newPosition : $position
360  );
361  }
362  }
363  unset($typeDetails);
364  }
365 
409  public static function addFieldsToAllPalettesOfField($table, $field, $addFields, $insertionPosition = '')
410  {
411  if (!isset($GLOBALS['TCA'][$table]['columns'][$field])) {
412  return;
413  }
414  if (!is_array($GLOBALS['TCA'][$table]['types'])) {
415  return;
416  }
417 
418  // Iterate through all types and search for the field that defines the palette to be extended
419  foreach ($GLOBALS['TCA'][$table]['types'] as $typeName => $typeArray) {
420  // Continue if types has no showitem at all or if requested field is not in it
421  if (!isset($typeArray['showitem']) || strpos($typeArray['showitem'], $field) === false) {
422  continue;
423  }
424  $fieldArrayWithOptions = GeneralUtility::trimExplode(',', $typeArray['showitem']);
425  // Find the field we're handling
426  $newFieldStringArray = [];
427  foreach ($fieldArrayWithOptions as $fieldNumber => $fieldString) {
428  $newFieldStringArray[] = $fieldString;
429  $fieldArray = GeneralUtility::trimExplode(';', $fieldString);
430  if ($fieldArray[0] !== $field) {
431  continue;
432  }
433  if (
434  isset($fieldArrayWithOptions[$fieldNumber + 1])
435  && strpos($fieldArrayWithOptions[$fieldNumber + 1], '--palette--') === 0
436  ) {
437  // Match for $field and next field is a palette - add fields to this one
438  $paletteName = GeneralUtility::trimExplode(';', $fieldArrayWithOptions[$fieldNumber + 1]);
439  $paletteName = $paletteName[2];
440  self::addFieldsToPalette($table, $paletteName, $addFields, $insertionPosition);
441  } else {
442  // Match for $field but next field is no palette - create a new one
443  $newPaletteName = 'generatedFor-' . $field;
444  self::addFieldsToPalette($table, 'generatedFor-' . $field, $addFields, $insertionPosition);
445  $newFieldStringArray[] = '--palette--;;' . $newPaletteName;
446  }
447  }
448  $GLOBALS['TCA'][$table]['types'][$typeName]['showitem'] = implode(', ', $newFieldStringArray);
449  }
450  }
451 
462  public static function addFieldsToPalette($table, $palette, $addFields, $insertionPosition = '')
463  {
464  if (isset($GLOBALS['TCA'][$table])) {
465  $paletteData = &$GLOBALS['TCA'][$table]['palettes'][$palette];
466  // If palette already exists, merge the data:
467  if (is_array($paletteData)) {
468  $paletteData['showitem'] = self::executePositionedStringInsertion($paletteData['showitem'], $addFields, $insertionPosition);
469  } else {
470  $paletteData['showitem'] = self::removeDuplicatesForInsertion($addFields);
471  }
472  }
473  }
474 
503  public static function addTcaSelectItem($table, $field, array $item, $relativeToField = '', $relativePosition = '')
504  {
505  if (!is_string($table)) {
506  throw new \InvalidArgumentException('Given table is of type "' . gettype($table) . '" but a string is expected.', 1303236963);
507  }
508  if (!is_string($field)) {
509  throw new \InvalidArgumentException('Given field is of type "' . gettype($field) . '" but a string is expected.', 1303236964);
510  }
511  if (!is_string($relativeToField)) {
512  throw new \InvalidArgumentException('Given relative field is of type "' . gettype($relativeToField) . '" but a string is expected.', 1303236965);
513  }
514  if (!is_string($relativePosition)) {
515  throw new \InvalidArgumentException('Given relative position is of type "' . gettype($relativePosition) . '" but a string is expected.', 1303236966);
516  }
517  if ($relativePosition !== '' && $relativePosition !== 'before' && $relativePosition !== 'after' && $relativePosition !== 'replace') {
518  throw new \InvalidArgumentException('Relative position must be either empty or one of "before", "after", "replace".', 1303236967);
519  }
520  if (!is_array($GLOBALS['TCA'][$table]['columns'][$field]['config']['items'])) {
521  throw new \RuntimeException('Given select field item list was not found.', 1303237468);
522  }
523  // Make sure item keys are integers
524  $GLOBALS['TCA'][$table]['columns'][$field]['config']['items'] = array_values($GLOBALS['TCA'][$table]['columns'][$field]['config']['items']);
525  if ($relativePosition !== '') {
526  // Insert at specified position
527  $matchedPosition = ArrayUtility::filterByValueRecursive($relativeToField, $GLOBALS['TCA'][$table]['columns'][$field]['config']['items']);
528  if (!empty($matchedPosition)) {
529  $relativeItemKey = key($matchedPosition);
530  if ($relativePosition === 'replace') {
531  $GLOBALS['TCA'][$table]['columns'][$field]['config']['items'][$relativeItemKey] = $item;
532  } else {
533  if ($relativePosition === 'before') {
534  $offset = $relativeItemKey;
535  } else {
536  $offset = $relativeItemKey + 1;
537  }
538  array_splice($GLOBALS['TCA'][$table]['columns'][$field]['config']['items'], $offset, 0, [0 => $item]);
539  }
540  } else {
541  // Insert at new item at the end of the array if relative position was not found
542  $GLOBALS['TCA'][$table]['columns'][$field]['config']['items'][] = $item;
543  }
544  } else {
545  // Insert at new item at the end of the array
546  $GLOBALS['TCA'][$table]['columns'][$field]['config']['items'][] = $item;
547  }
548  }
549 
560  public static function getFileFieldTCAConfig($fieldName, array $customSettingOverride = [], $allowedFileExtensions = '', $disallowedFileExtensions = '')
561  {
562  $fileFieldTCAConfig = [
563  'type' => 'inline',
564  'foreign_table' => 'sys_file_reference',
565  'foreign_field' => 'uid_foreign',
566  'foreign_sortby' => 'sorting_foreign',
567  'foreign_table_field' => 'tablenames',
568  'foreign_match_fields' => [
569  'fieldname' => $fieldName
570  ],
571  'foreign_label' => 'uid_local',
572  'foreign_selector' => 'uid_local',
573  'overrideChildTca' => [
574  'columns' => [
575  'uid_local' => [
576  'config' => [
577  'appearance' => [
578  'elementBrowserType' => 'file',
579  'elementBrowserAllowed' => $allowedFileExtensions
580  ],
581  ],
582  ],
583  ],
584  ],
585  'filter' => [
586  [
587  'userFunc' => \TYPO3\CMS\Core\Resource\Filter\FileExtensionFilter::class . '->filterInlineChildren',
588  'parameters' => [
589  'allowedFileExtensions' => $allowedFileExtensions,
590  'disallowedFileExtensions' => $disallowedFileExtensions
591  ]
592  ]
593  ],
594  'appearance' => [
595  'useSortable' => true,
596  'headerThumbnail' => [
597  'field' => 'uid_local',
598  'width' => '45',
599  'height' => '45c',
600  ],
601 
602  'enabledControls' => [
603  'info' => true,
604  'new' => false,
605  'dragdrop' => true,
606  'sort' => false,
607  'hide' => true,
608  'delete' => true,
609  ],
610  ],
611  'behaviour' => [
612  'localizeChildrenAtParentLocalization' => true,
613  ],
614  ];
615  ArrayUtility::mergeRecursiveWithOverrule($fileFieldTCAConfig, $customSettingOverride);
616  return $fileFieldTCAConfig;
617  }
618 
627  public static function addFieldsToUserSettings($addFields, $insertionPosition = '')
628  {
629  $GLOBALS['TYPO3_USER_SETTINGS']['showitem'] = self::executePositionedStringInsertion($GLOBALS['TYPO3_USER_SETTINGS']['showitem'], $addFields, $insertionPosition);
630  }
631 
649  protected static function executePositionedStringInsertion($list, $insertionList, $insertionPosition = '')
650  {
651  $list = $newList = trim($list, ", \t\n\r\0\x0B");
652 
653  list($location, $positionName) = GeneralUtility::trimExplode(':', $insertionPosition, false, 2);
654 
655  if ($location !== 'replace') {
656  $insertionList = self::removeDuplicatesForInsertion($insertionList, $list);
657  }
658 
659  if ($insertionList === '') {
660  return $list;
661  }
662  if ($list === '') {
663  return $insertionList;
664  }
665  if ($insertionPosition === '') {
666  return $list . ', ' . $insertionList;
667  }
668 
669  // The $insertPosition may be a palette: after:--palette--;;title
670  // In the $list the palette may contain a LLL string in between the ;;
671  // Adjust the regex to match that
672  $positionName = preg_quote($positionName, '/');
673  if (strpos($positionName, ';;') !== false) {
674  $positionName = str_replace(';;', ';[^;]*;', $positionName);
675  }
676 
677  $pattern = ('/(^|,\\s*)(' . $positionName . ')(;[^,$]+)?(,|$)/');
678  switch ($location) {
679  case 'after':
680  $newList = preg_replace($pattern, '$1$2$3, ' . $insertionList . '$4', $list);
681  break;
682  case 'before':
683  $newList = preg_replace($pattern, '$1' . $insertionList . ', $2$3$4', $list);
684  break;
685  case 'replace':
686  $newList = preg_replace($pattern, '$1' . $insertionList . '$4', $list);
687  break;
688  default:
689  }
690 
691  // When preg_replace did not replace anything; append the $insertionList.
692  if ($list === $newList) {
693  return $list . ', ' . $insertionList;
694  }
695  return $newList;
696  }
697 
713  protected static function removeDuplicatesForInsertion($insertionList, $list = '')
714  {
715  $insertionListParts = preg_split('/\\s*,\\s*/', $insertionList);
716  $listMatches = [];
717  if ($list !== '') {
718  preg_match_all('/(?:^|,)\\s*\\b([^;,]+)\\b[^,]*/', $list, $listMatches);
719  $listMatches = $listMatches[1];
720  }
721 
722  $cleanInsertionListParts = [];
723  foreach ($insertionListParts as $fieldName) {
724  $fieldNameParts = explode(';', $fieldName, 2);
725  $cleanFieldName = $fieldNameParts[0];
726  if (
727  $cleanFieldName === '--linebreak--'
728  || (
729  !in_array($cleanFieldName, $cleanInsertionListParts, true)
730  && !in_array($cleanFieldName, $listMatches, true)
731  )
732  ) {
733  $cleanInsertionListParts[] = $fieldName;
734  }
735  }
736  return implode(', ', $cleanInsertionListParts);
737  }
738 
745  protected static function explodeItemList($itemList)
746  {
747  $items = [];
748  $itemParts = GeneralUtility::trimExplode(',', $itemList, true);
749  foreach ($itemParts as $itemPart) {
750  $itemDetails = GeneralUtility::trimExplode(';', $itemPart, false, 5);
751  $key = $itemDetails[0];
752  if (strpos($key, '--') !== false) {
753  // If $key is a separator (--div--) or palette (--palette--) then it will be appended by a unique number. This must be removed again when using this value!
754  $key .= count($items);
755  }
756  if (!isset($items[$key])) {
757  $items[$key] = [
758  'rawData' => $itemPart,
759  'details' => []
760  ];
761  $details = [0 => 'field', 1 => 'label', 2 => 'palette'];
762  foreach ($details as $id => $property) {
763  $items[$key]['details'][$property] = isset($itemDetails[$id]) ? $itemDetails[$id] : '';
764  }
765  }
766  }
767  return $items;
768  }
769 
778  protected static function generateItemList(array $items, $useRawData = false)
779  {
780  $itemParts = [];
781  foreach ($items as $item => $itemDetails) {
782  if (strpos($item, '--') !== false) {
783  // If $item is a separator (--div--) or palette (--palette--) then it may have been appended by a unique number. This must be stripped away here.
784  $item = str_replace([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], '', $item);
785  }
786  if ($useRawData) {
787  $itemParts[] = $itemDetails['rawData'];
788  } else {
789  if (count($itemDetails['details']) > 1) {
790  $details = ['palette', 'label', 'field'];
791  $elements = [];
792  $addEmpty = false;
793  foreach ($details as $property) {
794  if ($itemDetails['details'][$property] !== '' || $addEmpty) {
795  $addEmpty = true;
796  array_unshift($elements, $itemDetails['details'][$property]);
797  }
798  }
799  $item = implode(';', $elements);
800  }
801  $itemParts[] = $item;
802  }
803  }
804  return implode(', ', $itemParts);
805  }
806 
814  public static function allowTableOnStandardPages($table)
815  {
816  $GLOBALS['PAGES_TYPES']['default']['allowedTables'] .= ',' . $table;
817  }
818 
831  public static function addExtJSModule($extensionName, $mainModuleName, $subModuleName = '', $position = '', array $moduleConfiguration = [])
832  {
834  if (empty($extensionName)) {
835  throw new \InvalidArgumentException('The extension name must not be empty', 1325938973);
836  }
837  $extensionName = str_replace(' ', '', ucwords(str_replace('_', ' ', $extensionName)));
838  $defaultModuleConfiguration = [
839  'access' => 'admin',
840  'icon' => 'EXT:backend/Resources/Public/Images/Logo.png',
841  'labels' => '',
842  ];
843  // Add mandatory parameter to use new pagetree
844  if ($mainModuleName === 'web') {
845  $defaultModuleConfiguration['navigationComponentId'] = 'typo3-pagetree';
846  }
847  ArrayUtility::mergeRecursiveWithOverrule($defaultModuleConfiguration, $moduleConfiguration);
848  $moduleConfiguration = $defaultModuleConfiguration;
849  if ($subModuleName !== '') {
850  $moduleSignature = $mainModuleName . '_' . $subModuleName;
851  } else {
852  $moduleSignature = $mainModuleName;
853  }
854  $moduleConfiguration['name'] = $moduleSignature;
855  $moduleConfiguration['script'] = 'extjspaneldummy.html';
856  $moduleConfiguration['extensionName'] = $extensionName;
857  $moduleConfiguration['configureModuleFunction'] = [self::class, 'configureModule'];
858  $GLOBALS['TBE_MODULES']['_configuration'][$moduleSignature] = $moduleConfiguration;
859  self::addModule($mainModuleName, $subModuleName, $position);
860  }
861 
869  public static function configureModule($moduleSignature)
870  {
871  $moduleConfiguration = $GLOBALS['TBE_MODULES']['_configuration'][$moduleSignature];
872  if (!empty($moduleConfiguration['labels']['tabs_images']['tab'])) {
873  GeneralUtility::deprecationLog('Module registration for backend module "' . $moduleSignature . '" uses old referencing for the icon. Use the configuration option "icon" directly instead of [labels][tabs_images][tab]. The old option is removed with TYPO3 v9.');
874  $moduleConfiguration['icon'] = $moduleConfiguration['labels']['tabs_images']['tab'];
875  unset($moduleConfiguration['labels']['tabs_images']['tab']);
876  }
877 
878  // Register the icon and move it too "iconIdentifier"
879  if (!empty($moduleConfiguration['icon'])) {
880  $iconRegistry = GeneralUtility::makeInstance(IconRegistry::class);
881  $iconIdentifier = 'module-' . $moduleSignature;
882  $iconProvider = $iconRegistry->detectIconProvider($moduleConfiguration['icon']);
883  $iconRegistry->registerIcon(
884  $iconIdentifier,
885  $iconProvider,
886  [ 'source' => GeneralUtility::getFileAbsFileName($moduleConfiguration['icon']) ]
887  );
888  $moduleConfiguration['iconIdentifier'] = $iconIdentifier;
889  unset($moduleConfiguration['icon']);
890  }
891 
892  return $moduleConfiguration;
893  }
894 
905  public static function addModule($main, $sub = '', $position = '', $path = null, $moduleConfiguration = [])
906  {
907  // If there is already a main module by this name:
908  // Adding the submodule to the correct position:
909  if (isset($GLOBALS['TBE_MODULES'][$main]) && $sub) {
910  list($place, $modRef) = GeneralUtility::trimExplode(':', $position, true);
911  $modules = ',' . $GLOBALS['TBE_MODULES'][$main] . ',';
912  if ($place === null || ($modRef !== null && !GeneralUtility::inList($modules, $modRef))) {
913  $place = 'bottom';
914  }
915  $modRef = ',' . $modRef . ',';
916  if (!GeneralUtility::inList($modules, $sub)) {
917  switch (strtolower($place)) {
918  case 'after':
919  $modules = str_replace($modRef, $modRef . $sub . ',', $modules);
920  break;
921  case 'before':
922  $modules = str_replace($modRef, ',' . $sub . $modRef, $modules);
923  break;
924  case 'top':
925  $modules = $sub . $modules;
926  break;
927  case 'bottom':
928  default:
929  $modules = $modules . $sub;
930  }
931  }
932  // Re-inserting the submodule list:
933  $GLOBALS['TBE_MODULES'][$main] = trim($modules, ',');
934  } else {
935  // Create new main modules with only one submodule, $sub (or none if $sub is blank)
936  $GLOBALS['TBE_MODULES'][$main] = $sub;
937  }
938 
939  // add additional configuration
940  if (is_array($moduleConfiguration) && !empty($moduleConfiguration)) {
941  $fullModuleSignature = $main . ($sub ? '_' . $sub : '');
942 
943  if (!empty($moduleConfiguration['labels']['tabs_images']['tab'])) {
944  GeneralUtility::deprecationLog('Module registration for module "' . $fullModuleSignature . '" uses old referencing for the icon. Use the configuration option "icon" directly instead of [labels][tabs_images][tab]. The old option is removed with TYPO3 v9.');
945  $moduleConfiguration['icon'] = $moduleConfiguration['labels']['tabs_images']['tab'];
946  unset($moduleConfiguration['labels']['tabs_images']['tab']);
947  }
948 
949  if (!empty($moduleConfiguration['icon'])) {
950  $iconRegistry = GeneralUtility::makeInstance(IconRegistry::class);
951  $iconIdentifier = 'module-' . $fullModuleSignature;
952  $iconProvider = $iconRegistry->detectIconProvider($moduleConfiguration['icon']);
953  $iconRegistry->registerIcon(
954  $iconIdentifier,
955  $iconProvider,
956  [ 'source' => GeneralUtility::getFileAbsFileName($moduleConfiguration['icon']) ]
957  );
958  $moduleConfiguration['iconIdentifier'] = $iconIdentifier;
959  unset($moduleConfiguration['icon']);
960  }
961 
962  $GLOBALS['TBE_MODULES']['_configuration'][$fullModuleSignature] = $moduleConfiguration;
963  }
964  }
965 
975  public static function registerExtDirectComponent($endpointName, $callbackClass, $moduleName = null, $accessLevel = null)
976  {
977  $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ExtDirect'][$endpointName] = [
978  'callbackClass' => $callbackClass,
979  'moduleName' => $moduleName,
980  'accessLevel' => $accessLevel
981  ];
982  }
983 
992  public static function registerAjaxHandler($ajaxId, $callbackMethod, $csrfTokenCheck = true)
993  {
995  $GLOBALS['TYPO3_CONF_VARS']['BE']['AJAX'][$ajaxId] = [
996  'callbackMethod' => $callbackMethod,
997  'csrfTokenCheck' => $csrfTokenCheck
998  ];
999  }
1000 
1015  public static function insertModuleFunction($modname, $className, $classPath = null, $title, $MM_key = 'function', $WS = '')
1016  {
1017  $GLOBALS['TBE_MODULES_EXT'][$modname]['MOD_MENU'][$MM_key][$className] = [
1018  'name' => $className,
1019  'title' => $title,
1020  'ws' => $WS
1021  ];
1022  }
1023 
1035  public static function appendToTypoConfVars($group, $key, $content)
1036  {
1038  $GLOBALS['TYPO3_CONF_VARS_extensionAdded'][$group][$key] .= $content;
1039  $GLOBALS['TYPO3_CONF_VARS'][$group][$key] .= $content;
1040  }
1041 
1049  public static function addPageTSConfig($content)
1050  {
1051  $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultPageTSconfig'] .= '
1052 [GLOBAL]
1053 ' . $content;
1054  }
1055 
1063  public static function addUserTSConfig($content)
1064  {
1065  $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultUserTSconfig'] .= '
1066 [GLOBAL]
1067 ' . $content;
1068  }
1069 
1078  public static function addLLrefForTCAdescr($tca_descr_key, $file_ref)
1079  {
1080  if ($tca_descr_key) {
1081  if (!is_array($GLOBALS['TCA_DESCR'][$tca_descr_key] ?? false)) {
1082  $GLOBALS['TCA_DESCR'][$tca_descr_key] = [];
1083  }
1084  if (!is_array($GLOBALS['TCA_DESCR'][$tca_descr_key]['refs'] ?? false)) {
1085  $GLOBALS['TCA_DESCR'][$tca_descr_key]['refs'] = [];
1086  }
1087  $GLOBALS['TCA_DESCR'][$tca_descr_key]['refs'][] = $file_ref;
1088  }
1089  }
1090 
1099  public static function addNavigationComponent($module, $componentId, $extensionKey = null)
1100  {
1101  $extensionKey = $extensionKey ?: $GLOBALS['_EXTKEY'];
1102  if (!isset($extensionKey)) {
1103  throw new \RuntimeException('No extensionKey set in addNavigationComponent(). Provide it as third Parameter', 1404068039);
1104  }
1105  $GLOBALS['TBE_MODULES']['_navigationComponents'][$module] = [
1106  'componentId' => $componentId,
1107  'extKey' => $extensionKey,
1108  'isCoreComponent' => false
1109  ];
1110  }
1111 
1118  public static function addCoreNavigationComponent($module, $componentId)
1119  {
1120  self::addNavigationComponent($module, $componentId);
1121  $GLOBALS['TBE_MODULES']['_navigationComponents'][$module]['isCoreComponent'] = true;
1122  }
1123 
1124  /**************************************
1125  *
1126  * Adding SERVICES features
1127  *
1128  ***************************************/
1137  public static function addService($extKey, $serviceType, $serviceKey, $info)
1138  {
1139  if ($serviceType && is_array($info)) {
1140  $info['priority'] = max(0, min(100, $info['priority']));
1141  $GLOBALS['T3_SERVICES'][$serviceType][$serviceKey] = $info;
1142  $GLOBALS['T3_SERVICES'][$serviceType][$serviceKey]['extKey'] = $extKey;
1143  $GLOBALS['T3_SERVICES'][$serviceType][$serviceKey]['serviceKey'] = $serviceKey;
1144  $GLOBALS['T3_SERVICES'][$serviceType][$serviceKey]['serviceType'] = $serviceType;
1145  // Change the priority (and other values) from $GLOBALS['TYPO3_CONF_VARS']
1146  // $GLOBALS['TYPO3_CONF_VARS']['T3_SERVICES'][$serviceType][$serviceKey]['priority']
1147  // even the activation is possible (a unix service might be possible on windows for some reasons)
1148  if (is_array($GLOBALS['TYPO3_CONF_VARS']['T3_SERVICES'][$serviceType][$serviceKey])) {
1149  // No check is done here - there might be configuration values only the service type knows about, so
1150  // we pass everything
1151  $GLOBALS['T3_SERVICES'][$serviceType][$serviceKey] = array_merge($GLOBALS['T3_SERVICES'][$serviceType][$serviceKey], $GLOBALS['TYPO3_CONF_VARS']['T3_SERVICES'][$serviceType][$serviceKey]);
1152  }
1153  // OS check
1154  // Empty $os means 'not limited to one OS', therefore a check is not needed
1155  if ($GLOBALS['T3_SERVICES'][$serviceType][$serviceKey]['available'] && $GLOBALS['T3_SERVICES'][$serviceType][$serviceKey]['os'] != '') {
1156  $os_type = TYPO3_OS === 'WIN' ? 'WIN' : 'UNIX';
1157  $os = GeneralUtility::trimExplode(',', strtoupper($GLOBALS['T3_SERVICES'][$serviceType][$serviceKey]['os']));
1158  if (!in_array($os_type, $os, true)) {
1159  self::deactivateService($serviceType, $serviceKey);
1160  }
1161  }
1162  // Convert subtype list to array for quicker access
1163  $GLOBALS['T3_SERVICES'][$serviceType][$serviceKey]['serviceSubTypes'] = [];
1164  $serviceSubTypes = GeneralUtility::trimExplode(',', $info['subtype']);
1165  foreach ($serviceSubTypes as $subtype) {
1166  $GLOBALS['T3_SERVICES'][$serviceType][$serviceKey]['serviceSubTypes'][$subtype] = $subtype;
1167  }
1168  }
1169  }
1170 
1179  public static function findService($serviceType, $serviceSubType = '', $excludeServiceKeys = [])
1180  {
1181  $serviceKey = false;
1182  $serviceInfo = false;
1183  $priority = 0;
1184  $quality = 0;
1185  if (!is_array($excludeServiceKeys)) {
1186  $excludeServiceKeys = GeneralUtility::trimExplode(',', $excludeServiceKeys, true);
1187  }
1188  if (is_array($GLOBALS['T3_SERVICES'][$serviceType])) {
1189  foreach ($GLOBALS['T3_SERVICES'][$serviceType] as $key => $info) {
1190  if (in_array($key, $excludeServiceKeys)) {
1191  continue;
1192  }
1193  // Select a subtype randomly
1194  // Useful to start a service by service key without knowing his subtypes - for testing purposes
1195  if ($serviceSubType === '*') {
1196  $serviceSubType = key($info['serviceSubTypes']);
1197  }
1198  // This matches empty subtype too
1199  if ($info['available'] && ($info['subtype'] == $serviceSubType || $info['serviceSubTypes'][$serviceSubType]) && $info['priority'] >= $priority) {
1200  // Has a lower quality than the already found, therefore we skip this service
1201  if ($info['priority'] == $priority && $info['quality'] < $quality) {
1202  continue;
1203  }
1204  // Check if the service is available
1205  $info['available'] = self::isServiceAvailable($serviceType, $key, $info);
1206  // Still available after exec check?
1207  if ($info['available']) {
1208  $serviceKey = $key;
1209  $priority = $info['priority'];
1210  $quality = $info['quality'];
1211  }
1212  }
1213  }
1214  }
1215  if ($serviceKey) {
1216  $serviceInfo = $GLOBALS['T3_SERVICES'][$serviceType][$serviceKey];
1217  }
1218  return $serviceInfo;
1219  }
1220 
1229  public static function findServiceByKey($serviceKey)
1230  {
1231  if (is_array($GLOBALS['T3_SERVICES'])) {
1232  // Loop on all service types
1233  // NOTE: we don't care about the actual type, we are looking for a specific key
1234  foreach ($GLOBALS['T3_SERVICES'] as $serviceType => $servicesPerType) {
1235  if (isset($servicesPerType[$serviceKey])) {
1236  $serviceDetails = $servicesPerType[$serviceKey];
1237  // Test if service is available
1238  if (self::isServiceAvailable($serviceType, $serviceKey, $serviceDetails)) {
1239  // We have found the right service, return its information
1240  return $serviceDetails;
1241  }
1242  }
1243  }
1244  }
1245  throw new \TYPO3\CMS\Core\Exception('Service not found for key: ' . $serviceKey, 1319217244);
1246  }
1247 
1256  public static function isServiceAvailable($serviceType, $serviceKey, $serviceDetails)
1257  {
1258  // If the service depends on external programs - check if they exists
1259  if (trim($serviceDetails['exec'])) {
1260  $executables = GeneralUtility::trimExplode(',', $serviceDetails['exec'], true);
1261  foreach ($executables as $executable) {
1262  // If at least one executable file is not available, exit early returning FALSE
1263  if (!CommandUtility::checkCommand($executable)) {
1264  self::deactivateService($serviceType, $serviceKey);
1265  return false;
1266  }
1267  }
1268  }
1269  // The service is available
1270  return true;
1271  }
1272 
1279  public static function deactivateService($serviceType, $serviceKey)
1280  {
1281  // ... maybe it's better to move non-available services to a different array??
1282  $GLOBALS['T3_SERVICES'][$serviceType][$serviceKey]['available'] = false;
1283  }
1284 
1285  /**************************************
1286  *
1287  * Adding FRONTEND features
1288  *
1289  ***************************************/
1302  public static function addPlugin($itemArray, $type = 'list_type', $extensionKey = null)
1303  {
1304  $extensionKey = $extensionKey ?: $GLOBALS['_EXTKEY'];
1305  if (!isset($extensionKey)) {
1306  throw new \RuntimeException(
1307  'No extension key could be determined when calling addPlugin()!'
1308  . LF
1309  . 'This method is meant to be called from an ext_tables.php or Configuration/TCA/Overrides file. '
1310  . 'If you call it from Configuration/TCA/Overrides, the extension key needs to be specified as third parameter. '
1311  . 'Calling it from any other place e.g. ext_localconf.php does not work and is not supported.',
1312  1404068038
1313  );
1314  }
1315  if ($extensionKey && !$itemArray[2] && isset($GLOBALS['TYPO3_LOADED_EXT'][$extensionKey]['ext_icon'])) {
1316  $itemArray[2] = 'EXT:' . $extensionKey . '/' . $GLOBALS['TYPO3_LOADED_EXT'][$extensionKey]['ext_icon'];
1317  }
1318  if (is_array($GLOBALS['TCA']['tt_content']['columns']) && is_array($GLOBALS['TCA']['tt_content']['columns'][$type]['config']['items'])) {
1319  foreach ($GLOBALS['TCA']['tt_content']['columns'][$type]['config']['items'] as $k => $v) {
1320  if ((string)$v[1] === (string)$itemArray[1]) {
1321  $GLOBALS['TCA']['tt_content']['columns'][$type]['config']['items'][$k] = $itemArray;
1322  return;
1323  }
1324  }
1325  $GLOBALS['TCA']['tt_content']['columns'][$type]['config']['items'][] = $itemArray;
1326  }
1327  }
1328 
1339  public static function addPiFlexFormValue($piKeyToMatch, $value, $CTypeToMatch = 'list')
1340  {
1341  if (is_array($GLOBALS['TCA']['tt_content']['columns']) && is_array($GLOBALS['TCA']['tt_content']['columns']['pi_flexform']['config']['ds'])) {
1342  $GLOBALS['TCA']['tt_content']['columns']['pi_flexform']['config']['ds'][$piKeyToMatch . ',' . $CTypeToMatch] = $value;
1343  }
1344  }
1345 
1355  public static function addToInsertRecords($table, $content_table = 'tt_content', $content_field = 'records')
1356  {
1357  if (is_array($GLOBALS['TCA'][$content_table]['columns']) && isset($GLOBALS['TCA'][$content_table]['columns'][$content_field]['config']['allowed'])) {
1358  $GLOBALS['TCA'][$content_table]['columns'][$content_field]['config']['allowed'] .= ',' . $table;
1359  }
1360  }
1361 
1388  public static function addPItoST43($key, $_, $suffix = '', $type = 'list_type', $cached = 0)
1389  {
1390  $cN = self::getCN($key);
1391  // General plugin
1392  $pluginContent = trim('
1393 plugin.' . $cN . $suffix . ' = USER' . ($cached ? '' : '_INT') . '
1394 plugin.' . $cN . $suffix . '.userFunc = ' . $cN . $suffix . '->main
1395 ');
1396  self::addTypoScript($key, 'setup', '
1397 # Setting ' . $key . ' plugin TypoScript
1398 ' . $pluginContent);
1399  // Add after defaultContentRendering
1400  switch ($type) {
1401  case 'list_type':
1402  $addLine = 'tt_content.list.20.' . $key . $suffix . ' = < plugin.' . $cN . $suffix;
1403  break;
1404  case 'menu_type':
1405  $addLine = 'tt_content.menu.20.' . $key . $suffix . ' = < plugin.' . $cN . $suffix;
1406  break;
1407  case 'CType':
1408  $addLine = trim('
1409 tt_content.' . $key . $suffix . ' =< lib.contentElement
1410 tt_content.' . $key . $suffix . ' {
1411  templateName = Generic
1412  20 =< plugin.' . $cN . $suffix . '
1413 }
1414 ');
1415  break;
1416  case 'header_layout':
1417  $addLine = 'lib.stdheader.10.' . $key . $suffix . ' = < plugin.' . $cN . $suffix;
1418  break;
1419  case 'includeLib':
1420  $addLine = 'page.1000 = < plugin.' . $cN . $suffix;
1421  break;
1422  default:
1423  $addLine = '';
1424  }
1425  if ($addLine) {
1426  self::addTypoScript($key, 'setup', '
1427 # Setting ' . $key . ' plugin TypoScript
1428 ' . $addLine . '
1429 ', 'defaultContentRendering');
1430  }
1431  }
1432 
1442  public static function addStaticFile($extKey, $path, $title)
1443  {
1444  if ($extKey && $path && is_array($GLOBALS['TCA']['sys_template']['columns'])) {
1445  $value = str_replace(',', '', 'EXT:' . $extKey . '/' . $path);
1446  $itemArray = [trim($title . ' (' . $extKey . ')'), $value];
1447  $GLOBALS['TCA']['sys_template']['columns']['include_static_file']['config']['items'][] = $itemArray;
1448  }
1449  }
1450 
1459  public static function registerPageTSConfigFile($extKey, $filePath, $title)
1460  {
1461  if (!$extKey) {
1462  throw new \InvalidArgumentException('No extension key given.', 1447789490);
1463  }
1464  if (!$filePath) {
1465  throw new \InvalidArgumentException('No file path given.', 1447789491);
1466  }
1467  if (!isset($GLOBALS['TCA']['pages']['columns']) || !is_array($GLOBALS['TCA']['pages']['columns'])) {
1468  throw new \InvalidArgumentException('No TCA definition for table "pages".', 1447789492);
1469  }
1470 
1471  $value = str_replace(',', '', 'EXT:' . $extKey . '/' . $filePath);
1472  $itemArray = [trim($title . ' (' . $extKey . ')'), $value];
1473  $GLOBALS['TCA']['pages']['columns']['tsconfig_includes']['config']['items'][] = $itemArray;
1474  }
1475 
1483  public static function addTypoScriptSetup($content)
1484  {
1485  $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_setup'] .= '
1486 [GLOBAL]
1487 ' . $content;
1488  }
1489 
1497  public static function addTypoScriptConstants($content)
1498  {
1499  $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_constants'] .= '
1500 [GLOBAL]
1501 ' . $content;
1502  }
1503 
1520  public static function addTypoScript($key, $type, $content, $afterStaticUid = 0)
1521  {
1522  if ($type === 'setup' || $type === 'constants') {
1523  $content = '
1524 
1525 [GLOBAL]
1526 #############################################
1527 ## TypoScript added by extension "' . $key . '"
1528 #############################################
1529 
1530 ' . $content;
1531  if ($afterStaticUid) {
1532  // If 'content (default)' is targeted (static uid 43),
1533  // the content is added after typoscript of type contentRendering, eg. css_styled_content, see EXT:frontend/TemplateService for more information on how the code is parsed
1534  if ($afterStaticUid === 'defaultContentRendering' || $afterStaticUid == 43) {
1535  $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_' . $type . '.']['defaultContentRendering'] .= $content;
1536  } else {
1537  $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_' . $type . '.'][$afterStaticUid] .= $content;
1538  }
1539  } else {
1540  $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_' . $type] .= $content;
1541  }
1542  }
1543  }
1544 
1545  /***************************************
1546  *
1547  * Internal extension management methods
1548  *
1549  ***************************************/
1558  public static function getExtensionIcon($extensionPath, $returnFullPath = false)
1559  {
1560  $icon = '';
1561  $locationsToCheckFor = [
1562  'Resources/Public/Icons/Extension.svg',
1563  'Resources/Public/Icons/Extension.png',
1564  'Resources/Public/Icons/Extension.gif',
1565  'ext_icon.svg',
1566  'ext_icon.png',
1567  'ext_icon.gif',
1568  ];
1569  foreach ($locationsToCheckFor as $fileLocation) {
1570  if (file_exists($extensionPath . $fileLocation)) {
1571  $icon = $fileLocation;
1572  break;
1573  }
1574  }
1575  return $returnFullPath ? $extensionPath . $icon : $icon;
1576  }
1577 
1589  public static function loadExtLocalconf($allowCaching = true)
1590  {
1591  if ($allowCaching) {
1592  $cacheIdentifier = self::getExtLocalconfCacheIdentifier();
1594  $codeCache = self::getCacheManager()->getCache('cache_core');
1595  if ($codeCache->has($cacheIdentifier)) {
1596  $codeCache->requireOnce($cacheIdentifier);
1597  } else {
1598  self::loadSingleExtLocalconfFiles();
1599  self::createExtLocalconfCacheEntry();
1600  }
1601  } else {
1602  self::loadSingleExtLocalconfFiles();
1603  }
1604  }
1605 
1609  protected static function loadSingleExtLocalconfFiles()
1610  {
1611  // This is the main array meant to be manipulated in the ext_localconf.php files
1612  // In general it is recommended to not rely on it to be globally defined in that
1613  // scope but to use $GLOBALS['TYPO3_CONF_VARS'] instead.
1614  // Nevertheless we define it here as global for backwards compatibility.
1615  global $TYPO3_CONF_VARS;
1616  foreach ($GLOBALS['TYPO3_LOADED_EXT'] as $_EXTKEY => $extensionInformation) {
1617  if ((is_array($extensionInformation) || $extensionInformation instanceof \ArrayAccess) && isset($extensionInformation['ext_localconf.php'])) {
1618  // $_EXTKEY and $_EXTCONF are available in ext_localconf.php
1619  // and are explicitly set in cached file as well
1620  $_EXTCONF = $GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'][$_EXTKEY] ?? null;
1621  require $extensionInformation['ext_localconf.php'];
1622  }
1623  }
1624  }
1625 
1629  protected static function createExtLocalconfCacheEntry()
1630  {
1631  $extensionInformation = $GLOBALS['TYPO3_LOADED_EXT'];
1632  $phpCodeToCache = [];
1633  // Set same globals as in loadSingleExtLocalconfFiles()
1634  $phpCodeToCache[] = '';
1637  $phpCodeToCache[] = '';
1638  $phpCodeToCache[] = 'global $TYPO3_CONF_VARS, $T3_SERVICES, $T3_VAR;';
1639  $phpCodeToCache[] = '';
1640  // Iterate through loaded extensions and add ext_localconf content
1641  foreach ($extensionInformation as $extensionKey => $extensionDetails) {
1642  if (isset($extensionDetails['ext_localconf.php']) && $extensionDetails['ext_localconf.php']) {
1643  // Include a header per extension to make the cache file more readable
1644  $phpCodeToCache[] = '';
1648  $phpCodeToCache[] = '';
1649  // Set $_EXTKEY and $_EXTCONF for this extension
1650  $phpCodeToCache[] = '$_EXTKEY = \'' . $extensionKey . '\';';
1651  $phpCodeToCache[] = '$_EXTCONF = $GLOBALS[\'TYPO3_CONF_VARS\'][\'EXT\'][\'extConf\'][$_EXTKEY] ?? null;';
1652  $phpCodeToCache[] = '';
1653  // Add ext_localconf.php content of extension
1654  $phpCodeToCache[] = trim(file_get_contents($extensionDetails['ext_localconf.php']));
1655  $phpCodeToCache[] = '';
1656  $phpCodeToCache[] = '';
1657  }
1658  }
1659  $phpCodeToCache = implode(LF, $phpCodeToCache);
1660  // Remove all start and ending php tags from content
1661  $phpCodeToCache = preg_replace('/<\\?php|\\?>/is', '', $phpCodeToCache);
1662  self::getCacheManager()->getCache('cache_core')->set(self::getExtLocalconfCacheIdentifier(), $phpCodeToCache);
1663  }
1664 
1670  protected static function getExtLocalconfCacheIdentifier()
1671  {
1672  return 'ext_localconf_' . sha1(TYPO3_version . PATH_site . 'extLocalconf' . serialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['runtimeActivatedPackages']));
1673  }
1674 
1688  public static function loadBaseTca($allowCaching = true)
1689  {
1690  if ($allowCaching) {
1691  $cacheIdentifier = static::getBaseTcaCacheIdentifier();
1693  $codeCache = static::getCacheManager()->getCache('cache_core');
1694  $cacheData = $codeCache->requireOnce($cacheIdentifier);
1695  if ($cacheData === true) {
1696  // requireOnce returns true if the file was already required before. Thus TCA is already loaded.
1697  return;
1698  }
1699  if (is_array($cacheData)) {
1700  $GLOBALS['TCA'] = $cacheData['tca'];
1702  CategoryRegistry::class,
1703  unserialize(
1704  $cacheData['categoryRegistry'],
1705  ['allowed_classes' => [CategoryRegistry::class]]
1706  )
1707  );
1708  } else {
1709  static::buildBaseTcaFromSingleFiles();
1710  static::createBaseTcaCacheFile();
1711  }
1712  } else {
1713  static::buildBaseTcaFromSingleFiles();
1714  }
1715  }
1716 
1724  protected static function buildBaseTcaFromSingleFiles()
1725  {
1726  $GLOBALS['TCA'] = [];
1727 
1728  $activePackages = static::$packageManager->getActivePackages();
1729 
1730  // First load "full table" files from Configuration/TCA
1731  foreach ($activePackages as $package) {
1732  $tcaConfigurationDirectory = $package->getPackagePath() . 'Configuration/TCA';
1733  if (is_dir($tcaConfigurationDirectory)) {
1734  $files = scandir($tcaConfigurationDirectory);
1735  foreach ($files as $file) {
1736  if (
1737  is_file($tcaConfigurationDirectory . '/' . $file)
1738  && ($file !== '.')
1739  && ($file !== '..')
1740  && (substr($file, -4, 4) === '.php')
1741  ) {
1742  $tcaOfTable = require($tcaConfigurationDirectory . '/' . $file);
1743  if (is_array($tcaOfTable)) {
1744  // TCA table name is filename without .php suffix, eg 'sys_notes', not 'sys_notes.php'
1745  $tcaTableName = substr($file, 0, -4);
1746  $GLOBALS['TCA'][$tcaTableName] = $tcaOfTable;
1747  }
1748  }
1749  }
1750  }
1751  }
1752 
1753  // Apply category stuff
1754  CategoryRegistry::getInstance()->applyTcaForPreRegisteredTables();
1755 
1756  // Execute override files from Configuration/TCA/Overrides
1757  foreach ($activePackages as $package) {
1758  $tcaOverridesPathForPackage = $package->getPackagePath() . 'Configuration/TCA/Overrides';
1759  if (is_dir($tcaOverridesPathForPackage)) {
1760  $files = scandir($tcaOverridesPathForPackage);
1761  foreach ($files as $file) {
1762  if (
1763  is_file($tcaOverridesPathForPackage . '/' . $file)
1764  && ($file !== '.')
1765  && ($file !== '..')
1766  && (substr($file, -4, 4) === '.php')
1767  ) {
1768  require($tcaOverridesPathForPackage . '/' . $file);
1769  }
1770  }
1771  }
1772  }
1773 
1774  // TCA migration
1775  // @deprecated since TYPO3 CMS 7. Not removed in TYPO3 CMS 8 though. This call will stay for now to allow further TCA migrations in 8.
1776  $tcaMigration = GeneralUtility::makeInstance(TcaMigration::class);
1777  $GLOBALS['TCA'] = $tcaMigration->migrate($GLOBALS['TCA']);
1778  $messages = $tcaMigration->getMessages();
1779  if (!empty($messages)) {
1780  $context = 'Automatic TCA migration done during bootstrap. Please adapt TCA accordingly, these migrations'
1781  . ' will be removed. The backend module "Configuration -> TCA" shows the modified values.'
1782  . ' Please adapt these areas:';
1783  array_unshift($messages, $context);
1784  GeneralUtility::deprecationLog(implode(LF, $messages));
1785  }
1786 
1787  // TCA preparation
1788  $tcaPreparation = GeneralUtility::makeInstance(TcaPreparation::class);
1789  $GLOBALS['TCA'] = $tcaPreparation->prepare($GLOBALS['TCA']);
1790 
1791  static::emitTcaIsBeingBuiltSignal($GLOBALS['TCA']);
1792  }
1793 
1804  protected static function emitTcaIsBeingBuiltSignal(array $tca)
1805  {
1806  list($tca) = static::getSignalSlotDispatcher()->dispatch(__CLASS__, 'tcaIsBeingBuilt', [$tca]);
1807  $GLOBALS['TCA'] = $tca;
1808  }
1809 
1814  protected static function createBaseTcaCacheFile()
1815  {
1817  $codeCache = self::getCacheManager()->getCache('cache_core');
1818  $codeCache->set(
1819  static::getBaseTcaCacheIdentifier(),
1820  'return '
1821  . var_export(['tca' => $GLOBALS['TCA'], 'categoryRegistry' => serialize(CategoryRegistry::getInstance())], true)
1822  . ';'
1823  );
1824  }
1825 
1831  protected static function getBaseTcaCacheIdentifier()
1832  {
1833  return 'tca_base_' . sha1(TYPO3_version . PATH_site . 'tca_code' . serialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['runtimeActivatedPackages']));
1834  }
1835 
1847  public static function loadExtTables($allowCaching = true)
1848  {
1849  if ($allowCaching && !self::$extTablesWasReadFromCacheOnce) {
1850  self::$extTablesWasReadFromCacheOnce = true;
1851  $cacheIdentifier = self::getExtTablesCacheIdentifier();
1853  $codeCache = self::getCacheManager()->getCache('cache_core');
1854  if ($codeCache->has($cacheIdentifier)) {
1855  $codeCache->requireOnce($cacheIdentifier);
1856  } else {
1857  self::loadSingleExtTablesFiles();
1858  self::createExtTablesCacheEntry();
1859  }
1860  } else {
1861  self::loadSingleExtTablesFiles();
1862  }
1863  }
1864 
1868  protected static function loadSingleExtTablesFiles()
1869  {
1870  // In general it is recommended to not rely on it to be globally defined in that
1871  // scope, but we can not prohibit this without breaking backwards compatibility
1872  global $T3_SERVICES, $T3_VAR, $TYPO3_CONF_VARS;
1873  global $TBE_MODULES, $TBE_MODULES_EXT, $TCA;
1874  global $PAGES_TYPES, $TBE_STYLES;
1875  global $_EXTKEY;
1876  // Load each ext_tables.php file of loaded extensions
1877  foreach ($GLOBALS['TYPO3_LOADED_EXT'] as $_EXTKEY => $extensionInformation) {
1878  if ((is_array($extensionInformation) || $extensionInformation instanceof \ArrayAccess) && $extensionInformation['ext_tables.php']) {
1879  // $_EXTKEY and $_EXTCONF are available in ext_tables.php
1880  // and are explicitly set in cached file as well
1881  $_EXTCONF = $GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'][$_EXTKEY] ?? null;
1882  require $extensionInformation['ext_tables.php'];
1883  }
1884  }
1885  }
1886 
1890  protected static function createExtTablesCacheEntry()
1891  {
1892  $extensionInformation = $GLOBALS['TYPO3_LOADED_EXT'];
1893  $phpCodeToCache = [];
1894  // Set same globals as in loadSingleExtTablesFiles()
1895  $phpCodeToCache[] = '';
1898  $phpCodeToCache[] = '';
1899  $phpCodeToCache[] = 'global $T3_SERVICES, $T3_VAR, $TYPO3_CONF_VARS;';
1900  $phpCodeToCache[] = 'global $TBE_MODULES, $TBE_MODULES_EXT, $TCA;';
1901  $phpCodeToCache[] = 'global $PAGES_TYPES, $TBE_STYLES;';
1902  $phpCodeToCache[] = 'global $_EXTKEY;';
1903  $phpCodeToCache[] = '';
1904  // Iterate through loaded extensions and add ext_tables content
1905  foreach ($extensionInformation as $extensionKey => $extensionDetails) {
1906  if (isset($extensionDetails['ext_tables.php']) && $extensionDetails['ext_tables.php']) {
1907  // Include a header per extension to make the cache file more readable
1908  $phpCodeToCache[] = '';
1912  $phpCodeToCache[] = '';
1913  // Set $_EXTKEY and $_EXTCONF for this extension
1914  $phpCodeToCache[] = '$_EXTKEY = \'' . $extensionKey . '\';';
1915  $phpCodeToCache[] = '$_EXTCONF = $GLOBALS[\'TYPO3_CONF_VARS\'][\'EXT\'][\'extConf\'][$_EXTKEY] ?? null;';
1916  $phpCodeToCache[] = '';
1917  // Add ext_tables.php content of extension
1918  $phpCodeToCache[] = trim(file_get_contents($extensionDetails['ext_tables.php']));
1919  $phpCodeToCache[] = '';
1920  }
1921  }
1922  $phpCodeToCache = implode(LF, $phpCodeToCache);
1923  // Remove all start and ending php tags from content
1924  $phpCodeToCache = preg_replace('/<\\?php|\\?>/is', '', $phpCodeToCache);
1925  self::getCacheManager()->getCache('cache_core')->set(self::getExtTablesCacheIdentifier(), $phpCodeToCache);
1926  }
1927 
1933  protected static function getExtTablesCacheIdentifier()
1934  {
1935  return 'ext_tables_' . sha1(TYPO3_version . PATH_site . 'extTables' . serialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['runtimeActivatedPackages']));
1936  }
1937 
1951  public static function removeCacheFiles()
1952  {
1953  self::getCacheManager()->flushCachesInGroup('system');
1954  }
1955 
1961  public static function getLoadedExtensionListArray()
1962  {
1963  return array_keys(static::$packageManager->getActivePackages());
1964  }
1965 
1975  public static function loadExtension($extensionKey)
1976  {
1977  if (static::$packageManager->isPackageActive($extensionKey)) {
1978  throw new \RuntimeException('Extension already loaded', 1342345486);
1979  }
1980  static::$packageManager->activatePackage($extensionKey);
1981  }
1982 
1992  public static function unloadExtension($extensionKey)
1993  {
1994  if (!static::$packageManager->isPackageActive($extensionKey)) {
1995  throw new \RuntimeException('Extension not loaded', 1342345487);
1996  }
1997  static::$packageManager->deactivatePackage($extensionKey);
1998  }
1999 
2012  public static function makeCategorizable($extensionKey, $tableName, $fieldName = 'categories', array $options = [], $override = false)
2013  {
2014  // Update the category registry
2015  $result = CategoryRegistry::getInstance()->add($extensionKey, $tableName, $fieldName, $options, $override);
2016  if ($result === false) {
2017  $message = CategoryRegistry::class . ': no category registered for table "%s". Key was already registered.';
2019  $logger = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Log\LogManager::class)->getLogger(__CLASS__);
2020  $logger->warning(
2021  sprintf($message, $tableName)
2022  );
2023  }
2024  }
2025 }
static addFieldsToAllPalettesOfField($table, $field, $addFields, $insertionPosition='')
static addFieldsToUserSettings($addFields, $insertionPosition='')
static checkCommand($cmd, $handler='')
static filterByValueRecursive($needle='', array $haystack=[])
static addNavigationComponent($module, $componentId, $extensionKey=null)
static addService($extKey, $serviceType, $serviceKey, $info)
static addToInsertRecords($table, $content_table='tt_content', $content_field='records')
static setSingletonInstance($className, SingletonInterface $instance)
static addTypoScript($key, $type, $content, $afterStaticUid=0)
static addExtJSModule($extensionName, $mainModuleName, $subModuleName='', $position='', array $moduleConfiguration=[])
static getFileAbsFileName($filename, $_=null, $_2=null)
static addPItoST43($key, $_, $suffix='', $type='list_type', $cached=0)
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
static generateItemList(array $items, $useRawData=false)
static addPiFlexFormValue($piKeyToMatch, $value, $CTypeToMatch='list')
static makeInstance($className,... $constructorArguments)
static getFileFieldTCAConfig($fieldName, array $customSettingOverride=[], $allowedFileExtensions='', $disallowedFileExtensions='')
static addToAllTCAtypes($table, $newFieldsString, $typeList='', $position='')
$iconRegistry
static insertModuleFunction($modname, $className, $classPath=null, $title, $MM_key='function', $WS='')
static isServiceAvailable($serviceType, $serviceKey, $serviceDetails)
$signalSlotDispatcher
static addTcaSelectItem($table, $field, array $item, $relativeToField='', $relativePosition='')
static mergeRecursiveWithOverrule(array &$original, array $overrule, $addKeys=true, $includeEmptyValues=true, $enableUnsetFeature=true)
static registerExtDirectComponent($endpointName, $callbackClass, $moduleName=null, $accessLevel=null)
static addPlugin($itemArray, $type='list_type', $extensionKey=null)
static executePositionedStringInsertion($list, $insertionList, $insertionPosition='')
static setPackageManager(PackageManager $packageManager)
static findService($serviceType, $serviceSubType='', $excludeServiceKeys=[])
static registerAjaxHandler($ajaxId, $callbackMethod, $csrfTokenCheck=true)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
static addModule($main, $sub='', $position='', $path=null, $moduleConfiguration=[])
static addFieldsToPalette($table, $palette, $addFields, $insertionPosition='')
static getExtensionIcon($extensionPath, $returnFullPath=false)