‪TYPO3CMS  11.5
SiteConfiguration.php
Go to the documentation of this file.
1 <?php
2 
3 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 
19 
20 use Symfony\Component\Finder\Finder;
21 use Symfony\Component\Yaml\Yaml;
34 
43 {
45 
49  protected ‪$configPath;
50 
57  protected ‪$configFileName = 'config.yaml';
58 
65  protected ‪$cacheIdentifier = 'sites-configuration';
66 
73  protected ‪$firstLevelCache;
74 
79  public function ‪__construct(string ‪$configPath, ?‪PhpFrontend $coreCache = null)
80  {
81  $this->configPath = ‪$configPath;
82  // The following fallback to GeneralUtility;:getContainer() is only used in acceptance tests
83  // @todo: Fix testing-framework/typo3/sysext/core/Classes/Configuration/SiteConfiguration.php
84  // to inject the cache instance
85  $this->cache = $coreCache ?? GeneralUtility::getContainer()->get('cache.core');
86  }
87 
94  public function ‪getAllExistingSites(bool $useCache = true): array
95  {
96  if ($useCache && $this->firstLevelCache !== null) {
98  }
99  return $this->‪resolveAllExistingSites($useCache);
100  }
101 
110  public function ‪createNewBasicSite(string $identifier, int $rootPageId, string $base): void
111  {
112  // Create a default site configuration called "main" as best practice
113  $this->‪write($identifier, [
114  'rootPageId' => $rootPageId,
115  'base' => $base,
116  'languages' => [
117  0 => [
118  'title' => 'English',
119  'enabled' => true,
120  'languageId' => 0,
121  'base' => '/',
122  'typo3Language' => 'default',
123  'locale' => 'en_US.UTF-8',
124  'iso-639-1' => 'en',
125  'navigationTitle' => 'English',
126  'hreflang' => 'en-us',
127  'direction' => 'ltr',
128  'flag' => 'us',
129  ],
130  ],
131  'errorHandling' => [],
132  'routes' => [],
133  ]);
134  }
135 
142  public function ‪resolveAllExistingSites(bool $useCache = true): array
143  {
144  $sites = [];
145  $siteConfiguration = $this->‪getAllSiteConfigurationFromFiles($useCache);
146  foreach ($siteConfiguration as $identifier => $configuration) {
147  $rootPageId = (int)($configuration['rootPageId'] ?? 0);
148  if ($rootPageId > 0) {
149  $sites[$identifier] = GeneralUtility::makeInstance(Site::class, $identifier, $rootPageId, $configuration);
150  }
151  }
152  $this->firstLevelCache = $sites;
153  return $sites;
154  }
155 
163  protected function ‪getAllSiteConfigurationFromFiles(bool $useCache = true): array
164  {
165  // Check if the data is already cached
166  $siteConfiguration = $useCache ? $this->cache->require($this->cacheIdentifier) : false;
167  if ($siteConfiguration !== false) {
168  return $siteConfiguration;
169  }
170  ‪$finder = new Finder();
171  try {
172  ‪$finder->files()->depth(0)->name($this->configFileName)->in($this->configPath . '/*');
173  } catch (\InvalidArgumentException $e) {
174  // Directory $this->configPath does not exist yet
175  ‪$finder = [];
176  }
177  $loader = GeneralUtility::makeInstance(YamlFileLoader::class);
178  $siteConfiguration = [];
179  foreach (‪$finder as $fileInfo) {
180  $configuration = $loader->load(GeneralUtility::fixWindowsFilePath((string)$fileInfo));
181  $identifier = basename($fileInfo->getPath());
182  $siteConfiguration[$identifier] = $configuration;
183  }
184  $this->cache->set($this->cacheIdentifier, 'return ' . var_export($siteConfiguration, true) . ';');
185 
186  return $siteConfiguration;
187  }
188 
199  public function ‪load(string $siteIdentifier): array
200  {
201  $fileName = $this->configPath . '/' . $siteIdentifier . '/' . ‪$this->configFileName;
202  $loader = GeneralUtility::makeInstance(YamlFileLoader::class);
203  return $loader->load(GeneralUtility::fixWindowsFilePath($fileName), ‪YamlFileLoader::PROCESS_IMPORTS);
204  }
205 
214  public function ‪write(string $siteIdentifier, array $configuration, bool $protectPlaceholders = false): void
215  {
216  $folder = $this->configPath . '/' . $siteIdentifier;
217  $fileName = $folder . '/' . ‪$this->configFileName;
218  $newConfiguration = $configuration;
219  if (!file_exists($folder)) {
221  if ($protectPlaceholders && $newConfiguration !== []) {
222  $newConfiguration = $this->protectPlaceholders([], $newConfiguration);
223  }
224  } elseif (file_exists($fileName)) {
225  $loader = GeneralUtility::makeInstance(YamlFileLoader::class);
226  // load without any processing to have the unprocessed base to modify
227  $newConfiguration = $loader->load(GeneralUtility::fixWindowsFilePath($fileName), 0);
228  // load the processed configuration to diff changed values
229  $processed = $loader->load(GeneralUtility::fixWindowsFilePath($fileName));
230  // find properties that were modified via GUI
231  $newModified = array_replace_recursive(
232  self::findRemoved($processed, $configuration),
233  self::findModified($processed, $configuration)
234  );
235  if ($protectPlaceholders && $newModified !== []) {
236  $newModified = $this->protectPlaceholders($newConfiguration, $newModified);
237  }
238  // change _only_ the modified keys, leave the original non-changed areas alone
239  ‪ArrayUtility::mergeRecursiveWithOverrule($newConfiguration, $newModified);
240  }
241  $newConfiguration = $this->‪sortConfiguration($newConfiguration);
242  $yamlFileContents = Yaml::dump($newConfiguration, 99, 2);
243  if (!‪GeneralUtility::writeFile($fileName, $yamlFileContents)) {
244  throw new SiteConfigurationWriteException('Unable to write site configuration in sites/' . $siteIdentifier . '/' . $this->configFileName, 1590487011);
245  }
246  $this->firstLevelCache = null;
247  $this->cache->remove($this->cacheIdentifier);
248  }
249 
257  public function ‪rename(string $currentIdentifier, string $newIdentifier): void
258  {
259  if (!‪rename($this->configPath . '/' . $currentIdentifier, $this->configPath . '/' . $newIdentifier)) {
260  throw new SiteConfigurationWriteException('Unable to rename folder sites/' . $currentIdentifier, 1522491300);
261  }
262  $this->cache->remove($this->cacheIdentifier);
263  $this->firstLevelCache = null;
264  }
265 
273  public function delete(string $siteIdentifier): void
274  {
275  $sites = $this->‪getAllExistingSites();
276  if (!isset($sites[$siteIdentifier])) {
277  throw new SiteNotFoundException('Site configuration named ' . $siteIdentifier . ' not found.', 1522866183);
278  }
279  $fileName = $this->configPath . '/' . $siteIdentifier . '/' . ‪$this->configFileName;
280  if (!file_exists($fileName)) {
281  throw new SiteNotFoundException('Site configuration file ' . $this->configFileName . ' within the site ' . $siteIdentifier . ' not found.', 1522866184);
282  }
283  if (!unlink($fileName)) {
284  throw new SiteConfigurationWriteException('Unable to delete folder sites/' . $siteIdentifier, 1596462020);
285  }
286  $this->cache->remove($this->cacheIdentifier);
287  $this->firstLevelCache = null;
288  }
289 
299  protected function protectPlaceholders(array $existingConfiguration, array $modifiedConfiguration): array
300  {
301  try {
302  return GeneralUtility::makeInstance(YamlPlaceholderGuard::class, $existingConfiguration)
303  ->process($modifiedConfiguration);
304  } catch (‪YamlPlaceholderException $exception) {
305  throw new ‪SiteConfigurationWriteException($exception->getMessage(), 1670361271, $exception);
306  }
307  }
308 
313  protected function ‪sortConfiguration(array $newConfiguration): array
314  {
315  ksort($newConfiguration);
316  if (isset($newConfiguration['imports'])) {
317  $imports = $newConfiguration['imports'];
318  unset($newConfiguration['imports']);
319  $newConfiguration['imports'] = $imports;
320  }
321  return $newConfiguration;
322  }
323 
324  protected static function ‪findModified(array $currentConfiguration, array $newConfiguration): array
325  {
326  $differences = [];
327  foreach ($newConfiguration as $key => $value) {
328  if (!isset($currentConfiguration[$key]) || $currentConfiguration[$key] !== $newConfiguration[$key]) {
329  if (!isset($newConfiguration[$key]) && isset($currentConfiguration[$key])) {
330  $differences[$key] = '__UNSET';
331  } elseif (isset($currentConfiguration[$key])
332  && is_array($newConfiguration[$key])
333  && is_array($currentConfiguration[$key])
334  ) {
335  $differences[$key] = ‪self::findModified($currentConfiguration[$key], $newConfiguration[$key]);
336  } else {
337  $differences[$key] = $value;
338  }
339  }
340  }
341  return $differences;
342  }
343 
344  protected static function ‪findRemoved(array $currentConfiguration, array $newConfiguration): array
345  {
346  $removed = [];
347  foreach ($currentConfiguration as $key => $value) {
348  if (!isset($newConfiguration[$key])) {
349  $removed[$key] = '__UNSET';
350  } elseif (isset($currentConfiguration[$key]) && is_array($currentConfiguration[$key]) && is_array($newConfiguration[$key])) {
351  $removedInRecursion = ‪self::findRemoved($currentConfiguration[$key], $newConfiguration[$key]);
352  if (!empty($removedInRecursion)) {
353  $removed[$key] = $removedInRecursion;
354  }
355  }
356  }
357 
358  return $removed;
359  }
360 
361  public function ‪warmupCaches(‪CacheWarmupEvent $event): void
362  {
363  if ($event->‪hasGroup('system')) {
365  }
366  }
367 }
‪TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader\PROCESS_IMPORTS
‪const PROCESS_IMPORTS
Definition: YamlFileLoader.php:53
‪TYPO3\CMS\Core\Configuration\Exception\SiteConfigurationWriteException
Definition: SiteConfigurationWriteException.php:25
‪$finder
‪if(PHP_SAPI !=='cli') $finder
Definition: header-comment.php:22
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\getAllExistingSites
‪Site[] getAllExistingSites(bool $useCache=true)
Definition: SiteConfiguration.php:90
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\findRemoved
‪static findRemoved(array $currentConfiguration, array $newConfiguration)
Definition: SiteConfiguration.php:340
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\getAllSiteConfigurationFromFiles
‪array getAllSiteConfigurationFromFiles(bool $useCache=true)
Definition: SiteConfiguration.php:159
‪TYPO3\CMS\Core\Cache\Frontend\PhpFrontend
Definition: PhpFrontend.php:25
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\$cache
‪PhpFrontend $cache
Definition: SiteConfiguration.php:44
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\rename
‪rename(string $currentIdentifier, string $newIdentifier)
Definition: SiteConfiguration.php:253
‪TYPO3\CMS\Core\Exception\SiteNotFoundException
Definition: SiteNotFoundException.php:25
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\warmupCaches
‪warmupCaches(CacheWarmupEvent $event)
Definition: SiteConfiguration.php:357
‪TYPO3\CMS\Core\Utility\ArrayUtility\mergeRecursiveWithOverrule
‪static mergeRecursiveWithOverrule(array &$original, array $overrule, $addKeys=true, $includeEmptyValues=true, $enableUnsetFeature=true)
Definition: ArrayUtility.php:654
‪TYPO3\CMS\Core\Cache\Event\CacheWarmupEvent
Definition: CacheWarmupEvent.php:24
‪TYPO3\CMS\Core\Configuration\SiteConfiguration
Definition: SiteConfiguration.php:43
‪TYPO3\CMS\Core\Site\Entity\Site
Definition: Site.php:42
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\sortConfiguration
‪catch(YamlPlaceholderException $exception) array sortConfiguration(array $newConfiguration)
Definition: SiteConfiguration.php:309
‪TYPO3\CMS\Core\Configuration\Loader\Exception\YamlPlaceholderException
Definition: YamlPlaceholderException.php:20
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\resolveAllExistingSites
‪Site[] resolveAllExistingSites(bool $useCache=true)
Definition: SiteConfiguration.php:138
‪TYPO3\CMS\Core\Utility\GeneralUtility\mkdir_deep
‪static mkdir_deep($directory)
Definition: GeneralUtility.php:1908
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\__construct
‪__construct(string $configPath, ?PhpFrontend $coreCache=null)
Definition: SiteConfiguration.php:75
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\$firstLevelCache
‪array null $firstLevelCache
Definition: SiteConfiguration.php:69
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\createNewBasicSite
‪createNewBasicSite(string $identifier, int $rootPageId, string $base)
Definition: SiteConfiguration.php:106
‪TYPO3\CMS\Core\Cache\Exception\InvalidDataException
Definition: InvalidDataException.php:23
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\$configFileName
‪string $configFileName
Definition: SiteConfiguration.php:55
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\$configPath
‪string $configPath
Definition: SiteConfiguration.php:48
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\load
‪array load(string $siteIdentifier)
Definition: SiteConfiguration.php:195
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\$cacheIdentifier
‪string $cacheIdentifier
Definition: SiteConfiguration.php:62
‪TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader
Definition: YamlFileLoader.php:48
‪TYPO3\CMS\Core\Utility\ArrayUtility
Definition: ArrayUtility.php:24
‪TYPO3\CMS\Core\SingletonInterface
Definition: SingletonInterface.php:22
‪TYPO3\CMS\Core\Configuration
Definition: ConfigurationManager.php:16
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\findModified
‪static findModified(array $currentConfiguration, array $newConfiguration)
Definition: SiteConfiguration.php:320
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:50
‪TYPO3\CMS\Core\Utility\GeneralUtility\writeFile
‪static bool writeFile($file, $content, $changePermissions=false)
Definition: GeneralUtility.php:1722
‪TYPO3\CMS\Core\Configuration\Loader\YamlPlaceholderGuard
Definition: YamlPlaceholderGuard.php:27
‪TYPO3\CMS\Core\Cache\Event\CacheWarmupEvent\hasGroup
‪hasGroup(string $group)
Definition: CacheWarmupEvent.php:38
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\write
‪write(string $siteIdentifier, array $configuration, bool $protectPlaceholders=false)
Definition: SiteConfiguration.php:210