[BUGFIX] Remove caches if a site is deleted
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Configuration / SiteConfiguration.php
1 <?php
2 declare(strict_types = 1);
3
4 namespace TYPO3\CMS\Core\Configuration;
5
6 /*
7 * This file is part of the TYPO3 CMS project.
8 *
9 * It is free software; you can redistribute it and/or modify it under
10 * the terms of the GNU General Public License, either version 2
11 * of the License, or any later version.
12 *
13 * For the full copyright and license information, please read the
14 * LICENSE.txt file that was distributed with this source code.
15 *
16 * The TYPO3 project - inspiring people to share!
17 */
18
19 use Symfony\Component\Finder\Finder;
20 use Symfony\Component\Yaml\Yaml;
21 use TYPO3\CMS\Core\Cache\CacheManager;
22 use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
23 use TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader;
24 use TYPO3\CMS\Core\Exception\SiteNotFoundException;
25 use TYPO3\CMS\Core\Site\Entity\Site;
26 use TYPO3\CMS\Core\Utility\GeneralUtility;
27
28 /**
29 * Responsibility: Handles the format of the configuration (currently yaml), and the location of the file system folder
30 *
31 * Reads all available site configuration options, and puts them into Site objects.
32 *
33 * @internal
34 */
35 class SiteConfiguration
36 {
37 /**
38 * @var string
39 */
40 protected $configPath;
41
42 /**
43 * Config yaml file name.
44 *
45 * @internal
46 * @var string
47 */
48 protected $configFileName = 'config.yaml';
49
50 /**
51 * Identifier to store all configuration data in cache_core cache.
52 *
53 * @internal
54 * @var string
55 */
56 protected $cacheIdentifier = 'site-configuration';
57
58 /**
59 * @param string $configPath
60 */
61 public function __construct(string $configPath)
62 {
63 $this->configPath = $configPath;
64 }
65
66 /**
67 * Return all site objects which have been found in the filesystem.
68 *
69 * @return Site[]
70 */
71 public function resolveAllExistingSites(): array
72 {
73 $sites = [];
74 $siteConfiguration = $this->getAllSiteConfigurationFromFiles();
75 foreach ($siteConfiguration as $identifier => $configuration) {
76 $rootPageId = (int)($configuration['rootPageId'] ?? 0);
77 if ($rootPageId > 0) {
78 $sites[$identifier] = GeneralUtility::makeInstance(Site::class, $identifier, $rootPageId, $configuration);
79 }
80 }
81 return $sites;
82 }
83
84 /**
85 * Read the site configuration from config files.
86 *
87 * @return array
88 * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
89 */
90 protected function getAllSiteConfigurationFromFiles(): array
91 {
92 // Check if the data is already cached
93 if ($siteConfiguration = $this->getCache()->get($this->cacheIdentifier)) {
94 // Due to the nature of PhpFrontend, the `<?php` and `#` wraps have to be removed
95 $siteConfiguration = preg_replace('/^<\?php\s*|\s*#$/', '', $siteConfiguration);
96 $siteConfiguration = json_decode($siteConfiguration, true);
97 }
98
99 // Nothing in the cache (or no site found)
100 if (empty($siteConfiguration)) {
101 $finder = new Finder();
102 try {
103 $finder->files()->depth(0)->name($this->configFileName)->in($this->configPath . '/*');
104 } catch (\InvalidArgumentException $e) {
105 // Directory $this->configPath does not exist yet
106 $finder = [];
107 }
108 $loader = GeneralUtility::makeInstance(YamlFileLoader::class);
109 $siteConfiguration = [];
110 foreach ($finder as $fileInfo) {
111 $configuration = $loader->load(GeneralUtility::fixWindowsFilePath((string)$fileInfo));
112 $identifier = basename($fileInfo->getPath());
113 if (isset($configuration['site'])) {
114 trigger_error(
115 'Site configuration with key \'site\' has been deprecated, remove indentation level and site key.',
116 E_USER_DEPRECATED
117 );
118 $configuration = $configuration['site'];
119 }
120 $siteConfiguration[$identifier] = $configuration;
121 }
122 $this->getCache()->set($this->cacheIdentifier, json_encode($siteConfiguration));
123 }
124 return $siteConfiguration ?? [];
125 }
126
127 /**
128 * Load plain configuration
129 * This method should only be used in case the original configuration as it exists in the file should be loaded,
130 * for example for writing / editing configuration.
131 *
132 * All read related actions should be performed on the site entity.
133 *
134 * @param string $siteIdentifier
135 * @return array
136 */
137 public function load(string $siteIdentifier): array
138 {
139 $fileName = $this->configPath . '/' . $siteIdentifier . '/' . $this->configFileName;
140 $loader = GeneralUtility::makeInstance(YamlFileLoader::class);
141 return $loader->load(GeneralUtility::fixWindowsFilePath($fileName), YamlFileLoader::PROCESS_IMPORTS);
142 }
143
144 /**
145 * Add or update a site configuration
146 *
147 * @param string $siteIdentifier
148 * @param array $configuration
149 * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
150 */
151 public function write(string $siteIdentifier, array $configuration): void
152 {
153 $fileName = $this->configPath . '/' . $siteIdentifier . '/' . $this->configFileName;
154 if (!file_exists($fileName)) {
155 GeneralUtility::mkdir_deep($this->configPath . '/' . $siteIdentifier);
156 }
157 $yamlFileContents = Yaml::dump($configuration, 99, 2);
158 GeneralUtility::writeFile($fileName, $yamlFileContents);
159 $this->getCache()->remove($this->cacheIdentifier);
160 $this->getCache()->remove('pseudo-sites');
161 }
162
163 /**
164 * Renames a site identifier (and moves the folder)
165 *
166 * @param string $currentIdentifier
167 * @param string $newIdentifier
168 * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
169 */
170 public function rename(string $currentIdentifier, string $newIdentifier): void
171 {
172 $result = rename($this->configPath . '/' . $currentIdentifier, $this->configPath . '/' . $newIdentifier);
173 if (!$result) {
174 throw new \RuntimeException('Unable to rename folder sites/' . $currentIdentifier, 1522491300);
175 }
176 $this->getCache()->remove($this->cacheIdentifier);
177 }
178
179 /**
180 * Removes the config.yaml file of a site configuration.
181 * Also clears the cache.
182 *
183 * @param string $siteIdentifier
184 * @throws SiteNotFoundException
185 */
186 public function delete(string $siteIdentifier): void
187 {
188 $sites = $this->resolveAllExistingSites();
189 if (!isset($sites[$siteIdentifier])) {
190 throw new SiteNotFoundException('Site configuration named ' . $siteIdentifier . ' not found.', 1522866183);
191 }
192 $fileName = $this->configPath . '/' . $siteIdentifier . '/' . $this->configFileName;
193 if (!file_exists($fileName)) {
194 throw new SiteNotFoundException('Site configuration file ' . $this->configFileName . ' within the site ' . $siteIdentifier . ' not found.', 1522866184);
195 }
196 @unlink($fileName);
197 $this->getCache()->remove($this->cacheIdentifier);
198 $this->getCache()->remove('pseudo-sites');
199 }
200
201 /**
202 * Short-hand function for the cache
203 *
204 * @return FrontendInterface
205 * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
206 */
207 protected function getCache(): FrontendInterface
208 {
209 return GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_core');
210 }
211 }