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 = array();
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) {
272  'Usage of feInterface is no longer part of the TYPO3 CMS Core. Please check EXT:' . $GLOBALS['_EXTKEY'] . '.'
273  );
274  }
275  }
276  }
277 
292  public static function addToAllTCAtypes($table, $newFieldsString, $typeList = '', $position = '')
293  {
294  $newFieldsString = trim($newFieldsString);
295  if ($newFieldsString === '' || !is_array($GLOBALS['TCA'][$table]['types'])) {
296  return;
297  }
298  list($positionIdentifier, $entityName) = GeneralUtility::trimExplode(':', $position);
299  $palettesChanged = array();
300 
301  foreach ($GLOBALS['TCA'][$table]['types'] as $type => &$typeDetails) {
302  // skip if we don't want to add the field for this type
303  if ($typeList !== '' && !GeneralUtility::inList($typeList, $type)) {
304  continue;
305  }
306  // skip if fields were already added
307  if (!isset($typeDetails['showitem']) || strpos($typeDetails['showitem'], $newFieldsString) !== false) {
308  continue;
309  }
310 
311  $fieldExists = false;
312  $newPosition = '';
313  if (is_array($GLOBALS['TCA'][$table]['palettes'])) {
314  // Get the palette names used in current showitem
315  $paletteCount = preg_match_all('/(?:^|,) # Line start or a comma
316  (?:
317  \\s*\\-\\-palette\\-\\-;[^;]*;([^,$]*)| # --palette--;label;paletteName
318  \\s*\\b[^;,]+\\b(?:;[^;]*;([^;,]+);?[^;,]*;?)?[^,]* # @deprecated since TYPO3 CMS 7: field;label;paletteName[;options[;colors]]
319  )/x', $typeDetails['showitem'], $paletteMatches);
320  if ($paletteCount > 0) {
321  $paletteNames = array_filter(array_merge($paletteMatches[1], $paletteMatches[2]));
322  if (!empty($paletteNames)) {
323  foreach ($paletteNames as $paletteName) {
324  $palette = $GLOBALS['TCA'][$table]['palettes'][$paletteName];
325  switch ($positionIdentifier) {
326  case 'after':
327  case 'before':
328  if (preg_match('/\\b' . $entityName . '\\b/', $palette['showitem']) > 0) {
329  $newPosition = $positionIdentifier . ':--palette--;;' . $paletteName;
330  }
331  break;
332  case 'replace':
333  // check if fields have been added to palette before
334  if (isset($palettesChanged[$paletteName])) {
335  $fieldExists = true;
336  continue;
337  }
338  if (preg_match('/\\b' . $entityName . '\\b/', $palette['showitem']) > 0) {
339  self::addFieldsToPalette($table, $paletteName, $newFieldsString, $position);
340  // Memorize that we already changed this palette, in case other types also use it
341  $palettesChanged[$paletteName] = true;
342  $fieldExists = true;
343  continue;
344  }
345  break;
346  default:
347  // Intentionally left blank
348  }
349  }
350  }
351  }
352  }
353  if ($fieldExists === false) {
354  $typeDetails['showitem'] = self::executePositionedStringInsertion(
355  $typeDetails['showitem'],
356  $newFieldsString,
357  $newPosition !== '' ? $newPosition : $position
358  );
359  }
360  }
361  unset($typeDetails);
362  }
363 
407  public static function addFieldsToAllPalettesOfField($table, $field, $addFields, $insertionPosition = '')
408  {
409  if (!isset($GLOBALS['TCA'][$table]['columns'][$field])) {
410  return;
411  }
412  if (!is_array($GLOBALS['TCA'][$table]['types'])) {
413  return;
414  }
415 
416  // Iterate through all types and search for the field that defines the palette to be extended
417  foreach ($GLOBALS['TCA'][$table]['types'] as $typeName => $typeArray) {
418  // Continue if types has no showitem at all or if requested field is not in it
419  if (!isset($typeArray['showitem']) || strpos($typeArray['showitem'], $field) === false) {
420  continue;
421  }
422  $fieldArrayWithOptions = GeneralUtility::trimExplode(',', $typeArray['showitem']);
423  // Find the field we're handling
424  $newFieldStringArray = array();
425  foreach ($fieldArrayWithOptions as $fieldNumber => $fieldString) {
426  $newFieldStringArray[] = $fieldString;
427  $fieldArray = GeneralUtility::trimExplode(';', $fieldString);
428  if ($fieldArray[0] !== $field) {
429  continue;
430  }
431  if (
432  isset($fieldArrayWithOptions[$fieldNumber + 1])
433  && StringUtility::beginsWith($fieldArrayWithOptions[$fieldNumber + 1], '--palette--')
434  ) {
435  // Match for $field and next field is a palette - add fields to this one
436  $paletteName = GeneralUtility::trimExplode(';', $fieldArrayWithOptions[$fieldNumber + 1]);
437  $paletteName = $paletteName[2];
438  self::addFieldsToPalette($table, $paletteName, $addFields, $insertionPosition);
439  } else {
440  // Match for $field but next field is no palette - create a new one
441  $newPaletteName = 'generatedFor-' . $field;
442  self::addFieldsToPalette($table, 'generatedFor-' . $field, $addFields, $insertionPosition);
443  $newFieldStringArray[] = '--palette--;;' . $newPaletteName;
444  }
445  }
446  $GLOBALS['TCA'][$table]['types'][$typeName]['showitem'] = implode(', ', $newFieldStringArray);
447  }
448  }
449 
460  public static function addFieldsToPalette($table, $palette, $addFields, $insertionPosition = '')
461  {
462  if (isset($GLOBALS['TCA'][$table])) {
463  $paletteData = &$GLOBALS['TCA'][$table]['palettes'][$palette];
464  // If palette already exists, merge the data:
465  if (is_array($paletteData)) {
466  $paletteData['showitem'] = self::executePositionedStringInsertion($paletteData['showitem'], $addFields, $insertionPosition);
467  } else {
468  $paletteData['showitem'] = self::removeDuplicatesForInsertion($addFields);
469  }
470  }
471  }
472 
501  public static function addTcaSelectItem($table, $field, array $item, $relativeToField = '', $relativePosition = '')
502  {
503  if (!is_string($table)) {
504  throw new \InvalidArgumentException('Given table is of type "' . gettype($table) . '" but a string is expected.', 1303236963);
505  }
506  if (!is_string($field)) {
507  throw new \InvalidArgumentException('Given field is of type "' . gettype($field) . '" but a string is expected.', 1303236964);
508  }
509  if (!is_string($relativeToField)) {
510  throw new \InvalidArgumentException('Given relative field is of type "' . gettype($relativeToField) . '" but a string is expected.', 1303236965);
511  }
512  if (!is_string($relativePosition)) {
513  throw new \InvalidArgumentException('Given relative position is of type "' . gettype($relativePosition) . '" but a string is expected.', 1303236966);
514  }
515  if ($relativePosition !== '' && $relativePosition !== 'before' && $relativePosition !== 'after' && $relativePosition !== 'replace') {
516  throw new \InvalidArgumentException('Relative position must be either empty or one of "before", "after", "replace".', 1303236967);
517  }
518  if (!is_array($GLOBALS['TCA'][$table]['columns'][$field]['config']['items'])) {
519  throw new \RuntimeException('Given select field item list was not found.', 1303237468);
520  }
521  // Make sure item keys are integers
522  $GLOBALS['TCA'][$table]['columns'][$field]['config']['items'] = array_values($GLOBALS['TCA'][$table]['columns'][$field]['config']['items']);
523  if ($relativePosition !== '') {
524  // Insert at specified position
525  $matchedPosition = ArrayUtility::filterByValueRecursive($relativeToField, $GLOBALS['TCA'][$table]['columns'][$field]['config']['items']);
526  if (!empty($matchedPosition)) {
527  $relativeItemKey = key($matchedPosition);
528  if ($relativePosition === 'replace') {
529  $GLOBALS['TCA'][$table]['columns'][$field]['config']['items'][$relativeItemKey] = $item;
530  } else {
531  if ($relativePosition === 'before') {
532  $offset = $relativeItemKey;
533  } else {
534  $offset = $relativeItemKey + 1;
535  }
536  array_splice($GLOBALS['TCA'][$table]['columns'][$field]['config']['items'], $offset, 0, array(0 => $item));
537  }
538  } else {
539  // Insert at new item at the end of the array if relative position was not found
540  $GLOBALS['TCA'][$table]['columns'][$field]['config']['items'][] = $item;
541  }
542  } else {
543  // Insert at new item at the end of the array
544  $GLOBALS['TCA'][$table]['columns'][$field]['config']['items'][] = $item;
545  }
546  }
547 
558  public static function getFileFieldTCAConfig($fieldName, array $customSettingOverride = array(), $allowedFileExtensions = '', $disallowedFileExtensions = '')
559  {
560  $fileFieldTCAConfig = array(
561  'type' => 'inline',
562  'foreign_table' => 'sys_file_reference',
563  'foreign_field' => 'uid_foreign',
564  'foreign_sortby' => 'sorting_foreign',
565  'foreign_table_field' => 'tablenames',
566  'foreign_match_fields' => array(
567  'fieldname' => $fieldName
568  ),
569  'foreign_label' => 'uid_local',
570  'foreign_selector' => 'uid_local',
571  'foreign_selector_fieldTcaOverride' => array(
572  'config' => array(
573  'appearance' => array(
574  'elementBrowserType' => 'file',
575  'elementBrowserAllowed' => $allowedFileExtensions
576  )
577  )
578  ),
579  'filter' => array(
580  array(
581  'userFunc' => \TYPO3\CMS\Core\Resource\Filter\FileExtensionFilter::class . '->filterInlineChildren',
582  'parameters' => array(
583  'allowedFileExtensions' => $allowedFileExtensions,
584  'disallowedFileExtensions' => $disallowedFileExtensions
585  )
586  )
587  ),
588  'appearance' => array(
589  'useSortable' => true,
590  'headerThumbnail' => array(
591  'field' => 'uid_local',
592  'width' => '45',
593  'height' => '45c',
594  ),
595  'showPossibleLocalizationRecords' => false,
596  'showRemovedLocalizationRecords' => false,
597  'showSynchronizationLink' => false,
598  'showAllLocalizationLink' => false,
599 
600  'enabledControls' => array(
601  'info' => true,
602  'new' => false,
603  'dragdrop' => true,
604  'sort' => false,
605  'hide' => true,
606  'delete' => true,
607  'localize' => true,
608  ),
609  ),
610  'behaviour' => array(
611  'localizationMode' => 'select',
612  'localizeChildrenAtParentLocalization' => true,
613  ),
614  );
615  ArrayUtility::mergeRecursiveWithOverrule($fileFieldTCAConfig, $customSettingOverride);
616  return $fileFieldTCAConfig;
617  }
618 
628  public static function addFieldsToUserSettings($addFields, $insertionPosition = '')
629  {
630  $GLOBALS['TYPO3_USER_SETTINGS']['showitem'] = self::executePositionedStringInsertion($GLOBALS['TYPO3_USER_SETTINGS']['showitem'], $addFields, $insertionPosition);
631  }
632 
650  protected static function executePositionedStringInsertion($list, $insertionList, $insertionPosition = '')
651  {
652  $list = $newList = trim($list, ", \t\n\r\0\x0B");
653 
654  list($location, $positionName) = GeneralUtility::trimExplode(':', $insertionPosition);
655 
656  if ($location !== 'replace') {
657  $insertionList = self::removeDuplicatesForInsertion($insertionList, $list);
658  }
659 
660  if ($insertionList === '') {
661  return $list;
662  }
663  if ($list === '') {
664  return $insertionList;
665  }
666  if ($insertionPosition === '') {
667  return $list . ', ' . $insertionList;
668  }
669 
670  // The $insertPosition may be a palette: after:--palette--;;title
671  // In the $list the palette may contain a LLL string in between the ;;
672  // Adjust the regex to match that
673  if (strpos($positionName, ';;') !== false) {
674  $positionName = str_replace(';;', ';[^;]*;', $positionName);
675  }
676 
677  $pattern = ('/(^|,\\s*)(' . $positionName . ')(;[^,$]+)?(,|$)/');
678  switch ($location) {
679  case 'after':
680  $newList = preg_replace($pattern, '$1$2$3, ' . $insertionList . '$4', $list);
681  break;
682  case 'before':
683  $newList = preg_replace($pattern, '$1' . $insertionList . ', $2$3$4', $list);
684  break;
685  case 'replace':
686  $newList = preg_replace($pattern, '$1' . $insertionList . '$4', $list);
687  break;
688  default:
689  }
690 
691  // When preg_replace did not replace anything; append the $insertionList.
692  if ($list === $newList) {
693  return $list . ', ' . $insertionList;
694  }
695  return $newList;
696  }
697 
713  protected static function removeDuplicatesForInsertion($insertionList, $list = '')
714  {
715  $insertionListParts = preg_split('/\\s*,\\s*/', $insertionList);
716  $listMatches = array();
717  if ($list !== '') {
718  preg_match_all('/(?:^|,)\\s*\\b([^;,]+)\\b[^,]*/', $list, $listMatches);
719  $listMatches = $listMatches[1];
720  }
721 
722  $cleanInsertionListParts = array();
723  foreach ($insertionListParts as $fieldName) {
724  $fieldNameParts = explode(';', $fieldName, 2);
725  $cleanFieldName = $fieldNameParts[0];
726  if (
727  $cleanFieldName === '--linebreak--'
728  || (
729  !in_array($cleanFieldName, $cleanInsertionListParts, true)
730  && !in_array($cleanFieldName, $listMatches, true)
731  )
732  ) {
733  $cleanInsertionListParts[] = $fieldName;
734  }
735  }
736  return implode(', ', $cleanInsertionListParts);
737  }
738 
745  protected static function explodeItemList($itemList)
746  {
747  $items = array();
748  $itemParts = GeneralUtility::trimExplode(',', $itemList, true);
749  foreach ($itemParts as $itemPart) {
750  $itemDetails = GeneralUtility::trimExplode(';', $itemPart, false, 5);
751  $key = $itemDetails[0];
752  if (strpos($key, '--') !== false) {
753  // If $key is a separator (--div--) or palette (--palette--) then it will be appended by a unique number. This must be removed again when using this value!
754  $key .= count($items);
755  }
756  if (!isset($items[$key])) {
757  $items[$key] = array(
758  'rawData' => $itemPart,
759  'details' => array()
760  );
761  $details = array(0 => 'field', 1 => 'label', 2 => 'palette');
762  foreach ($details as $id => $property) {
763  $items[$key]['details'][$property] = isset($itemDetails[$id]) ? $itemDetails[$id] : '';
764  }
765  }
766  }
767  return $items;
768  }
769 
778  protected static function generateItemList(array $items, $useRawData = false)
779  {
780  $itemParts = array();
781  foreach ($items as $item => $itemDetails) {
782  if (strpos($item, '--') !== false) {
783  // If $item is a separator (--div--) or palette (--palette--) then it may have been appended by a unique number. This must be stripped away here.
784  $item = str_replace(array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9), '', $item);
785  }
786  if ($useRawData) {
787  $itemParts[] = $itemDetails['rawData'];
788  } else {
789  if (count($itemDetails['details']) > 1) {
790  $details = array('palette', 'label', 'field');
791  $elements = array();
792  $addEmpty = false;
793  foreach ($details as $property) {
794  if ($itemDetails['details'][$property] !== '' || $addEmpty) {
795  $addEmpty = true;
796  array_unshift($elements, $itemDetails['details'][$property]);
797  }
798  }
799  $item = implode(';', $elements);
800  }
801  $itemParts[] = $item;
802  }
803  }
804  return implode(', ', $itemParts);
805  }
806 
815  public static function allowTableOnStandardPages($table)
816  {
817  $GLOBALS['PAGES_TYPES']['default']['allowedTables'] .= ',' . $table;
818  }
819 
832  public static function addExtJSModule($extensionName, $mainModuleName, $subModuleName = '', $position = '', array $moduleConfiguration = array())
833  {
834  if (empty($extensionName)) {
835  throw new \InvalidArgumentException('The extension name must not be empty', 1325938973);
836  }
837  $extensionKey = GeneralUtility::camelCaseToLowerCaseUnderscored($extensionName);
838  $extensionName = str_replace(' ', '', ucwords(str_replace('_', ' ', $extensionName)));
839  $defaultModuleConfiguration = array(
840  'access' => 'admin',
841  'icon' => 'sysext/backend/Resources/Public/Images/Logo.png',
842  'labels' => '',
843  'extRelPath' => self::extRelPath($extensionKey) . 'Classes/'
844  );
845  // Add mandatory parameter to use new pagetree
846  if ($mainModuleName === 'web') {
847  $defaultModuleConfiguration['navigationComponentId'] = 'typo3-pagetree';
848  }
849  ArrayUtility::mergeRecursiveWithOverrule($defaultModuleConfiguration, $moduleConfiguration);
850  $moduleConfiguration = $defaultModuleConfiguration;
851  if ($subModuleName !== '') {
852  $moduleSignature = $mainModuleName . '_' . $subModuleName;
853  } else {
854  $moduleSignature = $mainModuleName;
855  }
856  $moduleConfiguration['name'] = $moduleSignature;
857  $moduleConfiguration['script'] = 'extjspaneldummy.html';
858  $moduleConfiguration['extensionName'] = $extensionName;
859  $moduleConfiguration['configureModuleFunction'] = array(ExtensionManagementUtility::class, 'configureModule');
860  $GLOBALS['TBE_MODULES']['_configuration'][$moduleSignature] = $moduleConfiguration;
861  self::addModule($mainModuleName, $subModuleName, $position);
862  }
863 
872  public static function configureModule($moduleSignature, $modulePath)
873  {
874  $moduleConfiguration = $GLOBALS['TBE_MODULES']['_configuration'][$moduleSignature];
875  $iconPathAndFilename = $moduleConfiguration['icon'];
876  if (substr($iconPathAndFilename, 0, 4) === 'EXT:') {
877  list($extensionKey, $relativePath) = explode('/', substr($iconPathAndFilename, 4), 2);
878  $iconPathAndFilename = self::extPath($extensionKey) . $relativePath;
879  }
880  // @todo skin support
881  $moduleLabels = array(
882  'tabs_images' => array(
883  'tab' => $iconPathAndFilename
884  ),
885  'labels' => array(
886  'tablabel' => $GLOBALS['LANG']->sL($moduleConfiguration['labels'] . ':mlang_labels_tablabel'),
887  'tabdescr' => $GLOBALS['LANG']->sL($moduleConfiguration['labels'] . ':mlang_labels_tabdescr')
888  ),
889  'tabs' => array(
890  'tab' => $GLOBALS['LANG']->sL($moduleConfiguration['labels'] . ':mlang_tabs_tab')
891  )
892  );
893  $GLOBALS['LANG']->addModuleLabels($moduleLabels, $moduleSignature . '_');
894  return $moduleConfiguration;
895  }
896 
908  public static function addModule($main, $sub = '', $position = '', $path = '', $moduleConfiguration = array())
909  {
910  // If there is already a main module by this name:
911  // Adding the submodule to the correct position:
912  if (isset($GLOBALS['TBE_MODULES'][$main]) && $sub) {
913  list($place, $modRef) = GeneralUtility::trimExplode(':', $position, true);
914  $modules = ',' . $GLOBALS['TBE_MODULES'][$main] . ',';
915  if ($place === null || ($modRef !== null && !GeneralUtility::inList($modules, $modRef))) {
916  $place = 'bottom';
917  }
918  $modRef = ',' . $modRef . ',';
919  if (!GeneralUtility::inList($modules, $sub)) {
920  switch (strtolower($place)) {
921  case 'after':
922  $modules = str_replace($modRef, $modRef . $sub . ',', $modules);
923  break;
924  case 'before':
925  $modules = str_replace($modRef, ',' . $sub . $modRef, $modules);
926  break;
927  case 'top':
928  $modules = $sub . $modules;
929  break;
930  case 'bottom':
931  default:
932  $modules = $modules . $sub;
933  }
934  }
935  // Re-inserting the submodule list:
936  $GLOBALS['TBE_MODULES'][$main] = trim($modules, ',');
937  } else {
938  // Create new main modules with only one submodule, $sub (or none if $sub is blank)
939  $GLOBALS['TBE_MODULES'][$main] = $sub;
940  }
941  $fullModuleSignature = $main . ($sub ? '_' . $sub : '');
942  // Adding path:
943  if ($path) {
944  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.');
945  self::addModulePath($fullModuleSignature, $path);
946  }
947 
948  // add additional configuration
949  if (is_array($moduleConfiguration) && !empty($moduleConfiguration)) {
950  $GLOBALS['TBE_MODULES']['_configuration'][$fullModuleSignature] = $moduleConfiguration;
951  }
952  }
953 
963  public static function registerExtDirectComponent($endpointName, $callbackClass, $moduleName = null, $accessLevel = null)
964  {
965  $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ExtDirect'][$endpointName] = array(
966  'callbackClass' => $callbackClass,
967  'moduleName' => $moduleName,
968  'accessLevel' => $accessLevel
969  );
970  }
971 
979  public static function registerAjaxHandler($ajaxId, $callbackMethod, $csrfTokenCheck = true)
980  {
981  $GLOBALS['TYPO3_CONF_VARS']['BE']['AJAX'][$ajaxId] = array(
982  'callbackMethod' => $callbackMethod,
983  'csrfTokenCheck' => $csrfTokenCheck
984  );
985  }
986 
999  public static function addModulePath($name, $path)
1000  {
1002  if (StringUtility::beginsWith($path, 'EXT:')) {
1003  list($extensionKey, $relativePath) = explode('/', substr($path, 4), 2);
1004  $path = ExtensionManagementUtility::extPath($extensionKey) . $relativePath;
1005  }
1006  $GLOBALS['TBE_MODULES']['_PATHS'][$name] = $path;
1007  }
1008 
1024  public static function insertModuleFunction($modname, $className, $classPath = null, $title, $MM_key = 'function', $WS = '')
1025  {
1026  $GLOBALS['TBE_MODULES_EXT'][$modname]['MOD_MENU'][$MM_key][$className] = array(
1027  'name' => $className,
1028  'path' => null,
1029  'title' => $title,
1030  'ws' => $WS
1031  );
1032  }
1033 
1046  public static function appendToTypoConfVars($group, $key, $content)
1047  {
1048  $GLOBALS['TYPO3_CONF_VARS_extensionAdded'][$group][$key] .= $content;
1049  $GLOBALS['TYPO3_CONF_VARS'][$group][$key] .= $content;
1050  }
1051 
1060  public static function addPageTSConfig($content)
1061  {
1062  self::appendToTypoConfVars('BE', 'defaultPageTSconfig', '
1063 [GLOBAL]
1064 ' . $content);
1065  }
1066 
1075  public static function addUserTSConfig($content)
1076  {
1077  self::appendToTypoConfVars('BE', 'defaultUserTSconfig', '
1078 [GLOBAL]
1079 ' . $content);
1080  }
1081 
1091  public static function addLLrefForTCAdescr($tca_descr_key, $file_ref)
1092  {
1093  if ($tca_descr_key) {
1094  if (!is_array($GLOBALS['TCA_DESCR'][$tca_descr_key])) {
1095  $GLOBALS['TCA_DESCR'][$tca_descr_key] = array();
1096  }
1097  if (!is_array($GLOBALS['TCA_DESCR'][$tca_descr_key]['refs'])) {
1098  $GLOBALS['TCA_DESCR'][$tca_descr_key]['refs'] = array();
1099  }
1100  $GLOBALS['TCA_DESCR'][$tca_descr_key]['refs'][] = $file_ref;
1101  }
1102  }
1103 
1113  public static function addNavigationComponent($module, $componentId, $extensionKey = null)
1114  {
1115  $extensionKey = $extensionKey ?: $GLOBALS['_EXTKEY'];
1116  if (!isset($extensionKey)) {
1117  throw new \RuntimeException('No extensionKey set in addNavigationComponent(). Provide it as third Parameter', 1404068039);
1118  }
1119  $GLOBALS['TBE_MODULES']['_navigationComponents'][$module] = array(
1120  'componentId' => $componentId,
1121  'extKey' => $extensionKey,
1122  'isCoreComponent' => false
1123  );
1124  }
1125 
1133  public static function addCoreNavigationComponent($module, $componentId)
1134  {
1135  self::addNavigationComponent($module, $componentId);
1136  $GLOBALS['TBE_MODULES']['_navigationComponents'][$module]['isCoreComponent'] = true;
1137  }
1138 
1139  /**************************************
1140  *
1141  * Adding SERVICES features
1142  *
1143  ***************************************/
1153  public static function addService($extKey, $serviceType, $serviceKey, $info)
1154  {
1155  if ($serviceType && is_array($info)) {
1156  $info['priority'] = max(0, min(100, $info['priority']));
1157  $GLOBALS['T3_SERVICES'][$serviceType][$serviceKey] = $info;
1158  $GLOBALS['T3_SERVICES'][$serviceType][$serviceKey]['extKey'] = $extKey;
1159  $GLOBALS['T3_SERVICES'][$serviceType][$serviceKey]['serviceKey'] = $serviceKey;
1160  $GLOBALS['T3_SERVICES'][$serviceType][$serviceKey]['serviceType'] = $serviceType;
1161  // Change the priority (and other values) from $GLOBALS['TYPO3_CONF_VARS']
1162  // $GLOBALS['TYPO3_CONF_VARS']['T3_SERVICES'][$serviceType][$serviceKey]['priority']
1163  // even the activation is possible (a unix service might be possible on windows for some reasons)
1164  if (is_array($GLOBALS['TYPO3_CONF_VARS']['T3_SERVICES'][$serviceType][$serviceKey])) {
1165  // No check is done here - there might be configuration values only the service type knows about, so
1166  // we pass everything
1167  $GLOBALS['T3_SERVICES'][$serviceType][$serviceKey] = array_merge($GLOBALS['T3_SERVICES'][$serviceType][$serviceKey], $GLOBALS['TYPO3_CONF_VARS']['T3_SERVICES'][$serviceType][$serviceKey]);
1168  }
1169  // OS check
1170  // Empty $os means 'not limited to one OS', therefore a check is not needed
1171  if ($GLOBALS['T3_SERVICES'][$serviceType][$serviceKey]['available'] && $GLOBALS['T3_SERVICES'][$serviceType][$serviceKey]['os'] != '') {
1172  // TYPO3_OS is not yet defined
1173  $os_type = stripos(PHP_OS, 'win') !== false && !stripos(PHP_OS, 'darwin') !== false ? 'WIN' : 'UNIX';
1174  $os = GeneralUtility::trimExplode(',', strtoupper($GLOBALS['T3_SERVICES'][$serviceType][$serviceKey]['os']));
1175  if (!in_array($os_type, $os)) {
1176  self::deactivateService($serviceType, $serviceKey);
1177  }
1178  }
1179  // Convert subtype list to array for quicker access
1180  $GLOBALS['T3_SERVICES'][$serviceType][$serviceKey]['serviceSubTypes'] = array();
1181  $serviceSubTypes = GeneralUtility::trimExplode(',', $info['subtype']);
1182  foreach ($serviceSubTypes as $subtype) {
1183  $GLOBALS['T3_SERVICES'][$serviceType][$serviceKey]['serviceSubTypes'][$subtype] = $subtype;
1184  }
1185  }
1186  }
1187 
1196  public static function findService($serviceType, $serviceSubType = '', $excludeServiceKeys = array())
1197  {
1198  $serviceKey = false;
1199  $serviceInfo = false;
1200  $priority = 0;
1201  $quality = 0;
1202  if (!is_array($excludeServiceKeys)) {
1203  $excludeServiceKeys = GeneralUtility::trimExplode(',', $excludeServiceKeys, true);
1204  }
1205  if (is_array($GLOBALS['T3_SERVICES'][$serviceType])) {
1206  foreach ($GLOBALS['T3_SERVICES'][$serviceType] as $key => $info) {
1207  if (in_array($key, $excludeServiceKeys)) {
1208  continue;
1209  }
1210  // Select a subtype randomly
1211  // Useful to start a service by service key without knowing his subtypes - for testing purposes
1212  if ($serviceSubType == '*') {
1213  $serviceSubType = key($info['serviceSubTypes']);
1214  }
1215  // This matches empty subtype too
1216  if ($info['available'] && ($info['subtype'] == $serviceSubType || $info['serviceSubTypes'][$serviceSubType]) && $info['priority'] >= $priority) {
1217  // Has a lower quality than the already found, therefore we skip this service
1218  if ($info['priority'] == $priority && $info['quality'] < $quality) {
1219  continue;
1220  }
1221  // Check if the service is available
1222  $info['available'] = self::isServiceAvailable($serviceType, $key, $info);
1223  // Still available after exec check?
1224  if ($info['available']) {
1225  $serviceKey = $key;
1226  $priority = $info['priority'];
1227  $quality = $info['quality'];
1228  }
1229  }
1230  }
1231  }
1232  if ($serviceKey) {
1233  $serviceInfo = $GLOBALS['T3_SERVICES'][$serviceType][$serviceKey];
1234  }
1235  return $serviceInfo;
1236  }
1237 
1246  public static function findServiceByKey($serviceKey)
1247  {
1248  if (is_array($GLOBALS['T3_SERVICES'])) {
1249  // Loop on all service types
1250  // NOTE: we don't care about the actual type, we are looking for a specific key
1251  foreach ($GLOBALS['T3_SERVICES'] as $serviceType => $servicesPerType) {
1252  if (isset($servicesPerType[$serviceKey])) {
1253  $serviceDetails = $servicesPerType[$serviceKey];
1254  // Test if service is available
1255  if (self::isServiceAvailable($serviceType, $serviceKey, $serviceDetails)) {
1256  // We have found the right service, return its information
1257  return $serviceDetails;
1258  }
1259  }
1260  }
1261  }
1262  throw new \TYPO3\CMS\Core\Exception('Service not found for key: ' . $serviceKey, 1319217244);
1263  }
1264 
1273  public static function isServiceAvailable($serviceType, $serviceKey, $serviceDetails)
1274  {
1275  // If the service depends on external programs - check if they exists
1276  if (trim($serviceDetails['exec'])) {
1277  $executables = GeneralUtility::trimExplode(',', $serviceDetails['exec'], true);
1278  foreach ($executables as $executable) {
1279  // If at least one executable file is not available, exit early returning FALSE
1280  if (!CommandUtility::checkCommand($executable)) {
1281  self::deactivateService($serviceType, $serviceKey);
1282  return false;
1283  }
1284  }
1285  }
1286  // The service is available
1287  return true;
1288  }
1289 
1297  public static function deactivateService($serviceType, $serviceKey)
1298  {
1299  // ... maybe it's better to move non-available services to a different array??
1300  $GLOBALS['T3_SERVICES'][$serviceType][$serviceKey]['available'] = false;
1301  }
1302 
1303  /**************************************
1304  *
1305  * Adding FRONTEND features
1306  *
1307  ***************************************/
1321  public static function addPlugin($itemArray, $type = 'list_type', $extensionKey = null)
1322  {
1323  $extensionKey = $extensionKey ?: $GLOBALS['_EXTKEY'];
1324  if (!isset($extensionKey)) {
1325  throw new \RuntimeException(
1326  'No extension key could be determined when calling addPlugin()!'
1327  . LF
1328  . 'This method is meant to be called from an ext_tables.php or Configuration/TCA/Overrides file. '
1329  . 'If you call it from Configuration/TCA/Overrides, the extension key needs to be specified as third parameter. '
1330  . 'Calling it from any other place e.g. ext_localconf.php does not work and is not supported.',
1331  1404068038
1332  );
1333  }
1334  if ($extensionKey && !$itemArray[2] && isset($GLOBALS['TYPO3_LOADED_EXT'][$extensionKey]['ext_icon'])) {
1335  $itemArray[2] = 'EXT:' . $extensionKey . '/' . $GLOBALS['TYPO3_LOADED_EXT'][$extensionKey]['ext_icon'];
1336  }
1337  if (is_array($GLOBALS['TCA']['tt_content']['columns']) && is_array($GLOBALS['TCA']['tt_content']['columns'][$type]['config']['items'])) {
1338  foreach ($GLOBALS['TCA']['tt_content']['columns'][$type]['config']['items'] as $k => $v) {
1339  if ((string)$v[1] === (string)$itemArray[1]) {
1340  $GLOBALS['TCA']['tt_content']['columns'][$type]['config']['items'][$k] = $itemArray;
1341  return;
1342  }
1343  }
1344  $GLOBALS['TCA']['tt_content']['columns'][$type]['config']['items'][] = $itemArray;
1345  }
1346  }
1347 
1358  public static function addPiFlexFormValue($piKeyToMatch, $value, $CTypeToMatch = 'list')
1359  {
1360  if (is_array($GLOBALS['TCA']['tt_content']['columns']) && is_array($GLOBALS['TCA']['tt_content']['columns']['pi_flexform']['config']['ds'])) {
1361  $GLOBALS['TCA']['tt_content']['columns']['pi_flexform']['config']['ds'][$piKeyToMatch . ',' . $CTypeToMatch] = $value;
1362  }
1363  }
1364 
1375  public static function addToInsertRecords($table, $content_table = 'tt_content', $content_field = 'records')
1376  {
1377  if (is_array($GLOBALS['TCA'][$content_table]['columns']) && isset($GLOBALS['TCA'][$content_table]['columns'][$content_field]['config']['allowed'])) {
1378  $GLOBALS['TCA'][$content_table]['columns'][$content_field]['config']['allowed'] .= ',' . $table;
1379  }
1380  }
1381 
1410  public static function addPItoST43($key, $classFile = '', $suffix = '', $type = 'list_type', $cached = 0)
1411  {
1412  $classFile = $classFile ? $classFile : 'pi/class.tx_' . str_replace('_', '', $key) . $suffix . '.php';
1413  $cN = self::getCN($key);
1414  // General plugin
1415  $pluginContent = trim('
1416 plugin.' . $cN . $suffix . ' = USER' . ($cached ? '' : '_INT') . '
1417 plugin.' . $cN . $suffix . ' {
1418  includeLibs = ' . $GLOBALS['TYPO3_LOADED_EXT'][$key]['siteRelPath'] . $classFile . '
1419  userFunc = ' . $cN . $suffix . '->main
1420 }');
1421  self::addTypoScript($key, 'setup', '
1422 # Setting ' . $key . ' plugin TypoScript
1423 ' . $pluginContent);
1424  // Add after defaultContentRendering
1425  switch ($type) {
1426  case 'list_type':
1427  $addLine = 'tt_content.list.20.' . $key . $suffix . ' = < plugin.' . $cN . $suffix;
1428  break;
1429  case 'menu_type':
1430  $addLine = 'tt_content.menu.20.' . $key . $suffix . ' = < plugin.' . $cN . $suffix;
1431  break;
1432  case 'CType':
1433  $addLine = trim('
1434 tt_content.' . $key . $suffix . ' = COA
1435 tt_content.' . $key . $suffix . ' {
1436  10 = < lib.stdheader
1437  20 = < plugin.' . $cN . $suffix . '
1438 }
1439 ');
1440  break;
1441  case 'header_layout':
1442  $addLine = 'lib.stdheader.10.' . $key . $suffix . ' = < plugin.' . $cN . $suffix;
1443  break;
1444  case 'includeLib':
1445  $addLine = 'page.1000 = < plugin.' . $cN . $suffix;
1446  break;
1447  default:
1448  $addLine = '';
1449  }
1450  if ($addLine) {
1451  self::addTypoScript($key, 'setup', '
1452 # Setting ' . $key . ' plugin TypoScript
1453 ' . $addLine . '
1454 ', 'defaultContentRendering');
1455  }
1456  }
1457 
1468  public static function addStaticFile($extKey, $path, $title)
1469  {
1470  if ($extKey && $path && is_array($GLOBALS['TCA']['sys_template']['columns'])) {
1471  $value = str_replace(',', '', 'EXT:' . $extKey . '/' . $path);
1472  $itemArray = array(trim($title . ' (' . $extKey . ')'), $value);
1473  $GLOBALS['TCA']['sys_template']['columns']['include_static_file']['config']['items'][] = $itemArray;
1474  }
1475  }
1476 
1486  public static function registerPageTSConfigFile($extKey, $file, $title)
1487  {
1488  if ($extKey && $file && is_array($GLOBALS['TCA']['pages']['columns'])) {
1489  $value = str_replace(',', '', 'EXT:' . $extKey . '/' . $file);
1490  $itemArray = array(trim($title . ' (' . $extKey . ')'), $value);
1491  $GLOBALS['TCA']['pages']['columns']['tsconfig_includes']['config']['items'][] = $itemArray;
1492  }
1493  }
1494 
1503  public static function addTypoScriptSetup($content)
1504  {
1505  self::appendToTypoConfVars('FE', 'defaultTypoScript_setup', '
1506 [GLOBAL]
1507 ' . $content);
1508  }
1509 
1518  public static function addTypoScriptConstants($content)
1519  {
1520  self::appendToTypoConfVars('FE', 'defaultTypoScript_constants', '
1521 [GLOBAL]
1522 ' . $content);
1523  }
1524 
1543  public static function addTypoScript($key, $type, $content, $afterStaticUid = 0)
1544  {
1545  if ($type === 'setup' || $type === 'constants') {
1546  $content = '
1547 
1548 [GLOBAL]
1549 #############################################
1550 ## TypoScript added by extension "' . $key . '"
1551 #############################################
1552 
1553 ' . $content;
1554  if ($afterStaticUid) {
1555  // If 'content (default)' is targeted (static uid 43),
1556  // 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
1557  if ($afterStaticUid === 'defaultContentRendering' || $afterStaticUid == 43) {
1558  $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_' . $type . '.']['defaultContentRendering'] .= $content;
1559  } else {
1560  $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_' . $type . '.'][$afterStaticUid] .= $content;
1561  }
1562  } else {
1563  $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_' . $type] .= $content;
1564  }
1565  }
1566  }
1567 
1568  /***************************************
1569  *
1570  * Internal extension management methods
1571  *
1572  ***************************************/
1581  public static function getExtensionIcon($extensionPath, $returnFullPath = false)
1582  {
1583  $icon = '';
1584  $iconFileTypesToCheckFor = array('png', 'svg', 'gif');
1585  foreach ($iconFileTypesToCheckFor as $fileType) {
1586  if (file_exists($extensionPath . 'ext_icon.' . $fileType)) {
1587  $icon = 'ext_icon.' . $fileType;
1588  break;
1589  }
1590  }
1591  return $returnFullPath ? $extensionPath . $icon : $icon;
1592  }
1593 
1606  public static function loadExtLocalconf($allowCaching = true)
1607  {
1608  if ($allowCaching) {
1609  $cacheIdentifier = self::getExtLocalconfCacheIdentifier();
1611  $codeCache = self::getCacheManager()->getCache('cache_core');
1612  if ($codeCache->has($cacheIdentifier)) {
1613  $codeCache->requireOnce($cacheIdentifier);
1614  } else {
1615  self::loadSingleExtLocalconfFiles();
1616  self::createExtLocalconfCacheEntry();
1617  }
1618  } else {
1619  self::loadSingleExtLocalconfFiles();
1620  }
1621  }
1622 
1628  protected static function loadSingleExtLocalconfFiles()
1629  {
1630  // This is the main array meant to be manipulated in the ext_localconf.php files
1631  // In general it is recommended to not rely on it to be globally defined in that
1632  // scope but to use $GLOBALS['TYPO3_CONF_VARS'] instead.
1633  // Nevertheless we define it here as global for backwards compatibility.
1634  global $TYPO3_CONF_VARS;
1635  foreach ($GLOBALS['TYPO3_LOADED_EXT'] as $_EXTKEY => $extensionInformation) {
1636  if ((is_array($extensionInformation) || $extensionInformation instanceof \ArrayAccess) && isset($extensionInformation['ext_localconf.php'])) {
1637  // $_EXTKEY and $_EXTCONF are available in ext_localconf.php
1638  // and are explicitly set in cached file as well
1639  $_EXTCONF = isset($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'][$_EXTKEY]) ? $GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'][$_EXTKEY] : null;
1640  require $extensionInformation['ext_localconf.php'];
1641  }
1642  }
1643  }
1644 
1650  protected static function createExtLocalconfCacheEntry()
1651  {
1652  $extensionInformation = $GLOBALS['TYPO3_LOADED_EXT'];
1653  $phpCodeToCache = array();
1654  // Set same globals as in loadSingleExtLocalconfFiles()
1655  $phpCodeToCache[] = '';
1658  $phpCodeToCache[] = '';
1659  $phpCodeToCache[] = 'global $TYPO3_CONF_VARS, $T3_SERVICES, $T3_VAR;';
1660  $phpCodeToCache[] = '';
1661  // Iterate through loaded extensions and add ext_localconf content
1662  foreach ($extensionInformation as $extensionKey => $extensionDetails) {
1663  if (isset($extensionDetails['ext_localconf.php']) && $extensionDetails['ext_localconf.php']) {
1664  // Include a header per extension to make the cache file more readable
1665  $phpCodeToCache[] = '';
1669  $phpCodeToCache[] = '';
1670  // Set $_EXTKEY and $_EXTCONF for this extension
1671  $phpCodeToCache[] = '$_EXTKEY = \'' . $extensionKey . '\';';
1672  $phpCodeToCache[] = '$_EXTCONF = $GLOBALS[\'TYPO3_CONF_VARS\'][\'EXT\'][\'extConf\'][$_EXTKEY];';
1673  $phpCodeToCache[] = '';
1674  // Add ext_localconf.php content of extension
1675  $phpCodeToCache[] = trim(GeneralUtility::getUrl($extensionDetails['ext_localconf.php']));
1676  $phpCodeToCache[] = '';
1677  $phpCodeToCache[] = '';
1678  }
1679  }
1680  $phpCodeToCache = implode(LF, $phpCodeToCache);
1681  // Remove all start and ending php tags from content
1682  $phpCodeToCache = preg_replace('/<\\?php|\\?>/is', '', $phpCodeToCache);
1683  self::getCacheManager()->getCache('cache_core')->set(self::getExtLocalconfCacheIdentifier(), $phpCodeToCache);
1684  }
1685 
1691  protected static function getExtLocalconfCacheIdentifier()
1692  {
1693  return 'ext_localconf_' . sha1(TYPO3_version . PATH_site . 'extLocalconf' . serialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['runtimeActivatedPackages']));
1694  }
1695 
1710  public static function loadBaseTca($allowCaching = true)
1711  {
1712  if ($allowCaching) {
1713  $cacheIdentifier = static::getBaseTcaCacheIdentifier();
1715  $codeCache = static::getCacheManager()->getCache('cache_core');
1716  if ($codeCache->has($cacheIdentifier)) {
1717  // substr is necessary, because the php frontend wraps php code around the cache value
1718  $cacheData = unserialize(substr($codeCache->get($cacheIdentifier), 6, -2));
1719  $GLOBALS['TCA'] = $cacheData['tca'];
1720  GeneralUtility::setSingletonInstance(CategoryRegistry::class, $cacheData['categoryRegistry']);
1721  } else {
1722  static::buildBaseTcaFromSingleFiles();
1723  static::createBaseTcaCacheFile();
1724  }
1725  } else {
1726  static::buildBaseTcaFromSingleFiles();
1727  }
1728  }
1729 
1738  protected static function buildBaseTcaFromSingleFiles()
1739  {
1740  $GLOBALS['TCA'] = array();
1741 
1742  $activePackages = static::$packageManager->getActivePackages();
1743 
1744  // First load "full table" files from Configuration/TCA
1745  foreach ($activePackages as $package) {
1746  $tcaConfigurationDirectory = $package->getPackagePath() . 'Configuration/TCA';
1747  if (is_dir($tcaConfigurationDirectory)) {
1748  $files = scandir($tcaConfigurationDirectory);
1749  foreach ($files as $file) {
1750  if (
1751  is_file($tcaConfigurationDirectory . '/' . $file)
1752  && ($file !== '.')
1753  && ($file !== '..')
1754  && (substr($file, -4, 4) === '.php')
1755  ) {
1756  $tcaOfTable = require($tcaConfigurationDirectory . '/' . $file);
1757  if (is_array($tcaOfTable)) {
1758  // TCA table name is filename without .php suffix, eg 'sys_notes', not 'sys_notes.php'
1759  $tcaTableName = substr($file, 0, -4);
1760  $GLOBALS['TCA'][$tcaTableName] = $tcaOfTable;
1761  }
1762  }
1763  }
1764  }
1765  }
1766 
1767  // Apply category stuff
1768  CategoryRegistry::getInstance()->applyTcaForPreRegisteredTables();
1769 
1770  // Execute override files from Configuration/TCA/Overrides
1771  foreach ($activePackages as $package) {
1772  $tcaOverridesPathForPackage = $package->getPackagePath() . 'Configuration/TCA/Overrides';
1773  if (is_dir($tcaOverridesPathForPackage)) {
1774  $files = scandir($tcaOverridesPathForPackage);
1775  foreach ($files as $file) {
1776  if (
1777  is_file($tcaOverridesPathForPackage . '/' . $file)
1778  && ($file !== '.')
1779  && ($file !== '..')
1780  && (substr($file, -4, 4) === '.php')
1781  ) {
1782  require($tcaOverridesPathForPackage . '/' . $file);
1783  }
1784  }
1785  }
1786  }
1787 
1788  // TCA migration
1789  // @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
1790  $tcaMigration = GeneralUtility::makeInstance(TcaMigration::class);
1791  $GLOBALS['TCA'] = $tcaMigration->migrate($GLOBALS['TCA']);
1792  $messages = $tcaMigration->getMessages();
1793  if (!empty($messages)) {
1794  $context = 'Automatic TCA migration done during bootstrap. Please adapt TCA accordingly, these migrations'
1795  . ' will be removed with TYPO3 CMS 8. The backend module "Configuration -> TCA" shows the modified values.'
1796  . ' Please adapt these areas:';
1797  array_unshift($messages, $context);
1798  GeneralUtility::deprecationLog(implode(LF, $messages));
1799  }
1800 
1801  static::emitTcaIsBeingBuiltSignal($GLOBALS['TCA']);
1802  }
1803 
1814  protected static function emitTcaIsBeingBuiltSignal(array $tca)
1815  {
1816  list($tca) = static::getSignalSlotDispatcher()->dispatch(__CLASS__, 'tcaIsBeingBuilt', array($tca));
1817  $GLOBALS['TCA'] = $tca;
1818  }
1819 
1826  protected static function createBaseTcaCacheFile()
1827  {
1829  $codeCache = self::getCacheManager()->getCache('cache_core');
1830  $codeCache->set(static::getBaseTcaCacheIdentifier(), serialize(array('tca' => $GLOBALS['TCA'], 'categoryRegistry' => CategoryRegistry::getInstance())));
1831  }
1832 
1838  protected static function getBaseTcaCacheIdentifier()
1839  {
1840  return 'tca_base_' . sha1(TYPO3_version . PATH_site . 'tca_with_category_registry' . serialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['runtimeActivatedPackages']));
1841  }
1842 
1855  public static function loadExtTables($allowCaching = true)
1856  {
1857  if ($allowCaching && !self::$extTablesWasReadFromCacheOnce) {
1858  self::$extTablesWasReadFromCacheOnce = true;
1859  $cacheIdentifier = self::getExtTablesCacheIdentifier();
1861  $codeCache = self::getCacheManager()->getCache('cache_core');
1862  if ($codeCache->has($cacheIdentifier)) {
1863  $codeCache->requireOnce($cacheIdentifier);
1864  } else {
1865  self::loadSingleExtTablesFiles();
1866  self::createExtTablesCacheEntry();
1867  }
1868  } else {
1869  self::loadSingleExtTablesFiles();
1870  }
1871  }
1872 
1878  protected static function loadSingleExtTablesFiles()
1879  {
1880  // In general it is recommended to not rely on it to be globally defined in that
1881  // scope, but we can not prohibit this without breaking backwards compatibility
1882  global $T3_SERVICES, $T3_VAR, $TYPO3_CONF_VARS;
1883  global $TBE_MODULES, $TBE_MODULES_EXT, $TCA;
1884  global $PAGES_TYPES, $TBE_STYLES;
1885  global $_EXTKEY;
1886  // Load each ext_tables.php file of loaded extensions
1887  foreach ($GLOBALS['TYPO3_LOADED_EXT'] as $_EXTKEY => $extensionInformation) {
1888  if ((is_array($extensionInformation) || $extensionInformation instanceof \ArrayAccess) && $extensionInformation['ext_tables.php']) {
1889  // $_EXTKEY and $_EXTCONF are available in ext_tables.php
1890  // and are explicitly set in cached file as well
1891  $_EXTCONF = $GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'][$_EXTKEY];
1892  require $extensionInformation['ext_tables.php'];
1893  static::loadNewTcaColumnsConfigFiles();
1894  }
1895  }
1896  }
1897 
1903  protected static function createExtTablesCacheEntry()
1904  {
1905  $extensionInformation = $GLOBALS['TYPO3_LOADED_EXT'];
1906  $phpCodeToCache = array();
1907  // Set same globals as in loadSingleExtTablesFiles()
1908  $phpCodeToCache[] = '';
1911  $phpCodeToCache[] = '';
1912  $phpCodeToCache[] = 'global $T3_SERVICES, $T3_VAR, $TYPO3_CONF_VARS;';
1913  $phpCodeToCache[] = 'global $TBE_MODULES, $TBE_MODULES_EXT, $TCA;';
1914  $phpCodeToCache[] = 'global $PAGES_TYPES, $TBE_STYLES;';
1915  $phpCodeToCache[] = 'global $_EXTKEY;';
1916  $phpCodeToCache[] = '';
1917  // Iterate through loaded extensions and add ext_tables content
1918  foreach ($extensionInformation as $extensionKey => $extensionDetails) {
1919  if (isset($extensionDetails['ext_tables.php']) && $extensionDetails['ext_tables.php']) {
1920  // Include a header per extension to make the cache file more readable
1921  $phpCodeToCache[] = '';
1925  $phpCodeToCache[] = '';
1926  // Set $_EXTKEY and $_EXTCONF for this extension
1927  $phpCodeToCache[] = '$_EXTKEY = \'' . $extensionKey . '\';';
1928  $phpCodeToCache[] = '$_EXTCONF = $GLOBALS[\'TYPO3_CONF_VARS\'][\'EXT\'][\'extConf\'][$_EXTKEY];';
1929  $phpCodeToCache[] = '';
1930  // Add ext_tables.php content of extension
1931  $phpCodeToCache[] = trim(GeneralUtility::getUrl($extensionDetails['ext_tables.php']));
1932  $phpCodeToCache[] = '';
1933  $phpCodeToCache[] = ExtensionManagementUtility::class . '::loadNewTcaColumnsConfigFiles();';
1934  $phpCodeToCache[] = '';
1935  }
1936  }
1937  $phpCodeToCache = implode(LF, $phpCodeToCache);
1938  // Remove all start and ending php tags from content
1939  $phpCodeToCache = preg_replace('/<\\?php|\\?>/is', '', $phpCodeToCache);
1940  self::getCacheManager()->getCache('cache_core')->set(self::getExtTablesCacheIdentifier(), $phpCodeToCache);
1941  }
1942 
1964  public static function loadNewTcaColumnsConfigFiles()
1965  {
1966  global $TCA;
1967 
1968  foreach ($TCA as $tableName => $_) {
1969  if (!isset($TCA[$tableName]['columns'])) {
1970  $columnsConfigFile = $TCA[$tableName]['ctrl']['dynamicConfigFile'];
1971  if ($columnsConfigFile) {
1973  if (GeneralUtility::isAbsPath($columnsConfigFile)) {
1974  include($columnsConfigFile);
1975  } else {
1976  throw new \RuntimeException(
1977  'Columns configuration file not found',
1978  1341151261
1979  );
1980  }
1981  }
1982  }
1983  }
1984  }
1985 
1991  protected static function getExtTablesCacheIdentifier()
1992  {
1993  return 'ext_tables_' . sha1(TYPO3_version . PATH_site . 'extTables' . serialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['runtimeActivatedPackages']));
1994  }
1995 
2011  public static function removeCacheFiles()
2012  {
2013  self::getCacheManager()->flushCachesInGroup('system');
2014  }
2015 
2021  public static function getLoadedExtensionListArray()
2022  {
2023  return array_keys(static::$packageManager->getActivePackages());
2024  }
2025 
2036  public static function loadExtension($extensionKey)
2037  {
2038  if (static::$packageManager->isPackageActive($extensionKey)) {
2039  throw new \RuntimeException('Extension already loaded', 1342345486);
2040  }
2041  static::$packageManager->activatePackage($extensionKey);
2042  }
2043 
2054  public static function unloadExtension($extensionKey)
2055  {
2056  if (!static::$packageManager->isPackageActive($extensionKey)) {
2057  throw new \RuntimeException('Extension not loaded', 1342345487);
2058  }
2059  static::$packageManager->deactivatePackage($extensionKey);
2060  }
2061 
2074  public static function makeCategorizable($extensionKey, $tableName, $fieldName = 'categories', array $options = array(), $override = false)
2075  {
2076  // Update the category registry
2077  $result = CategoryRegistry::getInstance()->add($extensionKey, $tableName, $fieldName, $options, $override);
2078  if ($result === false) {
2079  $message = CategoryRegistry::class . ': no category registered for table "%s". Key was already registered.';
2081  $logger = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Log\LogManager::class)->getLogger(__CLASS__);
2082  $logger->warning(
2083  sprintf($message, $tableName)
2084  );
2085  }
2086  }
2087 }