[FEATURE] Use symfony/routing for Site Resolving
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Site / Entity / Site.php
1 <?php
2 declare(strict_types = 1);
3
4 namespace TYPO3\CMS\Core\Site\Entity;
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 TYPO3\CMS\Core\Error\PageErrorHandler\FluidPageErrorHandler;
20 use TYPO3\CMS\Core\Error\PageErrorHandler\InvalidPageErrorHandlerException;
21 use TYPO3\CMS\Core\Error\PageErrorHandler\PageContentErrorHandler;
22 use TYPO3\CMS\Core\Error\PageErrorHandler\PageErrorHandlerInterface;
23 use TYPO3\CMS\Core\Utility\GeneralUtility;
24
25 /**
26 * Entity representing a single site with available languages
27 */
28 class Site implements SiteInterface
29 {
30 protected const ERRORHANDLER_TYPE_PAGE = 'Page';
31 protected const ERRORHANDLER_TYPE_FLUID = 'Fluid';
32 protected const ERRORHANDLER_TYPE_PHP = 'PHP';
33
34 /**
35 * @var string
36 */
37 protected $identifier;
38
39 /**
40 * @var string
41 */
42 protected $base;
43
44 /**
45 * @var int
46 */
47 protected $rootPageId;
48
49 /**
50 * Any attributes for this site
51 * @var array
52 */
53 protected $configuration;
54
55 /**
56 * @var SiteLanguage[]
57 */
58 protected $languages;
59
60 /**
61 * @var array
62 */
63 protected $errorHandlers;
64
65 /**
66 * Sets up a site object, and its languages and error handlers
67 *
68 * @param string $identifier
69 * @param int $rootPageId
70 * @param array $configuration
71 */
72 public function __construct(string $identifier, int $rootPageId, array $configuration)
73 {
74 $this->identifier = $identifier;
75 $this->rootPageId = $rootPageId;
76 $this->configuration = $configuration;
77 $configuration['languages'] = $configuration['languages'] ?: [
78 0 => [
79 'languageId' => 0,
80 'title' => 'Default',
81 'navigationTitle' => '',
82 'typo3Language' => 'default',
83 'flag' => 'us',
84 'locale' => 'en_US.UTF-8',
85 'iso-639-1' => 'en',
86 'hreflang' => 'en-US',
87 'direction' => '',
88 ]
89 ];
90 $this->base = $this->sanitizeBaseUrl($configuration['base'] ?? '');
91 foreach ($configuration['languages'] as $languageConfiguration) {
92 $languageUid = (int)$languageConfiguration['languageId'];
93 // site language has defined its own base, this is the case most of the time.
94 if (!empty($languageConfiguration['base'])) {
95 $base = $languageConfiguration['base'];
96 $base = $this->sanitizeBaseUrl($base);
97 $baseParts = parse_url($base);
98 // no host given by the language-specific base, so lets prefix the main site base
99 if (empty($baseParts['scheme']) && empty($baseParts['host'])) {
100 $base = rtrim($this->base, '/') . '/' . ltrim($base, '/');
101 $base = $this->sanitizeBaseUrl($base);
102 }
103 } else {
104 // Language configuration does not have a base defined
105 // So the main site base is used (usually done for default languages)
106 $base = $this->sanitizeBaseUrl(rtrim($this->base, '/') . '/');
107 }
108 $this->languages[$languageUid] = new SiteLanguage(
109 $this,
110 $languageUid,
111 $languageConfiguration['locale'],
112 $base,
113 $languageConfiguration
114 );
115 }
116 foreach ($configuration['errorHandling'] ?? [] as $errorHandlingConfiguration) {
117 $code = $errorHandlingConfiguration['errorCode'];
118 unset($errorHandlingConfiguration['errorCode']);
119 $this->errorHandlers[(int)$code] = $errorHandlingConfiguration;
120 }
121 }
122
123 /**
124 * Gets the identifier of this site,
125 * mainly used when maintaining / configuring sites.
126 *
127 * @return string
128 */
129 public function getIdentifier(): string
130 {
131 return $this->identifier;
132 }
133
134 /**
135 * Returns the base URL of this site
136 *
137 * @return string
138 */
139 public function getBase(): string
140 {
141 return $this->base;
142 }
143
144 /**
145 * Returns the root page ID of this site
146 *
147 * @return int
148 */
149 public function getRootPageId(): int
150 {
151 return $this->rootPageId;
152 }
153
154 /**
155 * Returns all available languages of this site
156 *
157 * @return SiteLanguage[]
158 */
159 public function getLanguages(): array
160 {
161 return $this->languages;
162 }
163
164 /**
165 * Returns a language of this site, given by the sys_language_uid
166 *
167 * @param int $languageId
168 * @return SiteLanguage
169 * @throws \InvalidArgumentException
170 */
171 public function getLanguageById(int $languageId): SiteLanguage
172 {
173 if (isset($this->languages[$languageId])) {
174 return $this->languages[$languageId];
175 }
176 throw new \InvalidArgumentException(
177 'Language ' . $languageId . ' does not exist on site ' . $this->identifier . '.',
178 1522960188
179 );
180 }
181
182 /**
183 * Returns a ready-to-use error handler, to be used within the ErrorController
184 *
185 * @param int $statusCode
186 * @return PageErrorHandlerInterface
187 * @throws \RuntimeException
188 * @throws InvalidPageErrorHandlerException
189 */
190 public function getErrorHandler(int $statusCode): PageErrorHandlerInterface
191 {
192 $errorHandlerConfiguration = $this->errorHandlers[$statusCode] ?? null;
193 switch ($errorHandlerConfiguration['errorHandler']) {
194 case self::ERRORHANDLER_TYPE_FLUID:
195 return GeneralUtility::makeInstance(FluidPageErrorHandler::class, $statusCode, $errorHandlerConfiguration);
196 case self::ERRORHANDLER_TYPE_PAGE:
197 return GeneralUtility::makeInstance(PageContentErrorHandler::class, $statusCode, $errorHandlerConfiguration);
198 case self::ERRORHANDLER_TYPE_PHP:
199 $handler = GeneralUtility::makeInstance($errorHandlerConfiguration['errorPhpClassFQCN'], $statusCode, $errorHandlerConfiguration);
200 // Check if the interface is implemented
201 if (!($handler instanceof PageErrorHandlerInterface)) {
202 throw new InvalidPageErrorHandlerException('The configured error handler "' . (string)$errorHandlerConfiguration['errorPhpClassFQCN'] . '" for status code ' . $statusCode . ' must implement the PageErrorHandlerInterface.', 1527432330);
203 }
204 return $handler;
205 }
206 throw new \RuntimeException('No error handler given for the status code "' . $statusCode . '".', 1522495914);
207 }
208
209 /**
210 * Returns the whole configuration for this site
211 *
212 * @return array
213 */
214 public function getConfiguration(): array
215 {
216 return $this->configuration;
217 }
218
219 /**
220 * Returns a single configuration attribute
221 *
222 * @param string $attributeName
223 * @return mixed
224 * @throws \InvalidArgumentException
225 */
226 public function getAttribute(string $attributeName)
227 {
228 if (isset($this->configuration[$attributeName])) {
229 return $this->configuration[$attributeName];
230 }
231 throw new \InvalidArgumentException(
232 'Attribute ' . $attributeName . ' does not exist on site ' . $this->identifier . '.',
233 1522495954
234 );
235 }
236
237 /**
238 * If a site base contains "/" or "www.domain.com", it is ensured that
239 * parse_url() can handle this kind of configuration properly.
240 *
241 * @param string $base
242 * @return string
243 */
244 protected function sanitizeBaseUrl(string $base): string
245 {
246 // no protocol ("//") and the first part is no "/" (path), means that this is a domain like
247 // "www.domain.com/blabla", and we want to ensure that this one then gets a "no-scheme agnostic" part
248 if (!empty($base) && strpos($base, '//') === false && $base{0} !== '/') {
249 // either a scheme is added, or no scheme but with domain, or a path which is not absolute
250 // make the base prefixed with a slash, so it is recognized as path, not as domain
251 // treat as path
252 if (strpos($base, '.') === false) {
253 $base = '/' . $base;
254 } else {
255 // treat as domain name
256 $base = '//' . $base;
257 }
258 }
259 return $base;
260 }
261 }