TYPO3 CMS  TYPO3_7-6
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 
20 
28 {
32  protected static $extensionKeyMap;
33 
44  protected static $extTablesWasReadFromCacheOnce = false;
45 
49  protected static $packageManager;
50 
59  {
60  static::$packageManager = $packageManager;
61  }
62 
66  protected static $cacheManager;
67 
73  protected static function getCacheManager()
74  {
75  if (static::$cacheManager === null) {
76  static::$cacheManager = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Cache\CacheManager::class);
77  }
78  return static::$cacheManager;
79  }
80 
84  protected static $signalSlotDispatcher;
85 
91  protected static function getSignalSlotDispatcher()
92  {
93  if (static::$signalSlotDispatcher === null) {
94  static::$signalSlotDispatcher = GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher::class);
95  }
97  }
98 
99  /**************************************
100  *
101  * PATHS and other evaluation
102  *
103  ***************************************/
112  public static function isLoaded($key, $exitOnError = false)
113  {
114  $isLoaded = static::$packageManager->isPackageActive($key);
115  if ($exitOnError && !$isLoaded) {
116  throw new \BadFunctionCallException('TYPO3 Fatal Error: Extension "' . $key . '" is not loaded!', 1270853910);
117  }
118  return $isLoaded;
119  }
120 
129  public static function extPath($key, $script = '')
130  {
131  if (!static::$packageManager->isPackageActive($key)) {
132  throw new \BadFunctionCallException('TYPO3 Fatal Error: Extension key "' . $key . '" is NOT loaded!', 1365429656);
133  }
134  return static::$packageManager->getPackage($key)->getPackagePath() . $script;
135  }
136 
147  public static function extRelPath($key)
148  {
149  if (!static::$packageManager->isPackageActive($key)) {
150  throw new \BadFunctionCallException('TYPO3 Fatal Error: Extension key "' . $key . '" is NOT loaded!', 1365429673);
151  }
152  $relativePathToSiteRoot = self::siteRelPath($key);
153  $typo3MainDirLength = strlen(TYPO3_mainDir);
154  if (substr($relativePathToSiteRoot, 0, $typo3MainDirLength) === TYPO3_mainDir) {
155  $relativePathToSiteRoot = substr($relativePathToSiteRoot, $typo3MainDirLength);
156  } else {
157  $relativePathToSiteRoot = '../' . $relativePathToSiteRoot;
158  }
159  return $relativePathToSiteRoot;
160  }
161 
170  public static function siteRelPath($key)
171  {
172  return PathUtility::stripPathSitePrefix(self::extPath($key));
173  }
174 
182  public static function getCN($key)
183  {
184  return strpos($key, 'user_') === 0 ? 'user_' . str_replace('_', '', substr($key, 5)) : 'tx_' . str_replace('_', '', $key);
185  }
186 
193  public static function getExtensionKeyByPrefix($prefix)
194  {
195  $result = false;
196  // Build map of short keys referencing to real keys:
197  if (!isset(self::$extensionKeyMap)) {
198  self::$extensionKeyMap = [];
199  foreach (static::$packageManager->getActivePackages() as $package) {
200  $shortKey = str_replace('_', '', $package->getPackageKey());
201  self::$extensionKeyMap[$shortKey] = $package->getPackageKey();
202  }
203  }
204  // Lookup by the given short key:
205  $parts = explode('_', $prefix);
206  if (isset(self::$extensionKeyMap[$parts[1]])) {
207  $result = self::$extensionKeyMap[$parts[1]];
208  }
209  return $result;
210  }
211 
217  public static function clearExtensionKeyMap()
218  {
219  self::$extensionKeyMap = null;
220  }
221 
232  public static function getExtensionVersion($key)
233  {
234  if (!is_string($key) || empty($key)) {
235  throw new \InvalidArgumentException('Extension key must be a non-empty string.', 1294586096);
236  }
237  if (!static::isLoaded($key)) {
238  return '';
239  }
240  $version = static::$packageManager->getPackage($key)->getPackageMetaData()->getVersion();
241  if (empty($version)) {
242  throw new \TYPO3\CMS\Core\Package\Exception('Version number in composer manifest of package "' . $key . '" is missing or invalid', 1395614959);
243  }
244  return $version;
245  }
246 
247  /**************************************
248  *
249  * Adding BACKEND features
250  * (related to core features)
251  *
252  ***************************************/
265  public static function addTCAcolumns($table, $columnArray, $addTofeInterface = false)
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  if ($addTofeInterface) {
271  $message = 'Usage of feInterface is no longer part of the TYPO3 CMS Core. Please check EXT:%s for table "%s".';
272  GeneralUtility::deprecationLog(sprintf($message, $GLOBALS['_EXTKEY'], $table));
273  }
274  }
275  }
276 
291  public static function addToAllTCAtypes($table, $newFieldsString, $typeList = '', $position = '')
292  {
293  $newFieldsString = trim($newFieldsString);
294  if ($newFieldsString === '' || !is_array($GLOBALS['TCA'][$table]['types'])) {
295  return;
296  }
297  list($positionIdentifier, $entityName) = GeneralUtility::trimExplode(':', $position);
298  $palettesChanged = [];
299 
300  foreach ($GLOBALS['TCA'][$table]['types'] as $type => &$typeDetails) {
301  // skip if we don't want to add the field for this type
302  if ($typeList !== '' && !GeneralUtility::inList($typeList, $type)) {
303  continue;
304  }
305  // skip if fields were already added
306  if (!isset($typeDetails['showitem'])) {
307  continue;
308  }
309 
310  $fieldArray = GeneralUtility::trimExplode(',', $typeDetails['showitem'], true);
311  if (in_array($newFieldsString, $fieldArray, true)) {
312  continue;
313  }
314 
315  $fieldExists = false;
316  $newPosition = '';
317  if (is_array($GLOBALS['TCA'][$table]['palettes'])) {
318  // Get the palette names used in current showitem
319  $paletteCount = preg_match_all('/(?:^|,) # Line start or a comma
320  (?:
321  \\s*\\-\\-palette\\-\\-;[^;]*;([^,$]*)| # --palette--;label;paletteName
322  \\s*\\b[^;,]+\\b(?:;[^;]*;([^;,]+);?[^;,]*;?)?[^,]* # @deprecated since TYPO3 CMS 7: field;label;paletteName[;options[;colors]]
323  )/x', $typeDetails['showitem'], $paletteMatches);
324  if ($paletteCount > 0) {
325  $paletteNames = array_filter(array_merge($paletteMatches[1], $paletteMatches[2]));
326  if (!empty($paletteNames)) {
327  foreach ($paletteNames as $paletteName) {
328  $palette = $GLOBALS['TCA'][$table]['palettes'][$paletteName];
329  switch ($positionIdentifier) {
330  case 'after':
331  case 'before':
332  if (preg_match('/\\b' . $entityName . '\\b/', $palette['showitem']) > 0) {
333  $newPosition = $positionIdentifier . ':--palette--;;' . $paletteName;
334  }
335  break;
336  case 'replace':
337  // check if fields have been added to palette before
338  if (isset($palettesChanged[$paletteName])) {
339  $fieldExists = true;
340  continue 2;
341  }
342  if (preg_match('/\\b' . $entityName . '\\b/', $palette['showitem']) > 0) {
343  self::addFieldsToPalette($table, $paletteName, $newFieldsString, $position);
344  // Memorize that we already changed this palette, in case other types also use it
345  $palettesChanged[$paletteName] = true;
346  $fieldExists = true;
347  continue 2;
348  }
349  break;
350  default:
351  // Intentionally left blank
352  }
353  }
354  }
355  }
356  }
357  if ($fieldExists === false) {
358  $typeDetails['showitem'] = self::executePositionedStringInsertion(
359  $typeDetails['showitem'],
360  $newFieldsString,
361  $newPosition !== '' ? $newPosition : $position
362  );
363  }
364  }
365  unset($typeDetails);
366  }
367 
411  public static function addFieldsToAllPalettesOfField($table, $field, $addFields, $insertionPosition = '')
412  {
413  if (!isset($GLOBALS['TCA'][$table]['columns'][$field])) {
414  return;
415  }
416  if (!is_array($GLOBALS['TCA'][$table]['types'])) {
417  return;
418  }
419 
420  // Iterate through all types and search for the field that defines the palette to be extended
421  foreach ($GLOBALS['TCA'][$table]['types'] as $typeName => $typeArray) {
422  // Continue if types has no showitem at all or if requested field is not in it
423  if (!isset($typeArray['showitem']) || strpos($typeArray['showitem'], $field) === false) {
424  continue;
425  }
426  $fieldArrayWithOptions = GeneralUtility::trimExplode(',', $typeArray['showitem']);
427  // Find the field we're handling
428  $newFieldStringArray = [];
429  foreach ($fieldArrayWithOptions as $fieldNumber => $fieldString) {
430  $newFieldStringArray[] = $fieldString;
431  $fieldArray = GeneralUtility::trimExplode(';', $fieldString);
432  if ($fieldArray[0] !== $field) {
433  continue;
434  }
435  if (
436  isset($fieldArrayWithOptions[$fieldNumber + 1])
437  && StringUtility::beginsWith($fieldArrayWithOptions[$fieldNumber + 1], '--palette--')
438  ) {
439  // Match for $field and next field is a palette - add fields to this one
440  $paletteName = GeneralUtility::trimExplode(';', $fieldArrayWithOptions[$fieldNumber + 1]);
441  $paletteName = $paletteName[2];
442  self::addFieldsToPalette($table, $paletteName, $addFields, $insertionPosition);
443  } else {
444  // Match for $field but next field is no palette - create a new one
445  $newPaletteName = 'generatedFor-' . $field;
446  self::addFieldsToPalette($table, 'generatedFor-' . $field, $addFields, $insertionPosition);
447  $newFieldStringArray[] = '--palette--;;' . $newPaletteName;
448  }
449  }
450  $GLOBALS['TCA'][$table]['types'][$typeName]['showitem'] = implode(', ', $newFieldStringArray);
451  }
452  }
453 
464  public static function addFieldsToPalette($table, $palette, $addFields, $insertionPosition = '')
465  {
466  if (isset($GLOBALS['TCA'][$table])) {
467  $paletteData = &$GLOBALS['TCA'][$table]['palettes'][$palette];
468  // If palette already exists, merge the data:
469  if (is_array($paletteData)) {
470  $paletteData['showitem'] = self::executePositionedStringInsertion($paletteData['showitem'], $addFields, $insertionPosition);
471  } else {
472  $paletteData['showitem'] = self::removeDuplicatesForInsertion($addFields);
473  }
474  }
475  }
476 
505  public static function addTcaSelectItem($table, $field, array $item, $relativeToField = '', $relativePosition = '')
506  {
507  if (!is_string($table)) {
508  throw new \InvalidArgumentException('Given table is of type "' . gettype($table) . '" but a string is expected.', 1303236963);
509  }
510  if (!is_string($field)) {
511  throw new \InvalidArgumentException('Given field is of type "' . gettype($field) . '" but a string is expected.', 1303236964);
512  }
513  if (!is_string($relativeToField)) {
514  throw new \InvalidArgumentException('Given relative field is of type "' . gettype($relativeToField) . '" but a string is expected.', 1303236965);
515  }
516  if (!is_string($relativePosition)) {
517  throw new \InvalidArgumentException('Given relative position is of type "' . gettype($relativePosition) . '" but a string is expected.', 1303236966);
518  }
519  if ($relativePosition !== '' && $relativePosition !== 'before' && $relativePosition !== 'after' && $relativePosition !== 'replace') {
520  throw new \InvalidArgumentException('Relative position must be either empty or one of "before", "after", "replace".', 1303236967);
521  }
522  if (!is_array($GLOBALS['TCA'][$table]['columns'][$field]['config']['items'])) {
523  throw new \RuntimeException('Given select field item list was not found.', 1303237468);
524  }
525  // Make sure item keys are integers
526  $GLOBALS['TCA'][$table]['columns'][$field]['config']['items'] = array_values($GLOBALS['TCA'][$table]['columns'][$field]['config']['items']);
527  if ($relativePosition !== '') {
528  // Insert at specified position
529  $matchedPosition = ArrayUtility::filterByValueRecursive($relativeToField, $GLOBALS['TCA'][$table]['columns'][$field]['config']['items']);
530  if (!empty($matchedPosition)) {
531  $relativeItemKey = key($matchedPosition);
532  if ($relativePosition === 'replace') {
533  $GLOBALS['TCA'][$table]['columns'][$field]['config']['items'][$relativeItemKey] = $item;
534  } else {
535  if ($relativePosition === 'before') {
536  $offset = $relativeItemKey;
537  } else {
538  $offset = $relativeItemKey + 1;
539  }
540  array_splice($GLOBALS['TCA'][$table]['columns'][$field]['config']['items'], $offset, 0, [0 => $item]);
541  }
542  } else {
543  // Insert at new item at the end of the array if relative position was not found
544  $GLOBALS['TCA'][$table]['columns'][$field]['config']['items'][] = $item;
545  }
546  } else {
547  // Insert at new item at the end of the array
548  $GLOBALS['TCA'][$table]['columns'][$field]['config']['items'][] = $item;
549  }
550  }
551 
562  public static function getFileFieldTCAConfig($fieldName, array $customSettingOverride = [], $allowedFileExtensions = '', $disallowedFileExtensions = '')
563  {
564  $fileFieldTCAConfig = [
565  'type' => 'inline',
566  'foreign_table' => 'sys_file_reference',
567  'foreign_field' => 'uid_foreign',
568  'foreign_sortby' => 'sorting_foreign',
569  'foreign_table_field' => 'tablenames',
570  'foreign_match_fields' => [
571  'fieldname' => $fieldName
572  ],
573  'foreign_label' => 'uid_local',
574  'foreign_selector' => 'uid_local',
575  'foreign_selector_fieldTcaOverride' => [
576  'config' => [
577  'appearance' => [
578  'elementBrowserType' => 'file',
579  'elementBrowserAllowed' => $allowedFileExtensions
580  ]
581  ]
582  ],
583  'filter' => [
584  [
585  'userFunc' => \TYPO3\CMS\Core\Resource\Filter\FileExtensionFilter::class . '->filterInlineChildren',
586  'parameters' => [
587  'allowedFileExtensions' => $allowedFileExtensions,
588  'disallowedFileExtensions' => $disallowedFileExtensions
589  ]
590  ]
591  ],
592  'appearance' => [
593  'useSortable' => true,
594  'headerThumbnail' => [
595  'field' => 'uid_local',
596  'width' => '45',
597  'height' => '45c',
598  ],
599  'showPossibleLocalizationRecords' => false,
600  'showRemovedLocalizationRecords' => false,
601  'showSynchronizationLink' => false,
602  'showAllLocalizationLink' => false,
603 
604  'enabledControls' => [
605  'info' => true,
606  'new' => false,
607  'dragdrop' => true,
608  'sort' => false,
609  'hide' => true,
610  'delete' => true,
611  'localize' => true,
612  ],
613  ],
614  'behaviour' => [
615  'localizationMode' => 'select',
616  'localizeChildrenAtParentLocalization' => true,
617  ],
618  ];
619  ArrayUtility::mergeRecursiveWithOverrule($fileFieldTCAConfig, $customSettingOverride);
620  return $fileFieldTCAConfig;
621  }
622 
632  public static function addFieldsToUserSettings($addFields, $insertionPosition = '')
633  {
634  $GLOBALS['TYPO3_USER_SETTINGS']['showitem'] = self::executePositionedStringInsertion($GLOBALS['TYPO3_USER_SETTINGS']['showitem'], $addFields, $insertionPosition);
635  }
636 
654  protected static function executePositionedStringInsertion($list, $insertionList, $insertionPosition = '')
655  {
656  $list = $newList = trim($list, ", \t\n\r\0\x0B");
657 
658  list($location, $positionName) = GeneralUtility::trimExplode(':', $insertionPosition, false, 2);
659 
660  if ($location !== 'replace') {
661  $insertionList = self::removeDuplicatesForInsertion($insertionList, $list);
662  }
663 
664  if ($insertionList === '') {
665  return $list;
666  }
667  if ($list === '') {
668  return $insertionList;
669  }
670  if ($insertionPosition === '') {
671  return $list . ', ' . $insertionList;
672  }
673 
674  // The $insertPosition may be a palette: after:--palette--;;title
675  // In the $list the palette may contain a LLL string in between the ;;
676  // Adjust the regex to match that
677  $positionName = preg_quote($positionName, '/');
678  if (strpos($positionName, ';;') !== false) {
679  $positionName = str_replace(';;', ';[^;]*;', $positionName);
680  }
681 
682  $pattern = ('/(^|,\\s*)(' . $positionName . ')(;[^,$]+)?(,|$)/');
683  switch ($location) {
684  case 'after':
685  $newList = preg_replace($pattern, '$1$2$3, ' . $insertionList . '$4', $list);
686  break;
687  case 'before':
688  $newList = preg_replace($pattern, '$1' . $insertionList . ', $2$3$4', $list);
689  break;
690  case 'replace':
691  $newList = preg_replace($pattern, '$1' . $insertionList . '$4', $list);
692  break;
693  default:
694  }
695 
696  // When preg_replace did not replace anything; append the $insertionList.
697  if ($list === $newList) {
698  return $list . ', ' . $insertionList;
699  }
700  return $newList;
701  }
702 
718  protected static function removeDuplicatesForInsertion($insertionList, $list = '')
719  {
720  $insertionListParts = preg_split('/\\s*,\\s*/', $insertionList);
721  $listMatches = [];
722  if ($list !== '') {
723  preg_match_all('/(?:^|,)\\s*\\b([^;,]+)\\b[^,]*/', $list, $listMatches);
724  $listMatches = $listMatches[1];
725  }
726 
727  $cleanInsertionListParts = [];
728  foreach ($insertionListParts as $fieldName) {
729  $fieldNameParts = explode(';', $fieldName, 2);
730  $cleanFieldName = $fieldNameParts[0];
731  if (
732  $cleanFieldName === '--linebreak--'
733  || (
734  !in_array($cleanFieldName, $cleanInsertionListParts, true)
735  && !in_array($cleanFieldName, $listMatches, true)
736  )
737  ) {
738  $cleanInsertionListParts[] = $fieldName;
739  }
740  }
741  return implode(', ', $cleanInsertionListParts);
742  }
743 
750  protected static function explodeItemList($itemList)
751  {
752  $items = [];
753  $itemParts = GeneralUtility::trimExplode(',', $itemList, true);
754  foreach ($itemParts as $itemPart) {
755  $itemDetails = GeneralUtility::trimExplode(';', $itemPart, false, 5);
756  $key = $itemDetails[0];
757  if (strpos($key, '--') !== false) {
758  // 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!
759  $key .= count($items);
760  }
761  if (!isset($items[$key])) {
762  $items[$key] = [
763  'rawData' => $itemPart,
764  'details' => []
765  ];
766  $details = [0 => 'field', 1 => 'label', 2 => 'palette'];
767  foreach ($details as $id => $property) {
768  $items[$key]['details'][$property] = isset($itemDetails[$id]) ? $itemDetails[$id] : '';
769  }
770  }
771  }
772  return $items;
773  }
774 
783  protected static function generateItemList(array $items, $useRawData = false)
784  {
785  $itemParts = [];
786  foreach ($items as $item => $itemDetails) {
787  if (strpos($item, '--') !== false) {
788  // 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.
789  $item = str_replace([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], '', $item);
790  }
791  if ($useRawData) {
792  $itemParts[] = $itemDetails['rawData'];
793  } else {
794  if (count($itemDetails['details']) > 1) {
795  $details = ['palette', 'label', 'field'];
796  $elements = [];
797  $addEmpty = false;
798  foreach ($details as $property) {
799  if ($itemDetails['details'][$property] !== '' || $addEmpty) {
800  $addEmpty = true;
801  array_unshift($elements, $itemDetails['details'][$property]);
802  }
803  }
804  $item = implode(';', $elements);
805  }
806  $itemParts[] = $item;
807  }
808  }
809  return implode(', ', $itemParts);
810  }
811 
820  public static function allowTableOnStandardPages($table)
821  {
822  $GLOBALS['PAGES_TYPES']['default']['allowedTables'] .= ',' . $table;
823  }
824 
837  public static function addExtJSModule($extensionName, $mainModuleName, $subModuleName = '', $position = '', array $moduleConfiguration = [])
838  {
839  if (empty($extensionName)) {
840  throw new \InvalidArgumentException('The extension name must not be empty', 1325938973);
841  }
842  $extensionKey = GeneralUtility::camelCaseToLowerCaseUnderscored($extensionName);
843  $extensionName = str_replace(' ', '', ucwords(str_replace('_', ' ', $extensionName)));
844  $defaultModuleConfiguration = [
845  'access' => 'admin',
846  'icon' => self::extRelPath('backend') . 'Resources/Public/Images/Logo.png',
847  'labels' => '',
848  'extRelPath' => self::extRelPath($extensionKey) . 'Classes/'
849  ];
850  // Add mandatory parameter to use new pagetree
851  if ($mainModuleName === 'web') {
852  $defaultModuleConfiguration['navigationComponentId'] = 'typo3-pagetree';
853  }
854  ArrayUtility::mergeRecursiveWithOverrule($defaultModuleConfiguration, $moduleConfiguration);
855  $moduleConfiguration = $defaultModuleConfiguration;
856  if ($subModuleName !== '') {
857  $moduleSignature = $mainModuleName . '_' . $subModuleName;
858  } else {
859  $moduleSignature = $mainModuleName;
860  }
861  $moduleConfiguration['name'] = $moduleSignature;
862  $moduleConfiguration['script'] = 'extjspaneldummy.html';
863  $moduleConfiguration['extensionName'] = $extensionName;
864  $moduleConfiguration['configureModuleFunction'] = [self::class, 'configureModule'];
865  $GLOBALS['TBE_MODULES']['_configuration'][$moduleSignature] = $moduleConfiguration;
866  self::addModule($mainModuleName, $subModuleName, $position);
867  }
868 
877  public static function configureModule($moduleSignature, $modulePath)
878  {
879  $moduleConfiguration = $GLOBALS['TBE_MODULES']['_configuration'][$moduleSignature];
880  $iconPathAndFilename = $moduleConfiguration['icon'];
881  if (substr($iconPathAndFilename, 0, 4) === 'EXT:') {
882  list($extensionKey, $relativePath) = explode('/', substr($iconPathAndFilename, 4), 2);
883  $iconPathAndFilename = self::extPath($extensionKey) . $relativePath;
884  }
885  // @todo skin support
886  $moduleLabels = [
887  'tabs_images' => [
888  'tab' => $iconPathAndFilename
889  ],
890  'labels' => [
891  'tablabel' => $GLOBALS['LANG']->sL($moduleConfiguration['labels'] . ':mlang_labels_tablabel'),
892  'tabdescr' => $GLOBALS['LANG']->sL($moduleConfiguration['labels'] . ':mlang_labels_tabdescr')
893  ],
894  'tabs' => [
895  'tab' => $GLOBALS['LANG']->sL($moduleConfiguration['labels'] . ':mlang_tabs_tab')
896  ]
897  ];
898  $GLOBALS['LANG']->addModuleLabels($moduleLabels, $moduleSignature . '_');
899  return $moduleConfiguration;
900  }
901 
913  public static function addModule($main, $sub = '', $position = '', $path = '', $moduleConfiguration = [])
914  {
915  // If there is already a main module by this name:
916  // Adding the submodule to the correct position:
917  if (isset($GLOBALS['TBE_MODULES'][$main]) && $sub) {
918  list($place, $modRef) = GeneralUtility::trimExplode(':', $position, true);
919  $modules = ',' . $GLOBALS['TBE_MODULES'][$main] . ',';
920  if ($place === null || ($modRef !== null && !GeneralUtility::inList($modules, $modRef))) {
921  $place = 'bottom';
922  }
923  $modRef = ',' . $modRef . ',';
924  if (!GeneralUtility::inList($modules, $sub)) {
925  switch (strtolower($place)) {
926  case 'after':
927  $modules = str_replace($modRef, $modRef . $sub . ',', $modules);
928  break;
929  case 'before':
930  $modules = str_replace($modRef, ',' . $sub . $modRef, $modules);
931  break;
932  case 'top':
933  $modules = $sub . $modules;
934  break;
935  case 'bottom':
936  default:
937  $modules = $modules . $sub;
938  }
939  }
940  // Re-inserting the submodule list:
941  $GLOBALS['TBE_MODULES'][$main] = trim($modules, ',');
942  } else {
943  // Create new main modules with only one submodule, $sub (or none if $sub is blank)
944  $GLOBALS['TBE_MODULES'][$main] = $sub;
945  }
946  $fullModuleSignature = $main . ($sub ? '_' . $sub : '');
947  // Adding path:
948  if ($path) {
949  GeneralUtility::deprecationLog('Registered "' . $fullModuleSignature . '" as a script-based module. Script-based modules are deprecated since TYPO3 CMS 7. Support will be removed with TYPO3 CMS 8, use the "routeTarget" option or dispatched modules instead.');
950  self::addModulePath($fullModuleSignature, $path);
951  }
952 
953  // add additional configuration
954  if (is_array($moduleConfiguration) && !empty($moduleConfiguration)) {
955  $GLOBALS['TBE_MODULES']['_configuration'][$fullModuleSignature] = $moduleConfiguration;
956  }
957  }
958 
968  public static function registerExtDirectComponent($endpointName, $callbackClass, $moduleName = null, $accessLevel = null)
969  {
970  $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ExtDirect'][$endpointName] = [
971  'callbackClass' => $callbackClass,
972  'moduleName' => $moduleName,
973  'accessLevel' => $accessLevel
974  ];
975  }
976 
984  public static function registerAjaxHandler($ajaxId, $callbackMethod, $csrfTokenCheck = true)
985  {
986  $GLOBALS['TYPO3_CONF_VARS']['BE']['AJAX'][$ajaxId] = [
987  'callbackMethod' => $callbackMethod,
988  'csrfTokenCheck' => $csrfTokenCheck
989  ];
990  }
991 
1004  public static function addModulePath($name, $path)
1005  {
1007  if (StringUtility::beginsWith($path, 'EXT:')) {
1008  list($extensionKey, $relativePath) = explode('/', substr($path, 4), 2);
1009  $path = self::extPath($extensionKey) . $relativePath;
1010  }
1011  $GLOBALS['TBE_MODULES']['_PATHS'][$name] = $path;
1012  }
1013 
1029  public static function insertModuleFunction($modname, $className, $classPath = null, $title, $MM_key = 'function', $WS = '')
1030  {
1031  $GLOBALS['TBE_MODULES_EXT'][$modname]['MOD_MENU'][$MM_key][$className] = [
1032  'name' => $className,
1033  'path' => null,
1034  'title' => $title,
1035  'ws' => $WS
1036  ];
1037  }
1038 
1051  public static function appendToTypoConfVars($group, $key, $content)
1052  {
1053  $GLOBALS['TYPO3_CONF_VARS_extensionAdded'][$group][$key] .= $content;
1054  $GLOBALS['TYPO3_CONF_VARS'][$group][$key] .= $content;
1055  }
1056 
1065  public static function addPageTSConfig($content)
1066  {
1067  self::appendToTypoConfVars('BE', 'defaultPageTSconfig', '
1068 [GLOBAL]
1069 ' . $content);
1070  }
1071 
1080  public static function addUserTSConfig($content)
1081  {
1082  self::appendToTypoConfVars('BE', 'defaultUserTSconfig', '
1083 [GLOBAL]
1084 ' . $content);
1085  }
1086 
1096  public static function addLLrefForTCAdescr($tca_descr_key, $file_ref)
1097  {
1098  if ($tca_descr_key) {
1099  if (!is_array($GLOBALS['TCA_DESCR'][$tca_descr_key])) {
1100  $GLOBALS['TCA_DESCR'][$tca_descr_key] = [];
1101  }
1102  if (!is_array($GLOBALS['TCA_DESCR'][$tca_descr_key]['refs'])) {
1103  $GLOBALS['TCA_DESCR'][$tca_descr_key]['refs'] = [];
1104  }
1105  $GLOBALS['TCA_DESCR'][$tca_descr_key]['refs'][] = $file_ref;
1106  }
1107  }
1108 
1118  public static function addNavigationComponent($module, $componentId, $extensionKey = null)
1119  {
1120  $extensionKey = $extensionKey ?: $GLOBALS['_EXTKEY'];
1121  if (!isset($extensionKey)) {
1122  throw new \RuntimeException('No extensionKey set in addNavigationComponent(). Provide it as third Parameter', 1404068039);
1123  }
1124  $GLOBALS['TBE_MODULES']['_navigationComponents'][$module] = [
1125  'componentId' => $componentId,
1126  'extKey' => $extensionKey,
1127  'isCoreComponent' => false
1128  ];
1129  }
1130 
1138  public static function addCoreNavigationComponent($module, $componentId)
1139  {
1140  self::addNavigationComponent($module, $componentId);
1141  $GLOBALS['TBE_MODULES']['_navigationComponents'][$module]['isCoreComponent'] = true;
1142  }
1143 
1144  /**************************************
1145  *
1146  * Adding SERVICES features
1147  *
1148  ***************************************/
1158  public static function addService($extKey, $serviceType, $serviceKey, $info)
1159  {
1160  if ($serviceType && is_array($info)) {
1161  $info['priority'] = max(0, min(100, $info['priority']));
1162  $GLOBALS['T3_SERVICES'][$serviceType][$serviceKey] = $info;
1163  $GLOBALS['T3_SERVICES'][$serviceType][$serviceKey]['extKey'] = $extKey;
1164  $GLOBALS['T3_SERVICES'][$serviceType][$serviceKey]['serviceKey'] = $serviceKey;
1165  $GLOBALS['T3_SERVICES'][$serviceType][$serviceKey]['serviceType'] = $serviceType;
1166  // Change the priority (and other values) from $GLOBALS['TYPO3_CONF_VARS']
1167  // $GLOBALS['TYPO3_CONF_VARS']['T3_SERVICES'][$serviceType][$serviceKey]['priority']
1168  // even the activation is possible (a unix service might be possible on windows for some reasons)
1169  if (is_array($GLOBALS['TYPO3_CONF_VARS']['T3_SERVICES'][$serviceType][$serviceKey])) {
1170  // No check is done here - there might be configuration values only the service type knows about, so
1171  // we pass everything
1172  $GLOBALS['T3_SERVICES'][$serviceType][$serviceKey] = array_merge($GLOBALS['T3_SERVICES'][$serviceType][$serviceKey], $GLOBALS['TYPO3_CONF_VARS']['T3_SERVICES'][$serviceType][$serviceKey]);
1173  }
1174  // OS check
1175  // Empty $os means 'not limited to one OS', therefore a check is not needed
1176  if ($GLOBALS['T3_SERVICES'][$serviceType][$serviceKey]['available'] && $GLOBALS['T3_SERVICES'][$serviceType][$serviceKey]['os'] != '') {
1177  // TYPO3_OS is not yet defined
1178  $os_type = stripos(PHP_OS, 'win') !== false && !stripos(PHP_OS, 'darwin') !== false ? 'WIN' : 'UNIX';
1179  $os = GeneralUtility::trimExplode(',', strtoupper($GLOBALS['T3_SERVICES'][$serviceType][$serviceKey]['os']));
1180  if (!in_array($os_type, $os)) {
1181  self::deactivateService($serviceType, $serviceKey);
1182  }
1183  }
1184  // Convert subtype list to array for quicker access
1185  $GLOBALS['T3_SERVICES'][$serviceType][$serviceKey]['serviceSubTypes'] = [];
1186  $serviceSubTypes = GeneralUtility::trimExplode(',', $info['subtype']);
1187  foreach ($serviceSubTypes as $subtype) {
1188  $GLOBALS['T3_SERVICES'][$serviceType][$serviceKey]['serviceSubTypes'][$subtype] = $subtype;
1189  }
1190  }
1191  }
1192 
1201  public static function findService($serviceType, $serviceSubType = '', $excludeServiceKeys = [])
1202  {
1203  $serviceKey = false;
1204  $serviceInfo = false;
1205  $priority = 0;
1206  $quality = 0;
1207  if (!is_array($excludeServiceKeys)) {
1208  $excludeServiceKeys = GeneralUtility::trimExplode(',', $excludeServiceKeys, true);
1209  }
1210  if (is_array($GLOBALS['T3_SERVICES'][$serviceType])) {
1211  foreach ($GLOBALS['T3_SERVICES'][$serviceType] as $key => $info) {
1212  if (in_array($key, $excludeServiceKeys)) {
1213  continue;
1214  }
1215  // Select a subtype randomly
1216  // Useful to start a service by service key without knowing his subtypes - for testing purposes
1217  if ($serviceSubType == '*') {
1218  $serviceSubType = key($info['serviceSubTypes']);
1219  }
1220  // This matches empty subtype too
1221  if ($info['available'] && ($info['subtype'] == $serviceSubType || $info['serviceSubTypes'][$serviceSubType]) && $info['priority'] >= $priority) {
1222  // Has a lower quality than the already found, therefore we skip this service
1223  if ($info['priority'] == $priority && $info['quality'] < $quality) {
1224  continue;
1225  }
1226  // Check if the service is available
1227  $info['available'] = self::isServiceAvailable($serviceType, $key, $info);
1228  // Still available after exec check?
1229  if ($info['available']) {
1230  $serviceKey = $key;
1231  $priority = $info['priority'];
1232  $quality = $info['quality'];
1233  }
1234  }
1235  }
1236  }
1237  if ($serviceKey) {
1238  $serviceInfo = $GLOBALS['T3_SERVICES'][$serviceType][$serviceKey];
1239  }
1240  return $serviceInfo;
1241  }
1242 
1251  public static function findServiceByKey($serviceKey)
1252  {
1253  if (is_array($GLOBALS['T3_SERVICES'])) {
1254  // Loop on all service types
1255  // NOTE: we don't care about the actual type, we are looking for a specific key
1256  foreach ($GLOBALS['T3_SERVICES'] as $serviceType => $servicesPerType) {
1257  if (isset($servicesPerType[$serviceKey])) {
1258  $serviceDetails = $servicesPerType[$serviceKey];
1259  // Test if service is available
1260  if (self::isServiceAvailable($serviceType, $serviceKey, $serviceDetails)) {
1261  // We have found the right service, return its information
1262  return $serviceDetails;
1263  }
1264  }
1265  }
1266  }
1267  throw new \TYPO3\CMS\Core\Exception('Service not found for key: ' . $serviceKey, 1319217244);
1268  }
1269 
1278  public static function isServiceAvailable($serviceType, $serviceKey, $serviceDetails)
1279  {
1280  // If the service depends on external programs - check if they exists
1281  if (trim($serviceDetails['exec'])) {
1282  $executables = GeneralUtility::trimExplode(',', $serviceDetails['exec'], true);
1283  foreach ($executables as $executable) {
1284  // If at least one executable file is not available, exit early returning FALSE
1285  if (!CommandUtility::checkCommand($executable)) {
1286  self::deactivateService($serviceType, $serviceKey);
1287  return false;
1288  }
1289  }
1290  }
1291  // The service is available
1292  return true;
1293  }
1294 
1302  public static function deactivateService($serviceType, $serviceKey)
1303  {
1304  // ... maybe it's better to move non-available services to a different array??
1305  $GLOBALS['T3_SERVICES'][$serviceType][$serviceKey]['available'] = false;
1306  }
1307 
1308  /**************************************
1309  *
1310  * Adding FRONTEND features
1311  *
1312  ***************************************/
1326  public static function addPlugin($itemArray, $type = 'list_type', $extensionKey = null)
1327  {
1328  $extensionKey = $extensionKey ?: $GLOBALS['_EXTKEY'];
1329  if (!isset($extensionKey)) {
1330  throw new \RuntimeException(
1331  'No extension key could be determined when calling addPlugin()!'
1332  . LF
1333  . 'This method is meant to be called from an ext_tables.php or Configuration/TCA/Overrides file. '
1334  . 'If you call it from Configuration/TCA/Overrides, the extension key needs to be specified as third parameter. '
1335  . 'Calling it from any other place e.g. ext_localconf.php does not work and is not supported.',
1336  1404068038
1337  );
1338  }
1339  if ($extensionKey && !$itemArray[2] && isset($GLOBALS['TYPO3_LOADED_EXT'][$extensionKey]['ext_icon'])) {
1340  $itemArray[2] = 'EXT:' . $extensionKey . '/' . $GLOBALS['TYPO3_LOADED_EXT'][$extensionKey]['ext_icon'];
1341  }
1342  if (is_array($GLOBALS['TCA']['tt_content']['columns']) && is_array($GLOBALS['TCA']['tt_content']['columns'][$type]['config']['items'])) {
1343  foreach ($GLOBALS['TCA']['tt_content']['columns'][$type]['config']['items'] as $k => $v) {
1344  if ((string)$v[1] === (string)$itemArray[1]) {
1345  $GLOBALS['TCA']['tt_content']['columns'][$type]['config']['items'][$k] = $itemArray;
1346  return;
1347  }
1348  }
1349  $GLOBALS['TCA']['tt_content']['columns'][$type]['config']['items'][] = $itemArray;
1350  }
1351  }
1352 
1363  public static function addPiFlexFormValue($piKeyToMatch, $value, $CTypeToMatch = 'list')
1364  {
1365  if (is_array($GLOBALS['TCA']['tt_content']['columns']) && is_array($GLOBALS['TCA']['tt_content']['columns']['pi_flexform']['config']['ds'])) {
1366  $GLOBALS['TCA']['tt_content']['columns']['pi_flexform']['config']['ds'][$piKeyToMatch . ',' . $CTypeToMatch] = $value;
1367  }
1368  }
1369 
1380  public static function addToInsertRecords($table, $content_table = 'tt_content', $content_field = 'records')
1381  {
1382  if (is_array($GLOBALS['TCA'][$content_table]['columns']) && isset($GLOBALS['TCA'][$content_table]['columns'][$content_field]['config']['allowed'])) {
1383  $GLOBALS['TCA'][$content_table]['columns'][$content_field]['config']['allowed'] .= ',' . $table;
1384  }
1385  }
1386 
1415  public static function addPItoST43($key, $classFile = '', $suffix = '', $type = 'list_type', $cached = 0)
1416  {
1417  $classFile = $classFile ? $classFile : 'pi/class.tx_' . str_replace('_', '', $key) . $suffix . '.php';
1418  $cN = self::getCN($key);
1419  // General plugin
1420  $pluginContent = trim('
1421 plugin.' . $cN . $suffix . ' = USER' . ($cached ? '' : '_INT') . '
1422 plugin.' . $cN . $suffix . ' {
1423  includeLibs = ' . $GLOBALS['TYPO3_LOADED_EXT'][$key]['siteRelPath'] . $classFile . '
1424  userFunc = ' . $cN . $suffix . '->main
1425 }');
1426  self::addTypoScript($key, 'setup', '
1427 # Setting ' . $key . ' plugin TypoScript
1428 ' . $pluginContent);
1429  // Add after defaultContentRendering
1430  switch ($type) {
1431  case 'list_type':
1432  $addLine = 'tt_content.list.20.' . $key . $suffix . ' = < plugin.' . $cN . $suffix;
1433  break;
1434  case 'menu_type':
1435  $addLine = 'tt_content.menu.20.' . $key . $suffix . ' = < plugin.' . $cN . $suffix;
1436  break;
1437  case 'CType':
1438  $addLine = trim('
1439 tt_content.' . $key . $suffix . ' = COA
1440 tt_content.' . $key . $suffix . ' {
1441  10 = < lib.stdheader
1442  20 = < plugin.' . $cN . $suffix . '
1443 }
1444 ');
1445  break;
1446  case 'header_layout':
1447  $addLine = 'lib.stdheader.10.' . $key . $suffix . ' = < plugin.' . $cN . $suffix;
1448  break;
1449  case 'includeLib':
1450  $addLine = 'page.1000 = < plugin.' . $cN . $suffix;
1451  break;
1452  default:
1453  $addLine = '';
1454  }
1455  if ($addLine) {
1456  self::addTypoScript($key, 'setup', '
1457 # Setting ' . $key . ' plugin TypoScript
1458 ' . $addLine . '
1459 ', 'defaultContentRendering');
1460  }
1461  }
1462 
1473  public static function addStaticFile($extKey, $path, $title)
1474  {
1475  if ($extKey && $path && is_array($GLOBALS['TCA']['sys_template']['columns'])) {
1476  $value = str_replace(',', '', 'EXT:' . $extKey . '/' . $path);
1477  $itemArray = [trim($title . ' (' . $extKey . ')'), $value];
1478  $GLOBALS['TCA']['sys_template']['columns']['include_static_file']['config']['items'][] = $itemArray;
1479  }
1480  }
1481 
1491  public static function registerPageTSConfigFile($extKey, $filePath, $title)
1492  {
1493  if (!$extKey) {
1494  throw new \InvalidArgumentException('No extension key given.', 1447789490);
1495  }
1496  if (!$filePath) {
1497  throw new \InvalidArgumentException('No file path given.', 1447789491);
1498  }
1499  if (!isset($GLOBALS['TCA']['pages']['columns']) || !is_array($GLOBALS['TCA']['pages']['columns'])) {
1500  throw new \InvalidArgumentException('No TCA definition for table "pages".', 1447789492);
1501  }
1502 
1503  $value = str_replace(',', '', 'EXT:' . $extKey . '/' . $filePath);
1504  $itemArray = [trim($title . ' (' . $extKey . ')'), $value];
1505  $GLOBALS['TCA']['pages']['columns']['tsconfig_includes']['config']['items'][] = $itemArray;
1506  }
1507 
1516  public static function addTypoScriptSetup($content)
1517  {
1518  self::appendToTypoConfVars('FE', 'defaultTypoScript_setup', '
1519 [GLOBAL]
1520 ' . $content);
1521  }
1522 
1531  public static function addTypoScriptConstants($content)
1532  {
1533  self::appendToTypoConfVars('FE', 'defaultTypoScript_constants', '
1534 [GLOBAL]
1535 ' . $content);
1536  }
1537 
1556  public static function addTypoScript($key, $type, $content, $afterStaticUid = 0)
1557  {
1558  if ($type === 'setup' || $type === 'constants') {
1559  $content = '
1560 
1561 [GLOBAL]
1562 #############################################
1563 ## TypoScript added by extension "' . $key . '"
1564 #############################################
1565 
1566 ' . $content;
1567  if ($afterStaticUid) {
1568  // If 'content (default)' is targeted (static uid 43),
1569  // 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
1570  if ($afterStaticUid === 'defaultContentRendering' || $afterStaticUid == 43) {
1571  $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_' . $type . '.']['defaultContentRendering'] .= $content;
1572  } else {
1573  $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_' . $type . '.'][$afterStaticUid] .= $content;
1574  }
1575  } else {
1576  $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_' . $type] .= $content;
1577  }
1578  }
1579  }
1580 
1581  /***************************************
1582  *
1583  * Internal extension management methods
1584  *
1585  ***************************************/
1594  public static function getExtensionIcon($extensionPath, $returnFullPath = false)
1595  {
1596  $icon = '';
1597  $iconFileTypesToCheckFor = ['png', 'svg', 'gif'];
1598  foreach ($iconFileTypesToCheckFor as $fileType) {
1599  if (file_exists($extensionPath . 'ext_icon.' . $fileType)) {
1600  $icon = 'ext_icon.' . $fileType;
1601  break;
1602  }
1603  }
1604  return $returnFullPath ? $extensionPath . $icon : $icon;
1605  }
1606 
1619  public static function loadExtLocalconf($allowCaching = true)
1620  {
1621  if ($allowCaching) {
1622  $cacheIdentifier = self::getExtLocalconfCacheIdentifier();
1624  $codeCache = self::getCacheManager()->getCache('cache_core');
1625  if ($codeCache->has($cacheIdentifier)) {
1626  $codeCache->requireOnce($cacheIdentifier);
1627  } else {
1628  self::loadSingleExtLocalconfFiles();
1629  self::createExtLocalconfCacheEntry();
1630  }
1631  } else {
1632  self::loadSingleExtLocalconfFiles();
1633  }
1634  }
1635 
1641  protected static function loadSingleExtLocalconfFiles()
1642  {
1643  // This is the main array meant to be manipulated in the ext_localconf.php files
1644  // In general it is recommended to not rely on it to be globally defined in that
1645  // scope but to use $GLOBALS['TYPO3_CONF_VARS'] instead.
1646  // Nevertheless we define it here as global for backwards compatibility.
1647  global $TYPO3_CONF_VARS;
1648  foreach ($GLOBALS['TYPO3_LOADED_EXT'] as $_EXTKEY => $extensionInformation) {
1649  if ((is_array($extensionInformation) || $extensionInformation instanceof \ArrayAccess) && isset($extensionInformation['ext_localconf.php'])) {
1650  // $_EXTKEY and $_EXTCONF are available in ext_localconf.php
1651  // and are explicitly set in cached file as well
1652  $_EXTCONF = isset($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'][$_EXTKEY]) ? $GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'][$_EXTKEY] : null;
1653  require $extensionInformation['ext_localconf.php'];
1654  }
1655  }
1656  }
1657 
1663  protected static function createExtLocalconfCacheEntry()
1664  {
1665  $extensionInformation = $GLOBALS['TYPO3_LOADED_EXT'];
1666  $phpCodeToCache = [];
1667  // Set same globals as in loadSingleExtLocalconfFiles()
1668  $phpCodeToCache[] = '';
1671  $phpCodeToCache[] = '';
1672  $phpCodeToCache[] = 'global $TYPO3_CONF_VARS, $T3_SERVICES, $T3_VAR;';
1673  $phpCodeToCache[] = '';
1674  // Iterate through loaded extensions and add ext_localconf content
1675  foreach ($extensionInformation as $extensionKey => $extensionDetails) {
1676  if (isset($extensionDetails['ext_localconf.php']) && $extensionDetails['ext_localconf.php']) {
1677  // Include a header per extension to make the cache file more readable
1678  $phpCodeToCache[] = '';
1682  $phpCodeToCache[] = '';
1683  // Set $_EXTKEY and $_EXTCONF for this extension
1684  $phpCodeToCache[] = '$_EXTKEY = \'' . $extensionKey . '\';';
1685  $phpCodeToCache[] = '$_EXTCONF = $GLOBALS[\'TYPO3_CONF_VARS\'][\'EXT\'][\'extConf\'][$_EXTKEY];';
1686  $phpCodeToCache[] = '';
1687  // Add ext_localconf.php content of extension
1688  $phpCodeToCache[] = trim(GeneralUtility::getUrl($extensionDetails['ext_localconf.php']));
1689  $phpCodeToCache[] = '';
1690  $phpCodeToCache[] = '';
1691  }
1692  }
1693  $phpCodeToCache = implode(LF, $phpCodeToCache);
1694  // Remove all start and ending php tags from content
1695  $phpCodeToCache = preg_replace('/<\\?php|\\?>/is', '', $phpCodeToCache);
1696  self::getCacheManager()->getCache('cache_core')->set(self::getExtLocalconfCacheIdentifier(), $phpCodeToCache);
1697  }
1698 
1704  protected static function getExtLocalconfCacheIdentifier()
1705  {
1706  return 'ext_localconf_' . sha1(TYPO3_version . PATH_site . 'extLocalconf' . serialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['runtimeActivatedPackages']));
1707  }
1708 
1723  public static function loadBaseTca($allowCaching = true)
1724  {
1725  if ($allowCaching) {
1726  $cacheIdentifier = static::getBaseTcaCacheIdentifier();
1728  $codeCache = static::getCacheManager()->getCache('cache_core');
1729  if ($codeCache->has($cacheIdentifier)) {
1730  // substr is necessary, because the php frontend wraps php code around the cache value
1731  $cacheData = unserialize(substr($codeCache->get($cacheIdentifier), 6, -2));
1732  $GLOBALS['TCA'] = $cacheData['tca'];
1733  GeneralUtility::setSingletonInstance(CategoryRegistry::class, $cacheData['categoryRegistry']);
1734  } else {
1735  static::buildBaseTcaFromSingleFiles();
1736  static::createBaseTcaCacheFile();
1737  }
1738  } else {
1739  static::buildBaseTcaFromSingleFiles();
1740  }
1741  }
1742 
1751  protected static function buildBaseTcaFromSingleFiles()
1752  {
1753  $GLOBALS['TCA'] = [];
1754 
1755  $activePackages = static::$packageManager->getActivePackages();
1756 
1757  // First load "full table" files from Configuration/TCA
1758  foreach ($activePackages as $package) {
1759  $tcaConfigurationDirectory = $package->getPackagePath() . 'Configuration/TCA';
1760  if (is_dir($tcaConfigurationDirectory)) {
1761  $files = scandir($tcaConfigurationDirectory);
1762  foreach ($files as $file) {
1763  if (
1764  is_file($tcaConfigurationDirectory . '/' . $file)
1765  && ($file !== '.')
1766  && ($file !== '..')
1767  && (substr($file, -4, 4) === '.php')
1768  ) {
1769  $tcaOfTable = require($tcaConfigurationDirectory . '/' . $file);
1770  if (is_array($tcaOfTable)) {
1771  // TCA table name is filename without .php suffix, eg 'sys_notes', not 'sys_notes.php'
1772  $tcaTableName = substr($file, 0, -4);
1773  $GLOBALS['TCA'][$tcaTableName] = $tcaOfTable;
1774  }
1775  }
1776  }
1777  }
1778  }
1779 
1780  // Apply category stuff
1781  CategoryRegistry::getInstance()->applyTcaForPreRegisteredTables();
1782 
1783  // Execute override files from Configuration/TCA/Overrides
1784  foreach ($activePackages as $package) {
1785  $tcaOverridesPathForPackage = $package->getPackagePath() . 'Configuration/TCA/Overrides';
1786  if (is_dir($tcaOverridesPathForPackage)) {
1787  $files = scandir($tcaOverridesPathForPackage);
1788  foreach ($files as $file) {
1789  if (
1790  is_file($tcaOverridesPathForPackage . '/' . $file)
1791  && ($file !== '.')
1792  && ($file !== '..')
1793  && (substr($file, -4, 4) === '.php')
1794  ) {
1795  require($tcaOverridesPathForPackage . '/' . $file);
1796  }
1797  }
1798  }
1799  }
1800 
1801  // TCA migration
1802  // @deprecated since TYPO3 CMS 7, will be removed in TYPO3 CMS 8. This can be removed *if* no additional TCA migration is added with CMS 8, see class TcaMigration
1803  $tcaMigration = GeneralUtility::makeInstance(TcaMigration::class);
1804  $GLOBALS['TCA'] = $tcaMigration->migrate($GLOBALS['TCA']);
1805  $messages = $tcaMigration->getMessages();
1806  if (!empty($messages)) {
1807  $context = 'Automatic TCA migration done during bootstrap. Please adapt TCA accordingly, these migrations'
1808  . ' will be removed with TYPO3 CMS 8. The backend module "Configuration -> TCA" shows the modified values.'
1809  . ' Please adapt these areas:';
1810  array_unshift($messages, $context);
1811  GeneralUtility::deprecationLog(implode(LF, $messages));
1812  }
1813 
1814  static::emitTcaIsBeingBuiltSignal($GLOBALS['TCA']);
1815  }
1816 
1827  protected static function emitTcaIsBeingBuiltSignal(array $tca)
1828  {
1829  list($tca) = static::getSignalSlotDispatcher()->dispatch(__CLASS__, 'tcaIsBeingBuilt', [$tca]);
1830  $GLOBALS['TCA'] = $tca;
1831  }
1832 
1839  protected static function createBaseTcaCacheFile()
1840  {
1842  $codeCache = self::getCacheManager()->getCache('cache_core');
1843  $codeCache->set(static::getBaseTcaCacheIdentifier(), serialize(['tca' => $GLOBALS['TCA'], 'categoryRegistry' => CategoryRegistry::getInstance()]));
1844  }
1845 
1851  protected static function getBaseTcaCacheIdentifier()
1852  {
1853  return 'tca_base_' . sha1(TYPO3_version . PATH_site . 'tca_with_category_registry' . serialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['runtimeActivatedPackages']));
1854  }
1855 
1868  public static function loadExtTables($allowCaching = true)
1869  {
1870  if ($allowCaching && !self::$extTablesWasReadFromCacheOnce) {
1871  self::$extTablesWasReadFromCacheOnce = true;
1872  $cacheIdentifier = self::getExtTablesCacheIdentifier();
1874  $codeCache = self::getCacheManager()->getCache('cache_core');
1875  if ($codeCache->has($cacheIdentifier)) {
1876  $codeCache->requireOnce($cacheIdentifier);
1877  } else {
1878  self::loadSingleExtTablesFiles();
1879  self::createExtTablesCacheEntry();
1880  }
1881  } else {
1882  self::loadSingleExtTablesFiles();
1883  }
1884  }
1885 
1891  protected static function loadSingleExtTablesFiles()
1892  {
1893  // In general it is recommended to not rely on it to be globally defined in that
1894  // scope, but we can not prohibit this without breaking backwards compatibility
1895  global $T3_SERVICES, $T3_VAR, $TYPO3_CONF_VARS;
1896  global $TBE_MODULES, $TBE_MODULES_EXT, $TCA;
1897  global $PAGES_TYPES, $TBE_STYLES;
1898  global $_EXTKEY;
1899  // Load each ext_tables.php file of loaded extensions
1900  foreach ($GLOBALS['TYPO3_LOADED_EXT'] as $_EXTKEY => $extensionInformation) {
1901  if ((is_array($extensionInformation) || $extensionInformation instanceof \ArrayAccess) && $extensionInformation['ext_tables.php']) {
1902  // $_EXTKEY and $_EXTCONF are available in ext_tables.php
1903  // and are explicitly set in cached file as well
1904  $_EXTCONF = $GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'][$_EXTKEY];
1905  require $extensionInformation['ext_tables.php'];
1906  static::loadNewTcaColumnsConfigFiles();
1907  }
1908  }
1909  }
1910 
1916  protected static function createExtTablesCacheEntry()
1917  {
1918  $extensionInformation = $GLOBALS['TYPO3_LOADED_EXT'];
1919  $phpCodeToCache = [];
1920  // Set same globals as in loadSingleExtTablesFiles()
1921  $phpCodeToCache[] = '';
1924  $phpCodeToCache[] = '';
1925  $phpCodeToCache[] = 'global $T3_SERVICES, $T3_VAR, $TYPO3_CONF_VARS;';
1926  $phpCodeToCache[] = 'global $TBE_MODULES, $TBE_MODULES_EXT, $TCA;';
1927  $phpCodeToCache[] = 'global $PAGES_TYPES, $TBE_STYLES;';
1928  $phpCodeToCache[] = 'global $_EXTKEY;';
1929  $phpCodeToCache[] = '';
1930  // Iterate through loaded extensions and add ext_tables content
1931  foreach ($extensionInformation as $extensionKey => $extensionDetails) {
1932  if (isset($extensionDetails['ext_tables.php']) && $extensionDetails['ext_tables.php']) {
1933  // Include a header per extension to make the cache file more readable
1934  $phpCodeToCache[] = '';
1938  $phpCodeToCache[] = '';
1939  // Set $_EXTKEY and $_EXTCONF for this extension
1940  $phpCodeToCache[] = '$_EXTKEY = \'' . $extensionKey . '\';';
1941  $phpCodeToCache[] = '$_EXTCONF = $GLOBALS[\'TYPO3_CONF_VARS\'][\'EXT\'][\'extConf\'][$_EXTKEY];';
1942  $phpCodeToCache[] = '';
1943  // Add ext_tables.php content of extension
1944  $phpCodeToCache[] = trim(GeneralUtility::getUrl($extensionDetails['ext_tables.php']));
1945  $phpCodeToCache[] = '';
1946  $phpCodeToCache[] = self::class . '::loadNewTcaColumnsConfigFiles();';
1947  $phpCodeToCache[] = '';
1948  }
1949  }
1950  $phpCodeToCache = implode(LF, $phpCodeToCache);
1951  // Remove all start and ending php tags from content
1952  $phpCodeToCache = preg_replace('/<\\?php|\\?>/is', '', $phpCodeToCache);
1953  self::getCacheManager()->getCache('cache_core')->set(self::getExtTablesCacheIdentifier(), $phpCodeToCache);
1954  }
1955 
1977  public static function loadNewTcaColumnsConfigFiles()
1978  {
1979  global $TCA;
1980 
1981  foreach ($TCA as $tableName => $_) {
1982  if (!isset($TCA[$tableName]['columns'])) {
1983  $columnsConfigFile = $TCA[$tableName]['ctrl']['dynamicConfigFile'];
1984  if ($columnsConfigFile) {
1986  if (GeneralUtility::isAbsPath($columnsConfigFile)) {
1987  include($columnsConfigFile);
1988  } else {
1989  throw new \RuntimeException(
1990  'Columns configuration file not found',
1991  1341151261
1992  );
1993  }
1994  }
1995  }
1996  }
1997  }
1998 
2004  protected static function getExtTablesCacheIdentifier()
2005  {
2006  return 'ext_tables_' . sha1(TYPO3_version . PATH_site . 'extTables' . serialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['runtimeActivatedPackages']));
2007  }
2008 
2024  public static function removeCacheFiles()
2025  {
2026  self::getCacheManager()->flushCachesInGroup('system');
2027  }
2028 
2034  public static function getLoadedExtensionListArray()
2035  {
2036  return array_keys(static::$packageManager->getActivePackages());
2037  }
2038 
2049  public static function loadExtension($extensionKey)
2050  {
2051  if (static::$packageManager->isPackageActive($extensionKey)) {
2052  throw new \RuntimeException('Extension already loaded', 1342345486);
2053  }
2054  static::$packageManager->activatePackage($extensionKey);
2055  }
2056 
2067  public static function unloadExtension($extensionKey)
2068  {
2069  if (!static::$packageManager->isPackageActive($extensionKey)) {
2070  throw new \RuntimeException('Extension not loaded', 1342345487);
2071  }
2072  static::$packageManager->deactivatePackage($extensionKey);
2073  }
2074 
2087  public static function makeCategorizable($extensionKey, $tableName, $fieldName = 'categories', array $options = [], $override = false)
2088  {
2089  // Update the category registry
2090  $result = CategoryRegistry::getInstance()->add($extensionKey, $tableName, $fieldName, $options, $override);
2091  if ($result === false) {
2092  $message = CategoryRegistry::class . ': no category registered for table "%s". Key was already registered.';
2094  $logger = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Log\LogManager::class)->getLogger(__CLASS__);
2095  $logger->warning(
2096  sprintf($message, $tableName)
2097  );
2098  }
2099  }
2100 }
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 addPItoST43($key, $classFile='', $suffix='', $type='list_type', $cached=0)
static addTCAcolumns($table, $columnArray, $addTofeInterface=false)
static setSingletonInstance($className, SingletonInterface $instance)
static addTypoScript($key, $type, $content, $afterStaticUid=0)
static addExtJSModule($extensionName, $mainModuleName, $subModuleName='', $position='', array $moduleConfiguration=[])
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
static generateItemList(array $items, $useRawData=false)
static addPiFlexFormValue($piKeyToMatch, $value, $CTypeToMatch='list')
static getFileFieldTCAConfig($fieldName, array $customSettingOverride=[], $allowedFileExtensions='', $disallowedFileExtensions='')
static addToAllTCAtypes($table, $newFieldsString, $typeList='', $position='')
static insertModuleFunction($modname, $className, $classPath=null, $title, $MM_key='function', $WS='')
static isServiceAvailable($serviceType, $serviceKey, $serviceDetails)
$signalSlotDispatcher
$_EXTCONF
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 addModule($main, $sub='', $position='', $path='', $moduleConfiguration=[])
static getUrl($url, $includeHeader=0, $requestHeaders=false, &$report=null)
static addPlugin($itemArray, $type='list_type', $extensionKey=null)
static executePositionedStringInsertion($list, $insertionList, $insertionPosition='')
static setPackageManager(PackageManager $packageManager)
static findService($serviceType, $serviceSubType='', $excludeServiceKeys=[])
$TCA['tx_blogexample_domain_model_blog']
Definition: Blog.php:4
static registerAjaxHandler($ajaxId, $callbackMethod, $csrfTokenCheck=true)
static beginsWith($haystack, $needle)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
static addFieldsToPalette($table, $palette, $addFields, $insertionPosition='')
static getExtensionIcon($extensionPath, $returnFullPath=false)