[FEATURE] Add feature toggle interface to Settings 52/57952/4
authorChristian Kuhn <lolli@schwarzbu.ch>
Fri, 17 Aug 2018 15:15:19 +0000 (17:15 +0200)
committerChristian Kuhn <lolli@schwarzbu.ch>
Sat, 18 Aug 2018 19:09:04 +0000 (21:09 +0200)
Admin tools -> Settings is extended by a GUI to manipulate
feature toggles. Note this interface lists only core features
at the moment since extensions currently have no way to
extend DefaultConfigurationDescrition.yml with additional
information.

Resolves: #85894
Related: #83429
Releases: master
Change-Id: I7ae03c87f2bf94281d4d63d2ff80c6f0460807cb
Reviewed-on: https://review.typo3.org/57952
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Benni Mack <benni@typo3.org>
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
typo3/sysext/core/Configuration/DefaultConfigurationDescription.yaml
typo3/sysext/core/Documentation/Changelog/master/Feature-85894-FeatureTogglesInAdminToolsSettings.rst [new file with mode: 0644]
typo3/sysext/install/Classes/Controller/SettingsController.php
typo3/sysext/install/Resources/Private/Templates/Settings/Cards.html
typo3/sysext/install/Resources/Private/Templates/Settings/FeaturesGetContent.html [new file with mode: 0644]
typo3/sysext/install/Resources/Public/JavaScript/Modules/Features.js [new file with mode: 0644]

index aafb71c..af73f9a 100644 (file)
@@ -215,9 +215,12 @@ SYS:
             type: container
             description: 'New features of TYPO3 that are activated on new installations but upgrading installations can still use the old behaviour'
             items:
+              redirects.hitCount:
+                type: bool
+                description: 'If on, and if extension "redirects" is loaded, each performed redirect is counted and last hit time is logged to the database.'
               unifiedPageTranslationHandling:
                 type: bool
-                description: 'If activated, TCA configuration for pages_language_overlay will never be loaded, and the database table "pages_language_overlay" will not be created.'
+                description: 'If on, TCA configuration for pages_language_overlay is never loaded and the database table "pages_language_overlay" is not created by core. Enable this feature if no extensions fiddles with table pages_language_overlay to have a slightly quicker system with less deprecation log entries.'
         availablePasswordHashAlgorithms:
             type: array
             description: 'A list of available password hash mechanisms. Extensions may register additional mechanisms here. This is usually not extended in LocalConfiguration.php.'
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-85894-FeatureTogglesInAdminToolsSettings.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-85894-FeatureTogglesInAdminToolsSettings.rst
new file mode 100644 (file)
index 0000000..99f317e
--- /dev/null
@@ -0,0 +1,22 @@
+.. include:: ../../Includes.txt
+
+=========================================================
+Feature: #85894 - Feature toggles in Admin Tools Settings
+=========================================================
+
+See :issue:`85894`
+
+Description
+===========
+
+Feature toggles registered in :file:`DefaultConfiguration.php` and documented
+in :file:`DefaultConfigurationDescription.yml` can now be toggled using
+the interface Admin Tools -> Settings -> Feature toggles.
+
+
+Impact
+======
+
+Toggling core features is possible using the backend.
+
+.. index:: Backend, LocalConfiguration, ext:install
\ No newline at end of file
index 8f1c06a..b703369 100644 (file)
@@ -19,6 +19,7 @@ use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;
 use TYPO3\CMS\Core\Configuration\ConfigurationManager;
 use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
+use TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader;
 use TYPO3\CMS\Core\Core\Bootstrap;
 use TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory;
 use TYPO3\CMS\Core\Database\Connection;
@@ -397,4 +398,81 @@ class SettingsController extends AbstractController
             'status' => $messages,
         ]);
     }
+
+    /**
+     * Render feature toggles
+     *
+     * @param ServerRequestInterface $request
+     * @return ResponseInterface
+     */
+    public function featuresGetContentAction(ServerRequestInterface $request): ResponseInterface
+    {
+        $configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class);
+        $configurationDescription = GeneralUtility::makeInstance(YamlFileLoader::class)
+            ->load($configurationManager->getDefaultConfigurationDescriptionFileLocation());
+        $allFeatures = $GLOBALS['TYPO3_CONF_VARS']['SYS']['features'] ?? [];
+        $features = [];
+        foreach ($allFeatures as $featureName => $featureValue) {
+            // Only features that have a .yml description will be listed. There is currently no
+            // way for extensions to extend this, so feature toggles of non-core extensions are
+            // not listed here.
+            if (isset($configurationDescription['SYS']['items']['features']['items'][$featureName]['description'])) {
+                $default = $configurationManager->getDefaultConfigurationValueByPath('SYS/features/' . $featureName);
+                $features[] = [
+                    'name' => $featureName,
+                    'description' => $configurationDescription['SYS']['items']['features']['items'][$featureName]['description'],
+                    'default' => $default,
+                    'value' => $featureValue,
+                ];
+            }
+        }
+        $formProtection = FormProtectionFactory::get(InstallToolFormProtection::class);
+        $view = $this->initializeStandaloneView($request, 'Settings/FeaturesGetContent.html');
+        $view->assignMultiple([
+            'features' => $features,
+            'featuresSaveToken' => $formProtection->generateToken('installTool', 'featuresSave'),
+        ]);
+        return new JsonResponse([
+            'success' => true,
+            'html' => $view->render(),
+        ]);
+    }
+
+    /**
+     * Update feature toggles state
+     *
+     * @param ServerRequestInterface $request
+     * @return ResponseInterface
+     */
+    public function featuresSaveAction(ServerRequestInterface $request): ResponseInterface
+    {
+        $configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class);
+        $enabledFeaturesFromPost = $request->getParsedBody()['install']['values'] ?? [];
+        $allFeatures = array_keys($GLOBALS['TYPO3_CONF_VARS']['SYS']['features'] ?? []);
+        $configurationDescription = GeneralUtility::makeInstance(YamlFileLoader::class)
+            ->load($configurationManager->getDefaultConfigurationDescriptionFileLocation());
+        foreach ($allFeatures as $featureName) {
+            // Only features that have a .yml description will be listed. There is currently no
+            // way for extensions to extend this, so feature toggles of non-core extensions are
+            // not considered.
+            if (isset($configurationDescription['SYS']['items']['features']['items'][$featureName]['description'])) {
+                if (isset($enabledFeaturesFromPost[$featureName])) {
+                    $configurationManager->enableFeature($featureName);
+                } else {
+                    $configurationManager->disableFeature($featureName);
+                }
+            }
+        }
+        $messages = [
+            new FlashMessage(
+                'Successfully updated feature toggles',
+                '',
+                FlashMessage::OK
+            )
+        ];
+        return new JsonResponse([
+            'success' => true,
+            'status' => $messages,
+        ]);
+    }
 }
index 512df00..c83f5e3 100644 (file)
        </div>
        <div class="card card-size-fixed-small">
                <div class="card-header">
+                       <h1 class="card-title">Feature toggles</h1>
+                       <span class="card-subtitle">Global Configuration</span>
+               </div>
+               <div class="card-content">
+                       <p class="card-text">Enable and disable core features.</p>
+               </div>
+               <div class="card-footer">
+                       <a href="#" class="btn btn-default" data-modal-size="medium" data-require="TYPO3/CMS/Install/Features">Configure Features</a>
+               </div>
+       </div>
+       <div class="card card-size-fixed-small">
+               <div class="card-header">
                        <h1 class="card-title">Configure Installation-Wide Options</h1>
                        <span class="card-subtitle">Global Configuration</span>
                </div>
diff --git a/typo3/sysext/install/Resources/Private/Templates/Settings/FeaturesGetContent.html b/typo3/sysext/install/Resources/Private/Templates/Settings/FeaturesGetContent.html
new file mode 100644 (file)
index 0000000..8c3d527
--- /dev/null
@@ -0,0 +1,40 @@
+<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
+
+<p>
+       Enable and disable certain core features. Feature toggles in the core are used to globally
+       use new features of TYPO3 that may be activated on new installations but upgrading installations
+       can still use the old behaviour.
+</p>
+
+<div class="t3js-features-content" data-features-save-token="{featuresSaveToken}">
+       <strong>Available features:</strong>
+       <form method="post" class="form-horizontal">
+               <f:for each="{features}" as="feature">
+                       <div class="checkbox checkbox-type-labeled-toggle">
+                               <input type="checkbox" class="checkbox-input"
+                                       value="1"
+                                       name="install[values][{feature.name}]"
+                                       id="t3-install-tool-features-{feature.name}"
+                                       {f:if(condition: '{feature.value} == 1', then: 'checked="checked"')}
+                               >
+                               <label class="checkbox-label" for="t3-install-tool-features-{feature.name}">
+                                       <span class="checkbox-label-switch">
+                                               <span class="checkbox-label-switch-checked">On</span>
+                                               <span class="checkbox-label-switch-unchecked">Off</span>
+                                       </span>
+                                       <span class="checkbox-label-text">
+                                               {feature.name} (default {f:if(condition: '{feature.default} == 1', then: 'on', else: 'off')}): {feature.description}
+                                       </span>
+                               </label>
+                       </div>
+               </f:for>
+
+               <button
+                       class="btn btn-default t3js-features-save"
+                       type="button"
+               >
+                       Save
+               </button>
+       </form>
+</div>
+</html>
diff --git a/typo3/sysext/install/Resources/Public/JavaScript/Modules/Features.js b/typo3/sysext/install/Resources/Public/JavaScript/Modules/Features.js
new file mode 100644 (file)
index 0000000..400e96c
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * 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/Features
+ */
+define([
+  'jquery',
+  'TYPO3/CMS/Install/Router',
+  'TYPO3/CMS/Install/Severity',
+  'TYPO3/CMS/Backend/Notification'
+], function($, Router, Severity, Notification) {
+  'use strict';
+
+  return {
+    selectorModalBody: '.t3js-modal-body',
+    selectorModuleContent: '.t3js-features-content',
+    selectorSaveTrigger: '.t3js-features-save',
+
+    initialize: function(currentModal) {
+      var self = this;
+      this.currentModal = currentModal;
+      self.getContent();
+
+      currentModal.on('click', this.selectorSaveTrigger, function(e) {
+        e.preventDefault();
+        self.save();
+      });
+    },
+
+    getContent: function() {
+      var self = this;
+      var modalContent = this.currentModal.find(self.selectorModalBody);
+      $.ajax({
+        url: Router.getUrl('featuresGetContent'),
+        cache: false,
+        success: function(data) {
+          if (data.success === true && data.html !== 'undefined' && data.html.length > 0) {
+            modalContent.empty().append(data.html);
+          } else {
+            Notification.error('Something went wrong');
+          }
+        },
+        error: function(xhr) {
+          Router.handleAjaxError(xhr);
+        }
+      });
+    },
+
+    save: function() {
+      var self = this;
+      var executeToken = self.currentModal.find(this.selectorModuleContent).data('features-save-token');
+      var postData = {};
+      $(self.currentModal.find(this.selectorModuleContent + ' form').serializeArray()).each(function() {
+        postData[this.name] = this.value;
+      });
+      postData['install[action]'] = 'featuresSave';
+      postData['install[token]'] = executeToken;
+      $.ajax({
+        url: Router.getUrl(),
+        method: 'POST',
+        data: postData,
+        cache: false,
+        success: function(data) {
+          if (data.success === true && Array.isArray(data.status)) {
+            data.status.forEach(function(element) {
+              Notification.showMessage(element.title, element.message, element.severity);
+            });
+          } else {
+            Notification.error('Something went wrong');
+          }
+        },
+        error: function(xhr) {
+          Router.handleAjaxError(xhr);
+        }
+      });
+    }
+  };
+});