‪TYPO3CMS  ‪main
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 Psr\EventDispatcher\EventDispatcherInterface;
21 use Symfony\Component\Finder\Finder;
22 use Symfony\Component\Yaml\Yaml;
38 
47 {
49 
50  protected string ‪$configPath;
51 
57  protected string ‪$configFileName = 'config.yaml';
58 
64  protected string ‪$settingsFileName = 'settings.yaml';
65 
71  protected string ‪$contentSecurityFileName = 'csp.yaml';
72 
78  protected string ‪$cacheIdentifier = 'sites-configuration';
79 
87  protected EventDispatcherInterface ‪$eventDispatcher;
88 
92  public function ‪__construct(string ‪$configPath, EventDispatcherInterface ‪$eventDispatcher, ‪PhpFrontend $coreCache = null)
93  {
94  $this->configPath = ‪$configPath;
95  // The following fallback to GeneralUtility;:getContainer() is only used in acceptance tests
96  // @todo: Fix testing-framework/typo3/sysext/core/Classes/Configuration/SiteConfiguration.php
97  // to inject the cache instance
98  $this->cache = $coreCache ?? GeneralUtility::getContainer()->get('cache.core');
99  $this->eventDispatcher = ‪$eventDispatcher;
100  }
101 
107  public function ‪getAllExistingSites(bool $useCache = true): array
108  {
109  if ($useCache && $this->firstLevelCache !== null) {
111  }
112  return $this->‪resolveAllExistingSites($useCache);
113  }
114 
120  public function ‪createNewBasicSite(string ‪$identifier, int $rootPageId, string $base): void
121  {
122  // Create a default site configuration called "main" as best practice
123  $this->‪write($identifier, [
124  'rootPageId' => $rootPageId,
125  'base' => $base,
126  'languages' => [
127  0 => [
128  'title' => 'English',
129  'enabled' => true,
130  'languageId' => 0,
131  'base' => '/',
132  'locale' => 'en_US.UTF-8',
133  'navigationTitle' => 'English',
134  'flag' => 'us',
135  ],
136  ],
137  'errorHandling' => [],
138  'routes' => [],
139  ]);
140  }
141 
147  public function ‪resolveAllExistingSites(bool $useCache = true): array
148  {
149  $sites = [];
150  $siteConfiguration = $this->‪getAllSiteConfigurationFromFiles($useCache);
151  foreach ($siteConfiguration as ‪$identifier => $configuration) {
152  // cast $identifier to string, as the identifier can potentially only consist of (int) digit numbers
153  ‪$identifier = (string)‪$identifier;
154  $siteSettings = $this->‪getSiteSettings(‪$identifier, $configuration);
155  $configuration['contentSecurityPolicies'] = $this->‪getContentSecurityPolicies(‪$identifier);
156 
157  $rootPageId = (int)($configuration['rootPageId'] ?? 0);
158  if ($rootPageId > 0) {
159  $sites[‪$identifier] = new Site(‪$identifier, $rootPageId, $configuration, $siteSettings);
160  }
161  }
162  $this->firstLevelCache = $sites;
163  return $sites;
164  }
165 
171  public function ‪getAllSiteConfigurationPaths(): array
172  {
173  ‪$finder = new Finder();
174  $paths = [];
175  try {
176  ‪$finder->files()->depth(0)->name($this->configFileName)->in($this->configPath . '/*');
177  } catch (\InvalidArgumentException $e) {
178  ‪$finder = [];
179  }
180 
181  foreach (‪$finder as $fileInfo) {
182  $path = $fileInfo->getPath();
183  $paths[basename($path)] = $path;
184  }
185  return $paths;
186  }
187 
193  protected function ‪getAllSiteConfigurationFromFiles(bool $useCache = true): array
194  {
195  // Check if the data is already cached
196  $siteConfiguration = $useCache ? $this->cache->require($this->cacheIdentifier) : false;
197  if ($siteConfiguration !== false) {
198  return $siteConfiguration;
199  }
200  ‪$finder = new Finder();
201  try {
202  ‪$finder->files()->depth(0)->name($this->configFileName)->in($this->configPath . '/*');
203  } catch (\InvalidArgumentException $e) {
204  // Directory $this->configPath does not exist yet
205  ‪$finder = [];
206  }
207  $loader = GeneralUtility::makeInstance(YamlFileLoader::class);
208  $siteConfiguration = [];
209  foreach (‪$finder as $fileInfo) {
210  $configuration = $loader->load(GeneralUtility::fixWindowsFilePath((string)$fileInfo));
211  ‪$identifier = basename($fileInfo->getPath());
212  $event = $this->eventDispatcher->dispatch(new SiteConfigurationLoadedEvent(‪$identifier, $configuration));
213  $siteConfiguration[‪$identifier] = $event->getConfiguration();
214  }
215  $this->cache->set($this->cacheIdentifier, 'return ' . var_export($siteConfiguration, true) . ';');
216 
217  return $siteConfiguration;
218  }
219 
230  public function ‪load(string ‪$siteIdentifier): array
231  {
232  $fileName = $this->configPath . '/' . ‪$siteIdentifier . '/' . ‪$this->configFileName;
233  $loader = GeneralUtility::makeInstance(YamlFileLoader::class);
234  return $loader->load(GeneralUtility::fixWindowsFilePath($fileName), ‪YamlFileLoader::PROCESS_IMPORTS);
235  }
236 
240  protected function ‪getSiteSettings(string ‪$siteIdentifier, array $siteConfiguration): SiteSettings
241  {
242  $fileName = $this->configPath . '/' . ‪$siteIdentifier . '/' . ‪$this->settingsFileName;
243  if (file_exists($fileName)) {
244  $loader = GeneralUtility::makeInstance(YamlFileLoader::class);
245  $settings = $loader->load(GeneralUtility::fixWindowsFilePath($fileName), ‪YamlFileLoader::PROCESS_IMPORTS);
246  } else {
247  $settings = $siteConfiguration['settings'] ?? [];
248  }
249  return new SiteSettings($settings);
250  }
251 
252  protected function ‪getContentSecurityPolicies(string ‪$siteIdentifier): array
253  {
254  $fileName = $this->configPath . '/' . ‪$siteIdentifier . '/' . ‪$this->contentSecurityFileName;
255  if (file_exists($fileName)) {
256  $loader = GeneralUtility::makeInstance(YamlFileLoader::class);
257  return $loader->load(GeneralUtility::fixWindowsFilePath($fileName), ‪YamlFileLoader::PROCESS_IMPORTS);
258  }
259  return [];
260  }
261 
262  public function ‪writeSettings(string ‪$siteIdentifier, array $settings): void
263  {
264  $fileName = $this->configPath . '/' . ‪$siteIdentifier . '/' . ‪$this->settingsFileName;
265  $yamlFileContents = Yaml::dump($settings, 99, 2);
266  if (!‪GeneralUtility::writeFile($fileName, $yamlFileContents)) {
267  throw new ‪SiteConfigurationWriteException('Unable to write site settings in sites/' . ‪$siteIdentifier . '/' . $this->configFileName, 1590487411);
268  }
269  }
270 
278  public function ‪write(string ‪$siteIdentifier, array $configuration, bool $protectPlaceholders = false): void
279  {
280  $folder = $this->configPath . '/' . ‪$siteIdentifier;
281  $fileName = $folder . '/' . ‪$this->configFileName;
282  $newConfiguration = $configuration;
283  if (!file_exists($folder)) {
285  if ($protectPlaceholders && $newConfiguration !== []) {
286  $newConfiguration = $this->protectPlaceholders([], $newConfiguration);
287  }
288  } elseif (file_exists($fileName)) {
289  $loader = GeneralUtility::makeInstance(YamlFileLoader::class);
290  // load without any processing to have the unprocessed base to modify
291  $newConfiguration = $loader->load(GeneralUtility::fixWindowsFilePath($fileName), 0);
292  // load the processed configuration to diff changed values
293  $processed = $loader->load(GeneralUtility::fixWindowsFilePath($fileName));
294  // find properties that were modified via GUI
295  $newModified = array_replace_recursive(
296  self::findRemoved($processed, $configuration),
297  self::findModified($processed, $configuration)
298  );
299  if ($protectPlaceholders && $newModified !== []) {
300  $newModified = $this->protectPlaceholders($newConfiguration, $newModified);
301  }
302  // change _only_ the modified keys, leave the original non-changed areas alone
303  ArrayUtility::mergeRecursiveWithOverrule($newConfiguration, $newModified);
304  }
305  $event = $this->eventDispatcher->dispatch(new SiteConfigurationBeforeWriteEvent(‪$siteIdentifier, $newConfiguration));
306  $newConfiguration = $this->‪sortConfiguration($event->getConfiguration());
307  $yamlFileContents = Yaml::dump($newConfiguration, 99, 2);
308  if (!‪GeneralUtility::writeFile($fileName, $yamlFileContents)) {
309  throw new SiteConfigurationWriteException('Unable to write site configuration in sites/' . ‪$siteIdentifier . '/' . $this->configFileName, 1590487011);
310  }
311  $this->firstLevelCache = null;
312  $this->cache->remove($this->cacheIdentifier);
313  }
314 
320  public function ‪rename(string $currentIdentifier, string $newIdentifier): void
321  {
322  if (!‪rename($this->configPath . '/' . $currentIdentifier, $this->configPath . '/' . $newIdentifier)) {
323  throw new SiteConfigurationWriteException('Unable to rename folder sites/' . $currentIdentifier, 1522491300);
324  }
325  $this->cache->remove($this->cacheIdentifier);
326  $this->firstLevelCache = null;
327  }
328 
335  public function delete(string ‪$siteIdentifier): void
336  {
337  $sites = $this->‪getAllExistingSites();
338  if (!isset($sites[‪$siteIdentifier])) {
339  throw new SiteNotFoundException('Site configuration named ' . ‪$siteIdentifier . ' not found.', 1522866183);
340  }
341  $fileName = $this->configPath . '/' . ‪$siteIdentifier . '/' . ‪$this->configFileName;
342  if (!file_exists($fileName)) {
343  throw new SiteNotFoundException('Site configuration file ' . $this->configFileName . ' within the site ' . ‪$siteIdentifier . ' not found.', 1522866184);
344  }
345  if (!unlink($fileName)) {
346  throw new SiteConfigurationWriteException('Unable to delete folder sites/' . ‪$siteIdentifier, 1596462020);
347  }
348  $this->cache->remove($this->cacheIdentifier);
349  $this->firstLevelCache = null;
350  }
351 
361  protected function protectPlaceholders(array $existingConfiguration, array $modifiedConfiguration): array
362  {
363  try {
364  return GeneralUtility::makeInstance(YamlPlaceholderGuard::class, $existingConfiguration)
365  ->process($modifiedConfiguration);
366  } catch (‪YamlPlaceholderException $exception) {
367  throw new ‪SiteConfigurationWriteException($exception->getMessage(), 1670361271, $exception);
368  }
369  }
370 
371  protected function ‪sortConfiguration(array $newConfiguration): array
372  {
373  ksort($newConfiguration);
374  if (isset($newConfiguration['imports'])) {
375  $imports = $newConfiguration['imports'];
376  unset($newConfiguration['imports']);
377  $newConfiguration['imports'] = $imports;
378  }
379  return $newConfiguration;
380  }
381 
382  protected static function ‪findModified(array $currentConfiguration, array $newConfiguration): array
383  {
384  $differences = [];
385  foreach ($newConfiguration as $key => $value) {
386  if (!isset($currentConfiguration[$key]) || $currentConfiguration[$key] !== $newConfiguration[$key]) {
387  if (!isset($newConfiguration[$key]) && isset($currentConfiguration[$key])) {
388  $differences[$key] = '__UNSET';
389  } elseif (isset($currentConfiguration[$key])
390  && is_array($newConfiguration[$key])
391  && is_array($currentConfiguration[$key])
392  ) {
393  $differences[$key] = ‪self::findModified($currentConfiguration[$key], $newConfiguration[$key]);
394  } else {
395  $differences[$key] = $value;
396  }
397  }
398  }
399  return $differences;
400  }
401 
402  protected static function ‪findRemoved(array $currentConfiguration, array $newConfiguration): array
403  {
404  $removed = [];
405  foreach ($currentConfiguration as $key => $value) {
406  if (!isset($newConfiguration[$key])) {
407  $removed[$key] = '__UNSET';
408  } elseif (isset($currentConfiguration[$key]) && is_array($currentConfiguration[$key]) && is_array($newConfiguration[$key])) {
409  $removedInRecursion = ‪self::findRemoved($currentConfiguration[$key], $newConfiguration[$key]);
410  if (!empty($removedInRecursion)) {
411  $removed[$key] = $removedInRecursion;
412  }
413  }
414  }
415 
416  return $removed;
417  }
418 
419  public function ‪warmupCaches(‪CacheWarmupEvent $event): void
420  {
421  if ($event->‪hasGroup('system')) {
423  }
424  }
425 }
‪TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader\PROCESS_IMPORTS
‪const PROCESS_IMPORTS
Definition: YamlFileLoader.php:52
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\$settingsFileName
‪string $settingsFileName
Definition: SiteConfiguration.php:64
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\$eventDispatcher
‪EventDispatcherInterface $eventDispatcher
Definition: SiteConfiguration.php:86
‪TYPO3\CMS\Core\Configuration\Exception\SiteConfigurationWriteException
Definition: SiteConfigurationWriteException.php:26
‪$finder
‪if(PHP_SAPI !=='cli') $finder
Definition: header-comment.php:22
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\getAllSiteConfigurationFromFiles
‪getAllSiteConfigurationFromFiles(bool $useCache=true)
Definition: SiteConfiguration.php:192
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\getAllExistingSites
‪Site[] getAllExistingSites(bool $useCache=true)
Definition: SiteConfiguration.php:106
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\findRemoved
‪static findRemoved(array $currentConfiguration, array $newConfiguration)
Definition: SiteConfiguration.php:401
‪TYPO3\CMS\Core\Cache\Frontend\PhpFrontend
Definition: PhpFrontend.php:25
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\$cache
‪PhpFrontend $cache
Definition: SiteConfiguration.php:48
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\rename
‪rename(string $currentIdentifier, string $newIdentifier)
Definition: SiteConfiguration.php:319
‪TYPO3\CMS\Core\Exception\SiteNotFoundException
Definition: SiteNotFoundException.php:26
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\writeSettings
‪writeSettings(string $siteIdentifier, array $settings)
Definition: SiteConfiguration.php:261
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\warmupCaches
‪warmupCaches(CacheWarmupEvent $event)
Definition: SiteConfiguration.php:418
‪TYPO3\CMS\Core\Cache\Event\CacheWarmupEvent
Definition: CacheWarmupEvent.php:24
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\getSiteSettings
‪getSiteSettings(string $siteIdentifier, array $siteConfiguration)
Definition: SiteConfiguration.php:239
‪TYPO3\CMS\Core\Configuration\SiteConfiguration
Definition: SiteConfiguration.php:47
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\load
‪load(string $siteIdentifier)
Definition: SiteConfiguration.php:229
‪TYPO3\CMS\Core\Site\Entity\Site
Definition: Site.php:42
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\getAllSiteConfigurationPaths
‪getAllSiteConfigurationPaths()
Definition: SiteConfiguration.php:170
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\__construct
‪__construct(string $configPath, EventDispatcherInterface $eventDispatcher, PhpFrontend $coreCache=null)
Definition: SiteConfiguration.php:91
‪TYPO3\CMS\Core\Configuration\Loader\Exception\YamlPlaceholderException
Definition: YamlPlaceholderException.php:21
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\resolveAllExistingSites
‪Site[] resolveAllExistingSites(bool $useCache=true)
Definition: SiteConfiguration.php:146
‪TYPO3\CMS\Core\Utility\GeneralUtility\mkdir_deep
‪static mkdir_deep($directory)
Definition: GeneralUtility.php:1753
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\sortConfiguration
‪catch(YamlPlaceholderException $exception) sortConfiguration(array $newConfiguration)
Definition: SiteConfiguration.php:370
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\$firstLevelCache
‪array null $firstLevelCache
Definition: SiteConfiguration.php:85
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\createNewBasicSite
‪createNewBasicSite(string $identifier, int $rootPageId, string $base)
Definition: SiteConfiguration.php:119
‪TYPO3\CMS\Core\Cache\Exception\InvalidDataException
Definition: InvalidDataException.php:24
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\$configFileName
‪string $configFileName
Definition: SiteConfiguration.php:57
‪TYPO3\CMS\Core\Configuration\Event\SiteConfigurationLoadedEvent
Definition: SiteConfigurationLoadedEvent.php:25
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\$configPath
‪string $configPath
Definition: SiteConfiguration.php:50
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\$cacheIdentifier
‪string $cacheIdentifier
Definition: SiteConfiguration.php:78
‪TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader
Definition: YamlFileLoader.php:47
‪TYPO3\CMS\Core\Configuration\Event\SiteConfigurationBeforeWriteEvent
Definition: SiteConfigurationBeforeWriteEvent.php:25
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\getContentSecurityPolicies
‪getContentSecurityPolicies(string $siteIdentifier)
Definition: SiteConfiguration.php:251
‪TYPO3\CMS\Core\Utility\ArrayUtility
Definition: ArrayUtility.php:26
‪TYPO3\CMS\Core\SingletonInterface
Definition: SingletonInterface.php:23
‪TYPO3\CMS\Core\Configuration
Definition: CKEditor5Migrator.php:18
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\$contentSecurityFileName
‪string $contentSecurityFileName
Definition: SiteConfiguration.php:71
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\findModified
‪static findModified(array $currentConfiguration, array $newConfiguration)
Definition: SiteConfiguration.php:381
‪TYPO3\CMS\Webhooks\Message\$siteIdentifier
‪identifier readonly int readonly array readonly string readonly string $siteIdentifier
Definition: PageModificationMessage.php:38
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:51
‪TYPO3\CMS\Core\Site\Entity\SiteSettings
Definition: SiteSettings.php:28
‪TYPO3\CMS\Core\Utility\GeneralUtility\writeFile
‪static bool writeFile($file, $content, $changePermissions=false)
Definition: GeneralUtility.php:1567
‪TYPO3\CMS\Core\Configuration\Loader\YamlPlaceholderGuard
Definition: YamlPlaceholderGuard.php:27
‪TYPO3\CMS\Webhooks\Message\$identifier
‪identifier readonly string $identifier
Definition: FileAddedMessage.php:37
‪TYPO3\CMS\Core\Cache\Event\CacheWarmupEvent\hasGroup
‪hasGroup(string $group)
Definition: CacheWarmupEvent.php:36
‪TYPO3\CMS\Core\Configuration\SiteConfiguration\write
‪write(string $siteIdentifier, array $configuration, bool $protectPlaceholders=false)
Definition: SiteConfiguration.php:277