|
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
|
|
|
|
|
18
|
|
|
namespace TYPO3\CMS\Extbase\Configuration; |
|
19
|
|
|
|
|
20
|
|
|
use Psr\Http\Message\ServerRequestInterface; |
|
21
|
|
|
use TYPO3\CMS\Core\Http\ApplicationType; |
|
22
|
|
|
use TYPO3\CMS\Core\SingletonInterface; |
|
23
|
|
|
use TYPO3\CMS\Core\TypoScript\TypoScriptService; |
|
24
|
|
|
use TYPO3\CMS\Core\Utility\ArrayUtility; |
|
25
|
|
|
use TYPO3\CMS\Core\Utility\GeneralUtility; |
|
26
|
|
|
use TYPO3\CMS\Extbase\Object\ObjectManagerInterface; |
|
27
|
|
|
use TYPO3\CMS\Extbase\Utility\FrontendSimulatorUtility; |
|
28
|
|
|
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; |
|
29
|
|
|
|
|
30
|
|
|
/** |
|
31
|
|
|
* Abstract base class for a general purpose configuration manager |
|
32
|
|
|
* @internal only to be used within Extbase, not part of TYPO3 Core API. |
|
33
|
|
|
*/ |
|
34
|
|
|
abstract class AbstractConfigurationManager implements SingletonInterface |
|
35
|
|
|
{ |
|
36
|
|
|
/** |
|
37
|
|
|
* Default backend storage PID |
|
38
|
|
|
*/ |
|
39
|
|
|
const DEFAULT_BACKEND_STORAGE_PID = 0; |
|
40
|
|
|
|
|
41
|
|
|
/** |
|
42
|
|
|
* Storage of the raw TypoScript configuration |
|
43
|
|
|
* |
|
44
|
|
|
* @var array |
|
45
|
|
|
*/ |
|
46
|
|
|
protected $configuration = []; |
|
47
|
|
|
|
|
48
|
|
|
/** |
|
49
|
|
|
* @var \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer |
|
50
|
|
|
*/ |
|
51
|
|
|
protected $contentObject; |
|
52
|
|
|
|
|
53
|
|
|
/** |
|
54
|
|
|
* @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface |
|
55
|
|
|
*/ |
|
56
|
|
|
protected $objectManager; |
|
57
|
|
|
|
|
58
|
|
|
/** |
|
59
|
|
|
* @var \TYPO3\CMS\Core\TypoScript\TypoScriptService |
|
60
|
|
|
*/ |
|
61
|
|
|
protected $typoScriptService; |
|
62
|
|
|
|
|
63
|
|
|
/** |
|
64
|
|
|
* name of the extension this Configuration Manager instance belongs to |
|
65
|
|
|
* |
|
66
|
|
|
* @var string |
|
67
|
|
|
*/ |
|
68
|
|
|
protected $extensionName; |
|
69
|
|
|
|
|
70
|
|
|
/** |
|
71
|
|
|
* name of the plugin this Configuration Manager instance belongs to |
|
72
|
|
|
* |
|
73
|
|
|
* @var string |
|
74
|
|
|
*/ |
|
75
|
|
|
protected $pluginName; |
|
76
|
|
|
|
|
77
|
|
|
/** |
|
78
|
|
|
* 1st level configuration cache |
|
79
|
|
|
* |
|
80
|
|
|
* @var array |
|
81
|
|
|
*/ |
|
82
|
|
|
protected $configurationCache = []; |
|
83
|
|
|
|
|
84
|
|
|
/** |
|
85
|
|
|
* @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager |
|
86
|
|
|
* @param \TYPO3\CMS\Core\TypoScript\TypoScriptService $typoScriptService |
|
87
|
|
|
*/ |
|
88
|
|
|
public function __construct( |
|
89
|
|
|
ObjectManagerInterface $objectManager, |
|
90
|
|
|
TypoScriptService $typoScriptService |
|
91
|
|
|
) { |
|
92
|
|
|
$this->objectManager = $objectManager; |
|
93
|
|
|
$this->typoScriptService = $typoScriptService; |
|
94
|
|
|
} |
|
95
|
|
|
|
|
96
|
|
|
/** |
|
97
|
|
|
* @param \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer $contentObject |
|
98
|
|
|
*/ |
|
99
|
|
|
public function setContentObject(ContentObjectRenderer $contentObject): void |
|
100
|
|
|
{ |
|
101
|
|
|
$this->contentObject = $contentObject; |
|
102
|
|
|
} |
|
103
|
|
|
|
|
104
|
|
|
/** |
|
105
|
|
|
* @return \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer|null |
|
106
|
|
|
*/ |
|
107
|
|
|
public function getContentObject(): ?ContentObjectRenderer |
|
108
|
|
|
{ |
|
109
|
|
|
return $this->contentObject; |
|
110
|
|
|
} |
|
111
|
|
|
|
|
112
|
|
|
/** |
|
113
|
|
|
* Sets the specified raw configuration coming from the outside. |
|
114
|
|
|
* Note that this is a low level method and only makes sense to be used by Extbase internally. |
|
115
|
|
|
* |
|
116
|
|
|
* @param array $configuration The new configuration |
|
117
|
|
|
*/ |
|
118
|
|
|
public function setConfiguration(array $configuration = []): void |
|
119
|
|
|
{ |
|
120
|
|
|
// reset 1st level cache |
|
121
|
|
|
$this->configurationCache = []; |
|
122
|
|
|
$this->extensionName = $configuration['extensionName'] ?? null; |
|
123
|
|
|
$this->pluginName = $configuration['pluginName'] ?? null; |
|
124
|
|
|
$this->configuration = $this->typoScriptService->convertTypoScriptArrayToPlainArray($configuration); |
|
125
|
|
|
} |
|
126
|
|
|
|
|
127
|
|
|
/** |
|
128
|
|
|
* Loads the Extbase Framework configuration. |
|
129
|
|
|
* |
|
130
|
|
|
* The Extbase framework configuration HAS TO be retrieved using this method, as they are come from different places than the normal settings. |
|
131
|
|
|
* Framework configuration is, in contrast to normal settings, needed for the Extbase framework to operate correctly. |
|
132
|
|
|
* |
|
133
|
|
|
* @param string|null $extensionName if specified, the configuration for the given extension will be returned (plugin.tx_extensionname) |
|
134
|
|
|
* @param string|null $pluginName if specified, the configuration for the given plugin will be returned (plugin.tx_extensionname_pluginname) |
|
135
|
|
|
* @return array the Extbase framework configuration |
|
136
|
|
|
*/ |
|
137
|
|
|
public function getConfiguration(?string $extensionName = null, ?string $pluginName = null): array |
|
138
|
|
|
{ |
|
139
|
|
|
// 1st level cache |
|
140
|
|
|
$configurationCacheKey = strtolower(($extensionName ?: $this->extensionName) . '_' . ($pluginName ?: $this->pluginName)); |
|
141
|
|
|
if (isset($this->configurationCache[$configurationCacheKey])) { |
|
142
|
|
|
return $this->configurationCache[$configurationCacheKey]; |
|
143
|
|
|
} |
|
144
|
|
|
$frameworkConfiguration = $this->getExtbaseConfiguration(); |
|
145
|
|
|
if (!isset($frameworkConfiguration['persistence']['storagePid'])) { |
|
146
|
|
|
$frameworkConfiguration['persistence']['storagePid'] = $this->getDefaultBackendStoragePid(); |
|
147
|
|
|
} |
|
148
|
|
|
// only merge $this->configuration and override controller configuration when retrieving configuration of the current plugin |
|
149
|
|
|
if ($extensionName === null || $extensionName === $this->extensionName && $pluginName === $this->pluginName) { |
|
150
|
|
|
$pluginConfiguration = $this->getPluginConfiguration((string)$this->extensionName, (string)$this->pluginName); |
|
151
|
|
|
ArrayUtility::mergeRecursiveWithOverrule($pluginConfiguration, $this->configuration); |
|
152
|
|
|
$pluginConfiguration['controllerConfiguration'] = $this->getControllerConfiguration((string)$this->extensionName, (string)$this->pluginName); |
|
153
|
|
|
if (isset($this->configuration['switchableControllerActions'])) { |
|
154
|
|
|
$this->overrideControllerConfigurationWithSwitchableControllerActions($pluginConfiguration, $this->configuration['switchableControllerActions']); |
|
155
|
|
|
} |
|
156
|
|
|
} else { |
|
157
|
|
|
$pluginConfiguration = $this->getPluginConfiguration((string)$extensionName, (string)$pluginName); |
|
158
|
|
|
$pluginConfiguration['controllerConfiguration'] = $this->getControllerConfiguration((string)$extensionName, (string)$pluginName); |
|
159
|
|
|
} |
|
160
|
|
|
ArrayUtility::mergeRecursiveWithOverrule($frameworkConfiguration, $pluginConfiguration); |
|
161
|
|
|
// only load context specific configuration when retrieving configuration of the current plugin |
|
162
|
|
|
if ($extensionName === null || $extensionName === $this->extensionName && $pluginName === $this->pluginName) { |
|
163
|
|
|
$frameworkConfiguration = $this->getContextSpecificFrameworkConfiguration($frameworkConfiguration); |
|
164
|
|
|
} |
|
165
|
|
|
|
|
166
|
|
|
if (!empty($frameworkConfiguration['persistence']['storagePid'])) { |
|
167
|
|
|
if (is_array($frameworkConfiguration['persistence']['storagePid'])) { |
|
168
|
|
|
// We simulate the frontend to enable the use of cObjects in |
|
169
|
|
|
// stdWrap. We then convert the configuration to normal TypoScript |
|
170
|
|
|
// and apply the stdWrap to the storagePid |
|
171
|
|
|
$isBackend = ($GLOBALS['TYPO3_REQUEST'] ?? null) instanceof ServerRequestInterface |
|
172
|
|
|
&& ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isBackend(); |
|
173
|
|
|
if ($isBackend) { |
|
174
|
|
|
FrontendSimulatorUtility::simulateFrontendEnvironment($this->getContentObject()); |
|
175
|
|
|
} |
|
176
|
|
|
$conf = $this->typoScriptService->convertPlainArrayToTypoScriptArray($frameworkConfiguration['persistence']); |
|
177
|
|
|
$frameworkConfiguration['persistence']['storagePid'] = $GLOBALS['TSFE']->cObj->stdWrapValue('storagePid', $conf ?? []); |
|
178
|
|
|
if ($isBackend) { |
|
179
|
|
|
FrontendSimulatorUtility::resetFrontendEnvironment(); |
|
180
|
|
|
} |
|
181
|
|
|
} |
|
182
|
|
|
|
|
183
|
|
|
if (!empty($frameworkConfiguration['persistence']['recursive'])) { |
|
184
|
|
|
// All implementations of getTreeList allow to pass the ids negative to include them into the result |
|
185
|
|
|
// otherwise only childpages are returned |
|
186
|
|
|
$storagePids = GeneralUtility::intExplode(',', $frameworkConfiguration['persistence']['storagePid']); |
|
187
|
|
|
array_walk($storagePids, function (&$storagePid) { |
|
188
|
|
|
if ($storagePid > 0) { |
|
189
|
|
|
$storagePid = -$storagePid; |
|
190
|
|
|
} |
|
191
|
|
|
}); |
|
192
|
|
|
$storagePids = $this->getRecursiveStoragePids( |
|
193
|
|
|
$storagePids, |
|
194
|
|
|
(int)$frameworkConfiguration['persistence']['recursive'] |
|
195
|
|
|
); |
|
196
|
|
|
|
|
197
|
|
|
$frameworkConfiguration['persistence']['storagePid'] = implode(',', $storagePids); |
|
198
|
|
|
} |
|
199
|
|
|
} |
|
200
|
|
|
// 1st level cache |
|
201
|
|
|
$this->configurationCache[$configurationCacheKey] = $frameworkConfiguration; |
|
202
|
|
|
return $frameworkConfiguration; |
|
203
|
|
|
} |
|
204
|
|
|
|
|
205
|
|
|
/** |
|
206
|
|
|
* Returns the TypoScript configuration found in config.tx_extbase |
|
207
|
|
|
* |
|
208
|
|
|
* @return array |
|
209
|
|
|
*/ |
|
210
|
|
|
protected function getExtbaseConfiguration(): array |
|
211
|
|
|
{ |
|
212
|
|
|
$setup = $this->getTypoScriptSetup(); |
|
213
|
|
|
$extbaseConfiguration = []; |
|
214
|
|
|
if (isset($setup['config.']['tx_extbase.'])) { |
|
215
|
|
|
$extbaseConfiguration = $this->typoScriptService->convertTypoScriptArrayToPlainArray($setup['config.']['tx_extbase.']); |
|
216
|
|
|
} |
|
217
|
|
|
return $extbaseConfiguration; |
|
218
|
|
|
} |
|
219
|
|
|
|
|
220
|
|
|
/** |
|
221
|
|
|
* Returns the default backend storage pid |
|
222
|
|
|
* |
|
223
|
|
|
* @return int |
|
224
|
|
|
*/ |
|
225
|
|
|
public function getDefaultBackendStoragePid(): int |
|
226
|
|
|
{ |
|
227
|
|
|
return self::DEFAULT_BACKEND_STORAGE_PID; |
|
228
|
|
|
} |
|
229
|
|
|
|
|
230
|
|
|
/** |
|
231
|
|
|
* This method possibly overrides the controller configuration with an alternative configuration passed along via |
|
232
|
|
|
* $frameworkConfiguration. |
|
233
|
|
|
* |
|
234
|
|
|
* If called by \TYPO3\CMS\Extbase\Configuration\AbstractConfigurationManager::getConfiguration, |
|
235
|
|
|
* $switchableControllerActions may contain an alternative controller configuration defined via typoscript: |
|
236
|
|
|
* |
|
237
|
|
|
* Example: |
|
238
|
|
|
* tt_content.list.20.indexedsearch_pi2.switchableControllerActions { |
|
239
|
|
|
* Search { |
|
240
|
|
|
* 0 = search |
|
241
|
|
|
* } |
|
242
|
|
|
* } |
|
243
|
|
|
* |
|
244
|
|
|
* If called by \TYPO3\CMS\Extbase\Configuration\FrontendConfigurationManager::overrideSwitchableControllerActionsFromFlexForm, |
|
245
|
|
|
* $switchableControllerActions may contain an alternative controller configuration defined via plugin flexform. |
|
246
|
|
|
* |
|
247
|
|
|
* @param array $frameworkConfiguration |
|
248
|
|
|
* @param array $switchableControllerActions |
|
249
|
|
|
* @deprecated since TYPO3 v10, will be removed in one of the next major versions of TYPO3, probably version 11.0 or 12.0. |
|
250
|
|
|
*/ |
|
251
|
|
|
protected function overrideControllerConfigurationWithSwitchableControllerActions(array &$frameworkConfiguration, array $switchableControllerActions): void |
|
252
|
|
|
{ |
|
253
|
|
|
trigger_error( |
|
254
|
|
|
sprintf( |
|
255
|
|
|
'Plugin "%s" of extension "%s" uses switchable controller actions which has been marked as deprecated as of version TYPO3 10 and will be removed in one of the next major versions of TYPO3, probably version 11.0 or 12.0', |
|
256
|
|
|
$frameworkConfiguration['pluginName'], |
|
257
|
|
|
$frameworkConfiguration['extensionName'] |
|
258
|
|
|
), |
|
259
|
|
|
E_USER_DEPRECATED |
|
260
|
|
|
); |
|
261
|
|
|
|
|
262
|
|
|
$controllerAliasToClass = []; |
|
263
|
|
|
foreach ($frameworkConfiguration['controllerConfiguration'] as $controllerClass => $controllerConfiguration) { |
|
264
|
|
|
$controllerAliasToClass[$controllerConfiguration['alias']] = $controllerClass; |
|
265
|
|
|
} |
|
266
|
|
|
|
|
267
|
|
|
$overriddenControllerConfiguration = []; |
|
268
|
|
|
foreach ($switchableControllerActions as $controllerName => $actions) { |
|
269
|
|
|
// Trim leading backslashes if a fully qualified controller class name with leading slashes is used. |
|
270
|
|
|
$controllerName = ltrim($controllerName, '\\'); |
|
271
|
|
|
|
|
272
|
|
|
$controllerIsConfigured = false; |
|
273
|
|
|
if (array_key_exists($controllerName, $controllerAliasToClass)) { |
|
274
|
|
|
/* |
|
275
|
|
|
* If $controllerName can be found in the keys of $controllerAliasToClass, $controllerName is a |
|
276
|
|
|
* controller alias and not a FQCN. In this case switchable controller actions have been defined with |
|
277
|
|
|
* controller aliases as such: |
|
278
|
|
|
* |
|
279
|
|
|
* tt_content.list.20.indexedsearch_pi2.switchableControllerActions { |
|
280
|
|
|
* Search { |
|
281
|
|
|
* 0 = search |
|
282
|
|
|
* } |
|
283
|
|
|
* } |
|
284
|
|
|
*/ |
|
285
|
|
|
$controllerIsConfigured = true; |
|
286
|
|
|
$controllerClassName = $controllerAliasToClass[$controllerName]; |
|
287
|
|
|
$controllerAlias = $controllerName; |
|
288
|
|
|
} |
|
289
|
|
|
|
|
290
|
|
|
if (in_array($controllerName, $controllerAliasToClass, true)) { |
|
291
|
|
|
/* |
|
292
|
|
|
* If $controllerName can be found in the values of $controllerAliasToClass, $controllerName is a |
|
293
|
|
|
* FQCN. In this case switchable controller actions have been defined with fully qualified controller |
|
294
|
|
|
* class names as such: |
|
295
|
|
|
* |
|
296
|
|
|
* tt_content.list.20.indexedsearch_pi2.switchableControllerActions { |
|
297
|
|
|
* TYPO3\CMS\IndexedSearch\Controller\SearchController { |
|
298
|
|
|
* 0 = search |
|
299
|
|
|
* } |
|
300
|
|
|
* } |
|
301
|
|
|
*/ |
|
302
|
|
|
$controllerIsConfigured = true; |
|
303
|
|
|
$controllerClassName = $controllerName; |
|
304
|
|
|
$controllerAlias = $frameworkConfiguration['controllerConfiguration'][$controllerName]['alias']; |
|
305
|
|
|
} |
|
306
|
|
|
|
|
307
|
|
|
if (!$controllerIsConfigured) { |
|
308
|
|
|
continue; |
|
309
|
|
|
} |
|
310
|
|
|
|
|
311
|
|
|
if (!isset($overriddenControllerConfiguration[$controllerClassName])) { |
|
|
|
|
|
|
312
|
|
|
$overriddenControllerConfiguration[$controllerClassName] = [ |
|
313
|
|
|
'alias' => $controllerAlias, |
|
|
|
|
|
|
314
|
|
|
'className' => $controllerClassName, |
|
315
|
|
|
'actions' => [] |
|
316
|
|
|
]; |
|
317
|
|
|
} |
|
318
|
|
|
$overriddenControllerConfiguration[$controllerClassName]['actions'] = array_merge( |
|
319
|
|
|
$overriddenControllerConfiguration[$controllerClassName]['actions'], |
|
320
|
|
|
$actions |
|
321
|
|
|
); |
|
322
|
|
|
$nonCacheableActions = $frameworkConfiguration['controllerConfiguration'][$controllerClassName]['nonCacheableActions'] ?? null; |
|
323
|
|
|
if (!is_array($nonCacheableActions)) { |
|
324
|
|
|
// There are no non-cacheable actions, thus we can directly continue |
|
325
|
|
|
// with the next controller name. |
|
326
|
|
|
continue; |
|
327
|
|
|
} |
|
328
|
|
|
$overriddenNonCacheableActions = array_intersect($nonCacheableActions, $actions); |
|
329
|
|
|
if (!empty($overriddenNonCacheableActions)) { |
|
330
|
|
|
$overriddenControllerConfiguration[$controllerClassName]['nonCacheableActions'] = $overriddenNonCacheableActions; |
|
331
|
|
|
} |
|
332
|
|
|
} |
|
333
|
|
|
$frameworkConfiguration['controllerConfiguration'] = $overriddenControllerConfiguration; |
|
334
|
|
|
} |
|
335
|
|
|
|
|
336
|
|
|
/** |
|
337
|
|
|
* The context specific configuration returned by this method |
|
338
|
|
|
* will override the framework configuration which was |
|
339
|
|
|
* obtained from TypoScript. This can be used f.e. to override the storagePid |
|
340
|
|
|
* with the value set inside the Plugin Instance. |
|
341
|
|
|
* |
|
342
|
|
|
* WARNING: Make sure this method ALWAYS returns an array! |
|
343
|
|
|
* |
|
344
|
|
|
* @param array $frameworkConfiguration The framework configuration until now |
|
345
|
|
|
* @return array context specific configuration which will override the configuration obtained by TypoScript |
|
346
|
|
|
*/ |
|
347
|
|
|
abstract protected function getContextSpecificFrameworkConfiguration(array $frameworkConfiguration): array; |
|
348
|
|
|
|
|
349
|
|
|
/** |
|
350
|
|
|
* Returns TypoScript Setup array from current Environment. |
|
351
|
|
|
* |
|
352
|
|
|
* @return array the TypoScript setup |
|
353
|
|
|
*/ |
|
354
|
|
|
abstract public function getTypoScriptSetup(): array; |
|
355
|
|
|
|
|
356
|
|
|
/** |
|
357
|
|
|
* Returns the TypoScript configuration found in plugin.tx_yourextension_yourplugin / module.tx_yourextension_yourmodule |
|
358
|
|
|
* merged with the global configuration of your extension from plugin.tx_yourextension / module.tx_yourextension |
|
359
|
|
|
* |
|
360
|
|
|
* @param string $extensionName |
|
361
|
|
|
* @param string $pluginName in FE mode this is the specified plugin name, in BE mode this is the full module signature |
|
362
|
|
|
* @return array |
|
363
|
|
|
*/ |
|
364
|
|
|
abstract protected function getPluginConfiguration(string $extensionName, string $pluginName = null): array; |
|
365
|
|
|
|
|
366
|
|
|
/** |
|
367
|
|
|
* Returns the configured controller/action configuration of the specified plugin/module in the format |
|
368
|
|
|
* array( |
|
369
|
|
|
* 'Controller1' => array('action1', 'action2'), |
|
370
|
|
|
* 'Controller2' => array('action3', 'action4') |
|
371
|
|
|
* ) |
|
372
|
|
|
* |
|
373
|
|
|
* @param string $extensionName |
|
374
|
|
|
* @param string $pluginName in FE mode this is the specified plugin name, in BE mode this is the full module signature |
|
375
|
|
|
* @return array |
|
376
|
|
|
*/ |
|
377
|
|
|
abstract protected function getControllerConfiguration(string $extensionName, string $pluginName): array; |
|
378
|
|
|
|
|
379
|
|
|
/** |
|
380
|
|
|
* The implementation of the methods to return a list of storagePid that are below a certain |
|
381
|
|
|
* storage pid. |
|
382
|
|
|
* |
|
383
|
|
|
* @param array|int[] $storagePids Storage PIDs to start at; multiple PIDs possible as comma-separated list |
|
384
|
|
|
* @param int $recursionDepth Maximum number of levels to search, 0 to disable recursive lookup |
|
385
|
|
|
* @return array|int[] storage PIDs |
|
386
|
|
|
*/ |
|
387
|
|
|
abstract protected function getRecursiveStoragePids(array $storagePids, int $recursionDepth = 0): array; |
|
388
|
|
|
} |
|
389
|
|
|
|