From 511e82369b17562ca2956796d0ee964a5942d502 Mon Sep 17 00:00:00 2001
From: Christian Kuhn <lolli@schwarzbu.ch>
Date: Wed, 6 Sep 2017 19:22:33 +0200
Subject: [PATCH] [TASK] Install tool: System maintainer administration
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Adds a card to the "Settings" menu to allow configuration of
"System Maintainers" in the install tool writing SYS/systemMaintainers
array to LocalConfiguration.

Change-Id: I0219b60e9a261373befab7b846e55b61ea215e5e
Resolves: #82319
Releases: master
Reviewed-on: https://review.typo3.org/53931
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Matthias Vogel <typo3@kanti.de>
Tested-by: Matthias Vogel <typo3@kanti.de>
Reviewed-by: Łukasz Uznański <l.uznanski@macopedia.pl>
Tested-by: Łukasz Uznański <l.uznanski@macopedia.pl>
Reviewed-by: Andreas Fernandez <typo3@scripting-base.de>
Tested-by: Andreas Fernandez <typo3@scripting-base.de>
---
 .../Action/Ajax/SystemMaintainerGetList.php   |  67 +++++++++
 .../Action/Ajax/SystemMaintainerWrite.php     |  87 +++++++++++
 .../Controller/Action/Tool/Settings.php       |   4 +
 .../Classes/Controller/AjaxController.php     |   4 +
 .../Tool/Settings/SystemMaintainer.html       |  43 ++++++
 .../Templates/Action/Tool/Settings.html       |  11 +-
 .../JavaScript/Modules/SystemMaintainer.js    | 141 ++++++++++++++++++
 7 files changed, 355 insertions(+), 2 deletions(-)
 create mode 100644 typo3/sysext/install/Classes/Controller/Action/Ajax/SystemMaintainerGetList.php
 create mode 100644 typo3/sysext/install/Classes/Controller/Action/Ajax/SystemMaintainerWrite.php
 create mode 100644 typo3/sysext/install/Resources/Private/Partials/Action/Tool/Settings/SystemMaintainer.html
 create mode 100644 typo3/sysext/install/Resources/Public/JavaScript/Modules/SystemMaintainer.js

diff --git a/typo3/sysext/install/Classes/Controller/Action/Ajax/SystemMaintainerGetList.php b/typo3/sysext/install/Classes/Controller/Action/Ajax/SystemMaintainerGetList.php
new file mode 100644
index 000000000000..5caebd9ad97e
--- /dev/null
+++ b/typo3/sysext/install/Classes/Controller/Action/Ajax/SystemMaintainerGetList.php
@@ -0,0 +1,67 @@
+<?php
+declare(strict_types=1);
+namespace TYPO3\CMS\Install\Controller\Action\Ajax;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Get list of backend admin users with information if they are system maintainers
+ */
+class SystemMaintainerGetList extends AbstractAjaxAction
+{
+    /**
+     * Get backend admin user list
+     *
+     * @return array
+     */
+    protected function executeAction(): array
+    {
+        $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
+
+        // We have to respect the enable fields here by our own because no TCA is loaded in standalone mode
+        $queryBuilder = $connectionPool->getQueryBuilderForTable('be_users');
+        $queryBuilder->getRestrictions()->removeAll();
+
+        $users = $queryBuilder
+            ->select('uid', 'username', 'disable', 'starttime', 'endtime')
+            ->from('be_users')
+            ->where(
+                $queryBuilder->expr()->andX(
+                    $queryBuilder->expr()->eq('deleted', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)),
+                    $queryBuilder->expr()->eq('admin', $queryBuilder->createNamedParameter(1, \PDO::PARAM_INT))
+                )
+            )
+            ->orderBy('uid')
+            ->execute()
+            ->fetchAll();
+
+        $systemMaintainerList = $GLOBALS['TYPO3_CONF_VARS']['SYS']['systemMaintainers'] ?? [];
+        $currentTime = time();
+        foreach ($users as &$user) {
+            $user['disable'] = $user['disable'] ||
+                ((int)$user['starttime'] !== 0 && $user['starttime'] > $currentTime) ||
+                ((int)$user['endtime'] !== 0 && $user['endtime'] < $currentTime);
+            $user['isSystemMaintainer'] = in_array((int)$user['uid'], $systemMaintainerList, true);
+        }
+        $this->view->assignMultiple([
+            'success' => true,
+            'status' => [],
+            'users' => $users,
+        ]);
+        return $this->view->render();
+    }
+}
diff --git a/typo3/sysext/install/Classes/Controller/Action/Ajax/SystemMaintainerWrite.php b/typo3/sysext/install/Classes/Controller/Action/Ajax/SystemMaintainerWrite.php
new file mode 100644
index 000000000000..ecded37708ec
--- /dev/null
+++ b/typo3/sysext/install/Classes/Controller/Action/Ajax/SystemMaintainerWrite.php
@@ -0,0 +1,87 @@
+<?php
+declare(strict_types=1);
+namespace TYPO3\CMS\Install\Controller\Action\Ajax;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Configuration\ConfigurationManager;
+use TYPO3\CMS\Core\Database\Connection;
+use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Messaging\FlashMessage;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\MathUtility;
+
+/**
+ * Write out system maintainer list to LocalConfiguration
+ */
+class SystemMaintainerWrite extends AbstractAjaxAction
+{
+    /**
+     * Write system maintainer list
+     *
+     * @return array
+     */
+    protected function executeAction(): array
+    {
+        // Sanitize given user list and write out
+        $newUserList = [];
+        if (isset($this->postValues['users']) && is_array($this->postValues['users'])) {
+            foreach ($this->postValues['users'] as $uid) {
+                if (MathUtility::canBeInterpretedAsInteger($uid)) {
+                    $newUserList[] = (int)$uid;
+                }
+            }
+        }
+
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('be_users');
+        $validatedUserList = $queryBuilder
+            ->select('uid')
+            ->from('be_users')
+            ->where(
+                $queryBuilder->expr()->andX(
+                    $queryBuilder->expr()->eq('deleted', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)),
+                    $queryBuilder->expr()->eq('admin', $queryBuilder->createNamedParameter(1, \PDO::PARAM_INT)),
+                    $queryBuilder->expr()->in('uid', $queryBuilder->createNamedParameter($newUserList, Connection::PARAM_INT_ARRAY))
+                )
+            )->execute()->fetchAll();
+
+        $validatedUserList = array_column($validatedUserList, 'uid');
+
+        $configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class);
+        $configurationManager->setLocalConfigurationValuesByPathValuePairs(
+            [ 'SYS/systemMaintainers' => $validatedUserList ]
+        );
+
+        $messages = [];
+        if (empty($validatedUserList)) {
+            $messages[] = new FlashMessage(
+                '',
+                'Set system maintainer list to an empty array',
+                FlashMessage::INFO
+            );
+        } else {
+            $messages[] = new FlashMessage(
+                implode(', ', $validatedUserList),
+                'New system maintainer uid list',
+                FlashMessage::INFO
+            );
+        }
+
+        $this->view->assignMultiple([
+            'success' => true,
+            'status' => $messages
+        ]);
+        return $this->view->render();
+    }
+}
diff --git a/typo3/sysext/install/Classes/Controller/Action/Tool/Settings.php b/typo3/sysext/install/Classes/Controller/Action/Tool/Settings.php
index 4114873a5d4e..1520e269403e 100644
--- a/typo3/sysext/install/Classes/Controller/Action/Tool/Settings.php
+++ b/typo3/sysext/install/Classes/Controller/Action/Tool/Settings.php
@@ -50,6 +50,7 @@ class Settings extends Action\AbstractAction
         $presetFeatures = $this->featureManager->getInitializedFeatures($this->postValues['values'] ?? []);
         $localConfigurationValueService = new LocalConfigurationValueService();
         $formProtection = FormProtectionFactory::get(InstallToolFormProtection::class);
+
         $this->view->assignMultiple([
             'changeInstallToolPasswordToken' => $formProtection->generateToken('installTool', 'changeInstallToolPassword'),
 
@@ -59,6 +60,9 @@ class Settings extends Action\AbstractAction
 
             'presetActivateToken' => $formProtection->generateToken('installTool', 'presetActivate'),
             'presetFeatures' => $presetFeatures,
+
+            'systemMaintainerWriteToken' => $formProtection->generateToken('installTool', 'systemMaintainerWrite'),
+            'systemMaintainerIsDevelopmentContext' => GeneralUtility::getApplicationContext()->isDevelopment(),
         ]);
         return $this->view->render();
     }
diff --git a/typo3/sysext/install/Classes/Controller/AjaxController.php b/typo3/sysext/install/Classes/Controller/AjaxController.php
index b1d2316dff68..8631ec678f23 100644
--- a/typo3/sysext/install/Classes/Controller/AjaxController.php
+++ b/typo3/sysext/install/Classes/Controller/AjaxController.php
@@ -66,6 +66,10 @@ class AjaxController extends AbstractController
         'mailTest',
         'presetActivate',
         'resetBackendUserUc',
+
+        'systemMaintainerGetList',
+        'systemMaintainerWrite',
+
         'tcaExtTablesCheck',
         'tcaMigrationsCheck',
 
diff --git a/typo3/sysext/install/Resources/Private/Partials/Action/Tool/Settings/SystemMaintainer.html b/typo3/sysext/install/Resources/Private/Partials/Action/Tool/Settings/SystemMaintainer.html
new file mode 100644
index 000000000000..4384385a9ded
--- /dev/null
+++ b/typo3/sysext/install/Resources/Private/Partials/Action/Tool/Settings/SystemMaintainer.html
@@ -0,0 +1,43 @@
+<p>
+	Backend admin users listed here will see the system maintenance related main
+	module entries "Maintenance", "Settings", "Upgrade" and "Environment" in the
+	backend and are allowed to use them without further log in.
+	Note that all backend admin users see these menu entries if the system is in
+	development (not production) context.
+</p>
+
+<f:if condition="{systemMaintainerIsDevelopmentContext}">
+	<div class="typo3-message alert alert-info">
+		<div class="message-body">
+			This TYPO3 instance is set to "Development" context, all backend admin users
+			can see the system maintenance related main module entries.
+		</div>
+	</div>
+</f:if>
+
+<div style="display:none;">
+	<div id="t3js-systemMaintainer-write-token">{systemMaintainerWriteToken}</div>
+</div>
+
+<div class="form-group">
+	<div class="input-group t3js-systemMaintainer-chosen" style="display:none">
+		<span class="input-group-addon">System Maintainer:</span>
+		<select
+			class="chosen-select t3js-systemMaintainer-chosen-select"
+			data-placeholder="none"
+			style="width:100%;"
+			multiple
+			tabindex=""
+		>
+		</select>
+	</div>
+</div>
+
+<div class="t3js-systemMaintainer-output"></div>
+
+<button
+	class="btn btn-default t3js-systemMaintainer-write"
+	type="button"
+>
+	Save system maintainer list
+</button>
diff --git a/typo3/sysext/install/Resources/Private/Templates/Action/Tool/Settings.html b/typo3/sysext/install/Resources/Private/Templates/Action/Tool/Settings.html
index 55750a39e2fd..7c590df835ad 100644
--- a/typo3/sysext/install/Resources/Private/Templates/Action/Tool/Settings.html
+++ b/typo3/sysext/install/Resources/Private/Templates/Action/Tool/Settings.html
@@ -15,14 +15,21 @@
 			category: 'Access',
 			description: 'Set a new install tool password.'
 		},
-		1: {partial: 'Action/Tool/Settings/LocalConfiguration',
+		1: {partial: 'Action/Tool/Settings/SystemMaintainer',
+			require: 'TYPO3/CMS/Install/SystemMaintainer',
+			baseClass: 't3js-systemMaintainer',
+			title: 'Configure system maintainer',
+			category: 'Access',
+			description: 'Backend admin users with access to install tool.'
+		},
+		2: {partial: 'Action/Tool/Settings/LocalConfiguration',
 			require: 'TYPO3/CMS/Install/LocalConfiguration',
 			baseClass: 't3js-localConfiguration',
 			title: 'Configure Global Settings',
 			category: 'LocalConfiguration',
 			description: 'Modify LocalConfiguration.php settings.'
 		},
-		2: {partial: 'Action/Tool/Settings/Presets',
+		3: {partial: 'Action/Tool/Settings/Presets',
 			require: 'TYPO3/CMS/Install/Presets',
 			baseClass: 't3js-presets',
 			title: 'Configure Presets',
diff --git a/typo3/sysext/install/Resources/Public/JavaScript/Modules/SystemMaintainer.js b/typo3/sysext/install/Resources/Public/JavaScript/Modules/SystemMaintainer.js
new file mode 100644
index 000000000000..8bee9736912c
--- /dev/null
+++ b/typo3/sysext/install/Resources/Public/JavaScript/Modules/SystemMaintainer.js
@@ -0,0 +1,141 @@
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+/**
+ * Module: TYPO3/CMS/Install/SystemMaintainer
+ */
+define([
+	'jquery',
+	'TYPO3/CMS/Install/FlashMessage',
+	'TYPO3/CMS/Install/ProgressBar',
+	'TYPO3/CMS/Install/InfoBox',
+	'TYPO3/CMS/Install/Severity',
+	'bootstrap',
+	'chosen'
+], function($, FlashMessage, ProgressBar, InfoBox, Severity) {
+	'use strict';
+
+	return {
+		selectorGridderOpener: 't3js-systemMaintainer-open',
+		selectorWriteTrigger: '.t3js-systemMaintainer-write',
+		selectorWriteToken: '#t3js-systemMaintainer-write-token',
+		selectorOutputContainer: '.t3js-systemMaintainer-output',
+		selectorChosenContainer: '.t3js-systemMaintainer-chosen',
+		selectorChosenField: '.t3js-systemMaintainer-chosen-select',
+
+		initialize: function() {
+			var self = this;
+
+			// Get current system maintainer list on card open
+			$(document).on('cardlayout:card-opened', function(event, $card) {
+				if ($card.hasClass(self.selectorGridderOpener)) {
+					self.getList();
+				}
+			});
+
+			$(document).on('click', this.selectorWriteTrigger, function(e) {
+				e.preventDefault();
+				self.write();
+			});
+		},
+
+		getList: function() {
+			var self = this;
+			var url = location.href + '&install[controller]=ajax&install[action]=systemMaintainerGetList';
+			var $chosenContainer = $(this.selectorChosenContainer);
+			var $outputContainer = $(this.selectorOutputContainer);
+			var $chosenField = $(self.selectorChosenField);
+			var message = ProgressBar.render(Severity.loading, 'Loading...', '');
+			$outputContainer.empty().append(message);
+			$chosenContainer.hide();
+			$chosenField.empty();
+			$.ajax({
+				url: url,
+				cache: false,
+				success: function (data) {
+					if (data.success === true) {
+						$outputContainer.find('.alert-loading').remove();
+						if (Array.isArray(data.status)) {
+							data.status.forEach(function(element) {
+								var message = InfoBox.render(element.severity, element.title, element.message);
+								$outputContainer.append(message);
+							});
+						}
+						if (Array.isArray(data.users)) {
+							data.users.forEach(function(element) {
+								var name = element.username;
+								if (element.disable) {
+									name = '[DISABLED] ' + name;
+								}
+								var selected = '';
+								if (element.isSystemMaintainer) {
+									selected = 'selected="selected"';
+								}
+								$chosenField.append(
+									'<option value="' + element.uid + '" ' + selected + '>' + name + '</option>'
+								);
+							});
+						}
+						var config = {
+							'.chosen-select': {width: "100%", placeholder_text_multiple: "users"},
+							'.chosen-select-deselect': {allow_single_deselect: true},
+							'.chosen-select-width': {width: "100%"}
+						};
+						for (var selector in config) {
+							$(selector).chosen(config[selector]);
+						}
+						$chosenContainer.show();
+						$chosenField.trigger('chosen:updated');
+					}
+				},
+				error: function() {
+					var message = InfoBox.render(Severity.error, 'Something went wrong', '');
+					$outputContainer.empty().html(message);
+				}
+			});
+		},
+
+		write: function() {
+			var $outputContainer = $(this.selectorOutputContainer);
+			var selectedUsers = $(this.selectorChosenField).val();
+			var message = ProgressBar.render(Severity.loading, 'Loading...', '');
+			$outputContainer.append(message);
+			$.ajax({
+				method: 'POST',
+				url: location.href + '&install[controller]=ajax',
+				data: {
+					'install': {
+						'users': selectedUsers,
+						'token': $(this.selectorWriteToken).text(),
+						'action': 'systemMaintainerWrite'
+					}
+				},
+				success: function (data) {
+					if (data.success === true) {
+						$outputContainer.find('.alert-loading').remove();
+						if (Array.isArray(data.status)) {
+							data.status.forEach(function(element) {
+								var message = InfoBox.render(element.severity, element.title, element.message);
+								$outputContainer.empty().append(message);
+							});
+						}
+					}
+				},
+				error: function() {
+					var message = InfoBox.render(Severity.error, 'Something went wrong', '');
+					$outputContainer.empty().html(message);
+				}
+			});
+		}
+	};
+});
-- 
GitLab