50c9e324089982b0da68da5d469123eff46315d6
[Packages/TYPO3.CMS.git] / typo3 / sysext / lowlevel / Classes / Controller / ConfigurationController.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Lowlevel\Controller;
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 use Psr\Http\Message\ResponseInterface;
19 use Psr\Http\Message\ServerRequestInterface;
20 use TYPO3\CMS\Backend\Configuration\SiteTcaConfiguration;
21 use TYPO3\CMS\Backend\Routing\Router;
22 use TYPO3\CMS\Backend\Template\ModuleTemplate;
23 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
24 use TYPO3\CMS\Core\Cache\Backend\NullBackend;
25 use TYPO3\CMS\Core\Cache\Frontend\PhpFrontend;
26 use TYPO3\CMS\Core\Http\HtmlResponse;
27 use TYPO3\CMS\Core\Http\MiddlewareStackResolver;
28 use TYPO3\CMS\Core\Localization\LanguageService;
29 use TYPO3\CMS\Core\Package\PackageManager;
30 use TYPO3\CMS\Core\Service\DependencyOrderingService;
31 use TYPO3\CMS\Core\Utility\ArrayUtility;
32 use TYPO3\CMS\Core\Utility\GeneralUtility;
33 use TYPO3\CMS\Fluid\View\StandaloneView;
34 use TYPO3\CMS\Lowlevel\Utility\ArrayBrowser;
35
36 /**
37 * View configuration arrays in the backend
38 */
39 class ConfigurationController
40 {
41 /**
42 * Available trees to render.
43 * * label is an LLL identifier
44 * * type is used to identify the data source type
45 * * globalKey (only for type=global) is the name of a global variable
46 *
47 * @var array
48 */
49 protected $treeSetup = [
50 'confVars' => [
51 'label' => 'typo3ConfVars',
52 'type' => 'global',
53 'globalKey' => 'TYPO3_CONF_VARS',
54 ],
55 'tca' => [
56 'label' => 'tca',
57 'type' => 'global',
58 'globalKey' => 'TCA',
59 ],
60 'tcaDescr' => [
61 'label' => 'tcaDescr',
62 'type' => 'global',
63 'globalKey' => 'TCA_DESCR',
64 ],
65 'loadedExt' => [
66 'label' => 'loadedExt',
67 'type' => 'global',
68 'globalKey' => 'TYPO3_LOADED_EXT',
69 ],
70 'services' => [
71 'label' => 't3services',
72 'key' => 'services',
73 'type' => 'global',
74 'globalKey' => 'T3_SERVICES',
75 ],
76 'tbeModules' => [
77 'label' => 'tbemodules',
78 'type' => 'global',
79 'globalKey' => 'TBE_MODULES',
80 ],
81 'tbeModulesExt' => [
82 'label' => 'tbemodulesext',
83 'type' => 'global',
84 'globalKey' => 'TBE_MODULES_EXT',
85 ],
86 'tbeStyles' => [
87 'label' => 'tbeStyles',
88 'type' => 'global',
89 'globalKey' => 'TBE_STYLES',
90 ],
91 'userSettings' => [
92 'label' => 'usersettings',
93 'type' => 'global',
94 'globalKey' => 'TYPO3_USER_SETTINGS',
95 ],
96 'pagesTypes' => [
97 'label' => 'pagesTypes',
98 'type' => 'global',
99 'globalKey' => 'PAGES_TYPES',
100 ],
101 'beUserUc' => [
102 'label' => 'beUser',
103 'type' => 'uc',
104 ],
105 'beUserTsConfig' => [
106 'label' => 'beUserTsConfig',
107 'type' => 'beUserTsConfig',
108 ],
109 'beRoutes' => [
110 'label' => 'routes',
111 'type' => 'routes',
112 ],
113 'httpMiddlewareStacks' => [
114 'label' => 'httpMiddlewareStacks',
115 'type' => 'httpMiddlewareStacks',
116 ],
117 'siteConfiguration' => [
118 'label' => 'siteConfiguration',
119 'type' => 'siteConfiguration',
120 ],
121 ];
122
123 /**
124 * Blind configurations which should not be visible to mortal admins
125 *
126 * @var array
127 */
128 protected $blindedConfigurationOptions = [
129 'TYPO3_CONF_VARS' => [
130 'DB' => [
131 'database' => '******',
132 'host' => '******',
133 'password' => '******',
134 'port' => '******',
135 'socket' => '******',
136 'username' => '******',
137 'Connections' => [
138 'Default' => [
139 'dbname' => '******',
140 'host' => '******',
141 'password' => '******',
142 'port' => '******',
143 'user' => '******',
144 'unix_socket' => '******',
145 ],
146 ],
147 ],
148 'SYS' => [
149 'encryptionKey' => '******'
150 ],
151 ],
152 ];
153
154 /**
155 * Main controller action determines get/post values, takes care of
156 * stored backend user settings for this module, determines tree
157 * and renders it.
158 *
159 * @param ServerRequestInterface $request the current request
160 * @return ResponseInterface the response with the content
161 * @throws \RuntimeException
162 */
163 public function mainAction(ServerRequestInterface $request): ResponseInterface
164 {
165 $backendUser = $this->getBackendUser();
166 $languageService = $this->getLanguageService();
167
168 $queryParams = $request->getQueryParams();
169 $postValues = $request->getParsedBody();
170
171 $moduleState = $backendUser->uc['moduleData']['system_config'] ?? [];
172
173 // Determine validated tree key and tree detail setup
174 $selectedTreeKey = $this->treeSetup[$queryParams['tree']] ? $queryParams['tree']
175 : ($this->treeSetup[$moduleState['tree']] ? $moduleState['tree'] : key($this->treeSetup));
176 $selectedTreeDetails = $this->treeSetup[$selectedTreeKey];
177 $moduleState['tree'] = $selectedTreeKey;
178
179 // Search string given or regex search enabled?
180 $searchString = (string)($postValues['searchString'] ? trim($postValues['searchString']) : '');
181 $moduleState['regexSearch'] = (bool)($postValues['regexSearch'] ?? $moduleState['regexSearch'] ?? false);
182
183 // Prepare main array
184 $sortKeysByName = true;
185 if ($selectedTreeDetails['type'] === 'global') {
186 $globalArrayKey = $selectedTreeDetails['globalKey'];
187 $renderArray = $GLOBALS[$globalArrayKey];
188
189 // Hook for Processing blindedConfigurationOptions
190 $blindedConfigurationOptions = $this->blindedConfigurationOptions;
191
192 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][self::class]['modifyBlindedConfigurationOptions'] ?? [] as $classReference) {
193 $processingObject = GeneralUtility::makeInstance($classReference);
194 $blindedConfigurationOptions = $processingObject->modifyBlindedConfigurationOptions($blindedConfigurationOptions, $this);
195 }
196
197 if (isset($blindedConfigurationOptions[$globalArrayKey])) {
198 // Prepare blinding for all database connection types
199 foreach (array_keys($GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']) as $connectionName) {
200 if ($connectionName !== 'Default') {
201 $blindedConfigurationOptions['TYPO3_CONF_VARS']['DB']['Connections'][$connectionName] =
202 $blindedConfigurationOptions['TYPO3_CONF_VARS']['DB']['Connections']['Default'];
203 }
204 }
205 ArrayUtility::mergeRecursiveWithOverrule(
206 $renderArray,
207 ArrayUtility::intersectRecursive($blindedConfigurationOptions[$globalArrayKey], $renderArray)
208 );
209 }
210 } elseif ($selectedTreeDetails['type'] === 'beUserTsConfig') {
211 $renderArray = $backendUser->getTSConfig();
212 } elseif ($selectedTreeDetails['type'] === 'uc') {
213 $renderArray = $backendUser->uc;
214 } elseif ($selectedTreeDetails['type'] === 'routes') {
215 $router = GeneralUtility::makeInstance(Router::class);
216 $routes = $router->getRoutes();
217 $renderArray = [];
218 foreach ($routes as $identifier => $route) {
219 /** @var $route \TYPO3\CMS\Backend\Routing\Route */
220 $renderArray[$identifier] = [
221 'path' => $route->getPath(),
222 'options' => $route->getOptions()
223 ];
224 }
225 } elseif ($selectedTreeDetails['type'] === 'httpMiddlewareStacks') {
226 // Keep the order of the keys
227 $sortKeysByName = false;
228 // Fake a PHP frontend with a null backend to avoid PHP Opcache conflicts
229 // When using >requireOnce() multiple times in one request
230 $cache = GeneralUtility::makeInstance(
231 PhpFrontend::class,
232 'middleware',
233 GeneralUtility::makeInstance(NullBackend::class, 'Production')
234 );
235 $stackResolver = GeneralUtility::makeInstance(
236 MiddlewareStackResolver::class,
237 GeneralUtility::makeInstance(PackageManager::class),
238 GeneralUtility::makeInstance(DependencyOrderingService::class),
239 $cache
240 );
241 $renderArray = [];
242 foreach (['frontend', 'backend'] as $stackName) {
243 // reversing the array allows the admin to read the stack from top to bottom
244 $renderArray[$stackName] = array_reverse($stackResolver->resolve($stackName));
245 }
246 } elseif ($selectedTreeDetails['type'] === 'siteConfiguration') {
247 $renderArray = GeneralUtility::makeInstance(SiteTcaConfiguration::class)->getTca();
248 } else {
249 throw new \RuntimeException('Unknown array type "' . $selectedTreeDetails['type'] . '"', 1507845662);
250 }
251 if ($sortKeysByName) {
252 ArrayUtility::naturalKeySortRecursive($renderArray);
253 }
254
255 // Prepare array renderer class, apply search and expand / collapse states
256 $arrayBrowser = GeneralUtility::makeInstance(ArrayBrowser::class);
257 $arrayBrowser->dontLinkVar = true;
258 $arrayBrowser->searchKeysToo = true;
259 $arrayBrowser->regexMode = $moduleState['regexSearch'];
260 $node = $queryParams['node'];
261 if ($searchString) {
262 $arrayBrowser->depthKeys = $arrayBrowser->getSearchKeys($renderArray, '', $searchString, []);
263 } elseif (is_array($node)) {
264 $newExpandCollapse = $arrayBrowser->depthKeys($node, $moduleState['node_' . $selectedTreeKey]);
265 $arrayBrowser->depthKeys = $newExpandCollapse;
266 $moduleState['node_' . $selectedTreeKey] = $newExpandCollapse;
267 } else {
268 $arrayBrowser->depthKeys = $moduleState['node_' . $selectedTreeKey] ?? [];
269 }
270
271 // Store new state
272 $backendUser->uc['moduleData']['system_config'] = $moduleState;
273 $backendUser->writeUC();
274
275 // Render main body
276 $view = GeneralUtility::makeInstance(StandaloneView::class);
277 $view->getRequest()->setControllerExtensionName('lowlevel');
278 $view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName(
279 'EXT:lowlevel/Resources/Private/Templates/Backend/Configuration.html'
280 ));
281 $view->assignMultiple([
282 'treeName' => $selectedTreeDetails['label'],
283 'searchString' => $searchString,
284 'regexSearch' => $moduleState['regexSearch'],
285 'tree' => $arrayBrowser->tree($renderArray, ''),
286 ]);
287
288 // Prepare module setup
289 $moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
290 $moduleTemplate->setContent($view->render());
291 $moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Lowlevel/ConfigurationView');
292
293 // Shortcut in doc header
294 $shortcutButton = $moduleTemplate->getDocHeaderComponent()->getButtonBar()->makeShortcutButton();
295 $shortcutButton->setModuleName('system_config')
296 ->setDisplayName($languageService->sL(
297 'LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:' . $selectedTreeDetails['label']
298 ))
299 ->setSetVariables(['tree']);
300 $moduleTemplate->getDocHeaderComponent()->getButtonBar()->addButton($shortcutButton);
301
302 // Main drop down in doc header
303 $menu = $moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->makeMenu();
304 $menu->setIdentifier('tree');
305 foreach ($this->treeSetup as $treeKey => $treeDetails) {
306 $menuItem = $menu->makeMenuItem();
307 /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
308 $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
309 $menuItem->setHref((string)$uriBuilder->buildUriFromRoute('system_config', ['tree' => $treeKey]))
310 ->setTitle($languageService->sL(
311 'LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:' . $treeDetails['label']
312 ));
313 if ($selectedTreeKey === $treeKey) {
314 $menuItem->setActive(true);
315 }
316 $menu->addMenuItem($menuItem);
317 }
318 $moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->addMenu($menu);
319
320 return new HtmlResponse($moduleTemplate->renderContent());
321 }
322
323 /**
324 * Returns the Backend User
325 * @return BackendUserAuthentication
326 */
327 protected function getBackendUser()
328 {
329 return $GLOBALS['BE_USER'];
330 }
331
332 /**
333 * @return LanguageService
334 */
335 protected function getLanguageService()
336 {
337 return $GLOBALS['LANG'];
338 }
339 }