Passed
Push — master ( a7d46b...0a8eff )
by
unknown
14:11
created

AbstractConfigurationManager   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 354
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 35
eloc 100
dl 0
loc 354
rs 9.6
c 0
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A getContentObject() 0 3 1
A setContentObject() 0 3 1
F getConfiguration() 0 66 19
A getDefaultBackendStoragePid() 0 3 1
A getExtbaseConfiguration() 0 8 2
B overrideControllerConfigurationWithSwitchableControllerActions() 0 83 9
A setConfiguration() 0 7 1
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])) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $controllerClassName does not seem to be defined for all execution paths leading up to this point.
Loading history...
312
                $overriddenControllerConfiguration[$controllerClassName] = [
313
                    'alias' => $controllerAlias,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $controllerAlias does not seem to be defined for all execution paths leading up to this point.
Loading history...
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