TYPO3CMS  8
 All Classes Namespaces Files Functions Variables Pages
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 
221  public static function clearExtensionKeyMap()
222  {
223  self::$extensionKeyMap = null;
224  }
225 
236  public static function getExtensionVersion($key)
237  {
238  if (!is_string($key) || empty($key)) {
239  throw new \InvalidArgumentException('Extension key must be a non-empty string.', 1294586096);
240  }
241  if (!static::isLoaded($key)) {
242  return '';
243  }
244  $version = static::$packageManager->getPackage($key)->getPackageMetaData()->getVersion();
245  if (empty($version)) {
246  throw new \TYPO3\CMS\Core\Package\Exception('Version number in composer manifest of package "' . $key . '" is missing or invalid', 1395614959);
247  }
248  return $version;
249  }
250 
251  /**************************************
252  *
253  * Adding BACKEND features
254  * (related to core features)
255  *
256  ***************************************/
268  public static function addTCAcolumns($table, $columnArray)
269  {
270  if (is_array($columnArray) && is_array($GLOBALS['TCA'][$table]) && is_array($GLOBALS['TCA'][$table]['columns'])) {
271  // Candidate for array_merge() if integer-keys will some day make trouble...
272  $GLOBALS['TCA'][$table]['columns'] = array_merge($GLOBALS['TCA'][$table]['columns'], $columnArray);
273  }
274  }
275 
290  public static function addToAllTCAtypes($table, $newFieldsString, $typeList = '', $position = '')
291  {
292  $newFieldsString = trim($newFieldsString);
293  if ($newFieldsString === '' || !is_array($GLOBALS['TCA'][$table]['types'])) {
294  return;
295  }
296  list($positionIdentifier, $entityName) = GeneralUtility::trimExplode(':', $position);
297  $palettesChanged = [];
298 
299  foreach ($GLOBALS['TCA'][$table]['types'] as $type => &$typeDetails) {
300  // skip if we don't want to add the field for this type
301  if ($typeList !== '' && !GeneralUtility::inList($typeList, $type)) {
302  continue;
303  }
304  // skip if fields were already added
305  if (!isset($typeDetails['showitem'])) {
306  continue;
307  }
308 
309  $fieldArray = GeneralUtility::trimExplode(',', $typeDetails['showitem'], true);
310  if (in_array($newFieldsString, $fieldArray, true)) {
311  continue;
312  }
313 
314  $fieldExists = false;
315  $newPosition = '';
316  if (is_array($GLOBALS['TCA'][$table]['palettes'])) {
317  // Get the palette names used in current showitem
318  $paletteCount = preg_match_all('/(?:^|,) # Line start or a comma
319  (?:
320  \\s*\\-\\-palette\\-\\-;[^;]*;([^,$]*)| # --palette--;label;paletteName
321  \\s*\\b[^;,]+\\b(?:;[^;]*;([^;,]+))?[^,]* # field;label;paletteName
322  )/x', $typeDetails['showitem'], $paletteMatches);
323  if ($paletteCount > 0) {
324  $paletteNames = array_filter(array_merge($paletteMatches[1], $paletteMatches[2]));
325  if (!empty($paletteNames)) {
326  foreach ($paletteNames as $paletteName) {
327  $palette = $GLOBALS['TCA'][$table]['palettes'][$paletteName];
328  switch ($positionIdentifier) {
329  case 'after':
330  case 'before':
331  if (preg_match('/\\b' . $entityName . '\\b/', $palette['showitem']) > 0) {
332  $newPosition = $positionIdentifier . ':--palette--;;' . $paletteName;
333  }
334  break;
335  case 'replace':
336  // check if fields have been added to palette before
337  if (isset($palettesChanged[$paletteName])) {
338  $fieldExists = true;
339  continue;
340  }
341  if (preg_match('/\\b' . $entityName . '\\b/', $palette['showitem']) > 0) {
342  self::addFieldsToPalette($table, $paletteName, $newFieldsString, $position);
343  // Memorize that we already changed this palette, in case other types also use it
344  $palettesChanged[$paletteName] = true;
345  $fieldExists = true;
346  continue;
347  }
348  break;
349  default:
350  // Intentionally left blank
351  }
352  }
353  }
354  }
355  }
356  if ($fieldExists === false) {
357  $typeDetails['showitem'] = self::executePositionedStringInsertion(
358  $typeDetails['showitem'],
359  $newFieldsString,
360  $newPosition !== '' ? $newPosition : $position
361  );
362  }
363  }
364  unset($typeDetails);
365  }
366 
410  public static function addFieldsToAllPalettesOfField($table, $field, $addFields, $insertionPosition = '')
411  {
412  if (!isset($GLOBALS['TCA'][$table]['columns'][$field])) {
413  return;
414  }
415  if (!is_array($GLOBALS['TCA'][$table]['types'])) {
416  return;
417  }
418 
419  // Iterate through all types and search for the field that defines the palette to be extended
420  foreach ($GLOBALS['TCA'][$table]['types'] as $typeName => $typeArray) {
421  // Continue if types has no showitem at all or if requested field is not in it
422  if (!isset($typeArray['showitem']) || strpos($typeArray['showitem'], $field) === false) {
423  continue;
424  }
425  $fieldArrayWithOptions = GeneralUtility::trimExplode(',', $typeArray['showitem']);
426  // Find the field we're handling
427  $newFieldStringArray = [];
428  foreach ($fieldArrayWithOptions as $fieldNumber => $fieldString) {
429  $newFieldStringArray[] = $fieldString;
430  $fieldArray = GeneralUtility::trimExplode(';', $fieldString);
431  if ($fieldArray[0] !== $field) {
432  continue;
433  }
434  if (
435  isset($fieldArrayWithOptions[$fieldNumber + 1])
436  && strpos($fieldArrayWithOptions[$fieldNumber + 1], '--palette--') === 0
437  ) {
438  // Match for $field and next field is a palette - add fields to this one
439  $paletteName = GeneralUtility::trimExplode(';', $fieldArrayWithOptions[$fieldNumber + 1]);
440  $paletteName = $paletteName[2];
441  self::addFieldsToPalette($table, $paletteName, $addFields, $insertionPosition);
442  } else {
443  // Match for $field but next field is no palette - create a new one
444  $newPaletteName = 'generatedFor-' . $field;
445  self::addFieldsToPalette($table, 'generatedFor-' . $field, $addFields, $insertionPosition);
446  $newFieldStringArray[] = '--palette--;;' . $newPaletteName;
447  }
448  }
449  $GLOBALS['TCA'][$table]['types'][$typeName]['showitem'] = implode(', ', $newFieldStringArray);
450  }
451  }
452 
463  public static function addFieldsToPalette($table, $palette, $addFields, $insertionPosition = '')
464  {
465  if (isset($GLOBALS['TCA'][$table])) {
466  $paletteData = &$GLOBALS['TCA'][$table]['palettes'][$palette];
467  // If palette already exists, merge the data:
468  if (is_array($paletteData)) {
469  $paletteData['showitem'] = self::executePositionedStringInsertion($paletteData['showitem'], $addFields, $insertionPosition);
470  } else {
471  $paletteData['showitem'] = self::removeDuplicatesForInsertion($addFields);
472  }
473  }
474  }
475 
504  public static function addTcaSelectItem($table, $field, array $item, $relativeToField = '', $relativePosition = '')
505  {
506  if (!is_string($table)) {
507  throw new \InvalidArgumentException('Given table is of type "' . gettype($table) . '" but a string is expected.', 1303236963);
508  }
509  if (!is_string($field)) {
510  throw new \InvalidArgumentException('Given field is of type "' . gettype($field) . '" but a string is expected.', 1303236964);
511  }
512  if (!is_string($relativeToField)) {
513  throw new \InvalidArgumentException('Given relative field is of type "' . gettype($relativeToField) . '" but a string is expected.', 1303236965);
514  }
515  if (!is_string($relativePosition)) {
516  throw new \InvalidArgumentException('Given relative position is of type "' . gettype($relativePosition) . '" but a string is expected.', 1303236966);
517  }
518  if ($relativePosition !== '' && $relativePosition !== 'before' && $relativePosition !== 'after' && $relativePosition !== 'replace') {
519  throw new \InvalidArgumentException('Relative position must be either empty or one of "before", "after", "replace".', 1303236967);
520  }
521  if (!is_array($GLOBALS['TCA'][$table]['columns'][$field]['config']['items'])) {
522  throw new \RuntimeException('Given select field item list was not found.', 1303237468);
523  }
524  // Make sure item keys are integers
525  $GLOBALS['TCA'][$table]['columns'][$field]['config']['items'] = array_values($GLOBALS['TCA'][$table]['columns'][$field]['config']['items']);
526  if ($relativePosition !== '') {
527  // Insert at specified position
528  $matchedPosition = ArrayUtility::filterByValueRecursive($relativeToField, $GLOBALS['TCA'][$table]['columns'][$field]['config']['items']);
529  if (!empty($matchedPosition)) {
530  $relativeItemKey = key($matchedPosition);
531  if ($relativePosition === 'replace') {
532  $GLOBALS['TCA'][$table]['columns'][$field]['config']['items'][$relativeItemKey] = $item;
533  } else {
534  if ($relativePosition === 'before') {
535  $offset = $relativeItemKey;
536  } else {
537  $offset = $relativeItemKey + 1;
538  }
539  array_splice($GLOBALS['TCA'][$table]['columns'][$field]['config']['items'], $offset, 0, [0 => $item]);
540  }
541  } else {
542  // Insert at new item at the end of the array if relative position was not found
543  $GLOBALS['TCA'][$table]['columns'][$field]['config']['items'][] = $item;
544  }
545  } else {
546  // Insert at new item at the end of the array
547  $GLOBALS['TCA'][$table]['columns'][$field]['config']['items'][] = $item;
548  }
549  }
550 
561  public static function getFileFieldTCAConfig($fieldName, array $customSettingOverride = [], $allowedFileExtensions = '', $disallowedFileExtensions = '')
562  {
563  $fileFieldTCAConfig = [
564  'type' => 'inline',
565  'foreign_table' => 'sys_file_reference',
566  'foreign_field' => 'uid_foreign',
567  'foreign_sortby' => 'sorting_foreign',
568  'foreign_table_field' => 'tablenames',
569  'foreign_match_fields' => [
570  'fieldname' => $fieldName
571  ],
572  'foreign_label' => 'uid_local',
573  'foreign_selector' => 'uid_local',
574  'foreign_selector_fieldTcaOverride' => [
575  'config' => [
576  'appearance' => [
577  'elementBrowserType' => 'file',
578  'elementBrowserAllowed' => $allowedFileExtensions
579  ]
580  ]
581  ],
582  'filter' => [
583  [
584  'userFunc' => \TYPO3\CMS\Core\Resource\Filter\FileExtensionFilter::class . '->filterInlineChildren',
585  'parameters' => [
586  'allowedFileExtensions' => $allowedFileExtensions,
587  'disallowedFileExtensions' => $disallowedFileExtensions
588  ]
589  ]
590  ],
591  'appearance' => [
592  'useSortable' => true,
593  'headerThumbnail' => [
594  'field' => 'uid_local',
595  'width' => '45',
596  'height' => '45c',
597  ],
598  'showPossibleLocalizationRecords' => false,
599  'showRemovedLocalizationRecords' => false,
600  'showSynchronizationLink' => false,
601  'showAllLocalizationLink' => false,
602 
603  'enabledControls' => [
604  'info' => true,
605  'new' => false,
606  'dragdrop' => true,
607  'sort' => false,
608  'hide' => true,
609  'delete' => true,
610  'localize' => true,
611  ],
612  ],
613  'behaviour' => [
614  'localizationMode' => 'select',
615  'localizeChildrenAtParentLocalization' => true,
616  ],
617  ];
618  ArrayUtility::mergeRecursiveWithOverrule($fileFieldTCAConfig, $customSettingOverride);
619  return $fileFieldTCAConfig;
620  }
621 
631  public static function addFieldsToUserSettings($addFields, $insertionPosition = '')
632  {
633  $GLOBALS['TYPO3_USER_SETTINGS']['showitem'] = self::executePositionedStringInsertion($GLOBALS['TYPO3_USER_SETTINGS']['showitem'], $addFields, $insertionPosition);
634  }
635 
653  protected static function executePositionedStringInsertion($list, $insertionList, $insertionPosition = '')
654  {
655  $list = $newList = trim($list, ", \t\n\r\0\x0B");
656 
657  list($location, $positionName) = GeneralUtility::trimExplode(':', $insertionPosition, false, 2);
658 
659  if ($location !== 'replace') {
660  $insertionList = self::removeDuplicatesForInsertion($insertionList, $list);
661  }
662 
663  if ($insertionList === '') {
664  return $list;
665  }
666  if ($list === '') {
667  return $insertionList;
668  }
669  if ($insertionPosition === '') {
670  return $list . ', ' . $insertionList;
671  }
672 
673  // The $insertPosition may be a palette: after:--palette--;;title
674  // In the $list the palette may contain a LLL string in between the ;;
675  // Adjust the regex to match that
676  $positionName = preg_quote($positionName, '/');
677  if (strpos($positionName, ';;') !== false) {
678  $positionName = str_replace(';;', ';[^;]*;', $positionName);
679  }
680 
681  $pattern = ('/(^|,\\s*)(' . $positionName . ')(;[^,$]+)?(,|$)/');
682  switch ($location) {
683  case 'after':
684  $newList = preg_replace($pattern, '$1$2$3, ' . $insertionList . '$4', $list);
685  break;
686  case 'before':
687  $newList = preg_replace($pattern, '$1' . $insertionList . ', $2$3$4', $list);
688  break;
689  case 'replace':
690  $newList = preg_replace($pattern, '$1' . $insertionList . '$4', $list);
691  break;
692  default:
693  }
694 
695  // When preg_replace did not replace anything; append the $insertionList.
696  if ($list === $newList) {
697  return $list . ', ' . $insertionList;
698  }
699  return $newList;
700  }
701 
717  protected static function removeDuplicatesForInsertion($insertionList, $list = '')
718  {
719  $insertionListParts = preg_split('/\\s*,\\s*/', $insertionList);
720  $listMatches = [];
721  if ($list !== '') {
722  preg_match_all('/(?:^|,)\\s*\\b([^;,]+)\\b[^,]*/', $list, $listMatches);
723  $listMatches = $listMatches[1];
724  }
725 
726  $cleanInsertionListParts = [];
727  foreach ($insertionListParts as $fieldName) {
728  $fieldNameParts = explode(';', $fieldName, 2);
729  $cleanFieldName = $fieldNameParts[0];
730  if (
731  $cleanFieldName === '--linebreak--'
732  || (
733  !in_array($cleanFieldName, $cleanInsertionListParts, true)
734  && !in_array($cleanFieldName, $listMatches, true)
735  )
736  ) {
737  $cleanInsertionListParts[] = $fieldName;
738  }
739  }
740  return implode(', ', $cleanInsertionListParts);
741  }
742 
749  protected static function explodeItemList($itemList)
750  {
751  $items = [];
752  $itemParts = GeneralUtility::trimExplode(',', $itemList, true);
753  foreach ($itemParts as $itemPart) {
754  $itemDetails = GeneralUtility::trimExplode(';', $itemPart, false, 5);
755  $key = $itemDetails[0];
756  if (strpos($key, '--') !== false) {
757  // 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!
758  $key .= count($items);
759  }
760  if (!isset($items[$key])) {
761  $items[$key] = [
762  'rawData' => $itemPart,
763  'details' => []
764  ];
765  $details = [0 => 'field', 1 => 'label', 2 => 'palette'];
766  foreach ($details as $id => $property) {
767  $items[$key]['details'][$property] = isset($itemDetails[$id]) ? $itemDetails[$id] : '';
768  }
769  }
770  }
771  return $items;
772  }
773 
782  protected static function generateItemList(array $items, $useRawData = false)
783  {
784  $itemParts = [];
785  foreach ($items as $item => $itemDetails) {
786  if (strpos($item, '--') !== false) {
787  // 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.
788  $item = str_replace([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], '', $item);
789  }
790  if ($useRawData) {
791  $itemParts[] = $itemDetails['rawData'];
792  } else {
793  if (count($itemDetails['details']) > 1) {
794  $details = ['palette', 'label', 'field'];
795  $elements = [];
796  $addEmpty = false;
797  foreach ($details as $property) {
798  if ($itemDetails['details'][$property] !== '' || $addEmpty) {
799  $addEmpty = true;
800  array_unshift($elements, $itemDetails['details'][$property]);
801  }
802  }
803  $item = implode(';', $elements);
804  }
805  $itemParts[] = $item;
806  }
807  }
808  return implode(', ', $itemParts);
809  }
810 
819  public static function allowTableOnStandardPages($table)
820  {
821  $GLOBALS['PAGES_TYPES']['default']['allowedTables'] .= ',' . $table;
822  }
823 
836  public static function addExtJSModule($extensionName, $mainModuleName, $subModuleName = '', $position = '', array $moduleConfiguration = [])
837  {
838  if (empty($extensionName)) {
839  throw new \InvalidArgumentException('The extension name must not be empty', 1325938973);
840  }
841  $extensionName = str_replace(' ', '', ucwords(str_replace('_', ' ', $extensionName)));
842  $defaultModuleConfiguration = [
843  'access' => 'admin',
844  'icon' => 'EXT:backend/Resources/Public/Images/Logo.png',
845  'labels' => '',
846  ];
847  // Add mandatory parameter to use new pagetree
848  if ($mainModuleName === 'web') {
849  $defaultModuleConfiguration['navigationComponentId'] = 'typo3-pagetree';
850  }
851  ArrayUtility::mergeRecursiveWithOverrule($defaultModuleConfiguration, $moduleConfiguration);
852  $moduleConfiguration = $defaultModuleConfiguration;
853  if ($subModuleName !== '') {
854  $moduleSignature = $mainModuleName . '_' . $subModuleName;
855  } else {
856  $moduleSignature = $mainModuleName;
857  }
858  $moduleConfiguration['name'] = $moduleSignature;
859  $moduleConfiguration['script'] = 'extjspaneldummy.html';
860  $moduleConfiguration['extensionName'] = $extensionName;
861  $moduleConfiguration['configureModuleFunction'] = [self::class, 'configureModule'];
862  $GLOBALS['TBE_MODULES']['_configuration'][$moduleSignature] = $moduleConfiguration;
863  self::addModule($mainModuleName, $subModuleName, $position);
864  }
865 
873  public static function configureModule($moduleSignature)
874  {
875  $moduleConfiguration = $GLOBALS['TBE_MODULES']['_configuration'][$moduleSignature];
876  if (!empty($moduleConfiguration['labels']['tabs_images']['tab'])) {
877  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.');
878  $moduleConfiguration['icon'] = $moduleConfiguration['labels']['tabs_images']['tab'];
879  unset($moduleConfiguration['labels']['tabs_images']['tab']);
880  }
881 
882  // Register the icon and move it too "iconIdentifier"
883  if (!empty($moduleConfiguration['icon'])) {
884  $iconIdentifier = 'module-' . $moduleSignature;
885  $iconRegistry = GeneralUtility::makeInstance(IconRegistry::class);
886  $iconRegistry->registerIcon($iconIdentifier, BitmapIconProvider::class, [
887  'source' => GeneralUtility::getFileAbsFileName($moduleConfiguration['icon'])
888  ]);
889  $moduleConfiguration['iconIdentifier'] = $iconIdentifier;
890  unset($moduleConfiguration['icon']);
891  }
892 
893  return $moduleConfiguration;
894  }
895 
907  public static function addModule($main, $sub = '', $position = '', $path = null, $moduleConfiguration = [])
908  {
909  // If there is already a main module by this name:
910  // Adding the submodule to the correct position:
911  if (isset($GLOBALS['TBE_MODULES'][$main]) && $sub) {
912  list($place, $modRef) = GeneralUtility::trimExplode(':', $position, true);
913  $modules = ',' . $GLOBALS['TBE_MODULES'][$main] . ',';
914  if ($place === null || ($modRef !== null && !GeneralUtility::inList($modules, $modRef))) {
915  $place = 'bottom';
916  }
917  $modRef = ',' . $modRef . ',';
918  if (!GeneralUtility::inList($modules, $sub)) {
919  switch (strtolower($place)) {
920  case 'after':
921  $modules = str_replace($modRef, $modRef . $sub . ',', $modules);
922  break;
923  case 'before':
924  $modules = str_replace($modRef, ',' . $sub . $modRef, $modules);
925  break;
926  case 'top':
927  $modules = $sub . $modules;
928  break;
929  case 'bottom':
930  default:
931  $modules = $modules . $sub;
932  }
933  }
934  // Re-inserting the submodule list:
935  $GLOBALS['TBE_MODULES'][$main] = trim($modules, ',');
936  } else {
937  // Create new main modules with only one submodule, $sub (or none if $sub is blank)
938  $GLOBALS['TBE_MODULES'][$main] = $sub;
939  }
940 
941  // add additional configuration
942  if (is_array($moduleConfiguration) && !empty($moduleConfiguration)) {
943  $fullModuleSignature = $main . ($sub ? '_' . $sub : '');
944 
945  if (!empty($moduleConfiguration['labels']['tabs_images']['tab'])) {
946  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.');
947  $moduleConfiguration['icon'] = $moduleConfiguration['labels']['tabs_images']['tab'];
948  unset($moduleConfiguration['labels']['tabs_images']['tab']);
949  }
950 
951  if (!empty($moduleConfiguration['icon'])) {
952  $iconIdentifier = 'module-' . $fullModuleSignature;
953  $iconRegistry = GeneralUtility::makeInstance(IconRegistry::class);
954  $iconRegistry->registerIcon($iconIdentifier, BitmapIconProvider::class, [
955  'source' => GeneralUtility::getFileAbsFileName($moduleConfiguration['icon'])
956  ]);
957  $moduleConfiguration['iconIdentifier'] = $iconIdentifier;
958  unset($moduleConfiguration['icon']);
959  }
960 
961  $GLOBALS['TBE_MODULES']['_configuration'][$fullModuleSignature] = $moduleConfiguration;
962  }
963  }
964 
974  public static function registerExtDirectComponent($endpointName, $callbackClass, $moduleName = null, $accessLevel = null)
975  {
976  $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ExtDirect'][$endpointName] = [
977  'callbackClass' => $callbackClass,
978  'moduleName' => $moduleName,
979  'accessLevel' => $accessLevel
980  ];
981  }
982 
991  public static function registerAjaxHandler($ajaxId, $callbackMethod, $csrfTokenCheck = true)
992  {
994  $GLOBALS['TYPO3_CONF_VARS']['BE']['AJAX'][$ajaxId] = [
995  'callbackMethod' => $callbackMethod,
996  'csrfTokenCheck' => $csrfTokenCheck
997  ];
998  }
999 
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 
1036  public static function appendToTypoConfVars($group, $key, $content)
1037  {
1038  $GLOBALS['TYPO3_CONF_VARS_extensionAdded'][$group][$key] .= $content;
1039  $GLOBALS['TYPO3_CONF_VARS'][$group][$key] .= $content;
1040  }
1041 
1050  public static function addPageTSConfig($content)
1051  {
1052  self::appendToTypoConfVars('BE', 'defaultPageTSconfig', '
1053 [GLOBAL]
1054 ' . $content);
1055  }
1056 
1065  public static function addUserTSConfig($content)
1066  {
1067  self::appendToTypoConfVars('BE', 'defaultUserTSconfig', '
1068 [GLOBAL]
1069 ' . $content);
1070  }
1071 
1081  public static function addLLrefForTCAdescr($tca_descr_key, $file_ref)
1082  {
1083  if ($tca_descr_key) {
1084  if (!is_array($GLOBALS['TCA_DESCR'][$tca_descr_key])) {
1085  $GLOBALS['TCA_DESCR'][$tca_descr_key] = [];
1086  }
1087  if (!is_array($GLOBALS['TCA_DESCR'][$tca_descr_key]['refs'])) {
1088  $GLOBALS['TCA_DESCR'][$tca_descr_key]['refs'] = [];
1089  }
1090  $GLOBALS['TCA_DESCR'][$tca_descr_key]['refs'][] = $file_ref;
1091  }
1092  }
1093 
1103  public static function addNavigationComponent($module, $componentId, $extensionKey = null)
1104  {
1105  $extensionKey = $extensionKey ?: $GLOBALS['_EXTKEY'];
1106  if (!isset($extensionKey)) {
1107  throw new \RuntimeException('No extensionKey set in addNavigationComponent(). Provide it as third Parameter', 1404068039);
1108  }
1109  $GLOBALS['TBE_MODULES']['_navigationComponents'][$module] = [
1110  'componentId' => $componentId,
1111  'extKey' => $extensionKey,
1112  'isCoreComponent' => false
1113  ];
1114  }
1115 
1123  public static function addCoreNavigationComponent($module, $componentId)
1124  {
1125  self::addNavigationComponent($module, $componentId);
1126  $GLOBALS['TBE_MODULES']['_navigationComponents'][$module]['isCoreComponent'] = true;
1127  }
1128 
1129  /**************************************
1130  *
1131  * Adding SERVICES features
1132  *
1133  ***************************************/
1143  public static function addService($extKey, $serviceType, $serviceKey, $info)
1144  {
1145  if ($serviceType && is_array($info)) {
1146  $info['priority'] = max(0, min(100, $info['priority']));
1147  $GLOBALS['T3_SERVICES'][$serviceType][$serviceKey] = $info;
1148  $GLOBALS['T3_SERVICES'][$serviceType][$serviceKey]['extKey'] = $extKey;
1149  $GLOBALS['T3_SERVICES'][$serviceType][$serviceKey]['serviceKey'] = $serviceKey;
1150  $GLOBALS['T3_SERVICES'][$serviceType][$serviceKey]['serviceType'] = $serviceType;
1151  // Change the priority (and other values) from $GLOBALS['TYPO3_CONF_VARS']
1152  // $GLOBALS['TYPO3_CONF_VARS']['T3_SERVICES'][$serviceType][$serviceKey]['priority']
1153  // even the activation is possible (a unix service might be possible on windows for some reasons)
1154  if (is_array($GLOBALS['TYPO3_CONF_VARS']['T3_SERVICES'][$serviceType][$serviceKey])) {
1155  // No check is done here - there might be configuration values only the service type knows about, so
1156  // we pass everything
1157  $GLOBALS['T3_SERVICES'][$serviceType][$serviceKey] = array_merge($GLOBALS['T3_SERVICES'][$serviceType][$serviceKey], $GLOBALS['TYPO3_CONF_VARS']['T3_SERVICES'][$serviceType][$serviceKey]);
1158  }
1159  // OS check
1160  // Empty $os means 'not limited to one OS', therefore a check is not needed
1161  if ($GLOBALS['T3_SERVICES'][$serviceType][$serviceKey]['available'] && $GLOBALS['T3_SERVICES'][$serviceType][$serviceKey]['os'] != '') {
1162  // TYPO3_OS is not yet defined
1163  $os_type = stripos(PHP_OS, 'win') !== false && !stripos(PHP_OS, 'darwin') !== false ? 'WIN' : 'UNIX';
1164  $os = GeneralUtility::trimExplode(',', strtoupper($GLOBALS['T3_SERVICES'][$serviceType][$serviceKey]['os']));
1165  if (!in_array($os_type, $os)) {
1166  self::deactivateService($serviceType, $serviceKey);
1167  }
1168  }
1169  // Convert subtype list to array for quicker access
1170  $GLOBALS['T3_SERVICES'][$serviceType][$serviceKey]['serviceSubTypes'] = [];
1171  $serviceSubTypes = GeneralUtility::trimExplode(',', $info['subtype']);
1172  foreach ($serviceSubTypes as $subtype) {
1173  $GLOBALS['T3_SERVICES'][$serviceType][$serviceKey]['serviceSubTypes'][$subtype] = $subtype;
1174  }
1175  }
1176  }
1177 
1186  public static function findService($serviceType, $serviceSubType = '', $excludeServiceKeys = [])
1187  {
1188  $serviceKey = false;
1189  $serviceInfo = false;
1190  $priority = 0;
1191  $quality = 0;
1192  if (!is_array($excludeServiceKeys)) {
1193  $excludeServiceKeys = GeneralUtility::trimExplode(',', $excludeServiceKeys, true);
1194  }
1195  if (is_array($GLOBALS['T3_SERVICES'][$serviceType])) {
1196  foreach ($GLOBALS['T3_SERVICES'][$serviceType] as $key => $info) {
1197  if (in_array($key, $excludeServiceKeys)) {
1198  continue;
1199  }
1200  // Select a subtype randomly
1201  // Useful to start a service by service key without knowing his subtypes - for testing purposes
1202  if ($serviceSubType == '*') {
1203  $serviceSubType = key($info['serviceSubTypes']);
1204  }
1205  // This matches empty subtype too
1206  if ($info['available'] && ($info['subtype'] == $serviceSubType || $info['serviceSubTypes'][$serviceSubType]) && $info['priority'] >= $priority) {
1207  // Has a lower quality than the already found, therefore we skip this service
1208  if ($info['priority'] == $priority && $info['quality'] < $quality) {
1209  continue;
1210  }
1211  // Check if the service is available
1212  $info['available'] = self::isServiceAvailable($serviceType, $key, $info);
1213  // Still available after exec check?
1214  if ($info['available']) {
1215  $serviceKey = $key;
1216  $priority = $info['priority'];
1217  $quality = $info['quality'];
1218  }
1219  }
1220  }
1221  }
1222  if ($serviceKey) {
1223  $serviceInfo = $GLOBALS['T3_SERVICES'][$serviceType][$serviceKey];
1224  }
1225  return $serviceInfo;
1226  }
1227 
1236  public static function findServiceByKey($serviceKey)
1237  {
1238  if (is_array($GLOBALS['T3_SERVICES'])) {
1239  // Loop on all service types
1240  // NOTE: we don't care about the actual type, we are looking for a specific key
1241  foreach ($GLOBALS['T3_SERVICES'] as $serviceType => $servicesPerType) {
1242  if (isset($servicesPerType[$serviceKey])) {
1243  $serviceDetails = $servicesPerType[$serviceKey];
1244  // Test if service is available
1245  if (self::isServiceAvailable($serviceType, $serviceKey, $serviceDetails)) {
1246  // We have found the right service, return its information
1247  return $serviceDetails;
1248  }
1249  }
1250  }
1251  }
1252  throw new \TYPO3\CMS\Core\Exception('Service not found for key: ' . $serviceKey, 1319217244);
1253  }
1254 
1263  public static function isServiceAvailable($serviceType, $serviceKey, $serviceDetails)
1264  {
1265  // If the service depends on external programs - check if they exists
1266  if (trim($serviceDetails['exec'])) {
1267  $executables = GeneralUtility::trimExplode(',', $serviceDetails['exec'], true);
1268  foreach ($executables as $executable) {
1269  // If at least one executable file is not available, exit early returning FALSE
1270  if (!CommandUtility::checkCommand($executable)) {
1271  self::deactivateService($serviceType, $serviceKey);
1272  return false;
1273  }
1274  }
1275  }
1276  // The service is available
1277  return true;
1278  }
1279 
1287  public static function deactivateService($serviceType, $serviceKey)
1288  {
1289  // ... maybe it's better to move non-available services to a different array??
1290  $GLOBALS['T3_SERVICES'][$serviceType][$serviceKey]['available'] = false;
1291  }
1292 
1293  /**************************************
1294  *
1295  * Adding FRONTEND features
1296  *
1297  ***************************************/
1311  public static function addPlugin($itemArray, $type = 'list_type', $extensionKey = null)
1312  {
1313  $extensionKey = $extensionKey ?: $GLOBALS['_EXTKEY'];
1314  if (!isset($extensionKey)) {
1315  throw new \RuntimeException(
1316  'No extension key could be determined when calling addPlugin()!'
1317  . LF
1318  . 'This method is meant to be called from an ext_tables.php or Configuration/TCA/Overrides file. '
1319  . 'If you call it from Configuration/TCA/Overrides, the extension key needs to be specified as third parameter. '
1320  . 'Calling it from any other place e.g. ext_localconf.php does not work and is not supported.',
1321  1404068038
1322  );
1323  }
1324  if ($extensionKey && !$itemArray[2] && isset($GLOBALS['TYPO3_LOADED_EXT'][$extensionKey]['ext_icon'])) {
1325  $itemArray[2] = 'EXT:' . $extensionKey . '/' . $GLOBALS['TYPO3_LOADED_EXT'][$extensionKey]['ext_icon'];
1326  }
1327  if (is_array($GLOBALS['TCA']['tt_content']['columns']) && is_array($GLOBALS['TCA']['tt_content']['columns'][$type]['config']['items'])) {
1328  foreach ($GLOBALS['TCA']['tt_content']['columns'][$type]['config']['items'] as $k => $v) {
1329  if ((string)$v[1] === (string)$itemArray[1]) {
1330  $GLOBALS['TCA']['tt_content']['columns'][$type]['config']['items'][$k] = $itemArray;
1331  return;
1332  }
1333  }
1334  $GLOBALS['TCA']['tt_content']['columns'][$type]['config']['items'][] = $itemArray;
1335  }
1336  }
1337 
1348  public static function addPiFlexFormValue($piKeyToMatch, $value, $CTypeToMatch = 'list')
1349  {
1350  if (is_array($GLOBALS['TCA']['tt_content']['columns']) && is_array($GLOBALS['TCA']['tt_content']['columns']['pi_flexform']['config']['ds'])) {
1351  $GLOBALS['TCA']['tt_content']['columns']['pi_flexform']['config']['ds'][$piKeyToMatch . ',' . $CTypeToMatch] = $value;
1352  }
1353  }
1354 
1365  public static function addToInsertRecords($table, $content_table = 'tt_content', $content_field = 'records')
1366  {
1367  if (is_array($GLOBALS['TCA'][$content_table]['columns']) && isset($GLOBALS['TCA'][$content_table]['columns'][$content_field]['config']['allowed'])) {
1368  $GLOBALS['TCA'][$content_table]['columns'][$content_field]['config']['allowed'] .= ',' . $table;
1369  }
1370  }
1371 
1400  public static function addPItoST43($key, $_, $suffix = '', $type = 'list_type', $cached = 0)
1401  {
1402  $cN = self::getCN($key);
1403  // General plugin
1404  $pluginContent = trim('
1405 plugin.' . $cN . $suffix . ' = USER' . ($cached ? '' : '_INT') . '
1406 plugin.' . $cN . $suffix . '.userFunc = ' . $cN . $suffix . '->main
1407 ');
1408  self::addTypoScript($key, 'setup', '
1409 # Setting ' . $key . ' plugin TypoScript
1410 ' . $pluginContent);
1411  // Add after defaultContentRendering
1412  switch ($type) {
1413  case 'list_type':
1414  $addLine = 'tt_content.list.20.' . $key . $suffix . ' = < plugin.' . $cN . $suffix;
1415  break;
1416  case 'menu_type':
1417  $addLine = 'tt_content.menu.20.' . $key . $suffix . ' = < plugin.' . $cN . $suffix;
1418  break;
1419  case 'CType':
1420  $addLine = trim('
1421 tt_content.' . $key . $suffix . ' = COA
1422 tt_content.' . $key . $suffix . ' {
1423  10 = < lib.stdheader
1424  20 = < plugin.' . $cN . $suffix . '
1425 }
1426 ');
1427  break;
1428  case 'header_layout':
1429  $addLine = 'lib.stdheader.10.' . $key . $suffix . ' = < plugin.' . $cN . $suffix;
1430  break;
1431  case 'includeLib':
1432  $addLine = 'page.1000 = < plugin.' . $cN . $suffix;
1433  break;
1434  default:
1435  $addLine = '';
1436  }
1437  if ($addLine) {
1438  self::addTypoScript($key, 'setup', '
1439 # Setting ' . $key . ' plugin TypoScript
1440 ' . $addLine . '
1441 ', 'defaultContentRendering');
1442  }
1443  }
1444 
1455  public static function addStaticFile($extKey, $path, $title)
1456  {
1457  if ($extKey && $path && is_array($GLOBALS['TCA']['sys_template']['columns'])) {
1458  $value = str_replace(',', '', 'EXT:' . $extKey . '/' . $path);
1459  $itemArray = [trim($title . ' (' . $extKey . ')'), $value];
1460  $GLOBALS['TCA']['sys_template']['columns']['include_static_file']['config']['items'][] = $itemArray;
1461  }
1462  }
1463 
1473  public static function registerPageTSConfigFile($extKey, $filePath, $title)
1474  {
1475  if (!$extKey) {
1476  throw new \InvalidArgumentException('No extension key given.', 1447789490);
1477  }
1478  if (!$filePath) {
1479  throw new \InvalidArgumentException('No file path given.', 1447789491);
1480  }
1481  if (!isset($GLOBALS['TCA']['pages']['columns']) || !is_array($GLOBALS['TCA']['pages']['columns'])) {
1482  throw new \InvalidArgumentException('No TCA definition for table "pages".', 1447789492);
1483  }
1484 
1485  $value = str_replace(',', '', 'EXT:' . $extKey . '/' . $filePath);
1486  $itemArray = [trim($title . ' (' . $extKey . ')'), $value];
1487  $GLOBALS['TCA']['pages']['columns']['tsconfig_includes']['config']['items'][] = $itemArray;
1488  }
1489 
1498  public static function addTypoScriptSetup($content)
1499  {
1500  self::appendToTypoConfVars('FE', 'defaultTypoScript_setup', '
1501 [GLOBAL]
1502 ' . $content);
1503  }
1504 
1513  public static function addTypoScriptConstants($content)
1514  {
1515  self::appendToTypoConfVars('FE', 'defaultTypoScript_constants', '
1516 [GLOBAL]
1517 ' . $content);
1518  }
1519 
1538  public static function addTypoScript($key, $type, $content, $afterStaticUid = 0)
1539  {
1540  if ($type === 'setup' || $type === 'constants') {
1541  $content = '
1542 
1543 [GLOBAL]
1544 #############################################
1545 ## TypoScript added by extension "' . $key . '"
1546 #############################################
1547 
1548 ' . $content;
1549  if ($afterStaticUid) {
1550  // If 'content (default)' is targeted (static uid 43),
1551  // 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
1552  if ($afterStaticUid === 'defaultContentRendering' || $afterStaticUid == 43) {
1553  $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_' . $type . '.']['defaultContentRendering'] .= $content;
1554  } else {
1555  $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_' . $type . '.'][$afterStaticUid] .= $content;
1556  }
1557  } else {
1558  $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_' . $type] .= $content;
1559  }
1560  }
1561  }
1562 
1563  /***************************************
1564  *
1565  * Internal extension management methods
1566  *
1567  ***************************************/
1576  public static function getExtensionIcon($extensionPath, $returnFullPath = false)
1577  {
1578  $icon = '';
1579  $locationsToCheckFor = [
1580  'Resources/Public/Icons/Extension.png',
1581  'Resources/Public/Icons/Extension.svg',
1582  'Resources/Public/Icons/Extension.gif',
1583  'ext_icon.png',
1584  'ext_icon.svg',
1585  'ext_icon.gif',
1586  ];
1587  foreach ($locationsToCheckFor as $fileLocation) {
1588  if (file_exists($extensionPath . $fileLocation)) {
1589  $icon = $fileLocation;
1590  break;
1591  }
1592  }
1593  return $returnFullPath ? $extensionPath . $icon : $icon;
1594  }
1595 
1608  public static function loadExtLocalconf($allowCaching = true)
1609  {
1610  if ($allowCaching) {
1611  $cacheIdentifier = self::getExtLocalconfCacheIdentifier();
1613  $codeCache = self::getCacheManager()->getCache('cache_core');
1614  if ($codeCache->has($cacheIdentifier)) {
1615  $codeCache->requireOnce($cacheIdentifier);
1616  } else {
1617  self::loadSingleExtLocalconfFiles();
1618  self::createExtLocalconfCacheEntry();
1619  }
1620  } else {
1621  self::loadSingleExtLocalconfFiles();
1622  }
1623  }
1624 
1630  protected static function loadSingleExtLocalconfFiles()
1631  {
1632  // This is the main array meant to be manipulated in the ext_localconf.php files
1633  // In general it is recommended to not rely on it to be globally defined in that
1634  // scope but to use $GLOBALS['TYPO3_CONF_VARS'] instead.
1635  // Nevertheless we define it here as global for backwards compatibility.
1636  global $TYPO3_CONF_VARS;
1637  foreach ($GLOBALS['TYPO3_LOADED_EXT'] as $_EXTKEY => $extensionInformation) {
1638  if ((is_array($extensionInformation) || $extensionInformation instanceof \ArrayAccess) && isset($extensionInformation['ext_localconf.php'])) {
1639  // $_EXTKEY and $_EXTCONF are available in ext_localconf.php
1640  // and are explicitly set in cached file as well
1641  $_EXTCONF = isset($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'][$_EXTKEY]) ? $GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'][$_EXTKEY] : null;
1642  require $extensionInformation['ext_localconf.php'];
1643  }
1644  }
1645  }
1646 
1652  protected static function createExtLocalconfCacheEntry()
1653  {
1654  $extensionInformation = $GLOBALS['TYPO3_LOADED_EXT'];
1655  $phpCodeToCache = [];
1656  // Set same globals as in loadSingleExtLocalconfFiles()
1657  $phpCodeToCache[] = '';
1660  $phpCodeToCache[] = '';
1661  $phpCodeToCache[] = 'global $TYPO3_CONF_VARS, $T3_SERVICES, $T3_VAR;';
1662  $phpCodeToCache[] = '';
1663  // Iterate through loaded extensions and add ext_localconf content
1664  foreach ($extensionInformation as $extensionKey => $extensionDetails) {
1665  if (isset($extensionDetails['ext_localconf.php']) && $extensionDetails['ext_localconf.php']) {
1666  // Include a header per extension to make the cache file more readable
1667  $phpCodeToCache[] = '';
1671  $phpCodeToCache[] = '';
1672  // Set $_EXTKEY and $_EXTCONF for this extension
1673  $phpCodeToCache[] = '$_EXTKEY = \'' . $extensionKey . '\';';
1674  $phpCodeToCache[] = '$_EXTCONF = $GLOBALS[\'TYPO3_CONF_VARS\'][\'EXT\'][\'extConf\'][$_EXTKEY];';
1675  $phpCodeToCache[] = '';
1676  // Add ext_localconf.php content of extension
1677  $phpCodeToCache[] = trim(file_get_contents($extensionDetails['ext_localconf.php']));
1678  $phpCodeToCache[] = '';
1679  $phpCodeToCache[] = '';
1680  }
1681  }
1682  $phpCodeToCache = implode(LF, $phpCodeToCache);
1683  // Remove all start and ending php tags from content
1684  $phpCodeToCache = preg_replace('/<\\?php|\\?>/is', '', $phpCodeToCache);
1685  self::getCacheManager()->getCache('cache_core')->set(self::getExtLocalconfCacheIdentifier(), $phpCodeToCache);
1686  }
1687 
1693  protected static function getExtLocalconfCacheIdentifier()
1694  {
1695  return 'ext_localconf_' . sha1(TYPO3_version . PATH_site . 'extLocalconf' . serialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['runtimeActivatedPackages']));
1696  }
1697 
1712  public static function loadBaseTca($allowCaching = true)
1713  {
1714  if ($allowCaching) {
1715  $cacheIdentifier = static::getBaseTcaCacheIdentifier();
1717  $codeCache = static::getCacheManager()->getCache('cache_core');
1718  if ($codeCache->has($cacheIdentifier)) {
1719  // substr is necessary, because the php frontend wraps php code around the cache value
1720  $cacheData = unserialize(substr($codeCache->get($cacheIdentifier), 6, -2));
1721  $GLOBALS['TCA'] = $cacheData['tca'];
1722  GeneralUtility::setSingletonInstance(CategoryRegistry::class, $cacheData['categoryRegistry']);
1723  } else {
1724  static::buildBaseTcaFromSingleFiles();
1725  static::createBaseTcaCacheFile();
1726  }
1727  } else {
1728  static::buildBaseTcaFromSingleFiles();
1729  }
1730  }
1731 
1740  protected static function buildBaseTcaFromSingleFiles()
1741  {
1742  $GLOBALS['TCA'] = [];
1743 
1744  $activePackages = static::$packageManager->getActivePackages();
1745 
1746  // First load "full table" files from Configuration/TCA
1747  foreach ($activePackages as $package) {
1748  $tcaConfigurationDirectory = $package->getPackagePath() . 'Configuration/TCA';
1749  if (is_dir($tcaConfigurationDirectory)) {
1750  $files = scandir($tcaConfigurationDirectory);
1751  foreach ($files as $file) {
1752  if (
1753  is_file($tcaConfigurationDirectory . '/' . $file)
1754  && ($file !== '.')
1755  && ($file !== '..')
1756  && (substr($file, -4, 4) === '.php')
1757  ) {
1758  $tcaOfTable = require($tcaConfigurationDirectory . '/' . $file);
1759  if (is_array($tcaOfTable)) {
1760  // TCA table name is filename without .php suffix, eg 'sys_notes', not 'sys_notes.php'
1761  $tcaTableName = substr($file, 0, -4);
1762  $GLOBALS['TCA'][$tcaTableName] = $tcaOfTable;
1763  }
1764  }
1765  }
1766  }
1767  }
1768 
1769  // Apply category stuff
1770  CategoryRegistry::getInstance()->applyTcaForPreRegisteredTables();
1771 
1772  // Execute override files from Configuration/TCA/Overrides
1773  foreach ($activePackages as $package) {
1774  $tcaOverridesPathForPackage = $package->getPackagePath() . 'Configuration/TCA/Overrides';
1775  if (is_dir($tcaOverridesPathForPackage)) {
1776  $files = scandir($tcaOverridesPathForPackage);
1777  foreach ($files as $file) {
1778  if (
1779  is_file($tcaOverridesPathForPackage . '/' . $file)
1780  && ($file !== '.')
1781  && ($file !== '..')
1782  && (substr($file, -4, 4) === '.php')
1783  ) {
1784  require($tcaOverridesPathForPackage . '/' . $file);
1785  }
1786  }
1787  }
1788  }
1789 
1790  // TCA migration
1791  // @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.
1792  $tcaMigration = GeneralUtility::makeInstance(TcaMigration::class);
1793  $GLOBALS['TCA'] = $tcaMigration->migrate($GLOBALS['TCA']);
1794  $messages = $tcaMigration->getMessages();
1795  if (!empty($messages)) {
1796  $context = 'Automatic TCA migration done during bootstrap. Please adapt TCA accordingly, these migrations'
1797  . ' will be removed. The backend module "Configuration -> TCA" shows the modified values.'
1798  . ' Please adapt these areas:';
1799  array_unshift($messages, $context);
1800  GeneralUtility::deprecationLog(implode(LF, $messages));
1801  }
1802 
1803  static::emitTcaIsBeingBuiltSignal($GLOBALS['TCA']);
1804  }
1805 
1816  protected static function emitTcaIsBeingBuiltSignal(array $tca)
1817  {
1818  list($tca) = static::getSignalSlotDispatcher()->dispatch(__CLASS__, 'tcaIsBeingBuilt', [$tca]);
1819  $GLOBALS['TCA'] = $tca;
1820  }
1821 
1828  protected static function createBaseTcaCacheFile()
1829  {
1831  $codeCache = self::getCacheManager()->getCache('cache_core');
1832  $codeCache->set(static::getBaseTcaCacheIdentifier(), serialize(['tca' => $GLOBALS['TCA'], 'categoryRegistry' => CategoryRegistry::getInstance()]));
1833  }
1834 
1840  protected static function getBaseTcaCacheIdentifier()
1841  {
1842  return 'tca_base_' . sha1(TYPO3_version . PATH_site . 'tca_with_category_registry' . serialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['runtimeActivatedPackages']));
1843  }
1844 
1857  public static function loadExtTables($allowCaching = true)
1858  {
1859  if ($allowCaching && !self::$extTablesWasReadFromCacheOnce) {
1860  self::$extTablesWasReadFromCacheOnce = true;
1861  $cacheIdentifier = self::getExtTablesCacheIdentifier();
1863  $codeCache = self::getCacheManager()->getCache('cache_core');
1864  if ($codeCache->has($cacheIdentifier)) {
1865  $codeCache->requireOnce($cacheIdentifier);
1866  } else {
1867  self::loadSingleExtTablesFiles();
1868  self::createExtTablesCacheEntry();
1869  }
1870  } else {
1871  self::loadSingleExtTablesFiles();
1872  }
1873  }
1874 
1880  protected static function loadSingleExtTablesFiles()
1881  {
1882  // In general it is recommended to not rely on it to be globally defined in that
1883  // scope, but we can not prohibit this without breaking backwards compatibility
1884  global $T3_SERVICES, $T3_VAR, $TYPO3_CONF_VARS;
1885  global $TBE_MODULES, $TBE_MODULES_EXT, $TCA;
1886  global $PAGES_TYPES, $TBE_STYLES;
1887  global $_EXTKEY;
1888  // Load each ext_tables.php file of loaded extensions
1889  foreach ($GLOBALS['TYPO3_LOADED_EXT'] as $_EXTKEY => $extensionInformation) {
1890  if ((is_array($extensionInformation) || $extensionInformation instanceof \ArrayAccess) && $extensionInformation['ext_tables.php']) {
1891  // $_EXTKEY and $_EXTCONF are available in ext_tables.php
1892  // and are explicitly set in cached file as well
1893  $_EXTCONF = $GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'][$_EXTKEY];
1894  require $extensionInformation['ext_tables.php'];
1895  }
1896  }
1897  }
1898 
1904  protected static function createExtTablesCacheEntry()
1905  {
1906  $extensionInformation = $GLOBALS['TYPO3_LOADED_EXT'];
1907  $phpCodeToCache = [];
1908  // Set same globals as in loadSingleExtTablesFiles()
1909  $phpCodeToCache[] = '';
1912  $phpCodeToCache[] = '';
1913  $phpCodeToCache[] = 'global $T3_SERVICES, $T3_VAR, $TYPO3_CONF_VARS;';
1914  $phpCodeToCache[] = 'global $TBE_MODULES, $TBE_MODULES_EXT, $TCA;';
1915  $phpCodeToCache[] = 'global $PAGES_TYPES, $TBE_STYLES;';
1916  $phpCodeToCache[] = 'global $_EXTKEY;';
1917  $phpCodeToCache[] = '';
1918  // Iterate through loaded extensions and add ext_tables content
1919  foreach ($extensionInformation as $extensionKey => $extensionDetails) {
1920  if (isset($extensionDetails['ext_tables.php']) && $extensionDetails['ext_tables.php']) {
1921  // Include a header per extension to make the cache file more readable
1922  $phpCodeToCache[] = '';
1926  $phpCodeToCache[] = '';
1927  // Set $_EXTKEY and $_EXTCONF for this extension
1928  $phpCodeToCache[] = '$_EXTKEY = \'' . $extensionKey . '\';';
1929  $phpCodeToCache[] = '$_EXTCONF = $GLOBALS[\'TYPO3_CONF_VARS\'][\'EXT\'][\'extConf\'][$_EXTKEY];';
1930  $phpCodeToCache[] = '';
1931  // Add ext_tables.php content of extension
1932  $phpCodeToCache[] = trim(file_get_contents($extensionDetails['ext_tables.php']));
1933  $phpCodeToCache[] = '';
1934  }
1935  }
1936  $phpCodeToCache = implode(LF, $phpCodeToCache);
1937  // Remove all start and ending php tags from content
1938  $phpCodeToCache = preg_replace('/<\\?php|\\?>/is', '', $phpCodeToCache);
1939  self::getCacheManager()->getCache('cache_core')->set(self::getExtTablesCacheIdentifier(), $phpCodeToCache);
1940  }
1941 
1947  protected static function getExtTablesCacheIdentifier()
1948  {
1949  return 'ext_tables_' . sha1(TYPO3_version . PATH_site . 'extTables' . serialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['runtimeActivatedPackages']));
1950  }
1951 
1967  public static function removeCacheFiles()
1968  {
1969  self::getCacheManager()->flushCachesInGroup('system');
1970  }
1971 
1977  public static function getLoadedExtensionListArray()
1978  {
1979  return array_keys(static::$packageManager->getActivePackages());
1980  }
1981 
1992  public static function loadExtension($extensionKey)
1993  {
1994  if (static::$packageManager->isPackageActive($extensionKey)) {
1995  throw new \RuntimeException('Extension already loaded', 1342345486);
1996  }
1997  static::$packageManager->activatePackage($extensionKey);
1998  }
1999 
2010  public static function unloadExtension($extensionKey)
2011  {
2012  if (!static::$packageManager->isPackageActive($extensionKey)) {
2013  throw new \RuntimeException('Extension not loaded', 1342345487);
2014  }
2015  static::$packageManager->deactivatePackage($extensionKey);
2016  }
2017 
2030  public static function makeCategorizable($extensionKey, $tableName, $fieldName = 'categories', array $options = [], $override = false)
2031  {
2032  // Update the category registry
2033  $result = CategoryRegistry::getInstance()->add($extensionKey, $tableName, $fieldName, $options, $override);
2034  if ($result === false) {
2035  $message = CategoryRegistry::class . ': no category registered for table "%s". Key was already registered.';
2037  $logger = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Log\LogManager::class)->getLogger(__CLASS__);
2038  $logger->warning(
2039  sprintf($message, $tableName)
2040  );
2041  }
2042  }
2043 }
static addPlugin($itemArray, $type= 'list_type', $extensionKey=null)
static setPackageManager(PackageManager $packageManager)
static checkCommand($cmd, $handler= '')
static addToInsertRecords($table, $content_table= 'tt_content', $content_field= 'records')
static addModule($main, $sub= '', $position= '', $path=null, $moduleConfiguration=[])
static addToAllTCAtypes($table, $newFieldsString, $typeList= '', $position= '')
static addService($extKey, $serviceType, $serviceKey, $info)
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
static registerExtDirectComponent($endpointName, $callbackClass, $moduleName=null, $accessLevel=null)
static filterByValueRecursive($needle= '', array $haystack=[])
static addTcaSelectItem($table, $field, array $item, $relativeToField= '', $relativePosition= '')
static addTypoScript($key, $type, $content, $afterStaticUid=0)
static addPiFlexFormValue($piKeyToMatch, $value, $CTypeToMatch= 'list')
static isServiceAvailable($serviceType, $serviceKey, $serviceDetails)
static executePositionedStringInsertion($list, $insertionList, $insertionPosition= '')
static insertModuleFunction($modname, $className, $classPath=null, $title, $MM_key= 'function', $WS= '')
static addNavigationComponent($module, $componentId, $extensionKey=null)
static addFieldsToAllPalettesOfField($table, $field, $addFields, $insertionPosition= '')
static generateItemList(array $items, $useRawData=false)
static setSingletonInstance($className, SingletonInterface $instance)
static getFileFieldTCAConfig($fieldName, array $customSettingOverride=[], $allowedFileExtensions= '', $disallowedFileExtensions= '')
$signalSlotDispatcher
static findService($serviceType, $serviceSubType= '', $excludeServiceKeys=[])
static mergeRecursiveWithOverrule(array &$original, array $overrule, $addKeys=true, $includeEmptyValues=true, $enableUnsetFeature=true)
if(TYPO3_MODE=== 'BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
static makeInstance($className,...$constructorArguments)
static addExtJSModule($extensionName, $mainModuleName, $subModuleName= '', $position= '', array $moduleConfiguration=[])
static getFileAbsFileName($filename, $_=null, $_2=null)
static addPItoST43($key, $_, $suffix= '', $type= 'list_type', $cached=0)
static addFieldsToUserSettings($addFields, $insertionPosition= '')
static getExtensionIcon($extensionPath, $returnFullPath=false)
static addFieldsToPalette($table, $palette, $addFields, $insertionPosition= '')
static registerAjaxHandler($ajaxId, $callbackMethod, $csrfTokenCheck=true)