1<?php
2namespace TYPO3\CMS\Extbase\Configuration;
3
4/*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17/**
18 * Abstract base class for a general purpose configuration manager
19 * @internal only to be used within Extbase, not part of TYPO3 Core API.
20 */
21abstract class AbstractConfigurationManager implements \TYPO3\CMS\Core\SingletonInterface
22{
23    /**
24     * Default backend storage PID
25     */
26    const DEFAULT_BACKEND_STORAGE_PID = 0;
27
28    /**
29     * Storage of the raw TypoScript configuration
30     *
31     * @var array
32     */
33    protected $configuration = [];
34
35    /**
36     * @var \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer
37     */
38    protected $contentObject;
39
40    /**
41     * @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
42     */
43    protected $objectManager;
44
45    /**
46     * @var \TYPO3\CMS\Core\TypoScript\TypoScriptService
47     */
48    protected $typoScriptService;
49
50    /**
51     * name of the extension this Configuration Manager instance belongs to
52     *
53     * @var string
54     */
55    protected $extensionName;
56
57    /**
58     * name of the plugin this Configuration Manager instance belongs to
59     *
60     * @var string
61     */
62    protected $pluginName;
63
64    /**
65     * 1st level configuration cache
66     *
67     * @var array
68     */
69    protected $configurationCache = [];
70
71    /**
72     * @var \TYPO3\CMS\Extbase\Service\EnvironmentService
73     */
74    protected $environmentService;
75
76    /**
77     * @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
78     */
79    public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager)
80    {
81        $this->objectManager = $objectManager;
82    }
83
84    /**
85     * @param \TYPO3\CMS\Core\TypoScript\TypoScriptService $typoScriptService
86     */
87    public function injectTypoScriptService(\TYPO3\CMS\Core\TypoScript\TypoScriptService $typoScriptService)
88    {
89        $this->typoScriptService = $typoScriptService;
90    }
91
92    /**
93     * @param \TYPO3\CMS\Extbase\Service\EnvironmentService $environmentService
94     */
95    public function injectEnvironmentService(\TYPO3\CMS\Extbase\Service\EnvironmentService $environmentService)
96    {
97        $this->environmentService = $environmentService;
98    }
99
100    /**
101     * @param \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer $contentObject
102     */
103    public function setContentObject(\TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer $contentObject = null)
104    {
105        $this->contentObject = $contentObject;
106    }
107
108    /**
109     * @return \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer|null
110     */
111    public function getContentObject()
112    {
113        if ($this->contentObject !== null) {
114            return $this->contentObject;
115        }
116        return null;
117    }
118
119    /**
120     * Sets the specified raw configuration coming from the outside.
121     * Note that this is a low level method and only makes sense to be used by Extbase internally.
122     *
123     * @param array $configuration The new configuration
124     */
125    public function setConfiguration(array $configuration = [])
126    {
127        // reset 1st level cache
128        $this->configurationCache = [];
129        $this->extensionName = $configuration['extensionName'] ?? null;
130        $this->pluginName = $configuration['pluginName'] ?? null;
131        $this->configuration = $this->typoScriptService->convertTypoScriptArrayToPlainArray($configuration);
132    }
133
134    /**
135     * Loads the Extbase Framework configuration.
136     *
137     * The Extbase framework configuration HAS TO be retrieved using this method, as they are come from different places than the normal settings.
138     * Framework configuration is, in contrast to normal settings, needed for the Extbase framework to operate correctly.
139     *
140     * @param string $extensionName if specified, the configuration for the given extension will be returned (plugin.tx_extensionname)
141     * @param string $pluginName if specified, the configuration for the given plugin will be returned (plugin.tx_extensionname_pluginname)
142     * @return array the Extbase framework configuration
143     */
144    public function getConfiguration($extensionName = null, $pluginName = null)
145    {
146        // 1st level cache
147        $configurationCacheKey = strtolower(($extensionName ?: $this->extensionName) . '_' . ($pluginName ?: $this->pluginName));
148        if (isset($this->configurationCache[$configurationCacheKey])) {
149            return $this->configurationCache[$configurationCacheKey];
150        }
151        $frameworkConfiguration = $this->getExtbaseConfiguration();
152        if (!isset($frameworkConfiguration['persistence']['storagePid'])) {
153            $frameworkConfiguration['persistence']['storagePid'] = $this->getDefaultBackendStoragePid();
154        }
155        // only merge $this->configuration and override switchableControllerActions when retrieving configuration of the current plugin
156        if ($extensionName === null || $extensionName === $this->extensionName && $pluginName === $this->pluginName) {
157            $pluginConfiguration = $this->getPluginConfiguration($this->extensionName, $this->pluginName);
158            \TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($pluginConfiguration, $this->configuration);
159            $pluginConfiguration['controllerConfiguration'] = $this->getSwitchableControllerActions($this->extensionName, $this->pluginName);
160            if (isset($this->configuration['switchableControllerActions'])) {
161                $this->overrideSwitchableControllerActions($pluginConfiguration, $this->configuration['switchableControllerActions']);
162            }
163        } else {
164            $pluginConfiguration = $this->getPluginConfiguration($extensionName, $pluginName);
165            $pluginConfiguration['controllerConfiguration'] = $this->getSwitchableControllerActions($extensionName, $pluginName);
166        }
167        \TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($frameworkConfiguration, $pluginConfiguration);
168        // only load context specific configuration when retrieving configuration of the current plugin
169        if ($extensionName === null || $extensionName === $this->extensionName && $pluginName === $this->pluginName) {
170            $frameworkConfiguration = $this->getContextSpecificFrameworkConfiguration($frameworkConfiguration);
171        }
172
173        if (!empty($frameworkConfiguration['persistence']['storagePid'])) {
174            if (is_array($frameworkConfiguration['persistence']['storagePid'])) {
175                // We simulate the frontend to enable the use of cObjects in
176                // stdWrap. Than we convert the configuration to normal TypoScript
177                // and apply the stdWrap to the storagePid
178                if (!$this->environmentService->isEnvironmentInFrontendMode()) {
179                    \TYPO3\CMS\Extbase\Utility\FrontendSimulatorUtility::simulateFrontendEnvironment($this->getContentObject());
180                }
181                $conf = $this->typoScriptService->convertPlainArrayToTypoScriptArray($frameworkConfiguration['persistence']);
182                $frameworkConfiguration['persistence']['storagePid'] = $GLOBALS['TSFE']->cObj->stdWrap($conf['storagePid'], $conf['storagePid.']);
183                if (!$this->environmentService->isEnvironmentInFrontendMode()) {
184                    \TYPO3\CMS\Extbase\Utility\FrontendSimulatorUtility::resetFrontendEnvironment();
185                }
186            }
187
188            if (!empty($frameworkConfiguration['persistence']['recursive'])) {
189                // All implementations of getTreeList allow to pass the ids negative to include them into the result
190                // otherwise only childpages are returned
191                $storagePids = \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $frameworkConfiguration['persistence']['storagePid']);
192                array_walk($storagePids, function (&$storagePid) {
193                    if ($storagePid > 0) {
194                        $storagePid = -$storagePid;
195                    }
196                });
197                $frameworkConfiguration['persistence']['storagePid'] = $this->getRecursiveStoragePids(
198                    implode(',', $storagePids),
199                    (int)$frameworkConfiguration['persistence']['recursive']
200                );
201            }
202        }
203        // 1st level cache
204        $this->configurationCache[$configurationCacheKey] = $frameworkConfiguration;
205        return $frameworkConfiguration;
206    }
207
208    /**
209     * Returns the TypoScript configuration found in config.tx_extbase
210     *
211     * @return array
212     */
213    protected function getExtbaseConfiguration()
214    {
215        $setup = $this->getTypoScriptSetup();
216        $extbaseConfiguration = [];
217        if (isset($setup['config.']['tx_extbase.'])) {
218            $extbaseConfiguration = $this->typoScriptService->convertTypoScriptArrayToPlainArray($setup['config.']['tx_extbase.']);
219        }
220        return $extbaseConfiguration;
221    }
222
223    /**
224     * Returns the default backend storage pid
225     *
226     * @return string
227     */
228    public function getDefaultBackendStoragePid()
229    {
230        return self::DEFAULT_BACKEND_STORAGE_PID;
231    }
232
233    /**
234     * @param array &$frameworkConfiguration
235     * @param array $switchableControllerActions
236     */
237    protected function overrideSwitchableControllerActions(array &$frameworkConfiguration, array $switchableControllerActions)
238    {
239        $overriddenSwitchableControllerActions = [];
240        foreach ($switchableControllerActions as $controllerName => $actions) {
241            if (!isset($frameworkConfiguration['controllerConfiguration'][$controllerName])) {
242                continue;
243            }
244            $overriddenSwitchableControllerActions[$controllerName] = ['actions' => $actions];
245            $nonCacheableActions = $frameworkConfiguration['controllerConfiguration'][$controllerName]['nonCacheableActions'] ?? null;
246            if (!is_array($nonCacheableActions)) {
247                // There are no non-cacheable actions, thus we can directly continue
248                // with the next controller name.
249                continue;
250            }
251            $overriddenNonCacheableActions = array_intersect($nonCacheableActions, $actions);
252            if (!empty($overriddenNonCacheableActions)) {
253                $overriddenSwitchableControllerActions[$controllerName]['nonCacheableActions'] = $overriddenNonCacheableActions;
254            }
255        }
256        $frameworkConfiguration['controllerConfiguration'] = $overriddenSwitchableControllerActions;
257    }
258
259    /**
260     * The context specific configuration returned by this method
261     * will override the framework configuration which was
262     * obtained from TypoScript. This can be used f.e. to override the storagePid
263     * with the value set inside the Plugin Instance.
264     *
265     * WARNING: Make sure this method ALWAYS returns an array!
266     *
267     * @param array $frameworkConfiguration The framework configuration until now
268     * @return array context specific configuration which will override the configuration obtained by TypoScript
269     */
270    abstract protected function getContextSpecificFrameworkConfiguration(array $frameworkConfiguration);
271
272    /**
273     * Returns TypoScript Setup array from current Environment.
274     *
275     * @return array the TypoScript setup
276     */
277    abstract public function getTypoScriptSetup();
278
279    /**
280     * Returns the TypoScript configuration found in plugin.tx_yourextension_yourplugin / module.tx_yourextension_yourmodule
281     * merged with the global configuration of your extension from plugin.tx_yourextension / module.tx_yourextension
282     *
283     * @param string $extensionName
284     * @param string $pluginName in FE mode this is the specified plugin name, in BE mode this is the full module signature
285     * @return array
286     */
287    abstract protected function getPluginConfiguration($extensionName, $pluginName = null);
288
289    /**
290     * Returns the configured controller/action pairs of the specified plugin/module in the format
291     * array(
292     * 'Controller1' => array('action1', 'action2'),
293     * 'Controller2' => array('action3', 'action4')
294     * )
295     *
296     * @param string $extensionName
297     * @param string $pluginName in FE mode this is the specified plugin name, in BE mode this is the full module signature
298     * @return array
299     */
300    abstract protected function getSwitchableControllerActions($extensionName, $pluginName);
301
302    /**
303     * The implementation of the methods to return a list of storagePid that are below a certain
304     * storage pid.
305     *
306     * @param string $storagePid Storage PID to start at; multiple PIDs possible as comma-separated list
307     * @param int $recursionDepth Maximum number of levels to search, 0 to disable recursive lookup
308     * @return string storage PIDs
309     */
310    abstract protected function getRecursiveStoragePids($storagePid, $recursionDepth = 0);
311}
312