‪TYPO3CMS  9.5
ExtensionConfiguration.php
Go to the documentation of this file.
1 <?php
2 declare(strict_types = 1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
20 use TYPO3\CMS\Core\Package\PackageManager;
23 
42 {
49  protected ‪$setup = [];
50 
57  protected ‪$raw;
58 
65  protected ‪$rawPointer = 0;
66 
73  protected ‪$lastComment = '';
74 
81  protected ‪$commentSet = false;
82 
89  protected ‪$inBrace = 0;
90 
127  public function get(string $extension, string $path = '')
128  {
129  $hasBeenSynchronized = false;
130  if (!isset(‪$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'][$extension]) || !is_array(‪$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'][$extension])) {
131  // This if() should not be hit at "casual" runtime, but only in early setup phases
133  $hasBeenSynchronized = true;
134  if (!isset(‪$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'][$extension]) || !is_array(‪$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'][$extension])) {
135  // If there is still no such entry, even after sync -> throw
137  'No extension configuration for extension ' . $extension . ' found. Either this extension'
138  . ' has no extension configuration or the configuration is not up to date. Execute the'
139  . ' install tool to update configuration.',
140  1509654728
141  );
142  }
143  }
144  if (empty($path)) {
145  return ‪$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'][$extension];
146  }
147  if (!‪ArrayUtility::isValidPath(‪$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'], $extension . '/' . $path)) {
148  // This if() should not be hit at "casual" runtime, but only in early setup phases
149  if (!$hasBeenSynchronized) {
151  }
152  // If there is still no such entry, even after sync -> throw
153  if (!‪ArrayUtility::isValidPath(‪$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'], $extension . '/' . $path)) {
155  'Path ' . $path . ' does not exist in extension configuration',
156  1509977699
157  );
158  }
159  }
160  return ‪ArrayUtility::getValueByPath(‪$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'], $extension . '/' . $path);
161  }
162 
194  public function set(string $extension, string $path = '', $value = null)
195  {
196  if (empty($extension)) {
197  throw new \RuntimeException('extension name must not be empty', 1509715852);
198  }
199  if (!empty($path)) {
200  // @todo: this functionality can be removed once EXT:bootstrap_package is adapted to the new API.
201  $extensionConfiguration = $this->get($extension);
202  $value = ‪ArrayUtility::setValueByPath($extensionConfiguration, $path, $value);
203  }
204  $configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class);
205  if ($value === null) {
206  // Remove whole extension config
207  $configurationManager->removeLocalConfigurationKeysByPath(['EXTENSIONS/' . $extension]);
208  if (isset(‪$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'][$extension])) {
209  unset(‪$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'][$extension]);
210  }
211  } else {
212  // Set full extension config
213  $configurationManager->setLocalConfigurationValueByPath('EXTENSIONS/' . $extension, $value);
214  ‪$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'][$extension] = $value;
215  }
216 
217  // After TYPO3_CONF_VARS['EXTENSIONS'] has been written, update legacy layer TYPO3_CONF_VARS['EXTENSIONS']['extConf']
218  // @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0 with removal of old serialized 'extConf' layer
219  if (!empty(‪$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'])) {
220  $extConfArray = [];
221  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'] as $extensionName => $extensionConfig) {
222  $extConfArray[$extensionName] = serialize($this->‪addDotsToArrayKeysRecursiveForLegacyExtConf($extensionConfig));
223  }
224  $configurationManager->setLocalConfigurationValueByPath('EXT/extConf', $extConfArray);
225  ‪$GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'] = $extConfArray;
226  }
227  }
228 
237  public function ‪setAll(array $configuration)
238  {
239  $configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class);
240  $configurationManager->setLocalConfigurationValueByPath('EXTENSIONS', $configuration);
241  ‪$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'] = $configuration;
242 
243  // After TYPO3_CONF_VARS['EXTENSIONS'] has been written, update legacy layer TYPO3_CONF_VARS['EXTENSIONS']['extConf']
244  // @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0 with removal of old serialized 'extConf' layer
245  $extConfArray = [];
246  foreach ($configuration as $extensionName => $extensionConfig) {
247  $extConfArray[$extensionName] = serialize($this->‪addDotsToArrayKeysRecursiveForLegacyExtConf($extensionConfig));
248  }
249  $configurationManager->setLocalConfigurationValueByPath('EXT/extConf', $extConfArray);
250  ‪$GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'] = $extConfArray;
251  }
252 
263  {
264  $activePackages = GeneralUtility::makeInstance(PackageManager::class)->getActivePackages();
265  $fullConfiguration = [];
266  $currentLocalConfiguration = ‪$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'] ?? [];
267  foreach ($activePackages as $package) {
268  if (!@is_file($package->getPackagePath() . 'ext_conf_template.txt')) {
269  continue;
270  }
271  $extensionKey = $package->getPackageKey();
272  $currentExtensionConfig = $currentLocalConfiguration[$extensionKey] ?? [];
273  $extConfTemplateConfiguration = $this->‪getExtConfTablesWithoutCommentsAsNestedArrayWithoutDots($extensionKey);
274  ‪ArrayUtility::mergeRecursiveWithOverrule($extConfTemplateConfiguration, $currentExtensionConfig);
275  if (!empty($extConfTemplateConfiguration)) {
276  $fullConfiguration[$extensionKey] = $extConfTemplateConfiguration;
277  }
278  }
279  // Write new config if changed. Loose array comparison to not write if only array key order is different
280  if ($fullConfiguration != $currentLocalConfiguration) {
281  $this->‪setAll($fullConfiguration);
282  }
283  }
284 
294  public function ‪synchronizeExtConfTemplateWithLocalConfiguration(string $extensionKey)
295  {
296  $package = GeneralUtility::makeInstance(PackageManager::class)->getPackage($extensionKey);
297  if (!@is_file($package->getPackagePath() . 'ext_conf_template.txt')) {
298  return;
299  }
300  $currentLocalConfiguration = ‪$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'][$extensionKey] ?? [];
301  $extConfTemplateConfiguration = $this->‪getExtConfTablesWithoutCommentsAsNestedArrayWithoutDots($extensionKey);
302  ‪ArrayUtility::mergeRecursiveWithOverrule($extConfTemplateConfiguration, $currentLocalConfiguration);
303  // Write new config if changed. Loose array comparison to not write if only array key order is different
304  if ($extConfTemplateConfiguration != $currentLocalConfiguration) {
305  $this->set($extensionKey, '', $extConfTemplateConfiguration);
306  }
307  }
308 
335  private function ‪addDotsToArrayKeysRecursiveForLegacyExtConf(array $extensionConfig): array
336  {
337  $newArray = [];
338  foreach ($extensionConfig as $key => $value) {
339  if (is_array($value)) {
340  $newArray[$key . '.'] = $this->‪addDotsToArrayKeysRecursiveForLegacyExtConf($value);
341  } else {
342  $newArray[$key] = $value;
343  }
344  }
345  return $newArray;
346  }
347 
357  protected function ‪getExtConfTablesWithoutCommentsAsNestedArrayWithoutDots(string $extensionKey): array
358  {
359  $configuration = $this->‪getParsedExtConfTemplate($extensionKey);
360  return $this->‪removeCommentsAndDotsRecursive($configuration);
361  }
362 
372  public function ‪getParsedExtConfTemplate(string $extensionKey): array
373  {
374  $rawConfigurationString = $this->‪getDefaultConfigurationRawString($extensionKey);
375  $configuration = [];
376  if ((string)$rawConfigurationString !== '') {
377  $this->raw = explode(LF, $rawConfigurationString);
378  $this->rawPointer = 0;
379  $this->setup = [];
380  $this->‪parseSub($this->setup);
381  if ($this->inBrace) {
382  throw new \RuntimeException(
383  'Line ' . ($this->rawPointer - 1) . ': The script is short of ' . $this->inBrace . ' end brace(s)',
384  1507645349
385  );
386  }
387  $configuration = ‪$this->setup;
388  }
389  return $configuration;
390  }
391 
401  protected function ‪getDefaultConfigurationRawString(string $extensionKey): string
402  {
403  $rawString = '';
404  $extConfTemplateFileLocation = GeneralUtility::getFileAbsFileName(
405  'EXT:' . $extensionKey . '/ext_conf_template.txt'
406  );
407  if (file_exists($extConfTemplateFileLocation)) {
408  $rawString = file_get_contents($extConfTemplateFileLocation);
409  }
410  return $rawString;
411  }
412 
439  protected function ‪removeCommentsAndDotsRecursive(array $config): array
440  {
441  $cleanedConfig = [];
442  foreach ($config as $key => $value) {
443  if (substr($key, -2) === '..') {
444  continue;
445  }
446  if (substr($key, -1) === '.') {
447  $cleanedConfig[rtrim($key, '.')] = $this->‪removeCommentsAndDotsRecursive($value);
448  } else {
449  $cleanedConfig[$key] = $value;
450  }
451  }
452  return $cleanedConfig;
453  }
454 
462  protected function ‪parseSub(array &‪$setup)
463  {
464  while (isset($this->raw[$this->rawPointer])) {
465  $line = ltrim($this->raw[$this->rawPointer]);
466  $this->rawPointer++;
467  // Set comment flag?
468  if (strpos($line, '/*') === 0) {
469  $this->commentSet = 1;
470  }
471  if (!$this->commentSet && $line) {
472  if ($line[0] !== '}' && $line[0] !== '#' && $line[0] !== '/') {
473  // If not brace-end or comment
474  // Find object name string until we meet an operator
475  $varL = strcspn($line, "\t" . ' {=<>(');
476  // check for special ":=" operator
477  if ($varL > 0 && substr($line, $varL - 1, 2) === ':=') {
478  --$varL;
479  }
480  // also remove tabs after the object string name
481  $objStrName = substr($line, 0, $varL);
482  if ($objStrName !== '') {
483  $r = [];
484  if (preg_match('/[^[:alnum:]_\\\\\\.:-]/i', $objStrName, $r)) {
485  throw new \RuntimeException(
486  'Line ' . ($this->rawPointer - 1) . ': Object Name String, "' . htmlspecialchars($objStrName) . '" contains invalid character "' . $r[0] . '". Must be alphanumeric or one of: "_:-\\."',
487  1507645381
488  );
489  }
490  $line = ltrim(substr($line, $varL));
491  if ($line === '') {
492  throw new \RuntimeException(
493  'Line ' . ($this->rawPointer - 1) . ': Object Name String, "' . htmlspecialchars($objStrName) . '" was not followed by any operator, =<>({',
494  1507645417
495  );
496  }
497  switch ($line[0]) {
498  case '=':
499  if (strpos($objStrName, '.') !== false) {
500  $value = [];
501  $value[0] = trim(substr($line, 1));
502  $this->‪setVal($objStrName, ‪$setup, $value);
503  } else {
504  ‪$setup[$objStrName] = trim(substr($line, 1));
505  if ($this->lastComment) {
506  // Setting comment..
507  ‪$setup[$objStrName . '..'] .= ‪$this->lastComment;
508  }
509  }
510  break;
511  case '{':
512  $this->inBrace++;
513  if (strpos($objStrName, '.') !== false) {
514  $this->‪rollParseSub($objStrName, ‪$setup);
515  } else {
516  if (!isset(‪$setup[$objStrName . '.'])) {
517  ‪$setup[$objStrName . '.'] = [];
518  }
519  $this->‪parseSub($setup[$objStrName . '.']);
520  }
521  break;
522  default:
523  throw new \RuntimeException(
524  'Line ' . ($this->rawPointer - 1) . ': Object Name String, "' . htmlspecialchars($objStrName) . '" was not followed by any operator, =<>({',
525  1507645445
526  );
527  }
528 
529  $this->lastComment = '';
530  }
531  } elseif ($line[0] === '}') {
532  $this->inBrace--;
533  $this->lastComment = '';
534  if ($this->inBrace < 0) {
535  throw new \RuntimeException(
536  'Line ' . ($this->rawPointer - 1) . ': An end brace is in excess.',
537  1507645489
538  );
539  }
540  break;
541  } else {
542  $this->lastComment .= rtrim($line) . LF;
543  }
544  }
545  // Unset comment
546  if ($this->commentSet) {
547  if (strpos($line, '*/') === 0) {
548  $this->commentSet = 0;
549  }
550  }
551  }
552  }
553 
563  protected function ‪rollParseSub($string, array &‪$setup)
564  {
565  if ((string)$string === '') {
566  return;
567  }
568  list($key, $remainingKey) = $this->‪parseNextKeySegment($string);
569  $key .= '.';
570  if (!isset(‪$setup[$key])) {
571  ‪$setup[$key] = [];
572  }
573  $remainingKey === ''
574  ? $this->‪parseSub($setup[$key])
575  : $this->‪rollParseSub($remainingKey, ‪$setup[$key]);
576  }
577 
587  protected function ‪setVal($string, array &‪$setup, $value)
588  {
589  if ((string)$string === '') {
590  return;
591  }
592 
593  list($key, $remainingKey) = $this->‪parseNextKeySegment($string);
594  $subKey = $key . '.';
595  if ($remainingKey === '') {
596  if (isset($value[0])) {
597  ‪$setup[$key] = $value[0];
598  }
599  if (isset($value[1])) {
600  ‪$setup[$subKey] = $value[1];
601  }
602  if ($this->lastComment) {
603  ‪$setup[$key . '..'] .= ‪$this->lastComment;
604  }
605  } else {
606  if (!isset(‪$setup[$subKey])) {
607  ‪$setup[$subKey] = [];
608  }
609  $this->‪setVal($remainingKey, ‪$setup[$subKey], $value);
610  }
611  }
612 
626  protected function ‪parseNextKeySegment($key): array
627  {
628  // if no dot is in the key, nothing to do
629  $dotPosition = strpos($key, '.');
630  if ($dotPosition === false) {
631  return [$key, ''];
632  }
633 
634  if (strpos($key, '\\') !== false) {
635  // backslashes are in the key, so we do further parsing
636  while ($dotPosition !== false) {
637  if ($dotPosition > 0 && $key[$dotPosition - 1] !== '\\' || $dotPosition > 1 && $key[$dotPosition - 2] === '\\') {
638  break;
639  }
640  // escaped dot found, continue
641  $dotPosition = strpos($key, '.', $dotPosition + 1);
642  }
643 
644  if ($dotPosition === false) {
645  // no regular dot found
646  $keySegment = $key;
647  $remainingKey = '';
648  } else {
649  if ($dotPosition > 1 && $key[$dotPosition - 2] === '\\' && $key[$dotPosition - 1] === '\\') {
650  $keySegment = substr($key, 0, $dotPosition - 1);
651  } else {
652  $keySegment = substr($key, 0, $dotPosition);
653  }
654  $remainingKey = substr($key, $dotPosition + 1);
655  }
656 
657  // fix key segment by removing escape sequences
658  $keySegment = str_replace('\\.', '.', $keySegment);
659  } else {
660  // no backslash in the key, we're fine off
661  list($keySegment, $remainingKey) = explode('.', $key, 2);
662  }
663  return [$keySegment, $remainingKey];
664  }
665 }
‪TYPO3\CMS\Core\Configuration\ExtensionConfiguration\rollParseSub
‪rollParseSub($string, array &$setup)
Definition: ExtensionConfiguration.php:557
‪TYPO3\CMS\Core\Configuration\ExtensionConfiguration\parseSub
‪parseSub(array &$setup)
Definition: ExtensionConfiguration.php:456
‪TYPO3\CMS\Core\Configuration\Exception\ExtensionConfigurationPathDoesNotExistException
Definition: ExtensionConfigurationPathDoesNotExistException.php:26
‪TYPO3\CMS\Core\Configuration\ExtensionConfiguration
Definition: ExtensionConfiguration.php:42
‪TYPO3\CMS\Core\Utility\ArrayUtility\isValidPath
‪static bool isValidPath(array $array, $path, $delimiter='/')
Definition: ArrayUtility.php:143
‪TYPO3\CMS\Core\Utility\ArrayUtility\mergeRecursiveWithOverrule
‪static mergeRecursiveWithOverrule(array &$original, array $overrule, $addKeys=true, $includeEmptyValues=true, $enableUnsetFeature=true)
Definition: ArrayUtility.php:614
‪TYPO3\CMS\Core\Configuration\ExtensionConfiguration\$inBrace
‪int $inBrace
Definition: ExtensionConfiguration.php:83
‪TYPO3\CMS\Core\Utility\ArrayUtility\getValueByPath
‪static mixed getValueByPath(array $array, $path, $delimiter='/')
Definition: ArrayUtility.php:179
‪TYPO3\CMS\Core\Configuration\ExtensionConfiguration\getExtConfTablesWithoutCommentsAsNestedArrayWithoutDots
‪array getExtConfTablesWithoutCommentsAsNestedArrayWithoutDots(string $extensionKey)
Definition: ExtensionConfiguration.php:351
‪TYPO3\CMS\Core\Configuration\ExtensionConfiguration\$rawPointer
‪int $rawPointer
Definition: ExtensionConfiguration.php:62
‪TYPO3\CMS\Core\Configuration\ExtensionConfiguration\getParsedExtConfTemplate
‪array getParsedExtConfTemplate(string $extensionKey)
Definition: ExtensionConfiguration.php:366
‪TYPO3\CMS\Core\Configuration\ExtensionConfiguration\removeCommentsAndDotsRecursive
‪array removeCommentsAndDotsRecursive(array $config)
Definition: ExtensionConfiguration.php:433
‪TYPO3\CMS\Core\Configuration\ExtensionConfiguration\setAll
‪setAll(array $configuration)
Definition: ExtensionConfiguration.php:231
‪TYPO3\CMS\Core\Configuration\ExtensionConfiguration\synchronizeExtConfTemplateWithLocalConfiguration
‪synchronizeExtConfTemplateWithLocalConfiguration(string $extensionKey)
Definition: ExtensionConfiguration.php:288
‪TYPO3\CMS\Core\Configuration\ExtensionConfiguration\$setup
‪array $setup
Definition: ExtensionConfiguration.php:48
‪TYPO3\CMS\Core\Utility\ArrayUtility\setValueByPath
‪static array setValueByPath(array $array, $path, $value, $delimiter='/')
Definition: ArrayUtility.php:271
‪TYPO3\CMS\Core\Configuration\ExtensionConfiguration\$commentSet
‪bool $commentSet
Definition: ExtensionConfiguration.php:76
‪TYPO3\CMS\Core\Utility\ArrayUtility
Definition: ArrayUtility.php:23
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:5
‪TYPO3\CMS\Core\Configuration
Definition: ConfigurationManager.php:2
‪TYPO3\CMS\Core\Configuration\ExtensionConfiguration\parseNextKeySegment
‪array parseNextKeySegment($key)
Definition: ExtensionConfiguration.php:620
‪TYPO3\CMS\Core\Configuration\ExtensionConfiguration\addDotsToArrayKeysRecursiveForLegacyExtConf
‪array addDotsToArrayKeysRecursiveForLegacyExtConf(array $extensionConfig)
Definition: ExtensionConfiguration.php:329
‪TYPO3\CMS\Core\Configuration\Exception\ExtensionConfigurationExtensionNotConfiguredException
Definition: ExtensionConfigurationExtensionNotConfiguredException.php:26
‪TYPO3\CMS\Core\Configuration\ExtensionConfiguration\$raw
‪array $raw
Definition: ExtensionConfiguration.php:55
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:45
‪TYPO3\CMS\Core\Configuration\ExtensionConfiguration\setVal
‪setVal($string, array &$setup, $value)
Definition: ExtensionConfiguration.php:581
‪TYPO3\CMS\Core\Configuration\ExtensionConfiguration\getDefaultConfigurationRawString
‪string getDefaultConfigurationRawString(string $extensionKey)
Definition: ExtensionConfiguration.php:395
‪TYPO3\CMS\Core\Configuration\ExtensionConfiguration\$lastComment
‪string $lastComment
Definition: ExtensionConfiguration.php:69
‪TYPO3\CMS\Core\Configuration\ExtensionConfiguration\synchronizeExtConfTemplateWithLocalConfigurationOfAllExtensions
‪synchronizeExtConfTemplateWithLocalConfigurationOfAllExtensions()
Definition: ExtensionConfiguration.php:256