From 531be24a96726849402bfe2e9edc04730e9c96d8 Mon Sep 17 00:00:00 2001
From: Benni Mack <benni@typo3.org>
Date: Thu, 2 Nov 2017 11:46:40 +0100
Subject: [PATCH] [FEATURE] Introduce Feature Toggles

A new API class "Features" allows to check if a feature is enabled/disabled
for a certain installation.

This API method can be used to have admins switch to new features explicitly
or disable legacy functionality.

Examples for using the feature toggles in the future:
- Do not load TCA for pages_language_overlay after DB migration
- Do not parse HTML for legacy <link> tag anymore
- Explicitly enable new Logging API in DataHandler (skipping sys_log calls)

Resolves: #83429
Releases: master
Change-Id: I5da8f66e593e311c83fefe5fe9edb503a885943b
Reviewed-on: https://review.typo3.org/54529
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Andreas Fernandez <typo3@scripting-base.de>
Tested-by: Andreas Fernandez <typo3@scripting-base.de>
---
 .../Configuration/ConfigurationManager.php    | 24 +++++++
 .../core/Classes/Configuration/Features.php   | 65 +++++++++++++++++++
 .../Configuration/DefaultConfiguration.php    |  1 +
 .../master/Feature-83429-FeatureToggles.rst   | 40 ++++++++++++
 .../Tests/Unit/Configuration/FeaturesTest.php | 64 ++++++++++++++++++
 5 files changed, 194 insertions(+)
 create mode 100644 typo3/sysext/core/Classes/Configuration/Features.php
 create mode 100644 typo3/sysext/core/Documentation/Changelog/master/Feature-83429-FeatureToggles.rst
 create mode 100644 typo3/sysext/core/Tests/Unit/Configuration/FeaturesTest.php

diff --git a/typo3/sysext/core/Classes/Configuration/ConfigurationManager.php b/typo3/sysext/core/Classes/Configuration/ConfigurationManager.php
index 05e60cd2d74b..d3ce2786c824 100644
--- a/typo3/sysext/core/Classes/Configuration/ConfigurationManager.php
+++ b/typo3/sysext/core/Classes/Configuration/ConfigurationManager.php
@@ -286,6 +286,30 @@ class ConfigurationManager
         return $result;
     }
 
+    /**
+     * Enables a certain feature and writes the option to LocalConfiguration.php
+     * Short-hand method
+     *
+     * @param string $featureName something like "InlineSvgImages"
+     * @return bool true on successful writing the setting
+     */
+    public function enableFeature(string $featureName): bool
+    {
+        return $this->setLocalConfigurationValueByPath('SYS/features/' . $featureName, true);
+    }
+
+    /**
+     * Disables a feature and writes the option to LocalConfiguration.php
+     * Short-hand method
+     *
+     * @param string $featureName something like "InlineSvgImages"
+     * @return bool true on successful writing the setting
+     */
+    public function disableFeature(string $featureName): bool
+    {
+        return $this->setLocalConfigurationValueByPath('SYS/features/' . $featureName, false);
+    }
+
     /**
      * Checks if the configuration can be written.
      *
diff --git a/typo3/sysext/core/Classes/Configuration/Features.php b/typo3/sysext/core/Classes/Configuration/Features.php
new file mode 100644
index 000000000000..1cc400401836
--- /dev/null
+++ b/typo3/sysext/core/Classes/Configuration/Features.php
@@ -0,0 +1,65 @@
+<?php
+declare(strict_types=1);
+namespace TYPO3\CMS\Core\Configuration;
+
+/*
+ * 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!
+ */
+
+/**
+ * A lightweight API class to check if a feature is enabled.
+ *
+ * Features are simple options (true/false), and are stored in the
+ * global configuration array $TYPO3_CONF_VARS[SYS][features].
+ *
+ * For disabling or enabling a feature the "ConfigurationManager"
+ * should be used.
+ *
+ * -- Naming --
+ *
+ * Feature names should NEVER named "enable" or having a negation, or containing versions or years
+ *    "enableFeatureXyz"
+ *    "disableOverlays"
+ *    "schedulerRevamped"
+ *    "useDoctrineQueries"
+ *    "disablePreparedStatements"
+ *    "disableHooksInFE"
+ *
+ * Proper namings for features
+ *    "ExtendedRichtextFormat"
+ *    "NativeYamlParser"
+ *    "InlinePageTranslations"
+ *    "TypoScriptParserIncludesAsXml"
+ *    "NativeDoctrineQueries"
+ *
+ * Ideally, these feature switches are added via the Install Tool or via FactoryConfiguration
+ * and can be used for Extensions as well.
+ *
+ * --- Usage ---
+ *
+ * if (GeneralUtility::makeInstance(Features::class)->isFeatureEnabled('InlineSvg')) {
+ *   ... do stuff here ...
+ * }
+ */
+class Features
+{
+    /**
+     * Checks if a feature is active
+     *
+     * @param string $featureName the name of the feature
+     * @return bool
+     */
+    public function isFeatureEnabled(string $featureName): bool
+    {
+        return isset($GLOBALS['TYPO3_CONF_VARS']['SYS']['features'][$featureName]) && $GLOBALS['TYPO3_CONF_VARS']['SYS']['features'][$featureName] === true;
+    }
+}
diff --git a/typo3/sysext/core/Configuration/DefaultConfiguration.php b/typo3/sysext/core/Configuration/DefaultConfiguration.php
index 8a5cc4279d03..6edf83a26d23 100644
--- a/typo3/sysext/core/Configuration/DefaultConfiguration.php
+++ b/typo3/sysext/core/Configuration/DefaultConfiguration.php
@@ -67,6 +67,7 @@ return [
         ],
         'fileCreateMask' => '0664',
         'folderCreateMask' => '2775',
+        'features' => [],
         'createGroup' => '',
         'sitename' => 'TYPO3',
         'encryptionKey' => '',
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-83429-FeatureToggles.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-83429-FeatureToggles.rst
new file mode 100644
index 000000000000..952ed62ca726
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/master/Feature-83429-FeatureToggles.rst
@@ -0,0 +1,40 @@
+.. include:: ../../Includes.txt
+
+=================================
+Feature: #83429 - Feature Toggles
+=================================
+
+See :issue:`83429`
+
+Description
+===========
+
+In order to allow better support for alternative functionality while keeping old functionality, a new API
+for enabling installation-wide features - called "Feature Toggle" - has been added.
+
+The new API checks against a system-wide option array within :php:`$TYPO3_CONF_VARS['SYS']['features']` which can be
+enabled system-wide. Both TYPO3 Core and Extensions can then provide alternative functionality for a certain
+feature.
+
+Features are usually breaking changes for a minor version / sprint release, which site administrators can enable
+at their own risk, or stay fully compatible with third-party extensions.
+
+Examples for having features are:
+* Throw exceptions on certain occasions instead of just returning a string message as error message.
+* Disable obsolete functionality which might still be used, but slows down the system.
+* Enable alternative PageNotFound handling for an installation.
+
+
+Impact
+======
+
+Features are documented for TYPO3 Core. For extension authors, the API can be used for any custom
+feature provided by an extension:
+
+.. code-block:: php
+
+	if (GeneralUtility::makeInstance(Features::class)->isFeatureEnabled('myFeatureName')) {
+		// do custom processing
+	}
+
+.. index:: LocalConfiguration, PHP-API
diff --git a/typo3/sysext/core/Tests/Unit/Configuration/FeaturesTest.php b/typo3/sysext/core/Tests/Unit/Configuration/FeaturesTest.php
new file mode 100644
index 000000000000..8f093aac07bd
--- /dev/null
+++ b/typo3/sysext/core/Tests/Unit/Configuration/FeaturesTest.php
@@ -0,0 +1,64 @@
+<?php
+declare(strict_types=1);
+namespace TYPO3\CMS\Core\Tests\Unit\Configuration;
+
+/*
+ * 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\Features;
+use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
+
+/**
+ * Test case
+ */
+class FeaturesTest extends UnitTestCase
+{
+    /**
+     * @test
+     */
+    public function nonExistingFeatureReturnsFalse()
+    {
+        $features = new Features();
+        $this->assertFalse($features->isFeatureEnabled('nonExistingFeature'));
+    }
+
+    /**
+     * @test
+     */
+    public function checkIfExistingDisabledFeatureIsDisabled()
+    {
+        $GLOBALS['TYPO3_CONF_VARS']['SYS']['features']['nativeFunctionality'] = false;
+        $features = new Features();
+        $this->assertFalse($features->isFeatureEnabled('nativeFunctionality'));
+    }
+
+    /**
+     * @test
+     */
+    public function checkIfExistingEnabledFeatureIsEnabled()
+    {
+        $GLOBALS['TYPO3_CONF_VARS']['SYS']['features']['nativeFunctionality'] = true;
+        $features = new Features();
+        $this->assertTrue($features->isFeatureEnabled('nativeFunctionality'));
+    }
+
+    /**
+     * @test
+     */
+    public function checkIfExistingEnabledFeatureIsDisabled()
+    {
+        $GLOBALS['TYPO3_CONF_VARS']['SYS']['features']['nativeFunctionality'] = false;
+        $features = new Features();
+        $this->assertFalse($features->isFeatureEnabled('nativeFunctionality'));
+    }
+}
-- 
GitLab