2ab87dfaedd28f2e067c558d0487d7edd354a088
[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\Authentication\BackendUserAuthentication;
20 use TYPO3\CMS\Core\Error\PageErrorHandler\FluidPageErrorHandler;
21 use TYPO3\CMS\Core\Error\PageErrorHandler\InvalidPageErrorHandlerException;
22 use TYPO3\CMS\Core\Error\PageErrorHandler\PageContentErrorHandler;
23 use TYPO3\CMS\Core\Error\PageErrorHandler\PageErrorHandlerInterface;
24 use TYPO3\CMS\Core\Error\PageErrorHandler\PageErrorHandlerNotConfiguredException;
25 use TYPO3\CMS\Core\Localization\LanguageService;
26 use TYPO3\CMS\Core\Utility\GeneralUtility;
27
28 /**
29 * Entity representing a single site with available languages
30 */
31 class Site implements SiteInterface
32 {
33 protected const ERRORHANDLER_TYPE_PAGE = 'Page';
34 protected const ERRORHANDLER_TYPE_FLUID = 'Fluid';
35 protected const ERRORHANDLER_TYPE_PHP = 'PHP';
36
37 /**
38 * @var string
39 */
40 protected $identifier;
41
42 /**
43 * @var string
44 */
45 protected $base;
46
47 /**
48 * @var int
49 */
50 protected $rootPageId;
51
52 /**
53 * Any attributes for this site
54 * @var array
55 */
56 protected $configuration;
57
58 /**
59 * @var SiteLanguage[]
60 */
61 protected $languages;
62
63 /**
64 * @var array
65 */
66 protected $errorHandlers;
67
68 /**
69 * Sets up a site object, and its languages and error handlers
70 *
71 * @param string $identifier
72 * @param int $rootPageId
73 * @param array $configuration
74 */
75 public function __construct(string $identifier, int $rootPageId, array $configuration)
76 {
77 $this->identifier = $identifier;
78 $this->rootPageId = $rootPageId;
79 $this->configuration = $configuration;
80 $configuration['languages'] = $configuration['languages'] ?: [
81 0 => [
82 'languageId' => 0,
83 'title' => 'Default',
84 'navigationTitle' => '',
85 'typo3Language' => 'default',
86 'flag' => 'us',
87 'locale' => 'en_US.UTF-8',
88 'iso-639-1' => 'en',
89 'hreflang' => 'en-US',
90 'direction' => '',
91 ]
92 ];
93 $this->base = $this->sanitizeBaseUrl($configuration['base'] ?? '');
94 foreach ($configuration['languages'] as $languageConfiguration) {
95 $languageUid = (int)$languageConfiguration['languageId'];
96 // site language has defined its own base, this is the case most of the time.
97 if (!empty($languageConfiguration['base'])) {
98 $base = $languageConfiguration['base'];
99 $base = $this->sanitizeBaseUrl($base);
100 $baseParts = parse_url($base);
101 // no host given by the language-specific base, so lets prefix the main site base
102 if (empty($baseParts['scheme']) && empty($baseParts['host'])) {
103 $base = rtrim($this->base, '/') . '/' . ltrim($base, '/');
104 $base = $this->sanitizeBaseUrl($base);
105 }
106 } else {
107 // Language configuration does not have a base defined
108 // So the main site base is used (usually done for default languages)
109 $base = $this->sanitizeBaseUrl(rtrim($this->base, '/') . '/');
110 }
111 $this->languages[$languageUid] = new SiteLanguage(
112 $languageUid,
113 $languageConfiguration['locale'],
114 $base,
115 $languageConfiguration
116 );
117 }
118 foreach ($configuration['errorHandling'] ?? [] as $errorHandlingConfiguration) {
119 $code = $errorHandlingConfiguration['errorCode'];
120 unset($errorHandlingConfiguration['errorCode']);
121 $this->errorHandlers[(int)$code] = $errorHandlingConfiguration;
122 }
123 }
124
125 /**
126 * Gets the identifier of this site,
127 * mainly used when maintaining / configuring sites.
128 *
129 * @return string
130 */
131 public function getIdentifier(): string
132 {
133 return $this->identifier;
134 }
135
136 /**
137 * Returns the base URL of this site
138 *
139 * @return string
140 */
141 public function getBase(): string
142 {
143 return $this->base;
144 }
145
146 /**
147 * Returns the root page ID of this site
148 *
149 * @return int
150 */
151 public function getRootPageId(): int
152 {
153 return $this->rootPageId;
154 }
155
156 /**
157 * Returns all available languages of this site
158 *
159 * @return SiteLanguage[]
160 */
161 public function getLanguages(): array
162 {
163 $languages = [];
164 foreach ($this->languages as $languageId => $language) {
165 if ($language->enabled()) {
166 $languages[$languageId] = $language;
167 }
168 }
169 return $languages;
170 }
171
172 /**
173 * Returns all available languages of this site, even the ones disabled for frontend usages
174 *
175 * @return SiteLanguage[]
176 */
177 public function getAllLanguages(): array
178 {
179 return $this->languages;
180 }
181
182 /**
183 * Returns a language of this site, given by the sys_language_uid
184 *
185 * @param int $languageId
186 * @return SiteLanguage
187 * @throws \InvalidArgumentException
188 */
189 public function getLanguageById(int $languageId): SiteLanguage
190 {
191 if (isset($this->languages[$languageId])) {
192 return $this->languages[$languageId];
193 }
194 throw new \InvalidArgumentException(
195 'Language ' . $languageId . ' does not exist on site ' . $this->identifier . '.',
196 1522960188
197 );
198 }
199
200 /**
201 * Fetch the available languages for a specific backend user, used in various places in Backend and Frontend
202 * when a Backend User is authenticated.
203 *
204 * @param BackendUserAuthentication $user
205 * @param int $pageId
206 * @param bool $includeAllLanguagesFlag
207 * @return array
208 */
209 public function getAvailableLanguages(BackendUserAuthentication $user, int $pageId, bool $includeAllLanguagesFlag = false)
210 {
211 $availableLanguages = [];
212
213 // Check if we need to add language "-1"
214 if ($includeAllLanguagesFlag && $user->checkLanguageAccess(-1)) {
215 $availableLanguages[-1] = new SiteLanguage(-1, '', $this->getBase(), [
216 'title' => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:multipleLanguages'),
217 'flag' => 'flag-multiple'
218 ]);
219 }
220
221 // Do not add the ones that are not allowed by the user
222 foreach ($this->languages as $language) {
223 if ($user->checkLanguageAccess($language->getLanguageId())) {
224 $availableLanguages[$language->getLanguageId()] = $language;
225 }
226 }
227
228 return $availableLanguages;
229 }
230
231 /**
232 * Returns a ready-to-use error handler, to be used within the ErrorController
233 *
234 * @param int $statusCode
235 * @return PageErrorHandlerInterface
236 * @throws PageErrorHandlerNotConfiguredException
237 * @throws InvalidPageErrorHandlerException
238 */
239 public function getErrorHandler(int $statusCode): PageErrorHandlerInterface
240 {
241 $errorHandlerConfiguration = $this->errorHandlers[$statusCode] ?? null;
242 switch ($errorHandlerConfiguration['errorHandler']) {
243 case self::ERRORHANDLER_TYPE_FLUID:
244 return GeneralUtility::makeInstance(FluidPageErrorHandler::class, $statusCode, $errorHandlerConfiguration);
245 case self::ERRORHANDLER_TYPE_PAGE:
246 return GeneralUtility::makeInstance(PageContentErrorHandler::class, $statusCode, $errorHandlerConfiguration);
247 case self::ERRORHANDLER_TYPE_PHP:
248 $handler = GeneralUtility::makeInstance($errorHandlerConfiguration['errorPhpClassFQCN'], $statusCode, $errorHandlerConfiguration);
249 // Check if the interface is implemented
250 if (!($handler instanceof PageErrorHandlerInterface)) {
251 throw new InvalidPageErrorHandlerException('The configured error handler "' . (string)$errorHandlerConfiguration['errorPhpClassFQCN'] . '" for status code ' . $statusCode . ' must implement the PageErrorHandlerInterface.', 1527432330);
252 }
253 return $handler;
254 }
255 throw new PageErrorHandlerNotConfiguredException('No error handler given for the status code "' . $statusCode . '".', 1522495914);
256 }
257
258 /**
259 * Returns the whole configuration for this site
260 *
261 * @return array
262 */
263 public function getConfiguration(): array
264 {
265 return $this->configuration;
266 }
267
268 /**
269 * Returns a single configuration attribute
270 *
271 * @param string $attributeName
272 * @return mixed
273 * @throws \InvalidArgumentException
274 */
275 public function getAttribute(string $attributeName)
276 {
277 if (isset($this->configuration[$attributeName])) {
278 return $this->configuration[$attributeName];
279 }
280 throw new \InvalidArgumentException(
281 'Attribute ' . $attributeName . ' does not exist on site ' . $this->identifier . '.',
282 1522495954
283 );
284 }
285
286 /**
287 * If a site base contains "/" or "www.domain.com", it is ensured that
288 * parse_url() can handle this kind of configuration properly.
289 *
290 * @param string $base
291 * @return string
292 */
293 protected function sanitizeBaseUrl(string $base): string
294 {
295 // no protocol ("//") and the first part is no "/" (path), means that this is a domain like
296 // "www.domain.com/blabla", and we want to ensure that this one then gets a "no-scheme agnostic" part
297 if (!empty($base) && strpos($base, '//') === false && $base{0} !== '/') {
298 // either a scheme is added, or no scheme but with domain, or a path which is not absolute
299 // make the base prefixed with a slash, so it is recognized as path, not as domain
300 // treat as path
301 if (strpos($base, '.') === false) {
302 $base = '/' . $base;
303 } else {
304 // treat as domain name
305 $base = '//' . $base;
306 }
307 }
308 return $base;
309 }
310
311 /**
312 * Shorthand functionality for fetching the language service
313 * @return LanguageService
314 */
315 protected function getLanguageService(): LanguageService
316 {
317 return $GLOBALS['LANG'];
318 }
319 }