[!!!][FEATURE] Move extension configuration to install tool 34/54034/34
authorMarkus Hoelzle <typo3@markus-hoelzle.de>
Fri, 8 Sep 2017 10:01:53 +0000 (12:01 +0200)
committerBenni Mack <benni@typo3.org>
Fri, 8 Dec 2017 10:52:06 +0000 (11:52 +0100)
With #82254 LocalConfiguration serialized array EXT/extConf has been
changed to a not serialized array in EXTENSIONS. This patch follows
up on this task an finishes various tasks:

* An install tool silent upgrader upmerges given EXT/extConf settings
  to EXTENSIONS array. The resulting EXTENSIONS array does not contain
  dots for sub paths in its array key anymore and is accessible with a
  new ExtensionConfiguration->get() API to fetch values and whole
  extension config.
* A new API is introduced to get() and set() extension specific
  configuration, is documented and used throughout the core to not
  unserialize old EXT/extConf anymore. Setting values updates legacy
  EXT/extConf to new values including compatible 'dot' ending on
  nested array configurations.
* If extensions come with new configuration items in ext_conf_template.txt
  a silent upgrader of the install tool synchronizes these to the
  EXTENSIONS and old extConf array. Extension authors can rely on that
  and always fetch new keys from the new ExtensionConfiguration->get()
  API right away. The synchronization is also triggered when new
  extensions are loaded or extensions are updated via the extension
  manager.
* Core usages are adapted to the new API.
* Next to the main get() / set() API, the extension configuration
  form is extracted from the extension manager and put into the install
  tool as a new card in "Settings". The code below is streamlined
  and encapsulated with just a couple of public methods in class
  'ExtensionConfigurationService' as internal class for use in install
  tool and extension manager.

Resolves: #82368
Related: #82254
Releases: master
Change-Id: I88568fa355f8f6fd5acc9850dcdd718fdd9a1b2e
Reviewed-on: https://review.typo3.org/54034
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Daniel Gorges <daniel.gorges@b13.de>
Tested-by: Daniel Gorges <daniel.gorges@b13.de>
Reviewed-by: Andreas Fernandez <typo3@scripting-base.de>
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Benni Mack <benni@typo3.org>
69 files changed:
Build/Resources/Public/Sass/install.scss
typo3/sysext/backend/Classes/Controller/BackendController.php
typo3/sysext/backend/Classes/Controller/LoginController.php
typo3/sysext/backend/Classes/Template/DocumentTemplate.php
typo3/sysext/core/Classes/Configuration/Exception/ExtensionConfigurationExtensionNotConfiguredException.php [new file with mode: 0644]
typo3/sysext/core/Classes/Configuration/Exception/ExtensionConfigurationPathDoesNotExistException.php [new file with mode: 0644]
typo3/sysext/core/Classes/Configuration/ExtensionConfiguration.php [new file with mode: 0644]
typo3/sysext/core/Configuration/DefaultConfiguration.php
typo3/sysext/core/Configuration/FactoryConfiguration.php
typo3/sysext/core/Documentation/Changelog/master/Breaking-82368-SignalAfterExtensionConfigurationWriteRemoved.rst [new file with mode: 0644]
typo3/sysext/core/Documentation/Changelog/master/Deprecation-82254-DeprecateGLOBALSTYPO3_CONF_VARSEXTextConf.rst
typo3/sysext/core/Documentation/Changelog/master/Feature-82254-StoreExtensionConfigurationAsPlainArray.rst
typo3/sysext/core/Tests/Unit/Configuration/ExtensionConfigurationTest.php [new file with mode: 0644]
typo3/sysext/extensionmanager/Classes/Controller/ConfigurationController.php [deleted file]
typo3/sysext/extensionmanager/Classes/Controller/DistributionController.php
typo3/sysext/extensionmanager/Classes/Controller/DownloadController.php
typo3/sysext/extensionmanager/Classes/Controller/ListController.php
typo3/sysext/extensionmanager/Classes/Controller/UploadExtensionFileController.php
typo3/sysext/extensionmanager/Classes/Domain/Model/ConfigurationCategory.php [deleted file]
typo3/sysext/extensionmanager/Classes/Domain/Model/ConfigurationItem.php [deleted file]
typo3/sysext/extensionmanager/Classes/Domain/Model/ConfigurationSubcategory.php [deleted file]
typo3/sysext/extensionmanager/Classes/Domain/Repository/ConfigurationItemRepository.php [deleted file]
typo3/sysext/extensionmanager/Classes/Utility/ConfigurationUtility.php [deleted file]
typo3/sysext/extensionmanager/Classes/Utility/Connection/TerUtility.php
typo3/sysext/extensionmanager/Classes/Utility/InstallUtility.php
typo3/sysext/extensionmanager/Classes/Utility/Repository/Helper.php
typo3/sysext/extensionmanager/Classes/ViewHelpers/ConfigureExtensionViewHelper.php [deleted file]
typo3/sysext/extensionmanager/Classes/ViewHelpers/DownloadExtensionViewHelper.php
typo3/sysext/extensionmanager/Classes/ViewHelpers/Form/TypoScriptConstantsViewHelper.php [deleted file]
typo3/sysext/extensionmanager/Resources/Private/Language/locallang.xlf
typo3/sysext/extensionmanager/Resources/Private/Templates/Configuration/ShowConfigurationForm.html [deleted file]
typo3/sysext/extensionmanager/Resources/Private/Templates/Distribution/Show.html
typo3/sysext/extensionmanager/Resources/Private/Templates/List/Index.html
typo3/sysext/extensionmanager/Resources/Public/JavaScript/Main.js
typo3/sysext/extensionmanager/Tests/Unit/Domain/Repository/ConfigurationItemRepositoryTest.php [deleted file]
typo3/sysext/extensionmanager/Tests/Unit/Utility/ConfigurationUtilityTest.php [deleted file]
typo3/sysext/extensionmanager/Tests/Unit/Utility/Fixtures/ext_conf_template.txt [deleted file]
typo3/sysext/extensionmanager/ext_localconf.php
typo3/sysext/indexed_search/Classes/Controller/AdministrationController.php
typo3/sysext/indexed_search/Classes/Controller/SearchController.php
typo3/sysext/indexed_search/Classes/Domain/Repository/IndexSearchRepository.php
typo3/sysext/indexed_search/Classes/FileContentParser.php
typo3/sysext/indexed_search/Classes/Indexer.php
typo3/sysext/indexed_search/Classes/Service/DatabaseSchemaService.php
typo3/sysext/indexed_search/Tests/Functional/Tca/IndexConfigVisibleFieldsTest.php [deleted file]
typo3/sysext/indexed_search/ext_localconf.php
typo3/sysext/install/Classes/Controller/LayoutController.php
typo3/sysext/install/Classes/Controller/SettingsController.php
typo3/sysext/install/Classes/Service/ExtensionConfigurationService.php [new file with mode: 0644]
typo3/sysext/install/Classes/ViewHelpers/Form/TypoScriptConstantsViewHelper.php [new file with mode: 0644]
typo3/sysext/install/Classes/ViewHelpers/Format/NoSpaceViewHelper.php [new file with mode: 0644]
typo3/sysext/install/Resources/Private/Partials/Settings/ExtensionConfiguration.html [new file with mode: 0644]
typo3/sysext/install/Resources/Private/Partials/Settings/ExtensionConfiguration/ExtensionForm.html [new file with mode: 0644]
typo3/sysext/install/Resources/Private/Templates/Settings/Cards.html
typo3/sysext/install/Resources/Private/Templates/Settings/ExtensionConfigurationGetContent.html [new file with mode: 0644]
typo3/sysext/install/Resources/Public/Css/install.css
typo3/sysext/install/Resources/Public/JavaScript/Modules/ExtensionConfiguration.js [new file with mode: 0644]
typo3/sysext/install/Resources/Public/JavaScript/Modules/LocalConfiguration.js
typo3/sysext/install/Resources/Public/JavaScript/Modules/Router.js
typo3/sysext/install/Resources/Public/JavaScript/RequireJSConfig.js
typo3/sysext/rsaauth/Classes/Backend/CommandLineBackend.php
typo3/sysext/rsaauth/Classes/BackendWarnings.php
typo3/sysext/rsaauth/Tests/Unit/Backend/CommandLineBackendTest.php
typo3/sysext/saltedpasswords/Classes/Utility/ExtensionManagerConfigurationUtility.php
typo3/sysext/saltedpasswords/Classes/Utility/SaltedPasswordsUtility.php
typo3/sysext/saltedpasswords/Tests/Unit/Salt/SaltFactoryTest.php
typo3/sysext/saltedpasswords/Tests/Unit/Utility/SaltedPasswordsUtilityTest.php [deleted file]
typo3/sysext/scheduler/Classes/Scheduler.php
typo3/sysext/scheduler/ext_localconf.php

index 4f1ddf0..181b2bf 100644 (file)
@@ -20,6 +20,8 @@ $grid-float-breakpoint: $screen-md-min;
 @import "libs/chosen";
 @import "libs/gridder";
 @import "component/card";
+@import "component/colorpicker";
+@import "typo3/main_form";
 
 //
 // Include elements
index d1664da..bd78f9d 100644 (file)
@@ -21,6 +21,7 @@ use TYPO3\CMS\Backend\Module\ModuleLoader;
 use TYPO3\CMS\Backend\Routing\UriBuilder;
 use TYPO3\CMS\Backend\Toolbar\ToolbarItemInterface;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
@@ -280,7 +281,7 @@ class BackendController
         $view = $this->getFluidTemplateObject($this->partialPath . 'Backend/Topbar.html');
 
         // Extension Configuration to find the TYPO3 logo in the left corner
-        $extConf = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['backend'], ['allowed_classes' => false]);
+        $extConf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('backend');
         $logoPath = '';
         if (!empty($extConf['backendLogo'])) {
             $customBackendLogo = GeneralUtility::getFileAbsFileName($extConf['backendLogo']);
index ee78ab0..aa99e82 100644 (file)
@@ -21,6 +21,7 @@ use TYPO3\CMS\Backend\LoginProvider\LoginProviderInterface;
 use TYPO3\CMS\Backend\Routing\UriBuilder;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\FormProtection\BackendFormProtection;
 use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
@@ -165,7 +166,7 @@ class LoginController
         $this->checkRedirect();
 
         // Extension Configuration
-        $extConf = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['backend'], ['allowed_classes' => false]);
+        $extConf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('backend');
 
         // Background Image
         if (!empty($extConf['loginBackgroundImage'])) {
index 9e528f6..5c0a75f 100644 (file)
@@ -17,6 +17,7 @@ namespace TYPO3\CMS\Backend\Template;
 use Psr\Log\LoggerAwareInterface;
 use Psr\Log\LoggerAwareTrait;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Page\PageRenderer;
@@ -932,10 +933,9 @@ function jumpToUrl(URL) {
     */
     protected function getBackendFavicon()
     {
-        $extConf = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['backend'], ['allowed_classes' => false]);
-
-        if (!empty($extConf['backendFavicon'])) {
-            $path =  $this->getUriForFileName($extConf['backendFavicon']);
+        $backendFavicon = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('backend', 'backendFavicon');
+        if (!empty($backendFavicon)) {
+            $path =  $this->getUriForFileName($backendFavicon);
         } else {
             $path = ExtensionManagementUtility::extPath('backend') . 'Resources/Public/Icons/favicon.ico';
         }
diff --git a/typo3/sysext/core/Classes/Configuration/Exception/ExtensionConfigurationExtensionNotConfiguredException.php b/typo3/sysext/core/Classes/Configuration/Exception/ExtensionConfigurationExtensionNotConfiguredException.php
new file mode 100644 (file)
index 0000000..2e07269
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+namespace TYPO3\CMS\Core\Configuration\Exception;
+
+/*
+ * 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\Exception;
+
+/**
+ * An exception thrown if ExtensionConfiguration->get() is called for
+ * an extension that has no configuration.
+ *
+ * @internal
+ */
+class ExtensionConfigurationExtensionNotConfiguredException extends Exception
+{
+}
diff --git a/typo3/sysext/core/Classes/Configuration/Exception/ExtensionConfigurationPathDoesNotExistException.php b/typo3/sysext/core/Classes/Configuration/Exception/ExtensionConfigurationPathDoesNotExistException.php
new file mode 100644 (file)
index 0000000..bc3ee8a
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+namespace TYPO3\CMS\Core\Configuration\Exception;
+
+/*
+ * 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\Exception;
+
+/**
+ * An exception thrown if ExtensionConfiguration->get() is called with
+ * a path that does not exist within the extension configuration.
+ *
+ * @internal
+ */
+class ExtensionConfigurationPathDoesNotExistException extends Exception
+{
+}
diff --git a/typo3/sysext/core/Classes/Configuration/ExtensionConfiguration.php b/typo3/sysext/core/Classes/Configuration/ExtensionConfiguration.php
new file mode 100644 (file)
index 0000000..0556885
--- /dev/null
@@ -0,0 +1,208 @@
+<?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!
+ */
+
+use TYPO3\CMS\Core\Configuration\Exception\ExtensionConfigurationExtensionNotConfiguredException;
+use TYPO3\CMS\Core\Configuration\Exception\ExtensionConfigurationPathDoesNotExistException;
+use TYPO3\CMS\Core\Utility\ArrayUtility;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * API to get() and set() instance specific extension configuration options.
+ *
+ * Extension authors are encouraged to use this API - it is currently a simple
+ * wrapper to access TYPO3_CONF_VARS['EXTENSIONS'] but could later become something
+ * different in case core decides to store extension configuration elsewhere.
+ *
+ * Extension authors must not access TYPO3_CONF_VARS['EXTENSIONS'] on their own.
+ *
+ * Extension configurations are often 'feature flags' currently defined by
+ * ext_conf_template.txt files. The core (more specifically the install tool)
+ * takes care default values and overridden values are properly prepared upon
+ * loading or updating an extension.
+ */
+class ExtensionConfiguration
+{
+    /**
+     * Get a single configuration value, a sub array or the whole configuration.
+     *
+     * Examples:
+     * // Simple and typical usage: Get a single config value, or an array if the key is a "TypoScript"
+     * // a-like sub-path in ext_conf_template.txt "foo.bar = defaultValue"
+     * ->get('myExtension', 'aConfigKey');
+     *
+     * // Get all current configuration values, always an array
+     * ->get('myExtension');
+     *
+     * // Get a nested config value if the path is a "TypoScript" a-like sub-path
+     * // in ext_conf_template.txt "FE.forceSalted = defaultValue"
+     * ->get('myExtension', 'FE/forceSalted')
+     *
+     * Notes:
+     * - Return values are NOT type safe: A boolean false could be returned as string 0.
+     *   Cast accordingly.
+     * - This API throws exceptions if the path does not exist or the extension
+     *   configuration is not available. The install tool takes care any new
+     *   ext_conf_template.txt values are available TYPO3_CONF_VARS['EXTENSIONS'],
+     *   a thrown exception indicates a programming error on developer side
+     *   and should not be caught.
+     * - It is not checked if the extension in question is loaded at all,
+     *   it's just checked the extension configuration path exists.
+     * - Extensions should typically not get configuration of a different extension.
+     *
+     * @param string $extension Extension name
+     * @param string $path Configuration path - eg. "featureCategory/coolThingIsEnabled"
+     * @return mixed The value. Can be a sub array or a single value.
+     * @throws ExtensionConfigurationExtensionNotConfiguredException If ext configuration does no exist
+     * @throws ExtensionConfigurationPathDoesNotExistException If a requested extension path does not exist
+     * @api
+     */
+    public function get(string $extension, string $path = '')
+    {
+        if (!isset($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'][$extension]) || !is_array($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'][$extension])) {
+            throw new ExtensionConfigurationExtensionNotConfiguredException(
+                'No extension configuration for extension ' . $extension . ' found. Either this extension'
+                . ' has no extension configuration or the configuration is not up to date. Execute the'
+                . ' install tool to update configuration.',
+                1509654728
+            );
+        }
+        if (empty($path)) {
+            return $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'][$extension];
+        }
+        if (!ArrayUtility::isValidPath($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'], $extension . '/' . $path)) {
+            throw new ExtensionConfigurationPathDoesNotExistException(
+                'Path ' . $path . ' does not exist in extension configuration',
+                1509977699
+            );
+        }
+        return ArrayUtility::getValueByPath($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'], $extension . '/' . $path);
+    }
+
+    /**
+     * Store a new or overwrite an existing configuration value.
+     *
+     * This is typically used by core internal low level tasks like the install
+     * tool but may become handy if an extension needs to update extension configuration
+     * on the fly for whatever reason.
+     *
+     * Examples:
+     * // Enable a single feature
+     * ->set('myExtension', 'myFeature', true)
+     *
+     * // Set a full extension configuration ($value could be a nested array, too)
+     * ->set('myExtension', '', ['aFeature' => 'true', 'aCustomClass' => 'css-foo'])
+     *
+     * // Set a full sub path
+     * ->set('myExtension', 'myFeatureCategory', ['aFeature' => 'true', 'aCustomClass' => 'css-foo'])
+     *
+     * // Unset a whole extension configuration
+     * ->set('myExtension')
+     *
+     * // Unset a single value or sub path
+     * ->set('myExtension', 'myFeature')
+     *
+     * Notes:
+     * - $path is NOT validated. It is up to an ext author to also define them in
+     *   ext_conf_template.txt to have an interface in install tool reflecting these settings
+     * - If $path is currently an array, $value overrides the whole thing. Merging existing values
+     *   is up to the extension author
+     * - Values are not type safe, if the install tool wrote them,
+     *   boolean true could become string 1 on ->get()
+     * - It is not possible to store 'null' as value, giving $value=null
+     *   or no value at all will unset the path
+     * - Setting a value and calling ->get() afterwards will still return the old (!) value, the
+     *   new value is only available in ->get() with next request. This is to have consistent
+     *   values if the setting is possibly overwritten in AdditionalConfiguration again, which
+     *   this API does not know and is only evaluated early during bootstrap.
+     * - Warning on AdditionalConfiguration.php: If this file overwrites settings, it spoils the
+     *   ->set() call and values may not finally end up as expected. Avoid using AdditionalConfiguration.php
+     *   in general ...
+     *
+     * @param string $extension Extension name
+     * @param string $path Configuration path to set - eg. "featureCategory/coolThingIsEnabled"
+     * @param null $value The value. If null, unset the path
+     * @api
+     */
+    public function set(string $extension, string $path = '', $value = null)
+    {
+        if (empty($extension)) {
+            throw new \RuntimeException('extension name must not be empty', 1509715852);
+        }
+        $configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class);
+        if ($path === '' && $value === null) {
+            // Remove whole extension config
+            $configurationManager->removeLocalConfigurationKeysByPath([ 'EXTENSIONS/' . $extension ]);
+        } elseif ($path !== '' && $value === null) {
+            // Remove a single value or sub path
+            $configurationManager->removeLocalConfigurationKeysByPath([ 'EXTENSIONS/' . $extension . '/' . $path]);
+        } elseif ($path === '' && $value !== null) {
+            // Set full extension config
+            $configurationManager->setLocalConfigurationValueByPath('EXTENSIONS/' . $extension, $value);
+        } else {
+            // Set single path
+            $configurationManager->setLocalConfigurationValueByPath('EXTENSIONS/' . $extension . '/' . $path, $value);
+        }
+
+        // After TYPO3_CONF_VARS['EXTENSIONS'] has been written, update legacy layer TYPO3_CONF_VARS['EXTENSIONS']['extConf']
+        // @deprecated since TYPO3 v9, will be removed in v10 with removal of old serialized 'extConf' layer
+        $extensionsConfigs = $configurationManager->getConfigurationValueByPath('EXTENSIONS');
+        foreach ($extensionsConfigs as $extensionName => $extensionConfig) {
+            $extensionConfig = $this->addDotsToArrayKeysRecursiveForLegacyExtConf($extensionConfig);
+            $configurationManager->setLocalConfigurationValueByPath('EXT/extConf/' . $extensionName, serialize($extensionConfig));
+        }
+    }
+
+    /**
+     * The old EXT/extConf layer had '.' (dots) at the end of all nested array keys. This is created here
+     * to keep EXT/extConf format compatible with old not yet adapted extensions.
+     * Most prominent usage is ext:saltedpasswords which uses sub keys like FE.forceSalted and BE.forceSalted,
+     * but extensions may rely on ending dots if using legacy unserialize() on their extensions, too.
+     *
+     * A EXTENSIONS array like:
+     * TYPO3_CONF_VARS['EXTENSIONS']['someExtension'] => [
+     *      'someKey' => [
+     *          'someSubKey' => [
+     *              'someSubSubKey' => 'someValue',
+     *          ],
+     *      ],
+     * ]
+     * becomes (serialized) in old EXT/extConf (mind the dots and end of array keys for sub arrays):
+     * TYPO3_CONF_VARS['EXTENSIONS']['someExtension'] => [
+     *      'someKey.' => [
+     *          'someSubKey.' => [
+     *              'someSubSubKey' => 'someValue',
+     *          ],
+     *      ],
+     * ]
+     *
+     * @param array $extensionConfig
+     * @return array
+     * @deprecated since TYPO3 v9, will be removed in v10 with removal of old serialized 'extConf' layer
+     */
+    private function addDotsToArrayKeysRecursiveForLegacyExtConf(array $extensionConfig)
+    {
+        $newArray = [];
+        foreach ($extensionConfig as $key => $value) {
+            if (is_array($value)) {
+                $newArray[$key . '.'] = $this->addDotsToArrayKeysRecursiveForLegacyExtConf($value);
+            } else {
+                $newArray[$key] = $value;
+            }
+        }
+        return $newArray;
+    }
+}
index 5fa2e15..919a20d 100644 (file)
@@ -805,25 +805,26 @@ return [
         'allowGlobalInstall' => false,
         'allowLocalInstall' => true,
         'excludeForPackaging' => '(?:\\..*(?!htaccess)|.*~|.*\\.swp|.*\\.bak|\\.sass-cache|node_modules|bower_components)',
-        'extConf' => [
-            'saltedpasswords' => serialize([
-                'BE.' => [
-                    'saltedPWHashingMethod' => \TYPO3\CMS\Saltedpasswords\Salt\PhpassSalt::class,
-                    'forceSalted' => 0,
-                    'onlyAuthService' => 0,
-                    'updatePasswd' => 1,
-                ],
-                'FE.' => [
-                    'enabled' => 0,
-                    'saltedPWHashingMethod' => \TYPO3\CMS\Saltedpasswords\Salt\PhpassSalt::class,
-                    'forceSalted' => 0,
-                    'onlyAuthService' => 0,
-                    'updatePasswd' => 1,
-                ],
-            ]),
-        ],
         'runtimeActivatedPackages' => [],
     ],
+    // Custom options shipped by extensions
+    'EXTENSIONS' => [
+        'saltedpasswords' => [
+            'BE' => [
+                'saltedPWHashingMethod' => \TYPO3\CMS\Saltedpasswords\Salt\PhpassSalt::class,
+                'forceSalted' => 0,
+                'onlyAuthService' => 0,
+                'updatePasswd' => 1,
+            ],
+            'FE' => [
+                'enabled' => 0,
+                'saltedPWHashingMethod' => \TYPO3\CMS\Saltedpasswords\Salt\PhpassSalt::class,
+                'forceSalted' => 0,
+                'onlyAuthService' => 0,
+                'updatePasswd' => 1,
+            ],
+        ],
+    ],
     'BE' => [
         // Backend Configuration.
         'languageDebug' => false,
index b5474e2..dbac51c 100644 (file)
@@ -18,24 +18,45 @@ return [
             ],
         ],
     ],
-    'EXT' => [
-        'extConf' => [
-            'rsaauth' => 'a:1:{s:18:"temporaryDirectory";s:0:"";}',
-            'saltedpasswords' => serialize([
-                'BE.' => [
-                    'saltedPWHashingMethod' => \TYPO3\CMS\Saltedpasswords\Salt\Pbkdf2Salt::class,
-                    'forceSalted' => 0,
-                    'onlyAuthService' => 0,
-                    'updatePasswd' => 1,
-                ],
-                'FE.' => [
-                    'enabled' => 1,
-                    'saltedPWHashingMethod' => \TYPO3\CMS\Saltedpasswords\Salt\Pbkdf2Salt::class,
-                    'forceSalted' => 0,
-                    'onlyAuthService' => 0,
-                    'updatePasswd' => 1,
-                ],
-            ]),
+    'EXTENSIONS' => [
+        'backend' => [
+            'backendFavicon' => '',
+            'backendLogo' => '',
+            'loginBackgroundImage' => '',
+            'loginFootnote' => '',
+            'loginHighlightColor' => '',
+            'loginLogo' => '',
+        ],
+        'extensionmanager' => [
+            'automaticInstallation' => 1,
+            'offlineMode' => 0,
+        ],
+        'rsaauth' => [
+            'temporaryDirectory' => '',
+        ],
+        'saltedpasswords' => [
+            'BE' => [
+                'saltedPWHashingMethod' => \TYPO3\CMS\Saltedpasswords\Salt\Pbkdf2Salt::class,
+                'forceSalted' => 0,
+                'onlyAuthService' => 0,
+                'updatePasswd' => 1,
+            ],
+            'FE' => [
+                'enabled' => 1,
+                'saltedPWHashingMethod' => \TYPO3\CMS\Saltedpasswords\Salt\Pbkdf2Salt::class,
+                'forceSalted' => 0,
+                'onlyAuthService' => 0,
+                'updatePasswd' => 1,
+            ],
+            'checkConfigurationBE' => '0',
+            'checkConfigurationBE2' => '0',
+            'checkConfigurationFE' => '0',
+            'checkConfigurationFE2' => '0',
+        ],
+        'scheduler' => [
+            'enableBELog' => 1,
+            'maxLifetime' => 1440,
+            'showSampleTasks' => 1,
         ],
     ],
     'FE' => [
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Breaking-82368-SignalAfterExtensionConfigurationWriteRemoved.rst b/typo3/sysext/core/Documentation/Changelog/master/Breaking-82368-SignalAfterExtensionConfigurationWriteRemoved.rst
new file mode 100644 (file)
index 0000000..563eaa6
--- /dev/null
@@ -0,0 +1,36 @@
+.. include:: ../../Includes.txt
+
+====================================================================
+Breaking: #82368 - Signal 'afterExtensionConfigurationWrite' removed
+====================================================================
+
+See :issue:`82368`
+
+Description
+===========
+
+The extension manager no longer emits signal 'afterExtensionConfigurationWrite'.
+The code has been moved to the install tool which does not load signal / slot
+information at this point.
+
+
+Impact
+======
+
+Slots of this signal are no longer executed.
+
+
+Affected Installations
+======================
+
+Extensions that use signal 'afterExtensionConfigurationWrite'. This is a rather seldom
+used signal, relevant mostly only for distributions.
+
+
+Migration
+=========
+
+In many cases it should be possible to use signal 'hasInstalledExtensions' instead
+which is fired after an extension has been installed.
+
+.. index:: Backend, LocalConfiguration, PHP-API, NotScanned
\ No newline at end of file
index ee96a3a..e68362f 100644 (file)
@@ -9,7 +9,9 @@ See :issue:`82254`
 Description
 ===========
 
-The extension configuration stored in $GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'] has been deprecated and replaced by a plain array in $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'].
+The extension configuration stored in $GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'] has been
+deprecated and replaced by a plain array in $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']. A new
+API has been introduced to retrieve extension configuration.
 
 
 Affected Installations
@@ -21,6 +23,21 @@ All extensions manually getting settings and unserializing them from $GLOBALS['T
 Migration
 =========
 
-Switch to the use of $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'] instead and remove all unserialize calls.
+Use a new API to retrieve extension configuration, examples:
+
+.. code-block:: php
+
+    // Retrieve a single key
+    $backendFavicon = (bool)GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('backend', 'backendFavicon');
+
+    // Retrieve whole configuration
+    $backendConfiguration = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('backend');
+
+    // Fully qualified class names for usage in ext_localconf.php / ext_tables.php
+    $backendConfiguration = (bool)\TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(
+        \TYPO3\CMS\Core\Configuration\ExtensionConfiguration::class
+    )->get('backend');
+
+
 
 .. index:: LocalConfiguration, PHP-API, FullyScanned
index 4e12dbd..d79d860 100644 (file)
@@ -9,14 +9,30 @@ See :issue:`82254`
 Description
 ===========
 
-There is no reason to save the extension configuration as serialized values instead of an plain array. Arrays are easier to handle and there are already parts of the core using arrays (for example the avatar provider registration).
+There is no reason to save the extension configuration as serialized values instead of
+an plain array. Arrays are easier to handle and there are already parts of the core
+using arrays (for example the avatar provider registration).
 
-Therefore the API has been changed to store the extension configuration as an array in $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']. The configuration is merged on save with the default configuration and the full configuration is written to LocalConfiguration.
+Therefore the API has been changed to store the extension configuration as an array
+in $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']. The configuration is merged on save
+with the default configuration and the full configuration is written to LocalConfiguration.
 
 
 Impact
 ======
 
-Extension configuration can now be accessed as array directly, without calling unserialize(). The old and new API will co-exist in version 9.
+Extension configuration can now be accessed as array directly, without
+calling unserialize(). The old and new API will co-exist in version 9.
+
+Use a new API to retrieve extension configuration, examples:
+
+.. code-block:: php
+
+    // Retrieve a single key
+    $backendFavicon = (bool)GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('backend', 'backendFavicon');
+
+    // Retrieve whole configuration
+    $backendConfiguration = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('backend');
+
 
 .. index:: LocalConfiguration, PHP-API
diff --git a/typo3/sysext/core/Tests/Unit/Configuration/ExtensionConfigurationTest.php b/typo3/sysext/core/Tests/Unit/Configuration/ExtensionConfigurationTest.php
new file mode 100644 (file)
index 0000000..004596b
--- /dev/null
@@ -0,0 +1,204 @@
+<?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 Prophecy\Argument;
+use TYPO3\CMS\Core\Configuration\ConfigurationManager;
+use TYPO3\CMS\Core\Configuration\Exception\ExtensionConfigurationExtensionNotConfiguredException;
+use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
+
+/**
+ * Test case
+ */
+class ExtensionConfigurationTest extends UnitTestCase
+{
+    /**
+     * @test
+     */
+    public function getThrowExceptionIfExtensionConfigurationDoesNotExist()
+    {
+        $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['someOtherExtension']['someKey'] = 'someValue';
+        $this->expectException(ExtensionConfigurationExtensionNotConfiguredException::class);
+        $this->expectExceptionCode(1509654728);
+        (new ExtensionConfiguration())->get('someExtension');
+    }
+
+    /**
+     * @test
+     */
+    public function getWithEmptyPathReturnsEntireExtensionConfiguration()
+    {
+        $extConf = [
+            'aFeature' => 'iAmEnabled',
+            'aFlagCategory' => [
+                'someFlag' => 'foo',
+            ],
+        ];
+        $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['someExtension'] = $extConf;
+        $this->assertSame((new ExtensionConfiguration())->get('someExtension'), $extConf);
+    }
+
+    /**
+     * @test
+     */
+    public function getWithPathReturnsGivenValue()
+    {
+        $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['someExtension'] = [
+            'aFeature' => 'iAmEnabled',
+            'aFlagCategory' => [
+                'someFlag' => 'foo',
+            ],
+        ];
+        $this->assertSame((new ExtensionConfiguration())->get('someExtension', 'aFeature'), 'iAmEnabled');
+    }
+
+    /**
+     * @test
+     */
+    public function getWithPathReturnsGivenPathSegment()
+    {
+        $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['someExtension'] = [
+            'aFeature' => 'iAmEnabled',
+            'aFlagCategory' => [
+                'someFlag' => 'foo',
+            ],
+        ];
+        $this->assertSame((new ExtensionConfiguration())->get('someExtension', 'aFlagCategory'), ['someFlag' => 'foo']);
+    }
+
+    /**
+     * @test
+     */
+    public function setThrowsExceptionWithEmptyExtension()
+    {
+        $this->expectException(\RuntimeException::class);
+        $this->expectExceptionCode(1509715852);
+        (new ExtensionConfiguration())->set('');
+    }
+
+    /**
+     * @test
+     */
+    public function setRemovesFullExtensionConfiguration()
+    {
+        $configurationManagerProphecy = $this->prophesize(ConfigurationManager::class);
+        GeneralUtility::addInstance(ConfigurationManager::class, $configurationManagerProphecy->reveal());
+        $configurationManagerProphecy->getConfigurationValueByPath(Argument::cetera())->willReturn([]);
+        $configurationManagerProphecy->removeLocalConfigurationKeysByPath(['EXTENSIONS/foo'])->shouldBeCalled();
+        (new ExtensionConfiguration())->set('foo');
+    }
+
+    /**
+     * @test
+     */
+    public function setRemovesPath()
+    {
+        $configurationManagerProphecy = $this->prophesize(ConfigurationManager::class);
+        GeneralUtility::addInstance(ConfigurationManager::class, $configurationManagerProphecy->reveal());
+        $configurationManagerProphecy->getConfigurationValueByPath(Argument::cetera())->willReturn([]);
+        $configurationManagerProphecy->removeLocalConfigurationKeysByPath(['EXTENSIONS/foo/bar'])->shouldBeCalled();
+        (new ExtensionConfiguration())->set('foo', 'bar');
+    }
+
+    /**
+     * @test
+     */
+    public function setWritesFullExtensionConfig()
+    {
+        $configurationManagerProphecy = $this->prophesize(ConfigurationManager::class);
+        GeneralUtility::addInstance(ConfigurationManager::class, $configurationManagerProphecy->reveal());
+        $configurationManagerProphecy->getConfigurationValueByPath(Argument::cetera())->willReturn([]);
+        $configurationManagerProphecy->setLocalConfigurationValueByPath('EXTENSIONS/foo', ['bar' => 'baz'])->shouldBeCalled();
+        (new ExtensionConfiguration())->set('foo', '', ['bar' => 'baz']);
+    }
+
+    /**
+     * @test
+     */
+    public function setWritesPath()
+    {
+        $configurationManagerProphecy = $this->prophesize(ConfigurationManager::class);
+        GeneralUtility::addInstance(ConfigurationManager::class, $configurationManagerProphecy->reveal());
+        $configurationManagerProphecy->getConfigurationValueByPath(Argument::cetera())->willReturn([]);
+        $configurationManagerProphecy->setLocalConfigurationValueByPath('EXTENSIONS/foo/aPath', ['bar' => 'baz'])->shouldBeCalled();
+        (new ExtensionConfiguration())->set('foo', 'aPath', ['bar' => 'baz']);
+    }
+
+    /**
+     * @test
+     */
+    public function setUpdatesLegacyExtConfToNewValues()
+    {
+        $configurationManagerProphecy = $this->prophesize(ConfigurationManager::class);
+        GeneralUtility::addInstance(ConfigurationManager::class, $configurationManagerProphecy->reveal());
+        $configurationManagerProphecy->setLocalConfigurationValueByPath(Argument::cetera())->shouldBeCalled();
+        $configurationManagerProphecy->getConfigurationValueByPath('EXTENSIONS')->willReturn(['foo' => ['bar' => 'baz']]);
+        $configurationManagerProphecy->setLocalConfigurationValueByPath('EXT/extConf/foo', serialize(['bar' => 'baz']))->shouldBeCalled();
+        (new ExtensionConfiguration())->set('foo', '', ['bar' => 'baz']);
+    }
+
+    /**
+     * @test
+     */
+    public function setUpdatesLegacyExtConfWithDottedArrayKeysForNestedConfiguration()
+    {
+        $configurationManagerProphecy = $this->prophesize(ConfigurationManager::class);
+        GeneralUtility::addInstance(ConfigurationManager::class, $configurationManagerProphecy->reveal());
+        $configurationManagerProphecy->setLocalConfigurationValueByPath(Argument::cetera())->shouldBeCalled();
+        $nestedInput = [
+            'FE' => [
+                'forceSalted' => true,
+            ]
+        ];
+        $expectedLegacyExtConf = [
+            'FE.' => [
+                'forceSalted' => true,
+            ]
+        ];
+        $configurationManagerProphecy->getConfigurationValueByPath('EXTENSIONS')->willReturn(['saltedPasswords' => $nestedInput]);
+        $configurationManagerProphecy->setLocalConfigurationValueByPath('EXT/extConf/saltedPasswords', serialize($expectedLegacyExtConf))->shouldBeCalled();
+        (new ExtensionConfiguration())->set('saltedPasswords', '', $nestedInput);
+    }
+
+    /**
+     * @test
+     */
+    public function setUpdatesLegacyExtConfWithDottedArrayKeysForNestedConfigurationWithMultiNestedArrays()
+    {
+        $configurationManagerProphecy = $this->prophesize(ConfigurationManager::class);
+        GeneralUtility::addInstance(ConfigurationManager::class, $configurationManagerProphecy->reveal());
+        $configurationManagerProphecy->setLocalConfigurationValueByPath(Argument::cetera())->shouldBeCalled();
+        $nestedInput = [
+            'aCategory' => [
+                'aSubCategory' => [
+                    'aKey' => 'aValue',
+                ],
+            ],
+        ];
+        $expectedLegacyExtConf = [
+            'aCategory.' => [
+                'aSubCategory.' => [
+                    'aKey' => 'aValue',
+                ],
+            ],
+        ];
+        $configurationManagerProphecy->getConfigurationValueByPath('EXTENSIONS')->willReturn(['someExtension' => $nestedInput]);
+        $configurationManagerProphecy->setLocalConfigurationValueByPath('EXT/extConf/someExtension', serialize($expectedLegacyExtConf))->shouldBeCalled();
+        (new ExtensionConfiguration())->set('someExtension', '', $nestedInput);
+    }
+}
diff --git a/typo3/sysext/extensionmanager/Classes/Controller/ConfigurationController.php b/typo3/sysext/extensionmanager/Classes/Controller/ConfigurationController.php
deleted file mode 100644 (file)
index 495c5d8..0000000
+++ /dev/null
@@ -1,221 +0,0 @@
-<?php
-namespace TYPO3\CMS\Extensionmanager\Controller;
-
-/*
- * 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\Backend\Template\Components\ButtonBar;
-use TYPO3\CMS\Backend\View\BackendTemplateView;
-use TYPO3\CMS\Core\Imaging\Icon;
-use TYPO3\CMS\Extbase\Mvc\View\ViewInterface;
-use TYPO3\CMS\Extensionmanager\Domain\Model\Extension;
-use TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException;
-
-/**
- * Controller for configuration related actions.
- */
-class ConfigurationController extends AbstractModuleController
-{
-    /**
-     * @var \TYPO3\CMS\Extensionmanager\Domain\Repository\ConfigurationItemRepository
-     */
-    protected $configurationItemRepository;
-
-    /**
-     * @var \TYPO3\CMS\Extensionmanager\Domain\Repository\ExtensionRepository
-     */
-    protected $extensionRepository;
-
-    /**
-     * @param \TYPO3\CMS\Extensionmanager\Domain\Repository\ConfigurationItemRepository $configurationItemRepository
-     */
-    public function injectConfigurationItemRepository(\TYPO3\CMS\Extensionmanager\Domain\Repository\ConfigurationItemRepository $configurationItemRepository)
-    {
-        $this->configurationItemRepository = $configurationItemRepository;
-    }
-
-    /**
-     * @param \TYPO3\CMS\Extensionmanager\Domain\Repository\ExtensionRepository $extensionRepository
-     */
-    public function injectExtensionRepository(\TYPO3\CMS\Extensionmanager\Domain\Repository\ExtensionRepository $extensionRepository)
-    {
-        $this->extensionRepository = $extensionRepository;
-    }
-
-    /**
-     * Set up the doc header properly here
-     *
-     * @param ViewInterface $view
-     */
-    protected function initializeView(ViewInterface $view)
-    {
-        if ($view instanceof BackendTemplateView) {
-            /** @var BackendTemplateView $view */
-            parent::initializeView($view);
-            $this->generateMenu();
-            $this->registerDocheaderButtons();
-        }
-    }
-
-    /**
-     * Show the extension configuration form. The whole form field handling is done
-     * in the corresponding view helper
-     *
-     * @param array $extension Extension information, must contain at least the key
-     * @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException
-     */
-    public function showConfigurationFormAction(array $extension)
-    {
-        if (!isset($extension['key'])) {
-            throw new ExtensionManagerException('Extension key not found.', 1359206803);
-        }
-        $this->handleTriggerArguments();
-
-        $extKey = $extension['key'];
-        $configuration = $this->configurationItemRepository->findByExtensionKey($extKey);
-        if ($configuration) {
-            $this->view
-                ->assign('configuration', $configuration)
-                ->assign('extension', $extension);
-        } else {
-            throw new ExtensionManagerException('The extension ' . $extKey . ' has no configuration.', 1476047775);
-        }
-    }
-
-    /**
-     * Save configuration and redirects back to form
-     * or to the show page of a distribution
-     *
-     * @param array $config The new extension configuration
-     * @param string $extensionKey The extension key
-     */
-    public function saveAction(array $config, $extensionKey)
-    {
-        $this->saveConfiguration($config, $extensionKey);
-        /** @var Extension $extension */
-        $extension = $this->extensionRepository->findOneByCurrentVersionByExtensionKey($extensionKey);
-        // Different handling for distribution installation
-        if ($extension instanceof Extension &&
-            $extension->getCategory() === Extension::DISTRIBUTION_CATEGORY
-        ) {
-            $this->redirect('show', 'Distribution', null, ['extension' => $extension->getUid()]);
-        } else {
-            $this->redirect('showConfigurationForm', null, null, [
-                'extension' => [
-                    'key' => $extensionKey
-                ],
-                self::TRIGGER_RefreshTopbar => true
-            ]);
-        }
-    }
-
-    /**
-     * Saves new configuration and redirects back to list
-     *
-     * @param array $config
-     * @param string $extensionKey
-     */
-    public function saveAndCloseAction(array $config, $extensionKey)
-    {
-        $this->saveConfiguration($config, $extensionKey);
-        $this->redirect('index', 'List', null, [
-            self::TRIGGER_RefreshTopbar => true
-        ]);
-    }
-
-    /**
-     * Emits a signal after the configuration file was written
-     *
-     * @param string $extensionKey
-     * @param array $newConfiguration
-     */
-    protected function emitAfterExtensionConfigurationWriteSignal($extensionKey, array $newConfiguration)
-    {
-        $this->signalSlotDispatcher->dispatch(__CLASS__, 'afterExtensionConfigurationWrite', [$extensionKey, $newConfiguration, $this]);
-    }
-
-    /**
-     * Merge and save new configuration
-     *
-     * @param array $config
-     * @param $extensionKey
-     */
-    protected function saveConfiguration(array $config, $extensionKey)
-    {
-        /** @var $configurationUtility \TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility */
-        $configurationUtility = $this->objectManager->get(\TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility::class);
-        $newConfiguration = $configurationUtility->getCurrentConfiguration($extensionKey);
-        \TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($newConfiguration, $config);
-        $configurationUtility->writeConfiguration(
-            $configurationUtility->convertValuedToNestedConfiguration($newConfiguration),
-            $extensionKey
-        );
-        $this->emitAfterExtensionConfigurationWriteSignal($extensionKey, $newConfiguration);
-    }
-
-    /**
-     * Registers the Icons into the docheader
-     *
-     * @throws \InvalidArgumentException
-     */
-    protected function registerDocheaderButtons()
-    {
-        $moduleTemplate = $this->view->getModuleTemplate();
-        $lang = $this->getLanguageService();
-
-        /** @var ButtonBar $buttonBar */
-        $buttonBar = $moduleTemplate->getDocHeaderComponent()->getButtonBar();
-
-        $uriBuilder = $this->controllerContext->getUriBuilder();
-        $uri = $uriBuilder->reset()->uriFor('index', [], 'List');
-
-        $icon = $this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-view-go-back', Icon::SIZE_SMALL);
-        $goBackButton = $buttonBar->makeLinkButton()
-            ->setHref($uri)
-            ->setTitle($this->translate('extConfTemplate.backToList'))
-            ->setIcon($icon);
-        $buttonBar->addButton($goBackButton, ButtonBar::BUTTON_POSITION_LEFT);
-
-        $saveSplitButton = $buttonBar->makeSplitButton();
-        // SAVE button:
-        $saveButton = $buttonBar->makeInputButton()
-            ->setName('_savedok')
-            ->setValue('1')
-            ->setTitle($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:rm.saveDoc'))
-            ->setForm('configurationform')
-            ->setIcon($moduleTemplate->getIconFactory()->getIcon('actions-document-save', Icon::SIZE_SMALL));
-        $saveSplitButton->addItem($saveButton, true);
-
-        // SAVE / CLOSE
-        $saveAndCloseButton = $buttonBar->makeInputButton()
-            ->setName('_saveandclosedok')
-            ->setClasses('t3js-save-close')
-            ->setValue('1')
-            ->setTitle($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:rm.saveCloseDoc'))
-            ->setForm('configurationform')
-            ->setIcon($moduleTemplate->getIconFactory()->getIcon(
-                'actions-document-save-close',
-                Icon::SIZE_SMALL
-            ));
-        $saveSplitButton->addItem($saveAndCloseButton);
-        $buttonBar->addButton($saveSplitButton, ButtonBar::BUTTON_POSITION_LEFT, 2);
-    }
-
-    /**
-     * @return \TYPO3\CMS\Core\Localization\LanguageService
-     */
-    protected function getLanguageService()
-    {
-        return $GLOBALS['LANG'];
-    }
-}
index 7d4660d..cb0f05c 100644 (file)
@@ -62,20 +62,7 @@ class DistributionController extends AbstractModuleController
         // Check if extension/package is installed
         $active = $this->packageManager->isPackageActive($extensionKey);
 
-        // Create link for extension configuration
-        if ($active && file_exists(\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath($extensionKey) . 'ext_conf_template.txt')) {
-            $uriBuilder = $this->controllerContext->getUriBuilder();
-            $action = 'showConfigurationForm';
-            $configurationLink = $uriBuilder->reset()->uriFor(
-                $action,
-                ['extension' => ['key' => $extensionKey]],
-                'Configuration'
-            );
-        } else {
-            $configurationLink = false;
-        }
         $this->view->assign('distributionActive', $active);
-        $this->view->assign('configurationLink', $configurationLink);
         $this->view->assign('extension', $extension);
     }
 
index 35924b3..ef56929 100644 (file)
@@ -14,7 +14,9 @@ namespace TYPO3\CMS\Extensionmanager\Controller;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
 use TYPO3\CMS\Core\Messaging\FlashMessage;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Extbase\Mvc\View\JsonView;
 use TYPO3\CMS\Extensionmanager\Domain\Model\Extension;
 
@@ -49,11 +51,6 @@ class DownloadController extends AbstractController
     protected $downloadUtility;
 
     /**
-     * @var \TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility
-     */
-    protected $configurationUtility;
-
-    /**
      * @var JsonView
      */
     protected $defaultViewObjectName = JsonView::class;
@@ -104,14 +101,6 @@ class DownloadController extends AbstractController
     }
 
     /**
-     * @param \TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility $configurationUtility
-     */
-    public function injectConfigurationUtility(\TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility $configurationUtility)
-    {
-        $this->configurationUtility = $configurationUtility;
-    }
-
-    /**
      * Defines which view object should be used for the installFromTer action
      */
     protected function initializeInstallFromTerAction()
@@ -137,7 +126,11 @@ class DownloadController extends AbstractController
                 'dependencies' => [],
             ],
         ];
-        if ($this->configurationUtility->getCurrentConfiguration('extensionmanager')['automaticInstallation']['value']) {
+        $isAutomaticInstallationEnabled = (bool)GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('extensionmanager', 'automaticInstallation');
+        if (!$isAutomaticInstallationEnabled) {
+            // if automatic installation is deactivated, no dependency check is needed (download only)
+            $action = 'installExtensionWithoutSystemDependencyCheck';
+        } else {
             $action = 'installFromTer';
             try {
                 $dependencyTypes = $this->managementService->getAndResolveDependencies($extension);
@@ -177,9 +170,6 @@ class DownloadController extends AbstractController
                 $title = $this->translate('downloadExtension.dependencies.errorTitle');
                 $message = $e->getMessage();
             }
-        } else {
-            // if automatic installation is deactivated, no dependency check is needed (download only)
-            $action = 'installExtensionWithoutSystemDependencyCheck';
         }
 
         $url = $this->uriBuilder->uriFor(
@@ -207,11 +197,11 @@ class DownloadController extends AbstractController
     public function installFromTerAction(\TYPO3\CMS\Extensionmanager\Domain\Model\Extension $extension, $downloadPath = 'Local')
     {
         list($result, $errorMessages) = $this->installFromTer($extension, $downloadPath);
-        $emConfiguration = $this->configurationUtility->getCurrentConfiguration('extensionmanager');
+        $isAutomaticInstallationEnabled = (bool)GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('extensionmanager', 'automaticInstallation');
         $this->view
             ->assign('result', $result)
             ->assign('extension', $extension)
-            ->assign('installationTypeLanguageKey', (bool)$emConfiguration['automaticInstallation']['value'] ? '' : '.downloadOnly')
+            ->assign('installationTypeLanguageKey', $isAutomaticInstallationEnabled ?  '' : '.downloadOnly')
             ->assign('unresolvedDependencies', $errorMessages);
     }
 
@@ -362,7 +352,8 @@ class DownloadController extends AbstractController
         $errorMessages = [];
         try {
             $this->downloadUtility->setDownloadPath($downloadPath);
-            $this->managementService->setAutomaticInstallationEnabled($this->configurationUtility->getCurrentConfiguration('extensionmanager')['automaticInstallation']['value']);
+            $isAutomaticInstallationEnabled = (bool)GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('extensionmanager', 'automaticInstallation');
+            $this->managementService->setAutomaticInstallationEnabled($isAutomaticInstallationEnabled);
             if (($result = $this->managementService->installExtension($extension)) === false) {
                 $errorMessages = $this->managementService->getDependencyErrors();
             }
index f2c3c7a..ee20152 100644 (file)
@@ -16,10 +16,12 @@ namespace TYPO3\CMS\Extensionmanager\Controller;
 
 use TYPO3\CMS\Backend\Template\Components\ButtonBar;
 use TYPO3\CMS\Backend\View\BackendTemplateView;
+use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
 use TYPO3\CMS\Core\Core\Bootstrap;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Messaging\FlashMessage;
 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Extbase\Mvc\View\ViewInterface;
 use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
 use TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException;
@@ -52,11 +54,6 @@ class ListController extends AbstractModuleController
     protected $dependencyUtility;
 
     /**
-     * @var \TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility
-     */
-    protected $configurationUtility;
-
-    /**
      * @param \TYPO3\CMS\Extensionmanager\Domain\Repository\ExtensionRepository $extensionRepository
      */
     public function injectExtensionRepository(\TYPO3\CMS\Extensionmanager\Domain\Repository\ExtensionRepository $extensionRepository)
@@ -89,20 +86,13 @@ class ListController extends AbstractModuleController
     }
 
     /**
-     * @param \TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility $configurationUtility
-     */
-    public function injectConfigurationUtility(\TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility $configurationUtility)
-    {
-        $this->configurationUtility = $configurationUtility;
-    }
-
-    /**
      * Add the needed JavaScript files for all actions
      */
     public function initializeAction()
     {
         $this->pageRenderer->addInlineLanguageLabelFile('EXT:extensionmanager/Resources/Private/Language/locallang.xlf');
-        if ($this->configurationUtility->getCurrentConfiguration('extensionmanager')['offlineMode']['value']) {
+        $isAutomaticInstallationEnabled = (bool)GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('extensionmanager', 'offlineMode');
+        if ($isAutomaticInstallationEnabled) {
             $this->settings['offlineMode'] = true;
         }
     }
index c225fa5..0daa0a8 100644 (file)
@@ -14,6 +14,7 @@ namespace TYPO3\CMS\Extensionmanager\Controller;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
 use TYPO3\CMS\Core\Core\Bootstrap;
 use TYPO3\CMS\Core\Messaging\FlashMessage;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -27,11 +28,6 @@ use TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException;
 class UploadExtensionFileController extends AbstractController
 {
     /**
-     * @var \TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility
-     */
-    protected $configurationUtility;
-
-    /**
      * @var \TYPO3\CMS\Extensionmanager\Domain\Repository\ExtensionRepository
      */
     protected $extensionRepository;
@@ -62,14 +58,6 @@ class UploadExtensionFileController extends AbstractController
     protected $removeFromOriginalPath = false;
 
     /**
-     * @param \TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility $configurationUtility
-     */
-    public function injectConfigurationUtility(\TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility $configurationUtility)
-    {
-        $this->configurationUtility = $configurationUtility;
-    }
-
-    /**
      * @param \TYPO3\CMS\Extensionmanager\Domain\Repository\ExtensionRepository $extensionRepository
      */
     public function injectExtensionRepository(\TYPO3\CMS\Extensionmanager\Domain\Repository\ExtensionRepository $extensionRepository)
@@ -150,8 +138,8 @@ class UploadExtensionFileController extends AbstractController
                 );
             }
             $extensionData = $this->extractExtensionFromFile($tempFile, $fileName, $overwrite);
-            $emConfiguration = $this->configurationUtility->getCurrentConfiguration('extensionmanager');
-            if (!$emConfiguration['automaticInstallation']['value']) {
+            $isAutomaticInstallationEnabled = (bool)GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('extensionmanager', 'offlineMode');
+            if (!$isAutomaticInstallationEnabled) {
                 $this->addFlashMessage(
                     $this->translate('extensionList.uploadFlashMessage.message', [$extensionData['extKey']]),
                     $this->translate('extensionList.uploadFlashMessage.title'),
diff --git a/typo3/sysext/extensionmanager/Classes/Domain/Model/ConfigurationCategory.php b/typo3/sysext/extensionmanager/Classes/Domain/Model/ConfigurationCategory.php
deleted file mode 100644 (file)
index 97457e6..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-<?php
-namespace TYPO3\CMS\Extensionmanager\Domain\Model;
-
-/*
- * 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!
- */
-
-/**
- * Main model for extension configuration categories
- */
-class ConfigurationCategory extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
-{
-    /**
-     * @var string
-     */
-    protected $name = '';
-
-    /**
-     * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extensionmanager\Domain\Model\ConfigurationSubcategory>
-     */
-    protected $subcategories;
-
-    /**
-     * Constructs this Category
-     */
-    public function __construct()
-    {
-        $this->subcategories = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage();
-    }
-
-    /**
-     * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $subcategories
-     */
-    public function setSubcategories($subcategories)
-    {
-        $this->subcategories = $subcategories;
-    }
-
-    /**
-     * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage
-     */
-    public function getSubcategories()
-    {
-        return $this->subcategories;
-    }
-
-    /**
-     * Adds a subcategories
-     *
-     * @param \TYPO3\CMS\Extensionmanager\Domain\Model\ConfigurationSubcategory $subcategory
-     */
-    public function addSubcategory(\TYPO3\CMS\Extensionmanager\Domain\Model\ConfigurationSubcategory $subcategory)
-    {
-        $this->subcategories->attach($subcategory);
-    }
-
-    /**
-     * @param string $name
-     */
-    public function setName($name)
-    {
-        $this->name = $name;
-    }
-
-    /**
-     * @return string
-     */
-    public function getName()
-    {
-        return $this->name;
-    }
-}
diff --git a/typo3/sysext/extensionmanager/Classes/Domain/Model/ConfigurationItem.php b/typo3/sysext/extensionmanager/Classes/Domain/Model/ConfigurationItem.php
deleted file mode 100644 (file)
index 7af7ed0..0000000
+++ /dev/null
@@ -1,189 +0,0 @@
-<?php
-namespace TYPO3\CMS\Extensionmanager\Domain\Model;
-
-/*
- * 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!
- */
-
-/**
- * Model for extension configuration items
- */
-class ConfigurationItem extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
-{
-    /**
-     * @var string
-     */
-    protected $category = '';
-
-    /**
-     * @var string
-     */
-    protected $subCategory = '';
-
-    /**
-     * @var string
-     */
-    protected $type = '';
-
-    /**
-     * @var string
-     */
-    protected $labelHeadline = '';
-
-    /**
-     * @var string
-     */
-    protected $labelText = '';
-
-    /**
-     * @var mixed
-     */
-    protected $generic = '';
-
-    /**
-     * @var string
-     */
-    protected $name = '';
-
-    /**
-     * @var string
-     */
-    protected $value = '';
-
-    /**
-     * @param string $category
-     */
-    public function setCategory($category)
-    {
-        $this->category = $category;
-    }
-
-    /**
-     * @return string
-     */
-    public function getCategory()
-    {
-        return $this->category;
-    }
-
-    /**
-     * @param string $labelHeadline
-     */
-    public function setLabelHeadline($labelHeadline)
-    {
-        $this->labelHeadline = $labelHeadline;
-    }
-
-    /**
-     * @return string
-     */
-    public function getLabelHeadline()
-    {
-        return $this->labelHeadline;
-    }
-
-    /**
-     * @param string $labelText
-     */
-    public function setLabelText($labelText)
-    {
-        $this->labelText = $labelText;
-    }
-
-    /**
-     * @return string
-     */
-    public function getLabelText()
-    {
-        return $this->labelText;
-    }
-
-    /**
-     * @param string $subCategory
-     */
-    public function setSubCategory($subCategory)
-    {
-        $this->subCategory = $subCategory;
-    }
-
-    /**
-     * @return string
-     */
-    public function getSubCategory()
-    {
-        return $this->subCategory;
-    }
-
-    /**
-     * @param string $type
-     */
-    public function setType($type)
-    {
-        $this->type = $type;
-    }
-
-    /**
-     * @return string
-     */
-    public function getType()
-    {
-        return $this->type;
-    }
-
-    /**
-     * @param mixed $userFunc
-     */
-    public function setGeneric($userFunc)
-    {
-        $this->generic = $userFunc;
-    }
-
-    /**
-     * @return mixed
-     */
-    public function getGeneric()
-    {
-        return $this->generic;
-    }
-
-    /**
-     * @param string $name
-     */
-    public function setName($name)
-    {
-        $this->name = $name;
-    }
-
-    /**
-     * @return string
-     */
-    public function getName()
-    {
-        return $this->name;
-    }
-
-    /**
-     * @param string $value
-     */
-    public function setValue($value)
-    {
-        $this->value = $value;
-    }
-
-    /**
-     * @return string
-     */
-    public function getValue()
-    {
-        return $this->value;
-    }
-}
diff --git a/typo3/sysext/extensionmanager/Classes/Domain/Model/ConfigurationSubcategory.php b/typo3/sysext/extensionmanager/Classes/Domain/Model/ConfigurationSubcategory.php
deleted file mode 100644 (file)
index e02ad53..0000000
+++ /dev/null
@@ -1,110 +0,0 @@
-<?php
-namespace TYPO3\CMS\Extensionmanager\Domain\Model;
-
-/*
- * 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!
- */
-
-/**
- * Model for configuration sub categories
- *
- * Configuration options can be structured with categories and sub categories.
- * Categories are usually displayed as tabs and sub categories are used to
- * group configuration items in one tab.
- */
-class ConfigurationSubcategory extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
-{
-    /**
-     * @var string
-     */
-    protected $name = '';
-
-    /**
-     * @var string The sub category label
-     */
-    protected $label = '';
-
-    /**
-     * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extensionmanager\Domain\Model\ConfigurationItem>
-     */
-    protected $items;
-
-    /**
-     * Constructs this Category
-     */
-    public function __construct()
-    {
-        $this->items = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage();
-    }
-
-    /**
-     * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $items
-     */
-    public function setItems($items)
-    {
-        $this->items = $items;
-    }
-
-    /**
-     * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage
-     */
-    public function getItems()
-    {
-        return $this->items;
-    }
-
-    /**
-     * Adds a subcategory
-     *
-     * @param \TYPO3\CMS\Extensionmanager\Domain\Model\ConfigurationItem $item
-     */
-    public function addItem(\TYPO3\CMS\Extensionmanager\Domain\Model\ConfigurationItem $item)
-    {
-        $this->items->attach($item);
-    }
-
-    /**
-     * @param string $name
-     */
-    public function setName($name)
-    {
-        $this->name = $name;
-    }
-
-    /**
-     * @return string
-     */
-    public function getName()
-    {
-        return $this->name;
-    }
-
-    /**
-     * Set sub category label
-     *
-     * @param string $label
-     */
-    public function setLabel($label)
-    {
-        $this->label = $label;
-    }
-
-    /**
-     * Get sub category label
-     *
-     * @return string
-     */
-    public function getLabel()
-    {
-        return $this->label;
-    }
-}
diff --git a/typo3/sysext/extensionmanager/Classes/Domain/Repository/ConfigurationItemRepository.php b/typo3/sysext/extensionmanager/Classes/Domain/Repository/ConfigurationItemRepository.php
deleted file mode 100644 (file)
index 702ab4f..0000000
+++ /dev/null
@@ -1,230 +0,0 @@
-<?php
-namespace TYPO3\CMS\Extensionmanager\Domain\Repository;
-
-/*
- * 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\Utility\ArrayUtility;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-
-/**
- * A repository for extension configuration items
- */
-class ConfigurationItemRepository
-{
-    /**
-     * @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
-     */
-    protected $objectManager;
-
-    /**
-     * @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
-     */
-    public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager)
-    {
-        $this->objectManager = $objectManager;
-    }
-
-    /**
-     * Find configuration options by extension
-     *
-     * @param string $extensionKey Extension key
-     * @return \SplObjectStorage
-     */
-    public function findByExtensionKey($extensionKey)
-    {
-        $configurationArray = $this->getConfigurationArrayFromExtensionKey($extensionKey);
-        return $this->convertHierarchicArrayToObject($configurationArray);
-    }
-
-    /**
-     * Converts the raw configuration file content to an configuration object storage
-     *
-     * @param string $extensionKey Extension key
-     * @return array
-     */
-    protected function getConfigurationArrayFromExtensionKey($extensionKey)
-    {
-        /** @var $configurationUtility \TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility */
-        $configurationUtility = $this->objectManager->get(\TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility::class);
-        $configuration = $configurationUtility->getCurrentConfiguration($extensionKey);
-
-        $resultArray = [];
-        if (!empty($configuration)) {
-            $hierarchicConfiguration = [];
-            foreach ($configuration as $configurationOption) {
-                $originalConfiguration = $this->buildConfigurationArray($configurationOption, $extensionKey);
-                ArrayUtility::mergeRecursiveWithOverrule($originalConfiguration, $hierarchicConfiguration);
-                $hierarchicConfiguration = $originalConfiguration;
-            }
-
-            // Flip category array as it was merged the other way around
-            $hierarchicConfiguration = array_reverse($hierarchicConfiguration);
-
-            // Sort configurations of each subcategory
-            foreach ($hierarchicConfiguration as &$catConfigurationArray) {
-                foreach ($catConfigurationArray as &$subcatConfigurationArray) {
-                    uasort($subcatConfigurationArray, function ($a, $b) {
-                        return strnatcmp($a['subcat'], $b['subcat']);
-                    });
-                }
-                unset($subcatConfigurationArray);
-            }
-            unset($tempConfiguration);
-            $resultArray = $hierarchicConfiguration;
-        }
-
-        return $resultArray;
-    }
-
-    /**
-     * Builds a configuration array from each line (option) of the config file
-     *
-     * @param string $configurationOption config file line representing one setting
-     * @param string $extensionKey Extension key
-     * @return array
-     */
-    protected function buildConfigurationArray($configurationOption, $extensionKey)
-    {
-        $hierarchicConfiguration = [];
-        if (GeneralUtility::isFirstPartOfStr($configurationOption['type'], 'user')) {
-            $configurationOption = $this->extractInformationForConfigFieldsOfTypeUser($configurationOption);
-        } elseif (GeneralUtility::isFirstPartOfStr($configurationOption['type'], 'options')) {
-            $configurationOption = $this->extractInformationForConfigFieldsOfTypeOptions($configurationOption);
-        }
-        if ($this->translate($configurationOption['label'], $extensionKey)) {
-            $configurationOption['label'] = $this->translate($configurationOption['label'], $extensionKey);
-        }
-        $configurationOption['labels'] = GeneralUtility::trimExplode(':', $configurationOption['label'], false, 2);
-        $configurationOption['subcat_name'] = $configurationOption['subcat_name'] ?: '__default';
-        $hierarchicConfiguration[$configurationOption['cat']][$configurationOption['subcat_name']][$configurationOption['name']] = $configurationOption;
-        return $hierarchicConfiguration;
-    }
-
-    /**
-     * Extracts additional information for fields of type "options"
-     * Extracts "type", "label" and values information
-     *
-     * @param array $configurationOption
-     * @return array
-     */
-    protected function extractInformationForConfigFieldsOfTypeOptions(array $configurationOption)
-    {
-        preg_match('/options\[(.*)\]/is', $configurationOption['type'], $typeMatches);
-        $optionItems = GeneralUtility::trimExplode(',', $typeMatches[1]);
-        foreach ($optionItems as $optionItem) {
-            $optionPair = GeneralUtility::trimExplode('=', $optionItem);
-            if (count($optionPair) === 2) {
-                $configurationOption['generic'][$optionPair[0]] = $optionPair[1];
-            } else {
-                $configurationOption['generic'][$optionPair[0]] = $optionPair[0];
-            }
-        }
-        $configurationOption['type'] = 'options';
-        return $configurationOption;
-    }
-
-    /**
-     * Extract additional information for fields of type "user"
-     * Extracts "type" and the function to be called
-     *
-     * @param array $configurationOption
-     * @return array
-     */
-    protected function extractInformationForConfigFieldsOfTypeUser(array $configurationOption)
-    {
-        preg_match('/user\\[(.*)\\]/is', $configurationOption['type'], $matches);
-        $configurationOption['generic'] = $matches[1];
-        $configurationOption['type'] = 'user';
-        return $configurationOption;
-    }
-
-    /**
-     * Converts a hierarchic configuration array to an
-     * hierarchic object storage structure
-     *
-     * @param array $configuration
-     * @return \SplObjectStorage
-     */
-    protected function convertHierarchicArrayToObject(array $configuration)
-    {
-        $configurationObjectStorage = new \SplObjectStorage();
-        foreach ($configuration as $category => $subcategory) {
-            /** @var $configurationCategoryObject \TYPO3\CMS\Extensionmanager\Domain\Model\ConfigurationCategory */
-            $configurationCategoryObject = $this->objectManager->get(\TYPO3\CMS\Extensionmanager\Domain\Model\ConfigurationCategory::class);
-            $configurationCategoryObject->setName($category);
-            foreach ($subcategory as $subcatName => $configurationItems) {
-                /** @var $configurationSubcategoryObject \TYPO3\CMS\Extensionmanager\Domain\Model\ConfigurationSubcategory */
-                $configurationSubcategoryObject = $this->objectManager->get(\TYPO3\CMS\Extensionmanager\Domain\Model\ConfigurationSubcategory::class);
-                $configurationSubcategoryObject->setName($subcatName);
-                foreach ($configurationItems as $configurationItem) {
-                    // Set sub category label if configuration item contains a subcat label.
-                    // The sub category label is set multiple times if there is more than one item
-                    // in a sub category, but that is ok since all items of one sub category
-                    // share the same label.
-                    if (array_key_exists('subcat_label', $configurationItem)) {
-                        $configurationSubcategoryObject->setLabel($configurationItem['subcat_label']);
-                    }
-
-                    /** @var $configurationObject \TYPO3\CMS\Extensionmanager\Domain\Model\ConfigurationItem */
-                    $configurationObject = $this->objectManager->get(\TYPO3\CMS\Extensionmanager\Domain\Model\ConfigurationItem::class);
-                    if (isset($configurationItem['generic'])) {
-                        $configurationObject->setGeneric($configurationItem['generic']);
-                    }
-                    if (isset($configurationItem['cat'])) {
-                        $configurationObject->setCategory($configurationItem['cat']);
-                    }
-                    if (isset($configurationItem['subcat_name'])) {
-                        $configurationObject->setSubCategory($configurationItem['subcat_name']);
-                    }
-                    if (isset($configurationItem['labels']) && isset($configurationItem['labels'][0])) {
-                        $configurationObject->setLabelHeadline($configurationItem['labels'][0]);
-                    }
-                    if (isset($configurationItem['labels']) && isset($configurationItem['labels'][1])) {
-                        $configurationObject->setLabelText($configurationItem['labels'][1]);
-                    }
-                    if (isset($configurationItem['type'])) {
-                        $configurationObject->setType($configurationItem['type']);
-                    }
-                    if (isset($configurationItem['name'])) {
-                        $configurationObject->setName($configurationItem['name']);
-                    }
-                    if (isset($configurationItem['value'])) {
-                        $configurationObject->setValue($configurationItem['value']);
-                    }
-                    $configurationSubcategoryObject->addItem($configurationObject);
-                }
-                $configurationCategoryObject->addSubcategory($configurationSubcategoryObject);
-            }
-            $configurationObjectStorage->attach($configurationCategoryObject);
-        }
-        return $configurationObjectStorage;
-    }
-
-    /**
-     * Returns the localized label of the LOCAL_LANG key, $key.
-     * Wrapper for the static call.
-     *
-     * @param string $key The key from the LOCAL_LANG array for which to return the value.
-     * @param string $extensionName The name of the extension
-     * @return string|null The value from LOCAL_LANG or NULL if no translation was found.
-     */
-    protected function translate($key, $extensionName)
-    {
-        $translation = \TYPO3\CMS\Extbase\Utility\LocalizationUtility::translate($key, $extensionName);
-        if ($translation) {
-            return $translation;
-        }
-        return null;
-    }
-}
diff --git a/typo3/sysext/extensionmanager/Classes/Utility/ConfigurationUtility.php b/typo3/sysext/extensionmanager/Classes/Utility/ConfigurationUtility.php
deleted file mode 100644 (file)
index 7c8081d..0000000
+++ /dev/null
@@ -1,632 +0,0 @@
-<?php
-namespace TYPO3\CMS\Extensionmanager\Utility;
-
-/*
- * 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\Utility\ArrayUtility;
-use TYPO3\CMS\Lang\LanguageService;
-
-/**
- * Utility for dealing with ext_emconf and ext_conf_template settings
- */
-class ConfigurationUtility implements \TYPO3\CMS\Core\SingletonInterface
-{
-    /**
-     * TypoScript hierarchy being build during parsing.
-     *
-     * @var array
-     */
-    protected $setup = [];
-
-    /**
-     * Raw data, the input string exploded by LF
-     *
-     * @var array
-     */
-    protected $raw;
-
-    /**
-     * Pointer to entry in raw data array
-     *
-     * @var int
-     */
-    protected $rawP = 0;
-
-    /**
-     * Holding the value of the last comment
-     *
-     * @var string
-     */
-    protected $lastComment = '';
-
-    /**
-     * Internally set, used as internal flag to create a multi-line comment (one of those like /* ... * /
-     *
-     * @var bool
-     */
-    protected $commentSet = false;
-
-    /**
-     * Internally set, when in brace. Counter.
-     *
-     * @var int
-     */
-    protected $inBrace = 0;
-
-    /**
-     * This will be filled with the available categories of the current template.
-     *
-     * @var array
-     */
-    protected $subCategories = [
-        // Standard categories:
-        'enable' => ['Enable features', 'a'],
-        'dims' => ['Dimensions, widths, heights, pixels', 'b'],
-        'file' => ['Files', 'c'],
-        'typo' => ['Typography', 'd'],
-        'color' => ['Colors', 'e'],
-        'links' => ['Links and targets', 'f'],
-        'language' => ['Language specific constants', 'g'],
-        // subcategories based on the default content elements
-        'cheader' => ['Content: \'Header\'', 'ma'],
-        'cheader_g' => ['Content: \'Header\', Graphical', 'ma'],
-        'ctext' => ['Content: \'Text\'', 'mb'],
-        'cimage' => ['Content: \'Image\'', 'md'],
-        'ctextmedia' => ['Content: \'Textmedia\'', 'ml'],
-        'cbullets' => ['Content: \'Bullet list\'', 'me'],
-        'ctable' => ['Content: \'Table\'', 'mf'],
-        'cuploads' => ['Content: \'Filelinks\'', 'mg'],
-        'cmultimedia' => ['Content: \'Multimedia\'', 'mh'],
-        'cmedia' => ['Content: \'Media\'', 'mr'],
-        'cmailform' => ['Content: \'Form\'', 'mi'],
-        'csearch' => ['Content: \'Search\'', 'mj'],
-        'clogin' => ['Content: \'Login\'', 'mk'],
-        'cmenu' => ['Content: \'Menu/Sitemap\'', 'mm'],
-        'cshortcut' => ['Content: \'Insert records\'', 'mn'],
-        'clist' => ['Content: \'List of records\'', 'mo'],
-        'chtml' => ['Content: \'HTML\'', 'mq']
-    ];
-
-    /**
-     * @var \TYPO3\CMS\Extbase\Object\ObjectManager
-     */
-    protected $objectManager;
-
-    /**
-     * @param \TYPO3\CMS\Extbase\Object\ObjectManager $objectManager
-     */
-    public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManager $objectManager)
-    {
-        $this->objectManager = $objectManager;
-    }
-
-    /**
-     * Get default configuration from ext_conf_template of an extension
-     * and save as initial configuration to LocalConfiguration ['EXT']['extConf'].
-     *
-     * Used by the InstallUtility to initialize local extension config.
-     *
-     * @param string $extensionKey Extension key
-     */
-    public function saveDefaultConfiguration($extensionKey)
-    {
-        $currentConfiguration = $this->getCurrentConfiguration($extensionKey);
-        $nestedConfiguration = $this->convertValuedToNestedConfiguration($currentConfiguration);
-        $this->writeConfiguration($nestedConfiguration, $extensionKey);
-    }
-
-    /**
-     * Writes extension specific configuration to LocalConfiguration file
-     * in array ['EXT']['extConf'][$extensionKey].
-     *
-     * Removes core cache files afterwards.
-     *
-     * This low level method expects a nested configuration array that
-     * was already merged with default configuration and maybe new form values.
-     *
-     * @param array $configuration Configuration to save
-     * @param string $extensionKey Extension key
-     */
-    public function writeConfiguration(array $configuration = [], $extensionKey)
-    {
-        /** @var $configurationManager \TYPO3\CMS\Core\Configuration\ConfigurationManager */
-        $configurationManager = $this->objectManager->get(\TYPO3\CMS\Core\Configuration\ConfigurationManager::class);
-        $configurationManager->setLocalConfigurationValueByPath('EXT/extConf/' . $extensionKey, serialize($configuration));
-        $configurationManager->setLocalConfigurationValueByPath('EXTENSIONS/' . $extensionKey, $configuration);
-    }
-
-    /**
-     * Get current configuration of an extension. Will return the configuration as a valued object
-     *
-     * @param string $extensionKey
-     * @return array
-     */
-    public function getCurrentConfiguration(string $extensionKey): array
-    {
-        $mergedConfiguration = $this->getDefaultConfigurationFromExtConfTemplateAsValuedArray($extensionKey);
-
-        // @deprecated loading serialized configuration is deprecated and will be removed in v10 - use EXTENSIONS array instead
-        // No objects allowed in extConf at all - it is safe to deny that during unserialize()
-        $legacyCurrentExtensionConfiguration = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'][$extensionKey], ['allowed_classes' => false]);
-        $legacyCurrentExtensionConfiguration = is_array($legacyCurrentExtensionConfiguration) ? $legacyCurrentExtensionConfiguration : [];
-        $mergedConfiguration = $this->mergeExtensionConfigurations($mergedConfiguration, $legacyCurrentExtensionConfiguration);
-
-        if (isset($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'][$extensionKey]) && is_array($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'][$extensionKey])) {
-            $currentExtensionConfiguration = $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'][$extensionKey];
-            $mergedConfiguration = $this->mergeExtensionConfigurations($mergedConfiguration, $currentExtensionConfiguration);
-        }
-
-        return $mergedConfiguration;
-    }
-
-    /**
-     * Create a flat array of configuration options from
-     * ext_conf_template.txt of an extension using core's typoscript parser.
-     *
-     * Result is an array, with configuration item as array keys,
-     * and item properties as key-value sub-array:
-     *
-     * array(
-     *   'fooOption' => array(
-     *     'type' => 'string',
-     *     'value' => 'foo',
-     *     ...
-     *   ),
-     *   'barOption' => array(
-     *     'type' => boolean,
-     *     'default_value' => 0,
-     *     ...
-     *   ),
-     *   ...
-     * )
-     *
-     * @param string $extensionKey Extension key
-     * @return array
-     */
-    public function getDefaultConfigurationFromExtConfTemplateAsValuedArray($extensionKey)
-    {
-        $rawConfigurationString = $this->getDefaultConfigurationRawString($extensionKey);
-        $theConstants = [];
-        if ((string)$rawConfigurationString !== '') {
-            $this->raw = explode(LF, $rawConfigurationString);
-            $this->parseSub($this->setup);
-            if ($this->inBrace) {
-                throw new \RuntimeException(
-                    'Line ' . ($this->rawP - 1) . ': The script is short of ' . $this->inBrace . ' end brace(s)',
-                    1507645348
-                );
-            }
-            $parsedConstants = $this->setup;
-            $flatSetup = $this->flattenSetup($parsedConstants);
-            $theConstants = $this->parseComments($flatSetup);
-
-            // Loop through configuration items, see if it is assigned to a sub category
-            // and add the sub category label to the item property if so.
-            foreach ($theConstants as $configurationOptionName => $configurationOption) {
-                if (
-                    array_key_exists('subcat_name', $configurationOption)
-                    && isset($this->subCategories[$configurationOption['subcat_name']])
-                    && isset($this->subCategories[$configurationOption['subcat_name']][0])
-                ) {
-                    $theConstants[$configurationOptionName]['subcat_label'] = $this->subCategories[$configurationOption['subcat_name']][0];
-                }
-            }
-        }
-
-        return $theConstants;
-    }
-
-    /**
-     * Return content of an extensions ext_conf_template.txt file if
-     * the file exists, empty string if file does not exist.
-     *
-     * @param string $extensionKey Extension key
-     * @return string
-     */
-    protected function getDefaultConfigurationRawString($extensionKey)
-    {
-        $rawString = '';
-        $extConfTemplateFileLocation = \TYPO3\CMS\Core\Utility\GeneralUtility::getFileAbsFileName(
-            'EXT:' . $extensionKey . '/ext_conf_template.txt'
-        );
-        if (file_exists($extConfTemplateFileLocation)) {
-            $rawString = file_get_contents($extConfTemplateFileLocation);
-        }
-        return $rawString;
-    }
-
-    /**
-     * Converts a valued configuration to a nested configuration.
-     *
-     * array('first.second' => array('value' => 1))
-     * will become
-     * array('first.' => array('second' => ))
-     *
-     * @param array $valuedConfiguration
-     * @return array
-     */
-    public function convertValuedToNestedConfiguration(array $valuedConfiguration)
-    {
-        $nestedConfiguration = [];
-        foreach ($valuedConfiguration as $name => $section) {
-            $path = str_replace('.', './', $name);
-            $nestedConfiguration = ArrayUtility::setValueByPath($nestedConfiguration, $path, $section['value'], '/');
-        }
-        return $nestedConfiguration;
-    }
-
-    /**
-     * Convert a nested configuration to a valued configuration
-     *
-     * array('first.' => array('second' => 1))
-     * will become
-     * array('first.second' => array('value' => 1)
-     * @param array $nestedConfiguration
-     * @return array
-     */
-    public function convertNestedToValuedConfiguration(array $nestedConfiguration)
-    {
-        $flatExtensionConfig = ArrayUtility::flatten($nestedConfiguration);
-        $valuedCurrentExtensionConfig = [];
-        foreach ($flatExtensionConfig as $key => $value) {
-            $valuedCurrentExtensionConfig[$key]['value'] = $value;
-        }
-        return $valuedCurrentExtensionConfig;
-    }
-
-    /**
-     * Merges two existing configuration arrays,
-     * expects configuration as valued flat structure
-     * and overrides as nested array
-     *
-     * @see convertNestedToValuedConfiguration
-     *
-     * @param array $configuration
-     * @param array $configurationOverride
-     *
-     * @return array
-     */
-    private function mergeExtensionConfigurations(array $configuration, array $configurationOverride): array
-    {
-        $configurationOverride = $this->convertNestedToValuedConfiguration(
-            $configurationOverride
-        );
-        ArrayUtility::mergeRecursiveWithOverrule(
-            $configuration,
-            $configurationOverride
-        );
-        return $configuration;
-    }
-
-    /**
-     * This flattens a hierarchical TypoScript array to $this->flatSetup
-     *
-     * @param array $setupArray TypoScript array
-     * @param string $prefix Prefix to the object path. Used for recursive calls to this function.
-     * @return array
-     */
-    protected function flattenSetup($setupArray, $prefix = '')
-    {
-        $flatSetup = [];
-        if (is_array($setupArray)) {
-            foreach ($setupArray as $key => $val) {
-                if (is_array($val)) {
-                    $flatSetup = array_merge($flatSetup, $this->flattenSetup($val, $prefix . $key));
-                } else {
-                    $flatSetup[$prefix . $key] = $val;
-                }
-            }
-        }
-        return $flatSetup;
-    }
-
-    /**
-     * This function compares the flattened constants (default and all).
-     * Returns an array with the constants from the whole template which may be edited by the module.
-     *
-     * @param array $flatSetup
-     * @return array
-     */
-    protected function parseComments($flatSetup)
-    {
-        $categoryLabels = [];
-        $editableComments = [];
-        $counter = 0;
-        foreach ($flatSetup as $const => $value) {
-            if (substr($const, -2) === '..' || !isset($flatSetup[$const . '..'])) {
-                continue;
-            }
-            $counter++;
-            $comment = trim($flatSetup[$const . '..']);
-            $c_arr = explode(LF, $comment);
-            foreach ($c_arr as $k => $v) {
-                $line = trim(preg_replace('/^[#\\/]*/', '', $v));
-                if (!$line) {
-                    continue;
-                }
-                $parts = explode(';', $line);
-                foreach ($parts as $par) {
-                    if (strstr($par, '=')) {
-                        $keyValPair = explode('=', $par, 2);
-                        switch (trim(strtolower($keyValPair[0]))) {
-                            case 'type':
-                                // Type:
-                                $editableComments[$const]['type'] = trim($keyValPair[1]);
-                                break;
-                            case 'cat':
-                                // List of categories.
-                                $catSplit = explode('/', strtolower($keyValPair[1]));
-                                $catSplit[0] = trim($catSplit[0]);
-                                if (isset($categoryLabels[$catSplit[0]])) {
-                                    $catSplit[0] = $categoryLabels[$catSplit[0]];
-                                }
-                                $editableComments[$const]['cat'] = $catSplit[0];
-                                // This is the subcategory. Must be a key in $this->subCategories[].
-                                // catSplit[2] represents the search-order within the subcat.
-                                $catSplit[1] = trim($catSplit[1]);
-                                if ($catSplit[1] && isset($this->subCategories[$catSplit[1]])) {
-                                    $editableComments[$const]['subcat_name'] = $catSplit[1];
-                                    $orderIdentifier = isset($catSplit[2]) ? trim($catSplit[2]) : $counter;
-                                    $editableComments[$const]['subcat'] = $this->subCategories[$catSplit[1]][1]
-                                        . '/' . $catSplit[1] . '/' . $orderIdentifier . 'z';
-                                } elseif (isset($catSplit[2])) {
-                                    $editableComments[$const]['subcat'] = 'x' . '/' . trim($catSplit[2]) . 'z';
-                                } else {
-                                    $editableComments[$const]['subcat'] = 'x' . '/' . $counter . 'z';
-                                }
-                                break;
-                            case 'label':
-                                // Label
-                                $editableComments[$const]['label'] = trim($keyValPair[1]);
-                                break;
-                            case 'customcategory':
-                                // Custom category label
-                                $customCategory = explode('=', $keyValPair[1], 2);
-                                if (trim($customCategory[0])) {
-                                    $categoryKey = strtolower($customCategory[0]);
-                                    $categoryLabels[$categoryKey] = $this->getLanguageService()->sL($customCategory[1]);
-                                }
-                                break;
-                            case 'customsubcategory':
-                                // Custom subCategory label
-                                $customSubcategory = explode('=', $keyValPair[1], 2);
-                                if (trim($customSubcategory[0])) {
-                                    $subCategoryKey = strtolower($customSubcategory[0]);
-                                    $this->subCategories[$subCategoryKey][0] = $this->getLanguageService()->sL($customSubcategory[1]);
-                                }
-                                break;
-                        }
-                    }
-                }
-            }
-            if (isset($editableComments[$const])) {
-                $editableComments[$const]['name'] = $const;
-                $editableComments[$const]['value'] = trim($value);
-                $editableComments[$const]['default_value'] = trim($value);
-            }
-        }
-        return $editableComments;
-    }
-
-    /**
-     * Parsing the $this->raw TypoScript lines from pointer, $this->rawP
-     *
-     * @param array $setup Reference to the setup array in which to accumulate the values.
-     */
-    protected function parseSub(array &$setup)
-    {
-        while (isset($this->raw[$this->rawP])) {
-            $line = ltrim($this->raw[$this->rawP]);
-            $this->rawP++;
-            // Set comment flag?
-            if (strpos($line, '/*') === 0) {
-                $this->commentSet = 1;
-            }
-            if (!$this->commentSet && ($line)) {
-                if ($line[0] !== '}' && $line[0] !== '#' && $line[0] !== '/') {
-                    // If not brace-end or comment
-                    // Find object name string until we meet an operator
-                    $varL = strcspn($line, TAB . ' {=<>(');
-                    // check for special ":=" operator
-                    if ($varL > 0 && substr($line, $varL-1, 2) === ':=') {
-                        --$varL;
-                    }
-                    // also remove tabs after the object string name
-                    $objStrName = substr($line, 0, $varL);
-                    if ($objStrName !== '') {
-                        $r = [];
-                        if (preg_match('/[^[:alnum:]_\\\\\\.:-]/i', $objStrName, $r)) {
-                            throw new \RuntimeException(
-                                'Line ' . ($this->rawP - 1) . ': Object Name String, "' . htmlspecialchars($objStrName) . '" contains invalid character "' . $r[0] . '". Must be alphanumeric or one of: "_:-\\."',
-                                1507645381
-                            );
-                        }
-                        $line = ltrim(substr($line, $varL));
-                        if ($line === '') {
-                            throw new \RuntimeException(
-                                    'Line ' . ($this->rawP - 1) . ': Object Name String, "' . htmlspecialchars($objStrName) . '" was not followed by any operator, =<>({',
-                                    1507645417
-                                );
-                        }
-                        switch ($line[0]) {
-                                    case '=':
-                                        if (strpos($objStrName, '.') !== false) {
-                                            $value = [];
-                                            $value[0] = trim(substr($line, 1));
-                                            $this->setVal($objStrName, $setup, $value);
-                                        } else {
-                                            $setup[$objStrName] = trim(substr($line, 1));
-                                            if ($this->lastComment) {
-                                                // Setting comment..
-                                                $setup[$objStrName . '..'] .= $this->lastComment;
-                                            }
-                                        }
-                                        break;
-                                    case '{':
-                                        $this->inBrace++;
-                                        if (strpos($objStrName, '.') !== false) {
-                                            $this->rollParseSub($objStrName, $setup);
-                                        } else {
-                                            if (!isset($setup[$objStrName . '.'])) {
-                                                $setup[$objStrName . '.'] = [];
-                                            }
-                                            $this->parseSub($setup[$objStrName . '.']);
-                                        }
-                                        break;
-                                    default:
-                                        throw new \RuntimeException(
-                                            'Line ' . ($this->rawP - 1) . ': Object Name String, "' . htmlspecialchars($objStrName) . '" was not followed by any operator, =<>({',
-                                            1507645445
-                                        );
-                                }
-
-                        $this->lastComment = '';
-                    }
-                } elseif ($line[0] === '}') {
-                    $this->inBrace--;
-                    $this->lastComment = '';
-                    if ($this->inBrace < 0) {
-                        throw new \RuntimeException(
-                            'Line ' . ($this->rawP - 1) . ': An end brace is in excess.',
-                            1507645489
-                        );
-                    }
-                    break;
-                } else {
-                    $this->lastComment .= rtrim($line) . LF;
-                }
-            }
-            // Unset comment
-            if ($this->commentSet) {
-                if (strpos($line, '*/') === 0) {
-                    $this->commentSet = 0;
-                }
-            }
-        }
-    }
-
-    /**
-     * Parsing of TypoScript keys inside a curly brace where the key is composite of at least two keys,
-     * thus having to recursively call itself to get the value
-     *
-     * @param string $string The object sub-path, eg "thisprop.another_prot
-     * @param array $setup The local setup array from the function calling this function
-     */
-    protected function rollParseSub($string, array &$setup)
-    {
-        if ((string)$string === '') {
-            return;
-        }
-        list($key, $remainingKey) = $this->parseNextKeySegment($string);
-        $key .= '.';
-        if (!isset($setup[$key])) {
-            $setup[$key] = [];
-        }
-        $remainingKey === ''
-            ? $this->parseSub($setup[$key])
-            : $this->rollParseSub($remainingKey, $setup[$key]);
-    }
-
-    /**
-     * Setting a value/property of an object string in the setup array.
-     *
-     * @param string $string The object sub-path, eg "thisprop.another_prot
-     * @param array $setup The local setup array from the function calling this function.
-     * @param void
-     */
-    protected function setVal($string, array &$setup, $value)
-    {
-        if ((string)$string === '') {
-            return;
-        }
-
-        list($key, $remainingKey) = $this->parseNextKeySegment($string);
-        $subKey = $key . '.';
-        if ($remainingKey === '') {
-            if (isset($value[0])) {
-                $setup[$key] = $value[0];
-            }
-            if (isset($value[1])) {
-                $setup[$subKey] = $value[1];
-            }
-            if ($this->lastComment) {
-                $setup[$key . '..'] .= $this->lastComment;
-            }
-        } else {
-            if (!isset($setup[$subKey])) {
-                $setup[$subKey] = [];
-            }
-            $this->setVal($remainingKey, $setup[$subKey], $value);
-        }
-    }
-
-    /**
-     * Determines the first key segment of a TypoScript key by searching for the first
-     * unescaped dot in the given key string.
-     *
-     * Since the escape characters are only needed to correctly determine the key
-     * segment any escape characters before the first unescaped dot are
-     * stripped from the key.
-     *
-     * @param string $key The key, possibly consisting of multiple key segments separated by unescaped dots
-     * @return array Array with key segment and remaining part of $key
-     */
-    protected function parseNextKeySegment($key)
-    {
-        // if no dot is in the key, nothing to do
-        $dotPosition = strpos($key, '.');
-        if ($dotPosition === false) {
-            return [$key, ''];
-        }
-
-        if (strpos($key, '\\') !== false) {
-            // backslashes are in the key, so we do further parsing
-            while ($dotPosition !== false) {
-                if ($dotPosition > 0 && $key[$dotPosition - 1] !== '\\' || $dotPosition > 1 && $key[$dotPosition - 2] === '\\') {
-                    break;
-                }
-                // escaped dot found, continue
-                $dotPosition = strpos($key, '.', $dotPosition + 1);
-            }
-
-            if ($dotPosition === false) {
-                // no regular dot found
-                $keySegment = $key;
-                $remainingKey = '';
-            } else {
-                if ($dotPosition > 1 && $key[$dotPosition - 2] === '\\' && $key[$dotPosition - 1] === '\\') {
-                    $keySegment = substr($key, 0, $dotPosition - 1);
-                } else {
-                    $keySegment = substr($key, 0, $dotPosition);
-                }
-                $remainingKey = substr($key, $dotPosition + 1);
-            }
-
-            // fix key segment by removing escape sequences
-            $keySegment = str_replace('\\.', '.', $keySegment);
-        } else {
-            // no backslash in the key, we're fine off
-            list($keySegment, $remainingKey) = explode('.', $key, 2);
-        }
-        return [$keySegment, $remainingKey];
-    }
-
-    /**
-     * @return LanguageService
-     */
-    protected function getLanguageService()
-    {
-        return $GLOBALS['LANG'];
-    }
-}
index d15d62f..59a6759 100644 (file)
@@ -14,7 +14,9 @@ namespace TYPO3\CMS\Extensionmanager\Utility\Connection;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
 use TYPO3\CMS\Core\Core\Bootstrap;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException;
 
 /**
@@ -30,19 +32,6 @@ class TerUtility
     public $wsdlUrl;
 
     /**
-     * @var \TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility
-     */
-    protected $configurationUtility;
-
-    /**
-     * @param \TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility $configurationUtility
-     */
-    public function injectConfigurationUtility(\TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility $configurationUtility)
-    {
-        $this->configurationUtility = $configurationUtility;
-    }
-
-    /**
      * Fetches an extension from the given mirror
      *
      * @param string $extensionKey Extension Key
@@ -55,7 +44,7 @@ class TerUtility
     public function fetchExtension($extensionKey, $version, $expectedMd5, $mirrorUrl)
     {
         if (
-            !empty($this->configurationUtility->getCurrentConfiguration('extensionmanager')['offlineMode']['value'])
+            (bool)GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('extensionmanager', 'offlineMode')
             || Bootstrap::usesComposerClassLoading()
         ) {
             throw new ExtensionManagerException('Extension Manager is in offline mode. No TER connection available.', 1437078620);
index 091c8c7..f335ec3 100644 (file)
@@ -21,6 +21,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Extensionmanager\Domain\Model\Extension;
 use TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException;
 use TYPO3\CMS\Impexp\Utility\ImportExportUtility;
+use TYPO3\CMS\Install\Service\ExtensionConfigurationService;
 
 /**
  * Extension Manager Install Utility
@@ -438,9 +439,8 @@ class InstallUtility implements \TYPO3\CMS\Core\SingletonInterface
      */
     protected function saveDefaultConfiguration($extensionKey)
     {
-        /** @var $configUtility \TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility */
-        $configUtility = $this->objectManager->get(\TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility::class);
-        $configUtility->saveDefaultConfiguration($extensionKey);
+        $configUtility = $this->objectManager->get(ExtensionConfigurationService::class);
+        $configUtility->synchronizeExtConfTemplateWithLocalConfiguration($extensionKey);
     }
 
     /**
index 6ef7248..6b21f0e 100644 (file)
@@ -14,10 +14,10 @@ namespace TYPO3\CMS\Extensionmanager\Utility\Repository;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException;
-use TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility;
 
 /**
  * Central utility class for repository handling.
@@ -65,11 +65,6 @@ class Helper implements \TYPO3\CMS\Core\SingletonInterface
     protected $extensionRepository;
 
     /**
-     * @var \TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility
-     */
-    protected $configurationUtility;
-
-    /**
      * Class constructor.
      *
      * @access public
@@ -81,7 +76,6 @@ class Helper implements \TYPO3\CMS\Core\SingletonInterface
         /** @var \TYPO3\CMS\Extensionmanager\Domain\Repository\RepositoryRepository $repositoryRepository */
         $repositoryRepository = $this->objectManager->get(\TYPO3\CMS\Extensionmanager\Domain\Repository\RepositoryRepository::class);
         $this->extensionRepository = $this->objectManager->get(\TYPO3\CMS\Extensionmanager\Domain\Repository\ExtensionRepository::class);
-        $this->configurationUtility = $this->objectManager->get(ConfigurationUtility::class);
         /** @var \TYPO3\CMS\Extensionmanager\Domain\Model\Repository $repository */
         $repository = $repositoryRepository->findByUid(1);
         if (is_object($repository)) {
@@ -142,7 +136,8 @@ class Helper implements \TYPO3\CMS\Core\SingletonInterface
      */
     protected function fetchFile($remoteResource, $localResource)
     {
-        if ($this->configurationUtility->getCurrentConfiguration('extensionmanager')['offlineMode']['value']) {
+        $isOffline = (bool)GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('extensionmanager', 'offlineMode');
+        if ($isOffline) {
             throw new ExtensionManagerException('Extension Manager is in offline mode. No TER connection available.', 1437078780);
         }
         if (is_string($remoteResource) && is_string($localResource) && !empty($remoteResource) && !empty($localResource)) {
diff --git a/typo3/sysext/extensionmanager/Classes/ViewHelpers/ConfigureExtensionViewHelper.php b/typo3/sysext/extensionmanager/Classes/ViewHelpers/ConfigureExtensionViewHelper.php
deleted file mode 100644 (file)
index 71ac2be..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-<?php
-namespace TYPO3\CMS\Extensionmanager\ViewHelpers;
-
-/*
- * 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\Imaging\Icon;
-use TYPO3\CMS\Core\Imaging\IconFactory;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
-
-/**
- * View helper for configure extension link
- * @internal
- */
-class ConfigureExtensionViewHelper extends Link\ActionViewHelper
-{
-    /**
-     * Initialize arguments
-     */
-    public function initializeArguments()
-    {
-        parent::initializeArguments();
-        $this->registerArgument('extension', 'array', 'Extension configuration array with extension information', true);
-        $this->registerArgument('forceConfiguration', 'bool', 'If TRUE the content is only returned if a link could be generated', false, true);
-        $this->registerArgument('showDescription', 'bool', 'If TRUE the extension description is also shown in the title attribute', false, false);
-    }
-
-    /**
-     * Renders a configure extension link if the extension has configuration options
-     *
-     * @return string the rendered tag or child nodes content
-     */
-    public function render()
-    {
-        $extension = $this->arguments['extension'];
-        $forceConfiguration = $this->arguments['forceConfiguration'];
-        $showDescription = $this->arguments['showDescription'];
-
-        $content = (string)$this->renderChildren();
-        if ($extension['installed'] && file_exists(PATH_site . $extension['siteRelPath'] . 'ext_conf_template.txt')) {
-            $uriBuilder = $this->renderingContext->getControllerContext()->getUriBuilder();
-            $action = 'showConfigurationForm';
-            $uri = $uriBuilder->reset()->uriFor(
-                $action,
-                ['extension' => ['key' => $extension['key']]],
-                'Configuration'
-            );
-            if ($showDescription) {
-                $title = $extension['description'] . PHP_EOL .
-                    LocalizationUtility::translate('extensionList.clickToConfigure', 'extensionmanager');
-            } else {
-                $title = LocalizationUtility::translate('extensionList.configure', 'extensionmanager');
-            }
-            $this->tag->addAttribute('href', $uri);
-            $this->tag->addAttribute('title', $title);
-            $this->tag->setContent($content);
-            $content = $this->tag->render();
-        } elseif ($forceConfiguration) {
-            $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
-            $content = '<span class="btn btn-default disabled">' . $iconFactory->getIcon('empty-empty', Icon::SIZE_SMALL)->render() . '</span>';
-        } else {
-            $content = '<span title="' . htmlspecialchars($extension['description']) . '">' . $content . '</span>';
-        }
-
-        return $content;
-    }
-}
index 5c8a92b..365a0a0 100644 (file)
@@ -14,6 +14,8 @@ namespace TYPO3\CMS\Extensionmanager\ViewHelpers;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
 use TYPO3\CMS\Extensionmanager\Domain\Model\Extension;
 
@@ -29,11 +31,6 @@ class DownloadExtensionViewHelper extends \TYPO3\CMS\Fluid\ViewHelpers\Form\Abst
     protected $tagName = 'form';
 
     /**
-     * @var \TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility
-     */
-    protected $configurationUtility;
-
-    /**
      * @var \TYPO3\CMS\Extbase\Service\ExtensionService
      */
     protected $extensionService;
@@ -47,14 +44,6 @@ class DownloadExtensionViewHelper extends \TYPO3\CMS\Fluid\ViewHelpers\Form\Abst
     }
 
     /**
-     * @param \TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility $configurationUtility
-     */
-    public function injectConfigurationUtility(\TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility $configurationUtility)
-    {
-        $this->configurationUtility = $configurationUtility;
-    }
-
-    /**
      * Initialize arguments.
      */
     public function initializeArguments()
@@ -99,7 +88,7 @@ class DownloadExtensionViewHelper extends \TYPO3\CMS\Fluid\ViewHelpers\Form\Abst
         ], 'Download');
         $this->tag->addAttribute('data-href', $uri);
 
-        $automaticInstallation = $this->configurationUtility->getCurrentConfiguration('extensionmanager')['automaticInstallation']['value'];
+        $automaticInstallation = (bool)GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('extensionmanager', 'automaticInstallation');
         $labelKeySuffix = $automaticInstallation ? '' : '.downloadOnly';
         $label = '
                        <div class="btn-group">
diff --git a/typo3/sysext/extensionmanager/Classes/ViewHelpers/Form/TypoScriptConstantsViewHelper.php b/typo3/sysext/extensionmanager/Classes/ViewHelpers/Form/TypoScriptConstantsViewHelper.php
deleted file mode 100644 (file)
index 66478bb..0000000
+++ /dev/null
@@ -1,316 +0,0 @@
-<?php
-namespace TYPO3\CMS\Extensionmanager\ViewHelpers\Form;
-
-/*
- * 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\Extensionmanager\Domain\Model\ConfigurationItem;
-use TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper;
-
-/**
- * View Helper for rendering Extension Manager Configuration Form
- * @internal
- */
-class TypoScriptConstantsViewHelper extends \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper
-{
-    /**
-     * @var array
-     */
-    public $viewHelperMapping = [
-        'int' => 'renderIntegerField',
-        'int+' => 'renderPositiveIntegerField',
-        'integer' => 'renderIntegerField',
-        'color' => 'renderColorPicker',
-        'wrap' => 'renderWrapField',
-        'offset' => 'renderOffsetField',
-        'options' => 'renderOptionSelect',
-        'boolean' => 'renderCheckbox',
-        'user' => 'renderUserFunction',
-        'small' => 'renderSmallTextField',
-        'string' => 'renderTextField',
-        'input' => 'renderTextField',  // only for backwards compatibility, many extensions depend on that
-        'default' => 'renderTextField' // only for backwards compatibility, many extensions depend on that
-    ];
-
-    /**
-     * @var string
-     */
-    public $tagName = 'input';
-
-    /**
-     * Initialize arguments of this view helper
-     */
-    public function initializeArguments()
-    {
-        parent::initializeArguments();
-        $this->registerArgument('name', 'string', 'Name of input tag');
-        $this->registerArgument('value', 'mixed', 'Value of input tag');
-        $this->registerArgument('configuration', ConfigurationItem::class, '', true);
-        $this->registerUniversalTagAttributes();
-    }
-
-    /**
-     * Render
-     *
-     * @return string the rendered tag
-     */
-    public function render()
-    {
-        /** @var ConfigurationItem $configuration */
-        $configuration = $this->arguments['configuration'];
-        if (isset($this->viewHelperMapping[$configuration->getType()]) && method_exists($this, $this->viewHelperMapping[$configuration->getType()])) {
-            $input = $this->{$this->viewHelperMapping[$configuration->getType()]}($configuration);
-        } else {
-            $input = $this->{$this->viewHelperMapping['default']}($configuration);
-        }
-
-        return $input;
-    }
-
-    /**
-     * Render field of type color picker
-     *
-     * @param ConfigurationItem $configuration
-     * @return string
-     */
-    protected function renderColorPicker(ConfigurationItem $configuration)
-    {
-        $elementId = 'em-' . $configuration->getName();
-        $elementName = $this->getName($configuration);
-
-        // configure the field
-        $this->tag->setTagName('input');
-        $this->tag->addAttribute('type', 'text');
-        $this->tag->addAttribute('id', $elementId);
-        $this->tag->addAttribute('name', $elementName);
-        $this->tag->addAttribute('data-formengine-input-name', $elementName);
-        $this->tag->addAttribute('class', 'form-control');
-        if ($configuration->getValue() !== null) {
-            $this->tag->addAttribute('value', $configuration->getValue());
-        }
-
-        $output = '
-            <div class="form-wizards-element">
-                <input class="form-control t3js-color-input formengine-colorpickerelement t3js-color-picker" type="text"
-                  name="' . htmlspecialchars($elementName) . '" value="' . $this->tag->getAttribute('value') . '"/>
-                <script type="text/javascript">
-                    require([\'TYPO3/CMS/Backend/ColorPicker\'], function(ColorPicker){ColorPicker.initialize()});
-                </script>
-            </div>';
-
-        return $output;
-    }
-
-    /**
-     * Render field of type "offset"
-     *
-     * @param ConfigurationItem $configuration
-     * @return string
-     */
-    protected function renderOffsetField(ConfigurationItem $configuration)
-    {
-        $this->tag->setTagName('input');
-        $this->tag->addAttribute('type', 'text');
-        $this->tag->addAttribute('id', 'em-' . $configuration->getName());
-        $this->tag->addAttribute('name', $this->getName($configuration));
-        $this->tag->addAttribute('class', 'form-control t3js-emconf-offset');
-        if ($configuration->getValue() !== null) {
-            $this->tag->addAttribute('value', $configuration->getValue());
-        }
-        return $this->tag->render();
-    }
-
-    /**
-     * Render field of type "wrap"
-     *
-     * @param ConfigurationItem $configuration
-     * @return string
-     */
-    protected function renderWrapField(ConfigurationItem $configuration)
-    {
-        $this->tag->setTagName('input');
-        $this->tag->addAttribute('type', 'text');
-        $this->tag->addAttribute('id', 'em-' . $configuration->getName());
-        $this->tag->addAttribute('name', $this->getName($configuration));
-        $this->tag->addAttribute('class', 'form-control t3js-emconf-wrap');
-        if ($configuration->getValue() !== null) {
-            $this->tag->addAttribute('value', $configuration->getValue());
-        }
-        return $this->tag->render();
-    }
-
-    /**
-     * Render field of type "option"
-     *
-     * @param ConfigurationItem $configuration
-     * @return string
-     */
-    protected function renderOptionSelect(ConfigurationItem $configuration)
-    {
-        $this->tag->setTagName('select');
-        $this->tag->addAttribute('id', 'em-' . $configuration->getName());
-        $this->tag->addAttribute('name', $this->getName($configuration));
-        $this->tag->addAttribute('class', 'form-control');
-        $optionValueArray = $configuration->getGeneric();
-        $output = '';
-        foreach ($optionValueArray as $label => $value) {
-            $output .= '<option value="' . htmlspecialchars($value) . '"';
-            if ($configuration->getValue() == $value) {
-                $output .= ' selected="selected"';
-            }
-            $output .= '>' . htmlspecialchars($GLOBALS['LANG']->sL($label)) . '</option>';
-        }
-        $this->tag->setContent($output);
-        return $this->tag->render();
-    }
-
-    /**
-     * Render field of type "int+"
-     *
-     * @param ConfigurationItem $configuration
-     * @return string
-     */
-    protected function renderPositiveIntegerField(ConfigurationItem $configuration)
-    {
-        $this->tag->setTagName('input');
-        $this->tag->addAttribute('type', 'number');
-        $this->tag->addAttribute('id', 'em-' . $configuration->getName());
-        $this->tag->addAttribute('name', $this->getName($configuration));
-        $this->tag->addAttribute('class', 'form-control');
-        $this->tag->addAttribute('min', '0');
-        if ($configuration->getValue() !== null) {
-            $this->tag->addAttribute('value', $configuration->getValue());
-        }
-        return $this->tag->render();
-    }
-
-    /**
-     * Render field of type "integer"
-     *
-     * @param ConfigurationItem $configuration
-     * @return string
-     */
-    protected function renderIntegerField(ConfigurationItem $configuration)
-    {
-        $this->tag->setTagName('input');
-        $this->tag->addAttribute('type', 'number');
-        $this->tag->addAttribute('id', 'em-' . $configuration->getName());
-        $this->tag->addAttribute('name', $this->getName($configuration));
-        $this->tag->addAttribute('class', 'form-control');
-        if ($configuration->getValue() !== null) {
-            $this->tag->addAttribute('value', $configuration->getValue());
-        }
-        return $this->tag->render();
-    }
-
-    /**
-     * Render field of type "text"
-     *
-     * @param ConfigurationItem $configuration
-     * @return string
-     */
-    protected function renderTextField(ConfigurationItem $configuration)
-    {
-        $this->tag->setTagName('input');
-        $this->tag->addAttribute('type', 'text');
-        $this->tag->addAttribute('id', 'em-' . $configuration->getName());
-        $this->tag->addAttribute('name', $this->getName($configuration));
-        $this->tag->addAttribute('class', 'form-control');
-        if ($configuration->getValue() !== null) {
-            $this->tag->addAttribute('value', $configuration->getValue());
-        }
-        return $this->tag->render();
-    }
-
-    /**
-     * Render field of type "small text"
-     *
-     * @param ConfigurationItem $configuration
-     * @return string
-     */
-    protected function renderSmallTextField(ConfigurationItem $configuration)
-    {
-        return $this->renderTextField($configuration);
-    }
-
-    /**
-     * Render field of type "checkbox"
-     *
-     * @param ConfigurationItem $configuration
-     * @return string
-     */
-    public function renderCheckbox(ConfigurationItem $configuration)
-    {
-        $this->tag->addAttribute('type', 'checkbox');
-        $this->tag->addAttribute('name', $this->getName($configuration));
-        $this->tag->addAttribute('value', 1);
-        $this->tag->addAttribute('id', 'em-' . $configuration->getName());
-        if ($configuration->getValue() == 1) {
-            $this->tag->addAttribute('checked', 'checked');
-        }
-        $hiddenField = $this->renderHiddenFieldForEmptyValue($configuration);
-        return '<div class="checkbox">' . $hiddenField . '<label>' . $this->tag->render() . '</label></div>';
-    }
-
-    /**
-     * Render field of type "userFunc"
-     *
-     * @param ConfigurationItem $configuration
-     * @return string
-     */
-    protected function renderUserFunction(ConfigurationItem $configuration)
-    {
-        $userFunction = $configuration->getGeneric();
-        $userFunctionParams = [
-            'fieldName' => $this->getName($configuration),
-            'fieldValue' => $configuration->getValue(),
-            'propertyName' => $configuration->getName()
-        ];
-        return \TYPO3\CMS\Core\Utility\GeneralUtility::callUserFunction($userFunction, $userFunctionParams, $this);
-    }
-
-    /**
-     * Get Field Name
-     *
-     * @param ConfigurationItem $configuration
-     * @return string
-     */
-    protected function getName(ConfigurationItem $configuration)
-    {
-        return 'tx_extensionmanager_tools_extensionmanagerextensionmanager[config][' . $configuration->getName() . '][value]';
-    }
-
-    /**
-     * Render a hidden field for empty values
-     *
-     * @param ConfigurationItem $configuration
-     * @return string
-     */
-    protected function renderHiddenFieldForEmptyValue($configuration)
-    {
-        $hiddenFieldNames = [];
-        if ($this->viewHelperVariableContainer->exists(FormViewHelper::class, 'renderedHiddenFields')) {
-            $hiddenFieldNames = $this->viewHelperVariableContainer->get(FormViewHelper::class, 'renderedHiddenFields');
-        }
-        $fieldName = $this->getName($configuration);
-        if (substr($fieldName, -2) === '[]') {
-            $fieldName = substr($fieldName, 0, -2);
-        }
-        if (!in_array($fieldName, $hiddenFieldNames)) {
-            $hiddenFieldNames[] = $fieldName;
-            $this->viewHelperVariableContainer->addOrUpdate(FormViewHelper::class, 'renderedHiddenFields', $hiddenFieldNames);
-            return '<input type="hidden" name="' . htmlspecialchars($fieldName) . '" value="0" />';
-        }
-        return '';
-    }
-}
index 3abe4d5..bd9433b 100644 (file)
                        <trans-unit id="showAllVersions">
                                <source>Show all versions of</source>
                        </trans-unit>
-                       <trans-unit id="extConfTemplate.headline">
-                               <source>Configure Extension</source>
-                       </trans-unit>
-                       <trans-unit id="extConfTemplate.other">
-                               <source>Other</source>
-                       </trans-unit>
-                       <trans-unit id="extConfTemplate.type.int+">
-                               <source>positive integer</source>
-                       </trans-unit>
-                       <trans-unit id="extConfTemplate.type.int">
-                               <source>integer</source>
-                       </trans-unit>
-                       <trans-unit id="extConfTemplate.type.integer">
-                               <source>integer</source>
-                       </trans-unit>
-                       <trans-unit id="extConfTemplate.type.color">
-                               <source>color</source>
-                       </trans-unit>
-                       <trans-unit id="extConfTemplate.type.wrap">
-                               <source>wrap</source>
-                       </trans-unit>
-                       <trans-unit id="extConfTemplate.type.offset">
-                               <source>offset</source>
-                       </trans-unit>
-                       <trans-unit id="extConfTemplate.type.options">
-                               <source>options</source>
-                       </trans-unit>
-                       <trans-unit id="extConfTemplate.type.boolean">
-                               <source>boolean</source>
-                       </trans-unit>
-                       <trans-unit id="extConfTemplate.type.user">
-                               <source>user</source>
-                       </trans-unit>
-                       <trans-unit id="extConfTemplate.type.small">
-                               <source>small</source>
-                       </trans-unit>
-                       <trans-unit id="extConfTemplate.type.string">
-                               <source>string</source>
-                       </trans-unit>
                        <trans-unit id="extConfTemplate.backToList">
                                <source>Back to list</source>
                        </trans-unit>
                        <trans-unit id="button.resolveDependenciesIgnore">
                                <source>I know what I'm doing, continue anyway</source>
                        </trans-unit>
-                       <trans-unit id="extensionList.clickToConfigure">
-                               <source>(Click to configure)</source>
-                       </trans-unit>
-                       <trans-unit id="extensionList.configure">
-                               <source>Configure</source>
-                       </trans-unit>
                        <trans-unit id="extensionList.update.script">
                                <source>Execute the update script</source>
                        </trans-unit>
diff --git a/typo3/sysext/extensionmanager/Resources/Private/Templates/Configuration/ShowConfigurationForm.html b/typo3/sysext/extensionmanager/Resources/Private/Templates/Configuration/ShowConfigurationForm.html
deleted file mode 100644 (file)
index cfcfe6f..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-{namespace em=TYPO3\CMS\Extensionmanager\ViewHelpers}
-
-<f:layout name="main" />
-
-<f:section name="headline">
-       <h1>
-               <f:translate key="extConfTemplate.headline" />
-               {extension.key}
-       </h1>
-</f:section>
-
-<f:section name="content">
-       <div role="tabpanel">
-               <ul class="nav nav-tabs" role="tablist">
-                       <f:for each="{configuration}" as="category" key="categoryNumber" iteration="iteration">
-                               <f:if condition="{category.name}">
-                                       <li role="presentation" class="{f:if(condition:'{iteration.isFirst}', then:'active')}">
-                                               <a class="text-capitalize" href="#category-{categoryNumber}" aria-controls="category-{categoryNumber}" role="tab" data-toggle="tab">
-                                                       {category.name}
-                                               </a>
-                                       </li>
-                               </f:if>
-                       </f:for>
-               </ul>
-
-               <f:form action="save" name="configurationform" id="configurationform">
-                       <div class="tab-content">
-                               <f:form.hidden name="extensionKey" value="{extension.key}" />
-                               <f:for each="{configuration}" as="category" key="categoryNumber" iteration="iteration">
-                                       <f:if condition="{category.name}">
-                                               <div role="tabpanel" class="tab-pane {f:if(condition:'{iteration.isFirst}', then:'active')}" id="category-{categoryNumber}">
-                                                       <f:for each="{category.subcategories}" as="subcategory">
-                                                               <div class="form-section">
-                                                                       <f:if condition="{subcategory.label}">
-                                                                               <f:then>
-                                                                                       <h2 class="h4 form-section-headline">{subcategory.label}</h2>
-                                                                               </f:then>
-                                                                               <f:else>
-                                                                                       <f:if condition="{category.subcategories->f:count()} > 1">
-                                                                                               <h3 class="h4 form-section-headline"><f:translate key="extConfTemplate.other" /></h3>
-                                                                                       </f:if>
-                                                                               </f:else>
-                                                                       </f:if>
-                                                                       <f:for each="{subcategory.items}" as="item">
-                                                                               <div class="form-group form-group-dashed">
-                                                                                       <label for="em-{item.name}">
-                                                                                               {item.labelHeadline}<br>
-                                                                                               <span class="text-monospace text-normal">
-                                                                                                       {category.name}.{item.name}
-                                                                                                       <f:if condition="{item.type} != 'user'">
-                                                                                                               <f:alias map="{label: '{f:translate(key: \'extConfTemplate.type.{item.type}\')}'}">
-                                                                                                                       <f:if condition="{label}">
-                                                                                                                               ({label})
-                                                                                                                       </f:if>
-                                                                                                               </f:alias>
-                                                                                                       </f:if>
-                                                                                               </span>
-                                                                                       </label>
-                                                                                       <div class="form-control-wrap">
-                                                                                               <em:form.typoScriptConstants configuration="{item}" />
-                                                                                       </div>
-                                                                                       <f:if condition="{item.labelText}">
-                                                                                               <div class="help-block">{item.labelText -> f:format.nl2br()}</div>
-                                                                                       </f:if>
-                                                                               </div>
-                                                                       </f:for>
-                                                               </div>
-                                                       </f:for>
-                                               </div>
-                                       </f:if>
-                               </f:for>
-                       </div>
-                       <f:comment>
-                               In order to allow form submit on pressing enter, a submit button is needed. See #66846
-                               The submit button is hidden as long as the save buttons are not part of the form yet.
-                       </f:comment>
-                       <f:form.submit name="mySubmit" value="Go" class="hidden" />
-               </f:form>
-       </div>
-</f:section>
index 803a34d..f0e5a87 100644 (file)
                                                </li>
                                        </f:else>
                                        <f:then>
-                                               <f:if condition="{configurationLink}">
-                                                       <li>
-                                                               <a href="{configurationLink}" class="btn btn-default">
-                                                                       <core:icon identifier="actions-system-extension-configure" /> <f:translate key="extensionList.configure" />
-                                                               </a>
-                                                       </li>
-                                               </f:if>
                                                <li>
                                                        <button class="btn btn-default distribution-openViewModule" onclick="top.goToModule('web_ViewpageView');">
                                                                <core:icon identifier="actions-document-view" /> <f:translate key="distribution.welcome.openViewModule" />
                                                        </button>
                                                </li>
                                                <li>
-                                                       <button class="btn btn-default distribution-openPageModule" onclick="top.goToModule('web_page');">
+                                                       <button class="btn btn-default distribution-openPageModule" onclick="top.goToModule('web_layout');">
                                                                <core:icon identifier="actions-open" /> <f:translate key="distribution.welcome.openPageModule" />
                                                        </button>
                                                </li>
index 68c60d3..088caf4 100644 (file)
@@ -67,7 +67,7 @@
                                        <f:if condition="{extension.ext_icon}">
                                                <img class="ext-icon" src="../{extension.siteRelPath}{extension.ext_icon}" alt="{extension.title}" />
                                        </f:if>
-                                       <em:configureExtension extension="{extension}" forceConfiguration="0" showDescription="1">{extension.title}</em:configureExtension>
+                                       <span title="{extension.description}">{extension.title}</span>
                                </td>
                                <td>
                                        {extensionKey}
@@ -84,9 +84,6 @@
                                <td>
                                        <div class="btn-group">
                                                <em:processAvailableActions extension="{extension}">
-                                                       <em:configureExtension class="btn btn-default" extension="{extension}" title="{f:translate(key:'extensionList.configure')}">
-                                                               <core:icon identifier="actions-system-extension-configure" />
-                                                       </em:configureExtension>
                                                        <em:updateScript class="btn btn-default" extensionKey="{extension.key}" />
                                                        <em:removeExtension class="btn btn-default" extension="{extension}" />
                                                        <f:link.action action="downloadExtensionZip" controller="Action" arguments="{extension:extension.key}" title="{f:translate(key:'extensionList.downloadzip')}" class="btn btn-default">
index e455b9c..ade1fd3 100644 (file)
@@ -296,79 +296,6 @@ define([
        };
 
        /**
-        * configuration properties
-        */
-       ExtensionManager.configurationFieldSupport = function() {
-               $('.t3js-emconf-offset').each(function() {
-                       var $me = $(this),
-                               $parent = $me.parent(),
-                               id = $me.attr('id'),
-                               val = $me.attr('value'),
-                               valArr = val.split(',');
-
-                       $me.attr('data-offsetfield-x', '#' + id + '_offset_x')
-                               .attr('data-offsetfield-y', '#' + id + '_offset_y')
-                               .wrap('<div class="hidden"></div>');
-
-                       var elementX = '' +
-                               '<div class="form-multigroup-item">' +
-                                       '<div class="input-group">' +
-                                               '<div class="input-group-addon">x</div>' +
-                                               '<input id="' + id + '_offset_x" class="form-control t3js-emconf-offsetfield" data-target="#' + id + '" value="' + $.trim(valArr[0]) + '">' +
-                                       '</div>' +
-                               '</div>';
-                       var elementY = '' +
-                               '<div class="form-multigroup-item">' +
-                                       '<div class="input-group">' +
-                                               '<div class="input-group-addon">y</div>' +
-                                               '<input id="' + id + '_offset_y" class="form-control t3js-emconf-offsetfield" data-target="#' + id + '" value="' + $.trim(valArr[1]) + '">' +
-                                       '</div>' +
-                               '</div>';
-
-                       var offsetGroup = '<div class="form-multigroup-wrap">' + elementX + elementY + '</div>';
-                       $parent.append(offsetGroup);
-                       $parent.find('.t3js-emconf-offset').keyup(function() {
-                               var $target = $($(this).data('target'));
-                               $target.attr(
-                                       'value',
-                                       $($target.data('offsetfield-x')).val() + ',' + $($target.data('offsetfield-y')).val()
-                               );
-                       });
-               });
-
-               $('.t3js-emconf-wrap').each(function() {
-                       var $me = $(this),
-                               $parent = $me.parent(),
-                               id = $me.attr('id'),
-                               val = $me.attr('value'),
-                               valArr = val.split('|');
-
-                       $me.attr('data-wrapfield-start', '#' + id + '_wrap_start')
-                               .attr('data-wrapfield-end', '#' + id + '_wrap_end')
-                               .wrap('<div class="hidden"></div>');
-
-                       var elementStart = '' +
-                               '<div class="form-multigroup-item">' +
-                                       '<input id="' + id + '_wrap_start" class="form-control t3js-emconf-wrapfield" data-target="#' + id + '" value="' + $.trim(valArr[0]) + '">' +
-                               '</div>';
-                       var elementEnd = '' +
-                               '<div class="form-multigroup-item">' +
-                                       '<input id="' + id + '_wrap_end" class="form-control t3js-emconf-wrapfield" data-target="#' + id + '" value="' + $.trim(valArr[1]) + '">' +
-                               '</div>';
-
-                       var wrapGroup = '<div class="form-multigroup-wrap">' + elementStart + elementEnd + '</div>';
-                       $parent.append(wrapGroup);
-                       $parent.find('.t3js-emconf-wrapfield').keyup(function() {
-                               var $target = $($(this).data('target'));
-                               $target.attr(
-                                       'value',
-                                       $($target.data('wrapfield-start')).val() + '|' + $($target.data('wrapfield-end')).val()
-                               );
-                       });
-               });
-       };
-
-       /**
         *
         * @type {{downloadPath: string}}
         */
@@ -773,8 +700,6 @@ define([
                        NProgress.start();
                });
 
-               ExtensionManager.configurationFieldSupport();
-
                SplitButtons.addPreSubmitCallback(function(e) {
                        if ($(e.target).hasClass('t3js-save-close')) {
                                $('#configurationform').append($('<input />', {type: 'hidden', name: 'tx_extensionmanager_tools_extensionmanagerextensionmanager[action]', value: 'saveAndClose'}));
diff --git a/typo3/sysext/extensionmanager/Tests/Unit/Domain/Repository/ConfigurationItemRepositoryTest.php b/typo3/sysext/extensionmanager/Tests/Unit/Domain/Repository/ConfigurationItemRepositoryTest.php
deleted file mode 100644 (file)
index e22dada..0000000
+++ /dev/null
@@ -1,291 +0,0 @@
-<?php
-namespace TYPO3\CMS\Extensionmanager\Tests\Unit\Domain\Repository;
-
-/*
- * 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\Extbase\Object\ObjectManager;
-
-/**
- * Tests for ConfigurationItemRepository
- */
-class ConfigurationItemRepositoryTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
-{
-    /**
-     * @var \TYPO3\CMS\Extensionmanager\Domain\Repository\ConfigurationItemRepository|\TYPO3\TestingFramework\Core\AccessibleObjectInterface|\PHPUnit_Framework_MockObject_MockObject
-     */
-    protected $configurationItemRepository;
-
-    /**
-     * @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface|\PHPUnit_Framework_MockObject_MockObject
-     */
-    protected $injectedObjectManagerMock;
-
-    /**
-     * Set up
-     */
-    protected function setUp()
-    {
-        // Mock system under test to make protected methods accessible
-        $this->configurationItemRepository = $this->getAccessibleMock(
-            \TYPO3\CMS\Extensionmanager\Domain\Repository\ConfigurationItemRepository::class,
-            ['dummy']
-        );
-
-        $this->injectedObjectManagerMock = $this->createMock(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface::class);
-        $this->configurationItemRepository->_set(
-            'objectManager',
-            $this->injectedObjectManagerMock
-        );
-    }
-
-    /**
-     * @test
-     */
-    public function getConfigurationArrayFromExtensionKeyReturnsSortedHierarchicArray()
-    {
-        $flatConfigurationItemArray = [
-            'item1' => [
-                'cat' => 'basic',
-                'subcat_name' => 'enable',
-                'subcat' => 'a/enable/10z',
-                'type' => 'string',
-                'label' => 'Item 1: This is the first configuration item',
-                'name' =>'item1',
-                'value' => 'one',
-                'default_value' => 'one',
-                'subcat_label' => 'Enable features',
-            ],
-            'integerValue' => [
-                'cat' => 'basic',
-                'subcat_name' => 'enable',
-                'subcat' => 'a/enable/20z',
-                'type' => 'int+',
-                'label' => 'Integer Value: Please insert a positive integer value',
-                'name' =>'integerValue',
-                'value' => '1',
-                'default_value' => '1',
-                'subcat_label' => 'Enable features',
-            ],
-            'enableJquery' => [
-                'cat' => 'advanced',
-                'subcat_name' => 'file',
-                'subcat' => 'c/file/10z',
-                'type' => 'boolean',
-                'label' => 'enableJquery: Insert jQuery plugin',
-                'name' =>'enableJquery',
-                'value' => '1',
-                'default_value' => '1',
-                'subcat_label' => 'Files',
-            ],
-        ];
-
-        $configurationItemRepository = $this->getAccessibleMock(
-            \TYPO3\CMS\Extensionmanager\Domain\Repository\ConfigurationItemRepository::class,
-            ['translate']
-        );
-        $configurationItemRepository->_set(
-            'objectManager',
-            $this->injectedObjectManagerMock
-        );
-        $configurationUtilityMock = $this->createMock(\TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility::class);
-        $configurationUtilityMock
-            ->expects($this->once())
-            ->method('getCurrentConfiguration')
-            ->will($this->returnValue($flatConfigurationItemArray));
-
-        $this->injectedObjectManagerMock
-            ->expects($this->any())
-            ->method('get')
-            ->will($this->returnValue($configurationUtilityMock));
-
-        $expectedArray = [
-            'basic' => [
-                'enable' => [
-                    'item1' => [
-                        'cat' => 'basic',
-                        'subcat_name' => 'enable',
-                        'subcat' => 'a/enable/10z',
-                        'type' => 'string',
-                        'label' => 'Item 1: This is the first configuration item',
-                        'name' =>'item1',
-                        'value' => 'one',
-                        'default_value' => 'one',
-                        'subcat_label' => 'Enable features',
-                        'labels' => [
-                            0 => 'Item 1',
-                            1 => 'This is the first configuration item'
-                        ]
-                    ],
-                    'integerValue' => [
-                        'cat' => 'basic',
-                        'subcat_name' => 'enable',
-                        'subcat' => 'a/enable/20z',
-                        'type' => 'int+',
-                        'label' => 'Integer Value: Please insert a positive integer value',
-                        'name' =>'integerValue',
-                        'value' => '1',
-                        'default_value' => '1',
-                        'subcat_label' => 'Enable features',
-                        'labels' => [
-                            0 => 'Integer Value',
-                            1 => 'Please insert a positive integer value'
-                        ]
-                    ]
-                ]
-            ],
-            'advanced' => [
-                'file' => [
-                    'enableJquery' => [
-                        'cat' => 'advanced',
-                        'subcat_name' => 'file',
-                        'subcat' => 'c/file/10z',
-                        'type' => 'boolean',
-                        'label' => 'enableJquery: Insert jQuery plugin',
-                        'name' =>'enableJquery',
-                        'value' => '1',
-                        'default_value' => '1',
-                        'subcat_label' => 'Files',
-                        'labels' => [
-                            0 => 'enableJquery',
-                            1 => 'Insert jQuery plugin'
-                        ]
-                    ],
-                ]
-            ]
-        ];
-
-        $this->assertSame(
-            $expectedArray,
-            $configurationItemRepository->_call('getConfigurationArrayFromExtensionKey', $this->getUniqueId('some_extension'))
-        );
-    }
-
-    /**
-     * @return array
-     */
-    public function extractInformationForConfigFieldsOfTypeUserAddsGenericAndTypeInformationDataProvider()
-    {
-        return [
-            [
-                [
-                    'cat' => 'basic',
-                    'subcat_name' => 'enable',
-                    'subcat' => 'a/enable/z',
-                    'type' => 'user[TYPO3\\CMS\\Saltedpasswords\\Utility\\ExtensionManagerConfigurationUtility->checkConfigurationFrontend]',
-                    'label' => 'Frontend configuration check',
-                    'name' => 'checkConfigurationFE',
-                    'value' => 0,
-                    'default_value' => 0,
-                    'comparisonGeneric' => 'TYPO3\\CMS\\Saltedpasswords\\Utility\\ExtensionManagerConfigurationUtility->checkConfigurationFrontend'
-                ]
-            ],
-            [
-                [
-                    'cat' => 'basic',
-                    'subcat_name' => 'enable',
-                    'subcat' => 'a/enable/z',
-                    'type' => 'user[TYPO3\\CMS\\Saltedpasswords\\Utility\\ExtensionManagerConfigurationUtility->checkConfigurationBackend]',
-                    'label' => 'Backend configuration check',
-                    'name' => 'checkConfigurationBE',
-                    'value' => 0,
-                    'default_value' => 0,
-                    'comparisonGeneric' => 'TYPO3\\CMS\\Saltedpasswords\\Utility\\ExtensionManagerConfigurationUtility->checkConfigurationBackend'
-                ]
-            ],
-            [
-                [
-                    'cat' => 'basic',
-                    'subcat_name' => 'enable',
-                    'subcat' => 'a/enable/z',
-                    'type' => 'user[TYPO3\\CMS\\Saltedpasswords\\Utility\\ExtensionManagerConfigurationUtility->buildHashMethodSelectorFE]',
-                    'label' => 'Hashing method for the frontend: Defines salted hashing method to use. Choose "Portable PHP password hashing" to stay compatible with other CMS (e.g. Drupal, Wordpress). Choose "MD5 salted hashing" to reuse TYPO3 passwords for OS level authentication (other servers could use TYPO3 passwords). Choose "Blowfish salted hashing" for advanced security to reuse passwords on OS level (Blowfish might not be supported on your system TODO).',
-                    'name' => 'FE.saltedPWHashingMethod',
-                    'value' => \TYPO3\CMS\Saltedpasswords\Salt\PhpassSalt::class,
-                    'default_value' => \TYPO3\CMS\Saltedpasswords\Salt\PhpassSalt::class,
-                    'comparisonGeneric' => 'TYPO3\\CMS\\Saltedpasswords\\Utility\\ExtensionManagerConfigurationUtility->buildHashMethodSelectorFE'
-                ]
-            ]
-        ];
-    }
-
-    /**
-     * @test
-     * @dataProvider extractInformationForConfigFieldsOfTypeUserAddsGenericAndTypeInformationDataProvider
-     * @param $configurationOption
-     */
-    public function extractInformationForConfigFieldsOfTypeUserAddsGenericAndTypeInformation($configurationOption)
-    {
-        $configurationOptionModified = $this->configurationItemRepository->_callRef('extractInformationForConfigFieldsOfTypeUser', $configurationOption);
-        $this->assertEquals('user', $configurationOptionModified['type']);
-        $this->assertEquals($configurationOption['comparisonGeneric'], $configurationOptionModified['generic']);
-    }
-
-    /**
-     * @test
-     */
-    public function extractInformationForConfigFieldsOfTypeOptionsAddsGenericTypeAndLabelInformation()
-    {
-        $option = [
-            'cat' => 'basic',
-            'subcat_name' => 'enable',
-            'subcat' => 'a/enable/100z',
-            'type' => 'options[Minimal (Most features disabled. Administrator needs to enable them using TypoScript. For advanced administrators only.),Typical (Most commonly used features are enabled. Select this option if you are unsure which one to use.),Demo (Show-off configuration. Includes pre-configured styles. Not for production environments.)]',
-            'label' => 'Default configuration settings',
-            'name' => 'defaultConfiguration',
-            'value' => 'Typical (Most commonly used features are enabled. Select this option if you are unsure which one to use.)',
-            'default_value' => 'Typical (Most commonly used features are enabled. Select this option if you are unsure which one to use.)',
-            'genericComparisonValue' => [
-                'Minimal (Most features disabled. Administrator needs to enable them using TypoScript. For advanced administrators only.)' => 'Minimal (Most features disabled. Administrator needs to enable them using TypoScript. For advanced administrators only.)',
-                'Typical (Most commonly used features are enabled. Select this option if you are unsure which one to use.)' => 'Typical (Most commonly used features are enabled. Select this option if you are unsure which one to use.)',
-                'Demo (Show-off configuration. Includes pre-configured styles. Not for production environments.)' => 'Demo (Show-off configuration. Includes pre-configured styles. Not for production environments.)'
-            ],
-            'typeComparisonValue' => 'options'
-        ];
-        $optionModified = $this->configurationItemRepository->_callRef('extractInformationForConfigFieldsOfTypeOptions', $option);
-        $this->assertArrayHasKey('generic', $optionModified);
-        $this->assertArrayHasKey('type', $optionModified);
-        $this->assertArrayHasKey('label', $optionModified);
-        $this->assertEquals($option['genericComparisonValue'], $optionModified['generic']);
-        $this->assertEquals($option['typeComparisonValue'], $optionModified['type']);
-    }
-
-    /**
-     * @test
-     */
-    public function extractInformationForConfigFieldsOfTypeOptionsWithLabelsAndValuesAddsGenericTypeAndLabelInformation()
-    {
-        $option = [
-            'cat' => 'basic',
-            'subcat_name' => 'enable',
-            'subcat' => 'a/enable/100z',
-            'type' => 'options[Minimal (Most features disabled. Administrator needs to enable them using TypoScript. For advanced administrators only.)=MINIMAL,Typical (Most commonly used features are enabled. Select this option if you are unsure which one to use.) = TYPICAL,Demo (Show-off configuration. Includes pre-configured styles. Not for production environments.)=DEMO]',
-            'label' => 'Default configuration settings',
-            'name' => 'defaultConfiguration',
-            'value' => 'Typical (Most commonly used features are enabled. Select this option if you are unsure which one to use.)',
-            'default_value' => 'Typical (Most commonly used features are enabled. Select this option if you are unsure which one to use.)',
-            'genericComparisonValue' => [
-                'Minimal (Most features disabled. Administrator needs to enable them using TypoScript. For advanced administrators only.)' => 'MINIMAL',
-                'Typical (Most commonly used features are enabled. Select this option if you are unsure which one to use.)' => 'TYPICAL',
-                'Demo (Show-off configuration. Includes pre-configured styles. Not for production environments.)' => 'DEMO'
-            ],
-            'typeComparisonValue' => 'options'
-        ];
-        $optionModified = $this->configurationItemRepository->_callRef('extractInformationForConfigFieldsOfTypeOptions', $option);
-        $this->assertArrayHasKey('generic', $optionModified);
-        $this->assertArrayHasKey('type', $optionModified);
-        $this->assertArrayHasKey('label', $optionModified);
-        $this->assertEquals($option['genericComparisonValue'], $optionModified['generic']);
-        $this->assertEquals($option['typeComparisonValue'], $optionModified['type']);
-    }
-}
diff --git a/typo3/sysext/extensionmanager/Tests/Unit/Utility/ConfigurationUtilityTest.php b/typo3/sysext/extensionmanager/Tests/Unit/Utility/ConfigurationUtilityTest.php
deleted file mode 100644 (file)
index c838ed6..0000000
+++ /dev/null
@@ -1,325 +0,0 @@
-<?php
-namespace TYPO3\CMS\Extensionmanager\Tests\Unit\Utility;
-
-/*
- * 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\Extensionmanager\Utility\ConfigurationUtility;
-
-/**
- * Configuration utility test
- */
-class ConfigurationUtilityTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
-{
-    /**
-     * @test
-     */
-    public function getCurrentConfigurationReturnsExtensionConfigurationAsValuedConfiguration()
-    {
-        /** @var $configurationUtility ConfigurationUtility|\TYPO3\TestingFramework\Core\AccessibleObjectInterface|\PHPUnit_Framework_MockObject_MockObject */
-        $configurationUtility = $this->getMockBuilder(ConfigurationUtility::class)
-            ->setMethods(['getDefaultConfigurationFromExtConfTemplateAsValuedArray'])
-            ->getMock();
-        $configurationUtility
-            ->expects($this->once())
-            ->method('getDefaultConfigurationFromExtConfTemplateAsValuedArray')
-            ->will($this->returnValue([]));
-        $extensionKey = $this->getUniqueId('some-extension');
-
-        $currentConfiguration = [
-            'key1' => 'value1',
-            'key2.' => [
-                'subkey1' => 'value2'
-            ]
-        ];
-
-        $expected = [
-            'key1' => [
-                'value' => 'value1',
-            ],
-            'key2.subkey1' => [
-                'value' => 'value2',
-            ],
-        ];
-
-        $GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'][$extensionKey] = serialize($currentConfiguration);
-        $actual = $configurationUtility->getCurrentConfiguration($extensionKey);
-        $this->assertEquals($expected, $actual);
-    }
-
-    /**
-     * @test
-     */
-    public function getCurrentConfigurationReturnsExtensionConfigurationWithExtconfBasedConfigurationPrioritized()
-    {
-        /** @var $configurationUtility \TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility|\TYPO3\TestingFramework\Core\AccessibleObjectInterface|\PHPUnit_Framework_MockObject_MockObject */
-        $configurationUtility = $this->getMockBuilder(\TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility::class)
-            ->setMethods(['getDefaultConfigurationFromExtConfTemplateAsValuedArray'])
-            ->getMock();
-        $configurationUtility
-            ->expects($this->once())
-            ->method('getDefaultConfigurationFromExtConfTemplateAsValuedArray')
-            ->will($this->returnValue([]));
-        $extensionKey = $this->getUniqueId('some-extension');
-
-        $serializedConfiguration = [
-            'key1' => 'value1',
-            'key2.' => [
-                'subkey1' => 'somevalue'
-            ]
-        ];
-        $currentExtconfConfiguration = [
-            'key1' => 'value1',
-            'key2.' => [
-                'subkey1' => 'overwritten'
-            ]
-        ];
-
-        $expected = [
-            'key1' => [
-                'value' => 'value1',
-            ],
-            'key2.subkey1' => [
-                'value' => 'overwritten',
-            ],
-        ];
-
-        $GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'][$extensionKey] = serialize($serializedConfiguration);
-        $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'][$extensionKey] = $currentExtconfConfiguration;
-        $actual = $configurationUtility->getCurrentConfiguration($extensionKey);
-        $this->assertEquals($expected, $actual);
-    }
-
-    /**
-     * @test
-     */
-    public function mergeDefaultConfigurationReturnsEmptyArrayIfNoConfigurationForExtensionExists()
-    {
-        $configurationUtility = new ConfigurationUtility();
-        $result = $configurationUtility->getCurrentConfiguration('not_existing_extension');
-        self::assertSame([], $result);
-    }
-
-    /**
-     * @test
-     */
-    public function getDefaultConfigurationFromExtConfTemplateAsValuedArrayReturnsExpectedExampleArray()
-    {
-        /** @var $configurationUtility \TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility|\TYPO3\TestingFramework\Core\AccessibleObjectInterface|\PHPUnit_Framework_MockObject_MockObject */
-        $configurationUtility = $this->getAccessibleMock(
-            \TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility::class,
-            ['getDefaultConfigurationRawString']
-        );
-        $configurationUtility
-            ->expects($this->once())
-            ->method('getDefaultConfigurationRawString')
-            ->will($this->returnValue(file_get_contents(__DIR__ . '/Fixtures/ext_conf_template.txt')));
-
-        $objectManagerMock = $this->getMockBuilder(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface::class)->getMock();
-        $configurationUtility->_set('objectManager', $objectManagerMock);
-
-        $expected = [
-            'checkConfigurationFE' => [
-                'cat' => 'basic',
-                'subcat_name' => 'enable',
-                'subcat' => 'a/enable/1z',
-                'type' => 'user[TYPO3\\CMS\\Saltedpasswords\\Utility\\ExtensionManagerConfigurationUtility->checkConfigurationFrontend]',
-                'label' => 'Frontend configuration check',
-                'name' => 'checkConfigurationFE',
-                'value' => '0',
-                'default_value' => '0',
-                'subcat_label' => 'Enable features',
-            ],
-            'BE.forceSalted' => [
-                'cat' => 'backend',
-                'subcat' => 'x/2z',
-                'type' => 'boolean',
-                'label' => 'Force salted passwords: Enforce usage of SaltedPasswords. Old MD5 hashed passwords will stop working.',
-                'name' => 'BE.forceSalted',
-                'value' => '0',
-                'default_value' => '0',
-            ],
-        ];
-
-        $result = $configurationUtility->getDefaultConfigurationFromExtConfTemplateAsValuedArray($this->getUniqueId('some_extension'));
-        $this->assertEquals($expected, $result);
-    }
-
-    /**
-     * Data provider for convertValuedToNestedConfiguration
-     *
-     * @return array
-     */
-    public function convertValuedToNestedConfigurationDataProvider()
-    {
-        return [
-            'plain array' => [
-                [
-                    'first' => [
-                        'value' => 'value1'
-                    ],
-                    'second' => [
-                        'value' => 'value2'
-                    ]
-                ],
-                [
-                    'first' => 'value1',
-                    'second' => 'value2'
-                ]
-            ],
-            'nested value with 2 levels' => [
-                [
-                    'first.firstSub' => [
-                        'value' => 'value1'
-                    ],
-                    'second.secondSub' => [
-                        'value' => 'value2'
-                    ]
-                ],
-                [
-                    'first.' => [
-                        'firstSub' => 'value1'
-                    ],
-                    'second.' => [
-                        'secondSub' => 'value2'
-                    ]
-                ]
-            ],
-            'nested value with 3 levels' => [
-                [
-                    'first.firstSub.firstSubSub' => [
-                        'value' => 'value1'
-                    ],
-                    'second.secondSub.secondSubSub' => [
-                        'value' => 'value2'
-                    ]
-                ],
-                [
-                    'first.' => [
-                        'firstSub.' => [
-                            'firstSubSub' => 'value1'
-                        ]
-                    ],
-                    'second.' => [
-                        'secondSub.' => [
-                            'secondSubSub' => 'value2'
-                        ]
-                    ]
-                ]
-            ],
-            'mixed nested value with 2 levels' => [
-                [
-                    'first' => [
-                        'value' => 'firstValue'
-                    ],
-                    'first.firstSub' => [
-                        'value' => 'value1'
-                    ],
-                    'second.secondSub' => [
-                        'value' => 'value2'
-                    ]
-                ],
-                [
-                    'first' => 'firstValue',
-                    'first.' => [
-                        'firstSub' => 'value1'
-                    ],
-                    'second.' => [
-                        'secondSub' => 'value2'
-                    ]
-                ]
-            ]
-        ];
-    }
-
-    /**
-     * @test
-     * @dataProvider convertValuedToNestedConfigurationDataProvider
-     *
-     * @param array $configuration
-     * @param array $expected
-     */
-    public function convertValuedToNestedConfiguration(array $configuration, array $expected)
-    {
-        /** @var $subject \TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface */
-        $subject = $this->getAccessibleMock(\TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility::class, ['dummy'], [], '', false);
-        $this->assertEquals($expected, $subject->convertValuedToNestedConfiguration($configuration));
-    }
-
-    /**
-     * Data provider for convertNestedToValuedConfiguration
-     *
-     * @return array
-     */
-    public function convertNestedToValuedConfigurationDataProvider()
-    {
-        return [
-            'plain array' => [
-                [
-                    'first' => 'value1',
-                    'second' => 'value2'
-                ],
-                [
-                    'first' => ['value' => 'value1'],
-                    'second' => ['value' => 'value2'],
-                ]
-            ],
-            'two levels' => [
-                [
-                    'first.' => ['firstSub' => 'value1'],
-                    'second.' => ['firstSub' => 'value2'],
-                ],
-                [
-                    'first.firstSub' => ['value' => 'value1'],
-                    'second.firstSub' => ['value' => 'value2'],
-                ]
-            ],
-            'three levels' => [
-                [
-                    'first.' => ['firstSub.' => ['firstSubSub' => 'value1']],
-                    'second.' => ['firstSub.' => ['firstSubSub' => 'value2']]
-                ],
-                [
-                    'first.firstSub.firstSubSub' => ['value' => 'value1'],
-                    'second.firstSub.firstSubSub' => ['value' => 'value2'],
-                ]
-            ],
-            'mixed' => [
-                [
-                    'first.' => ['firstSub' => 'value1'],
-                    'second.' => ['firstSub.' => ['firstSubSub' => 'value2']],
-                    'third' => 'value3'
-                ],
-                [
-                    'first.firstSub' => ['value' => 'value1'],
-                    'second.firstSub.firstSubSub' => ['value' => 'value2'],
-                    'third' => ['value' => 'value3']
-                ]
-            ]
-        ];
-    }
-
-    /**
-     * @test
-     * @dataProvider convertNestedToValuedConfigurationDataProvider
-     *
-     * @param array $configuration
-     * @param array $expected
-     */
-    public function convertNestedToValuedConfiguration(array $configuration, array $expected)
-    {
-        /** @var $subject \TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface */
-        $subject = $this->getAccessibleMock(\TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility::class, ['dummy'], [], '', false);
-        $this->assertEquals($expected, $subject->convertNestedToValuedConfiguration($configuration));
-    }
-}
diff --git a/typo3/sysext/extensionmanager/Tests/Unit/Utility/Fixtures/ext_conf_template.txt b/typo3/sysext/extensionmanager/Tests/Unit/Utility/Fixtures/ext_conf_template.txt
deleted file mode 100644 (file)
index 1d8ab71..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-# cat=Basic/enable; type=user[TYPO3\CMS\Saltedpasswords\Utility\ExtensionManagerConfigurationUtility->checkConfigurationFrontend]; label=Frontend configuration check
-checkConfigurationFE=0
-
-# cat=Backend; type=boolean; label=Force salted passwords: Enforce usage of SaltedPasswords. Old MD5 hashed passwords will stop working.
-BE.forceSalted = 0
index e7242e2..6fff4aa 100644 (file)
@@ -2,8 +2,10 @@
 defined('TYPO3_MODE') or die();
 
 // Register extension list update task
-$extConf = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['extensionmanager'], ['allowed_classes' => false]);
-if (empty($extConf['offlineMode'])) {
+$offlineMode = (bool)\TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(
+    \TYPO3\CMS\Core\Configuration\ExtensionConfiguration::class
+)->get('extensionmanager', 'offlineMode');
+if (!$offlineMode) {
     $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][\TYPO3\CMS\Extensionmanager\Task\UpdateExtensionListTask::class] = [
         'extension' => 'extensionmanager',
         'title' => 'LLL:EXT:extensionmanager/Resources/Private/Language/locallang.xlf:task.updateExtensionListTask.name',
@@ -11,6 +13,7 @@ if (empty($extConf['offlineMode'])) {
         'additionalFields' => '',
     ];
 }
+unset($offlineMode);
 
 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['extbase']['commandControllers'][] = \TYPO3\CMS\Extensionmanager\Command\ExtensionCommandController::class;
 
@@ -37,8 +40,6 @@ if (TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_BE) {
     unset($signalSlotDispatcher);
 }
 
-unset($extConf);
-
 // Register extension status report system
 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['reports']['tx_reports']['status']['providers']['Extension Manager'][] =
     \TYPO3\CMS\Extensionmanager\Report\ExtensionStatus::class;
index a9bc649..9cbab0a 100644 (file)
@@ -16,6 +16,7 @@ namespace TYPO3\CMS\IndexedSearch\Controller;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Backend\View\BackendTemplateView;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Localization\LanguageService;
@@ -150,7 +151,7 @@ class AdministrationController extends ActionController
     public function initializeAction()
     {
         $this->pageUid = (int)GeneralUtility::_GET('id');
-        $this->indexerConfig = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['indexed_search'], ['allowed_classes' => false]);
+        $this->indexerConfig = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('indexed_search');
         $this->enableMetaphoneSearch = (bool)$this->indexerConfig['enableMetaphoneSearch'];
         $this->indexer = GeneralUtility::makeInstance(Indexer::class);
 
index fbe71c3..167980c 100644 (file)
@@ -15,6 +15,7 @@ namespace TYPO3\CMS\IndexedSearch\Controller;
  */
 
 use TYPO3\CMS\Core\Charset\CharsetConverter;
+use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
 use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer;
@@ -134,7 +135,7 @@ class SearchController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionControlle
     protected $iconFileNameCache = [];
 
     /**
-     * Indexer configuration, coming from $GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['indexed_search']
+     * Indexer configuration, coming from TYPO3's system configuration for EXT:indexed_search
      *
      * @var array
      */
@@ -198,7 +199,7 @@ class SearchController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionControlle
             $searchData = array_merge($this->settings['defaultOptions'], $searchData);
         }
         // Indexer configuration from Extension Manager interface:
-        $this->indexerConfig = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['indexed_search'], ['allowed_classes' => false]);
+        $this->indexerConfig = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('indexed_search');
         $this->enableMetaphoneSearch = (bool)$this->indexerConfig['enableMetaphoneSearch'];
         $this->initializeExternalParsers();
         // If "_sections" is set, this value overrides any existing value.
index bec21d0..e3ffce7 100644 (file)
@@ -15,6 +15,7 @@ namespace TYPO3\CMS\IndexedSearch\Domain\Repository;
  */
 
 use Doctrine\DBAL\Driver\Statement;
+use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
 use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\QueryHelper;
@@ -208,20 +209,12 @@ class IndexSearchRepository
      */
     public function doSearch($searchWords, $freeIndexUid = -1)
     {
-        // unserializing the configuration so we can use it here:
-        $extConf = [];
-        if (isset($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['indexed_search'])) {
-            $extConf = unserialize(
-                $GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['indexed_search'],
-                ['allowed_classes' => false]
-            );
-        }
-
+        $useMysqlFulltext = (bool)GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('indexed_search', 'useMysqlFulltext');
         // Getting SQL result pointer:
         $this->getTimeTracker()->push('Searching result');
         if ($hookObj = &$this->hookRequest('getResultRows_SQLpointer')) {
             $result = $hookObj->getResultRows_SQLpointer($searchWords, $freeIndexUid);
-        } elseif (isset($extConf['useMysqlFulltext']) && $extConf['useMysqlFulltext'] === '1') {
+        } elseif ($useMysqlFulltext) {
             $result = $this->getResultRows_SQLpointerMysqlFulltext($searchWords, $freeIndexUid);
         } else {
             $result = $this->getResultRows_SQLpointer($searchWords, $freeIndexUid);
index d31e2a8..31c75e9 100644 (file)
@@ -14,6 +14,7 @@ namespace TYPO3\CMS\IndexedSearch;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
 use TYPO3\CMS\Core\Utility\CommandUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
@@ -76,7 +77,7 @@ class FileContentParser
     public function initParser($extension)
     {
         // Then read indexer-config and set if appropriate:
-        $indexerConfig = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['indexed_search'], ['allowed_classes' => false]);
+        $indexerConfig = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('indexed_search');
         // If windows, apply extension to tool name:
         $exe = TYPO3_OS === 'WIN' ? '.exe' : '';
         // lg
@@ -288,7 +289,7 @@ class FileContentParser
     public function searchTypeMediaTitle($extension)
     {
         // Read indexer-config
-        $indexerConfig = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['indexed_search'], ['allowed_classes' => false]);
+        $indexerConfig = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('indexed_search');
         // Ignore extensions
         $ignoreExtensions = GeneralUtility::trimExplode(',', strtolower($indexerConfig['ignoreExtensions']), true);
         if (in_array($extension, $ignoreExtensions)) {
index 57c0dbc..f4afc54 100644 (file)
@@ -14,6 +14,7 @@ namespace TYPO3\CMS\IndexedSearch;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
 use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\TimeTracker\TimeTracker;
@@ -133,7 +134,7 @@ class Indexer
     public $indexerConfig = [];
 
     /**
-     * Indexer configuration, coming from $GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['indexed_search']
+     * Indexer configuration, coming from TYPO3's system configuration for EXT:indexed_search
      *
      * @var array
      */
@@ -251,7 +252,7 @@ class Indexer
     public function hook_indexContent(&$pObj)
     {
         // Indexer configuration from Extension Manager interface:
-        $indexerConfig = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['indexed_search'], ['allowed_classes' => false]);
+        $disableFrontendIndexing = (bool)GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('indexed_search', 'disableFrontendIndexing');
         // Crawler activation:
         // Requirements are that the crawler is loaded, a crawler session is running and re-indexing requested as processing instruction:
         if (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('crawler') && $pObj->applicationData['tx_crawler']['running'] && in_array('tx_indexedsearch_reindex', $pObj->applicationData['tx_crawler']['parameters']['procInstructions'])) {
@@ -265,7 +266,7 @@ class Indexer
         // Determine if page should be indexed, and if so, configure and initialize indexer
         if ($pObj->config['config']['index_enable']) {
             $this->log_push('Index page', '');
-            if (!$indexerConfig['disableFrontendIndexing'] || $this->crawlerActive) {
+            if (!$disableFrontendIndexing || $this->crawlerActive) {
                 if (!$pObj->page['no_search']) {
                     if (!$pObj->no_cache) {
                         if ((int)$pObj->sys_language_uid === (int)$pObj->sys_language_content) {
@@ -469,7 +470,7 @@ class Indexer
         // Setting phash / phash_grouping which identifies the indexed page based on some of these variables:
         $this->setT3Hashes();
         // Indexer configuration from Extension Manager interface:
-        $this->indexerConfig = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['indexed_search'], ['allowed_classes' => false]);
+        $this->indexerConfig = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('indexed_search');
         $this->tstamp_minAge = MathUtility::forceIntegerInRange($this->indexerConfig['minAge'] * 3600, 0);
         $this->tstamp_maxAge = MathUtility::forceIntegerInRange($this->indexerConfig['maxAge'] * 3600, 0);
         $this->maxExternalFiles = MathUtility::forceIntegerInRange($this->indexerConfig['maxExternalFiles'], 0, 1000, 5);
index 21ce31e..9f798ca 100644 (file)
@@ -13,6 +13,8 @@ namespace TYPO3\CMS\IndexedSearch\Service;
  *
  * The TYPO3 project - inspiring people to share!
  */
+use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
  * This service provides the mysql specific changes of the schema definition
@@ -28,16 +30,8 @@ class DatabaseSchemaService
      */
     public function addMysqlFulltextIndex(array $sqlString)
     {
-        // Check again if the extension flag is enabled to be on the safe side
-        // even if the slot registration is moved around in ext_localconf
-        $extConf = [];
-        if (isset($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['indexed_search'])) {
-            $extConf = unserialize(
-                $GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['indexed_search'],
-                ['allowed_classes' => false]
-            );
-        }
-        if (isset($extConf['useMysqlFulltext']) && $extConf['useMysqlFulltext'] === '1') {
+        $useMysqlFulltext = (bool)GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('indexed_search', 'useMysqlFulltext');
+        if ($useMysqlFulltext) {
             // @todo: With MySQL 5.7 fulltext index on InnoDB is possible, check for that and keep inno if so.
             $sqlString[] = 'CREATE TABLE index_fulltext ('
                 . LF . 'fulltextdata mediumtext,'
diff --git a/typo3/sysext/indexed_search/Tests/Functional/Tca/IndexConfigVisibleFieldsTest.php b/typo3/sysext/indexed_search/Tests/Functional/Tca/IndexConfigVisibleFieldsTest.php
deleted file mode 100644 (file)
index 56648fb..0000000
+++ /dev/null
@@ -1,116 +0,0 @@
-<?php
-namespace TYPO3\CMS\IndexedSearch\Tests\Functional\Tca;
-
-/*
- * 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\Backend\Tests\Functional\Form\FormTestService;
-use TYPO3\CMS\Core\Localization\LanguageService;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-
-class IndexConfigVisibleFieldsTest extends \TYPO3\TestingFramework\Core\Functional\FunctionalTestCase
-{
-    protected static $commonIndexConfigFields = [
-        'title',
-        'starttime',
-        'hidden',
-        'description',
-        'timer_next_indexing',
-        'timer_offset',
-        'timer_frequency',
-        'type',
-    ];
-
-    protected static $indexConfigFieldsByType = [
-        '0' => [],
-        '1' => [
-            'table2index',
-            'alternative_source_pid',
-            'fieldlist',
-            'get_params',
-            'chashcalc',
-            'recordsbatch',
-            'records_indexonchange',
-        ],
-        '2' => [
-            'filepath',
-            'extensions',
-            'depth',
-        ],
-        '3' => [
-            'externalUrl',
-            'depth',
-            'url_deny',
-        ],
-        '4' => [
-            'alternative_source_pid',
-            'depth',
-        ],
-    ];
-
-    protected static $metaIndexConfigFields = [
-        'title',
-        'description',
-        'type',
-        'indexcfgs',
-    ];
-
-    protected $coreExtensionsToLoad = ['indexed_search'];
-
-    /**
-     * @test
-     */
-    public function indexConfigFormContainsExpectedFields()
-    {
-        $this->setUpBackendUserFromFixture(1);
-        $GLOBALS['LANG'] = GeneralUtility::makeInstance(LanguageService::class);
-
-        $formEngineTestService = GeneralUtility::makeInstance(FormTestService::class);
-
-        foreach (static::$indexConfigFieldsByType as $type => $additionalFields) {
-            $formResult = $formEngineTestService->createNewRecordForm('index_config', ['type' => $type]);
-            $expectedFields = array_merge(static::$commonIndexConfigFields, $additionalFields);
-
-            foreach ($expectedFields as $expectedField) {
-                $this->assertNotFalse(
-                    $formEngineTestService->formHtmlContainsField($expectedField, $formResult['html']),
-                    'The field ' . $expectedField . ' is not in the form HTML for index type ' . $type
-                );
-            }
-
-            $this->assertNotFalse(
-                strpos($formResult['html'], 'Session ID'),
-                'The field Session ID is not in the form HTML'
-            );
-        }
-    }
-
-    /**
-     * @test
-     */
-    public function indexConfigFormContainsExpectedFieldsForMetaConfiguration()
-    {
-        $this->setUpBackendUserFromFixture(1);
-        $GLOBALS['LANG'] = GeneralUtility::makeInstance(LanguageService::class);
-
-        $formEngineTestService = GeneralUtility::makeInstance(FormTestService::class);
-        $formResult = $formEngineTestService->createNewRecordForm('index_config', ['type' => '5']);
-
-        foreach (static::$metaIndexConfigFields as $expectedField) {
-            $this->assertNotFalse(
-                $formEngineTestService->formHtmlContainsField($expectedField, $formResult['html']),
-                'The field ' . $expectedField . ' is not in the form HTML for index type ' . $type
-            );
-        }
-    }
-}
index 2e34627..4bff46a 100644 (file)
@@ -44,16 +44,11 @@ $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['external_parsers'] = [
     'tif'  => \TYPO3\CMS\IndexedSearch\FileContentParser::class
 ];
 
-// unserializing the configuration so we can use it here:
-$extConf = [];
-if (isset($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['indexed_search'])) {
-    $extConf = unserialize(
-        $GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['indexed_search'],
-        ['allowed_classes' => false]
-    );
-}
+$extConf = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(
+    \TYPO3\CMS\Core\Configuration\ExtensionConfiguration::class
+)->get('indexed_search');
 
-if (isset($extConf['useMysqlFulltext']) && $extConf['useMysqlFulltext'] === '1') {
+if (isset($extConf['useMysqlFulltext']) && (bool)$extConf['useMysqlFulltext']) {
     // Use all index_* tables except "index_rel" and "index_words"
     $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['use_tables'] =
         'index_phash,index_fulltext,index_section,index_grlist,index_stat_search,index_stat_word,index_debug,index_config';
index 9523325..7dd54bb 100644 (file)
@@ -17,9 +17,11 @@ namespace TYPO3\CMS\Install\Controller;
 
 use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;
+use TYPO3\CMS\Core\Configuration\ConfigurationManager;
 use TYPO3\CMS\Core\Http\HtmlResponse;
 use TYPO3\CMS\Core\Http\JsonResponse;
 use TYPO3\CMS\Install\Service\Exception\ConfigurationChangedException;
+use TYPO3\CMS\Install\Service\ExtensionConfigurationService;
 use TYPO3\CMS\Install\Service\SilentConfigurationUpgradeService;
 
 /**
@@ -57,7 +59,8 @@ class LayoutController extends AbstractController
 
     /**
      * Return a json response with the main HTML layout body: Toolbar, main menu and
-     * doc header in standalone, doc header only in backend context.
+     * doc header in standalone, doc header only in backend context. Silent updaters
+     * are executed before this main view is loaded.
      *
      * @param ServerRequestInterface $request
      * @return ResponseInterface
@@ -89,4 +92,68 @@ class LayoutController extends AbstractController
             'success' => $success,
         ]);
     }
+
+    /**
+     * Legacy ajax call. This silent updater takes care that all extensions configured in LocalConfiguration
+     * EXT/extConf serialized array are "upmerged" to arrays within EXTENSIONS if this extension does not
+     * exist in EXTENSIONS yet.
+     *
+     * @return ResponseInterface
+     * @deprecated since core v9, will be removed with core v10
+     */
+    public function executeSilentLegacyExtConfExtensionConfigurationUpdateAction(): ResponseInterface
+    {
+        $configurationManager = new ConfigurationManager();
+        $oldExtConfSettings = $configurationManager->getConfigurationValueByPath('EXT/extConf');
+        $newExtensionSettings = $configurationManager->getConfigurationValueByPath('EXTENSIONS');
+        foreach ($oldExtConfSettings as $extensionName => $extensionSettings) {
+            if (!array_key_exists($extensionName, $newExtensionSettings)) {
+                $newExtensionSettings = $this->removeDotsFromArrayKeysRecursive(unserialize($extensionSettings, ['allowed_classes' => false]));
+                $configurationManager->setLocalConfigurationValueByPath('EXTENSIONS/' . $extensionName, $newExtensionSettings);
+            }
+        }
+        return new JsonResponse([
+            'success' => true,
+        ]);
+    }
+
+    /**
+     * Synchronize TYPO3_CONF_VARS['EXTENSIONS'] with possibly new defaults from extensions
+     * ext_conf_template.txt files. This make LocalConfiguration the only source of truth for
+     * extension configuration and it is always up to date, also if an extension has been
+     * updated.
+     *
+     * @return ResponseInterface
+     */
+    public function executeSilentExtensionConfigurationSynchronizationAction(): ResponseInterface
+    {
+        $extensionConfigurationService = new ExtensionConfigurationService();
+        $extensionConfigurationService->synchronizeExtConfTemplateWithLocalConfigurationOfAllExtensions();
+        return new JsonResponse([
+            'success' => true,
+        ]);
+    }
+
+    /**
+     * Helper method for executeSilentLegacyExtConfExtensionConfigurationUpdateAction(). Old EXT/extConf
+     * settings have dots at the end of array keys if nested arrays were used. The new configuration does
+     * not use this funny nested representation anymore. The method removes all dots at the end of given
+     * array keys recursive to do this transition.
+     *
+     * @param array $settings
+     * @return array New settings
+     * @deprecated since core v9, will be removed with core v10 along with executeSilentLegacyExtConfExtensionConfigurationUpdateAction()
+     */
+    private function removeDotsFromArrayKeysRecursive(array $settings): array
+    {
+        $settingsWithoutDots = [];
+        foreach ($settings as $key => $value) {
+            if (is_array($value)) {
+                $settingsWithoutDots[rtrim($key, '.')] = $this->removeDotsFromArrayKeysRecursive($value);
+            } else {
+                $settingsWithoutDots[$key] = $value;
+            }
+        }
+        return $settingsWithoutDots;
+    }
 }
index 35d4292..f24a84e 100644 (file)
@@ -18,6 +18,8 @@ namespace TYPO3\CMS\Install\Controller;
 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\Core\Bootstrap;
 use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
@@ -25,9 +27,12 @@ use TYPO3\CMS\Core\FormProtection\InstallToolFormProtection;
 use TYPO3\CMS\Core\Http\JsonResponse;
 use TYPO3\CMS\Core\Messaging\FlashMessage;
 use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
+use TYPO3\CMS\Core\Package\PackageManager;
+use TYPO3\CMS\Core\Utility\ArrayUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
 use TYPO3\CMS\Install\Configuration\FeatureManager;
+use TYPO3\CMS\Install\Service\ExtensionConfigurationService;
 use TYPO3\CMS\Install\Service\LocalConfigurationValueService;
 use TYPO3\CMS\Saltedpasswords\Salt\SaltFactory;
 
@@ -308,4 +313,65 @@ class SettingsController extends AbstractController
             'status' => $messages,
         ]);
     }
+
+    /**
+     * Render a list of extensions with their configuration form.
+     *
+     * @param ServerRequestInterface $request
+     * @return ResponseInterface
+     */
+    public function extensionConfigurationGetContentAction(ServerRequestInterface $request): ResponseInterface
+    {
+        // Extension configuration needs initialized $GLOBALS['LANG']
+        Bootstrap::getInstance()->initializeLanguageObject();
+        $extensionConfigurationService = new ExtensionConfigurationService();
+        $extensionsWithConfigurations = [];
+        $activePackages = GeneralUtility::makeInstance(PackageManager::class)->getActivePackages();
+        foreach ($activePackages as $extensionKey => $activePackage) {
+            if (@file_exists($activePackage->getPackagePath() . 'ext_conf_template.txt')) {
+                $extensionsWithConfigurations[$extensionKey] = [
+                    'packageInfo' => $activePackage,
+                    'configuration' => $extensionConfigurationService->getConfigurationPreparedForView($extensionKey),
+                ];
+            }
+        }
+        $formProtection = FormProtectionFactory::get(InstallToolFormProtection::class);
+        $view = $this->initializeStandaloneView($request, 'Settings/ExtensionConfigurationGetContent.html');
+        $view->assignMultiple([
+            'extensionsWithConfigurations' => $extensionsWithConfigurations,
+            'extensionConfigurationWriteToken' => $formProtection->generateToken('installTool', 'extensionConfigurationWrite'),
+        ]);
+        return new JsonResponse([
+            'success' => true,
+            'html' => $view->render(),
+        ]);
+    }
+
+    /**
+     * Write extension configuration
+     *
+     * @param ServerRequestInterface $request
+     * @return ResponseInterface
+     */
+    public function extensionConfigurationWriteAction(ServerRequestInterface $request): ResponseInterface
+    {
+        $extensionKey = $request->getParsedBody()['install']['extensionKey'];
+        $configuration = $request->getParsedBody()['install']['extensionConfiguration'];
+        $nestedConfiguration = [];
+        foreach ($configuration as $configKey => $value) {
+            $nestedConfiguration = ArrayUtility::setValueByPath($nestedConfiguration, $configKey, $value, '.');
+        }
+        (new ExtensionConfiguration())->set($extensionKey, '', $nestedConfiguration);
+        $messages = [
+            new FlashMessage(
+                '',
+                'Successfully saved configuration for extension "' . $extensionKey . '"',
+                FlashMessage::OK
+            )
+        ];
+        return new JsonResponse([
+            'success' => true,
+            'status' => $messages,
+        ]);
+    }
 }
diff --git a/typo3/sysext/install/Classes/Service/ExtensionConfigurationService.php b/typo3/sysext/install/Classes/Service/ExtensionConfigurationService.php
new file mode 100644 (file)
index 0000000..2fb3124
--- /dev/null
@@ -0,0 +1,735 @@
+<?php
+declare(strict_types=1);
+namespace TYPO3\CMS\Install\Service;
+
+/*
+ * 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\Exception\ExtensionConfigurationExtensionNotConfiguredException;
+use TYPO3\CMS\Core\Configuration\Exception\ExtensionConfigurationPathDoesNotExistException;
+use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
+use TYPO3\CMS\Core\Localization\LanguageService;
+use TYPO3\CMS\Core\Package\PackageManager;
+use TYPO3\CMS\Core\Utility\ArrayUtility;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Utility for dealing with ext_conf_template settings and their instance
+ * specific LocalConfiguration settings. This class is @internal and only
+ * used by extension manager and install tool itself.
+ *
+ * Extension authors should use TYPO3\CMS\Core\Configuration\ExtensionConfiguration
+ * class to get() and set() extension configuration settings.
+ *
+ * @internal
+ */
+class ExtensionConfigurationService
+{
+    /**
+     * TypoScript hierarchy being build.
+     * Used parsing ext_conf_template.txt
+     *
+     * @var array
+     */
+    protected $setup = [];
+
+    /**
+     * Raw data, the input string exploded by LF.
+     * Used parsing ext_conf_template.txt
+     *
+     * @var array
+     */
+    protected $raw;
+
+    /**
+     * Pointer to entry in raw data array.
+     * Used parsing ext_conf_template.txt
+     *
+     * @var int
+     */
+    protected $rawPointer = 0;
+
+    /**
+     * Holding the value of the last comment
+     * Used parsing ext_conf_template.txt
+     *
+     * @var string
+     */
+    protected $lastComment = '';
+
+    /**
+     * Internal flag to create a multi-line comment (one of those like /* ... * /)
+     * Used parsing ext_conf_template.txt
+     *
+     * @var bool
+     */
+    protected $commentSet = false;
+
+    /**
+     * Internally set, when in brace. Counter.
+     * Used parsing ext_conf_template.txt
+     *
+     * @var int
+     */
+    protected $inBrace = 0;
+
+    /**
+     * This will be filled with the available categories of the current template.
+     * Used parsing ext_conf_template.txt
+     *
+     * @var array
+     */
+    protected $subCategories = [
+        // Standard categories:
+        'enable' => ['Enable features', 'a'],
+        'dims' => ['Dimensions, widths, heights, pixels', 'b'],
+        'file' => ['Files', 'c'],
+        'typo' => ['Typography', 'd'],
+        'color' => ['Colors', 'e'],
+        'links' => ['Links and targets', 'f'],
+        'language' => ['Language specific constants', 'g'],
+        // subcategories based on the default content elements
+        'cheader' => ['Content: \'Header\'', 'ma'],
+        'cheader_g' => ['Content: \'Header\', Graphical', 'ma'],
+        'ctext' => ['Content: \'Text\'', 'mb'],
+        'cimage' => ['Content: \'Image\'', 'md'],
+        'ctextmedia' => ['Content: \'Textmedia\'', 'ml'],
+        'cbullets' => ['Content: \'Bullet list\'', 'me'],
+        'ctable' => ['Content: \'Table\'', 'mf'],
+        'cuploads' => ['Content: \'Filelinks\'', 'mg'],
+        'cmultimedia' => ['Content: \'Multimedia\'', 'mh'],
+        'cmedia' => ['Content: \'Media\'', 'mr'],
+        'cmailform' => ['Content: \'Form\'', 'mi'],
+        'csearch' => ['Content: \'Search\'', 'mj'],
+        'clogin' => ['Content: \'Login\'', 'mk'],
+        'cmenu' => ['Content: \'Menu/Sitemap\'', 'mm'],
+        'cshortcut' => ['Content: \'Insert records\'', 'mn'],
+        'clist' => ['Content: \'List of records\'', 'mo'],
+        'chtml' => ['Content: \'HTML\'', 'mq']
+    ];
+
+    /**
+     * If there are new config settings in ext_conf_template of an extenison,
+     * they are found here and synchronized to LocalConfiguration['EXTENSIONS'].
+     *
+     * Used when entering the install tool and during installation.
+     */
+    public function synchronizeExtConfTemplateWithLocalConfigurationOfAllExtensions()
+    {
+        $activePackages = GeneralUtility::makeInstance(PackageManager::class)->getActivePackages();
+        foreach ($activePackages as $package) {
+            $this->synchronizeExtConfTemplateWithLocalConfiguration($package->getPackageKey());
+        }
+    }
+
+    /**
+     * Read values from ext_conf_template, verify if they are in LocalConfiguration.php
+     * already and if not, add them.
+     *
+     * Used public by extension manager when updating extension
+     *
+     * @param string $extensionKey The extension to sync
+     */
+    public function synchronizeExtConfTemplateWithLocalConfiguration(string $extensionKey)
+    {
+        $package = GeneralUtility::makeInstance(PackageManager::class)->getPackage($extensionKey);
+        if (!@is_file($package->getPackagePath() . 'ext_conf_template.txt')) {
+            return;
+        }
+        $extensionConfiguration = new ExtensionConfiguration();
+        try {
+            $currentLocalConfiguration = $extensionConfiguration->get($extensionKey);
+        } catch (ExtensionConfigurationExtensionNotConfiguredException $e) {
+            $currentLocalConfiguration = [];
+        }
+        $extConfTemplateConfiguration = $this->getExtConfTablesWithoutCommentsAsNestedArrayWithoutDots($extensionKey);
+        ArrayUtility::mergeRecursiveWithOverrule($extConfTemplateConfiguration, $currentLocalConfiguration);
+        $extensionConfiguration->set($extensionKey, '', $extConfTemplateConfiguration);
+    }
+
+    /**
+     * Compiles ext_conf_template file and merges it with values from LocalConfiguration['EXTENSIONS'].
+     * Returns a funny array used to display the configuration form in the install tool.
+     *
+     * @param string $extensionKey Extension key
+     * @return array
+     */
+    public function getConfigurationPreparedForView(string $extensionKey): array
+    {
+        $package = GeneralUtility::makeInstance(PackageManager::class)->getPackage($extensionKey);
+        if (!@is_file($package->getPackagePath() . 'ext_conf_template.txt')) {
+            return [];
+        }
+        $extensionConfiguration = new ExtensionConfiguration();
+        $configuration = $this->getDefaultConfigurationFromExtConfTemplateAsValuedArray($extensionKey);
+        foreach ($configuration as $configurationPath => &$details) {
+            try {
+                $valueFromLocalConfiguration = $extensionConfiguration->get($extensionKey, str_replace('.', '/', $configurationPath));
+                $details['value'] = $valueFromLocalConfiguration;
+            } catch (ExtensionConfigurationPathDoesNotExistException $e) {
+                // Deliberately empty - it can happen at runtime that a written config does not return
+                // back all values (eg. saltedpassword with its userFuncs), which then miss in the written
+                // configuration and are only synced after next install tool run. This edge case is
+                // taken care off here.
+            }
+        }
+        $resultArray = [];
+        if (!empty($configuration)) {
+            $hierarchicConfiguration = [];
+            foreach ($configuration as $configurationOption) {
+                $originalConfiguration = $this->buildConfigurationArray($configurationOption);
+                ArrayUtility::mergeRecursiveWithOverrule($originalConfiguration, $hierarchicConfiguration);
+                $hierarchicConfiguration = $originalConfiguration;
+            }
+            // Flip category array as it was merged the other way around
+            $hierarchicConfiguration = array_reverse($hierarchicConfiguration);
+            // Sort configurations of each subcategory
+            foreach ($hierarchicConfiguration as &$catConfigurationArray) {
+                foreach ($catConfigurationArray as &$subcatConfigurationArray) {
+                    uasort($subcatConfigurationArray, function ($a, $b) {
+                        return strnatcmp($a['subcat'], $b['subcat']);
+                    });
+                }
+                unset($subcatConfigurationArray);
+            }
+            unset($tempConfiguration);
+            $resultArray = $hierarchicConfiguration;
+        }
+        return $resultArray;
+    }
+
+    /**
+     * Poor man version of getDefaultConfigurationFromExtConfTemplateAsValuedArray() which ignores
+     * comments and returns ext_conf_template as array where nested keys have no dots.
+     */
+    protected function getExtConfTablesWithoutCommentsAsNestedArrayWithoutDots(string $extensionKey)
+    {
+        $rawConfigurationString = $this->getDefaultConfigurationRawString($extensionKey);
+        $configuration = [];
+        if ((string)$rawConfigurationString !== '') {
+            $this->raw = explode(LF, $rawConfigurationString);
+            $this->rawPointer = 0;
+            $this->setup = [];
+            $this->parseSub($this->setup);
+            if ($this->inBrace) {
+                throw new \RuntimeException(
+                    'Line ' . ($this->rawPointer - 1) . ': The script is short of ' . $this->inBrace . ' end brace(s)',
+                    1507645349
+                );
+            }
+            $configuration = $this->removeCommentsAndDotsRecursive($this->setup);
+        }
+        return $configuration;
+    }
+
+    /**
+     * Builds a configuration array from each line (option) of the config file.
+     * Helper method for getConfigurationPreparedForView()
+     *
+     * @param array $configurationOption config file line representing one setting
+     * @return array
+     */
+    protected function buildConfigurationArray(array $configurationOption): array
+    {
+        $hierarchicConfiguration = [];
+        if (GeneralUtility::isFirstPartOfStr($configurationOption['type'], 'user')) {
+            $configurationOption = $this->extractInformationForConfigFieldsOfTypeUser($configurationOption);
+        } elseif (GeneralUtility::isFirstPartOfStr($configurationOption['type'], 'options')) {
+            $configurationOption = $this->extractInformationForConfigFieldsOfTypeOptions($configurationOption);
+        }
+        $languageService = $this->getLanguageService();
+        if (is_string($configurationOption['label'])) {
+            $translatedLabel = $languageService->sL($configurationOption['label']);
+            if ($translatedLabel) {
+                $configurationOption['label'] = $translatedLabel;
+            }
+        }
+        $configurationOption['labels'] = GeneralUtility::trimExplode(':', $configurationOption['label'], false, 2);
+        $configurationOption['subcat_name'] = $configurationOption['subcat_name'] ?: '__default';
+        $hierarchicConfiguration[$configurationOption['cat']][$configurationOption['subcat_name']][$configurationOption['name']] = $configurationOption;
+        return $hierarchicConfiguration;
+    }
+
+    /**
+     * Extracts additional information for fields of type "options"
+     * Extracts "type", "label" and values information
+     *
+     * @param array $configurationOption
+     * @return array
+     */
+    protected function extractInformationForConfigFieldsOfTypeOptions(array $configurationOption): array
+    {
+        preg_match('/options\[(.*)\]/is', $configurationOption['type'], $typeMatches);
+        $optionItems = GeneralUtility::trimExplode(',', $typeMatches[1]);
+        foreach ($optionItems as $optionItem) {
+            $optionPair = GeneralUtility::trimExplode('=', $optionItem);
+            if (count($optionPair) === 2) {
+                $configurationOption['generic'][$optionPair[0]] = $optionPair[1];
+            } else {
+                $configurationOption['generic'][$optionPair[0]] = $optionPair[0];
+            }
+        }
+        $configurationOption['type'] = 'options';
+        return $configurationOption;
+    }
+
+    /**
+     * Extract additional information for fields of type "user"
+     * Extracts "type" and the function to be called
+     *
+     * @param array $configurationOption
+     * @return array
+     */
+    protected function extractInformationForConfigFieldsOfTypeUser(array $configurationOption): array
+    {
+        preg_match('/user\\[(.*)\\]/is', $configurationOption['type'], $matches);
+        $configurationOption['generic'] = $matches[1];
+        $configurationOption['type'] = 'user';
+        return $configurationOption;
+    }
+
+    /**
+     * Create a flat array of configuration options from
+     * ext_conf_template.txt of an extension using core's typoscript parser.
+     *
+     * Result is an array, with configuration item as array keys,
+     * and item properties as key-value sub-array:
+     *
+     * array(
+     *   'fooOption' => array(
+     *     'type' => 'string',
+     *     'value' => 'foo',
+     *     ...
+     *   ),
+     *   'barOption' => array(
+     *     'type' => boolean,
+     *     'default_value' => 0,
+     *     ...
+     *   ),
+     *   ...
+     * )
+     *
+     * @param string $extensionKey Extension key
+     * @return array
+     */
+    protected function getDefaultConfigurationFromExtConfTemplateAsValuedArray(string $extensionKey): array
+    {
+        $rawConfigurationString = $this->getDefaultConfigurationRawString($extensionKey);
+        $theConstants = [];
+        if ((string)$rawConfigurationString !== '') {
+            $this->raw = explode(LF, $rawConfigurationString);
+            $this->rawPointer = 0;
+            $this->setup = [];
+            $this->parseSub($this->setup);
+            if ($this->inBrace) {
+                throw new \RuntimeException(
+                    'Line ' . ($this->rawPointer - 1) . ': The script is short of ' . $this->inBrace . ' end brace(s)',
+                    1507645348
+                );
+            }
+            $parsedConstants = $this->setup;
+            $flatSetup = $this->flattenSetup($parsedConstants);
+            $theConstants = $this->parseComments($flatSetup);
+
+            // Loop through configuration items, see if it is assigned to a sub category
+            // and add the sub category label to the item property if so.
+            foreach ($theConstants as $configurationOptionName => $configurationOption) {
+                if (
+                    array_key_exists('subcat_name', $configurationOption)
+                    && isset($this->subCategories[$configurationOption['subcat_name']])
+                    && isset($this->subCategories[$configurationOption['subcat_name']][0])
+                ) {
+                    $theConstants[$configurationOptionName]['subcat_label'] = $this->subCategories[$configurationOption['subcat_name']][0];
+                }
+            }
+        }
+        return $theConstants;
+    }
+
+    /**
+     * Return content of an extensions ext_conf_template.txt file if
+     * the file exists, empty string if file does not exist.
+     *
+     * @param string $extensionKey Extension key
+     * @return string
+     */
+    protected function getDefaultConfigurationRawString(string $extensionKey): string
+    {
+        $rawString = '';
+        $extConfTemplateFileLocation = GeneralUtility::getFileAbsFileName(
+            'EXT:' . $extensionKey . '/ext_conf_template.txt'
+        );
+        if (file_exists($extConfTemplateFileLocation)) {
+            $rawString = file_get_contents($extConfTemplateFileLocation);
+        }
+        return $rawString;
+    }
+
+    /**
+     * This function compares the flattened constants (default and all).
+     * Returns an array with the constants from the whole template which may be edited by the module.
+     *
+     * @param array $flatSetup
+     * @return array
+     */
+    protected function parseComments($flatSetup)
+    {
+        $categoryLabels = [];
+        $editableComments = [];
+        $counter = 0;
+        foreach ($flatSetup as $const => $value) {
+            if (substr($const, -2) === '..' || !isset($flatSetup[$const . '..'])) {
+                continue;
+            }
+            $counter++;
+            $comment = trim($flatSetup[$const . '..']);
+            $c_arr = explode(LF, $comment);
+            foreach ($c_arr as $k => $v) {
+                $line = trim(preg_replace('/^[#\\/]*/', '', $v));
+                if (!$line) {
+                    continue;
+                }
+                $parts = explode(';', $line);
+                foreach ($parts as $par) {
+                    if (strstr($par, '=')) {
+                        $keyValPair = explode('=', $par, 2);
+                        switch (trim(strtolower($keyValPair[0]))) {
+                            case 'type':
+                                // Type:
+                                $editableComments[$const]['type'] = trim($keyValPair[1]);
+                                break;
+                            case 'cat':
+                                // List of categories.
+                                $catSplit = explode('/', strtolower($keyValPair[1]));
+                                $catSplit[0] = trim($catSplit[0]);
+                                if (isset($categoryLabels[$catSplit[0]])) {
+                                    $catSplit[0] = $categoryLabels[$catSplit[0]];
+                                }
+                                $editableComments[$const]['cat'] = $catSplit[0];
+                                // This is the subcategory. Must be a key in $this->subCategories[].
+                                // catSplit[2] represents the search-order within the subcat.
+                                $catSplit[1] = !empty($catSplit[1]) ? trim($catSplit[1]) : '';
+                                if ($catSplit[1] && isset($this->subCategories[$catSplit[1]])) {
+                                    $editableComments[$const]['subcat_name'] = $catSplit[1];
+                                    $orderIdentifier = isset($catSplit[2]) ? trim($catSplit[2]) : $counter;
+                                    $editableComments[$const]['subcat'] = $this->subCategories[$catSplit[1]][1]
+                                        . '/' . $catSplit[1] . '/' . $orderIdentifier . 'z';
+                                } elseif (isset($catSplit[2])) {
+                                    $editableComments[$const]['subcat'] = 'x' . '/' . trim($catSplit[2]) . 'z';
+                                } else {
+                                    $editableComments[$const]['subcat'] = 'x' . '/' . $counter . 'z';
+                                }
+                                break;
+                            case 'label':
+                                // Label
+                                $editableComments[$const]['label'] = trim($keyValPair[1]);
+                                break;
+                            case 'customcategory':
+                                // Custom category label
+                                $customCategory = explode('=', $keyValPair[1], 2);
+                                if (trim($customCategory[0])) {
+                                    $categoryKey = strtolower($customCategory[0]);
+                                    $categoryLabels[$categoryKey] = $this->getLanguageService()->sL($customCategory[1]);
+                                }
+                                break;
+                            case 'customsubcategory':
+                                // Custom subCategory label
+                                $customSubcategory = explode('=', $keyValPair[1], 2);
+                                if (trim($customSubcategory[0])) {
+                                    $subCategoryKey = strtolower($customSubcategory[0]);
+                                    $this->subCategories[$subCategoryKey][0] = $this->getLanguageService()->sL($customSubcategory[1]);
+                                }
+                                break;
+                        }
+                    }
+                }
+            }
+            if (isset($editableComments[$const])) {
+                $editableComments[$const]['name'] = $const;
+                $editableComments[$const]['value'] = trim($value);
+                $editableComments[$const]['default_value'] = trim($value);
+            }
+        }
+        return $editableComments;
+    }
+
+    /**
+     * Parsing the $this->raw TypoScript lines from pointer, $this->rawP
+     *
+     * @param array $setup Reference to the setup array in which to accumulate the values.
+     */
+    protected function parseSub(array &$setup)
+    {
+        while (isset($this->raw[$this->rawPointer])) {
+            $line = ltrim($this->raw[$this->rawPointer]);
+            $this->rawPointer++;
+            // Set comment flag?
+            if (strpos($line, '/*') === 0) {
+                $this->commentSet = 1;
+            }
+            if (!$this->commentSet && ($line)) {
+                if ($line[0] !== '}' && $line[0] !== '#' && $line[0] !== '/') {
+                    // If not brace-end or comment
+                    // Find object name string until we meet an operator
+                    $varL = strcspn($line, TAB . ' {=<>(');
+                    // check for special ":=" operator
+                    if ($varL > 0 && substr($line, $varL-1, 2) === ':=') {
+                        --$varL;
+                    }
+                    // also remove tabs after the object string name
+                    $objStrName = substr($line, 0, $varL);
+                    if ($objStrName !== '') {
+                        $r = [];
+                        if (preg_match('/[^[:alnum:]_\\\\\\.:-]/i', $objStrName, $r)) {
+                            throw new \RuntimeException(
+                                'Line ' . ($this->rawPointer - 1) . ': Object Name String, "' . htmlspecialchars($objStrName) . '" contains invalid character "' . $r[0] . '". Must be alphanumeric or one of: "_:-\\."',
+                                1507645381
+                            );
+                        }
+                        $line = ltrim(substr($line, $varL));
+                        if ($line === '') {
+                            throw new \RuntimeException(
+                                    'Line ' . ($this->rawPointer - 1) . ': Object Name String, "' . htmlspecialchars($objStrName) . '" was not followed by any operator, =<>({',
+                                    1507645417
+                                );
+                        }
+                        switch ($line[0]) {
+                                    case '=':
+                                        if (strpos($objStrName, '.') !== false) {
+                                            $value = [];
+                                            $value[0] = trim(substr($line, 1));
+                                            $this->setVal($objStrName, $setup, $value);
+                                        } else {
+                                            $setup[$objStrName] = trim(substr($line, 1));
+                                            if ($this->lastComment) {
+                                                // Setting comment..
+                                                $setup[$objStrName . '..'] .= $this->lastComment;
+                                            }
+                                        }
+                                        break;
+                                    case '{':
+                                        $this->inBrace++;
+                                        if (strpos($objStrName, '.') !== false) {
+                                            $this->rollParseSub($objStrName, $setup);
+                                        } else {
+                                            if (!isset($setup[$objStrName . '.'])) {
+                                                $setup[$objStrName . '.'] = [];
+                                            }
+                                            $this->parseSub($setup[$objStrName . '.']);
+                                        }
+                                        break;
+                                    default:
+                                        throw new \RuntimeException(
+                                            'Line ' . ($this->rawPointer - 1) . ': Object Name String, "' . htmlspecialchars($objStrName) . '" was not followed by any operator, =<>({',
+                                            1507645445
+                                        );
+                                }
+
+                        $this->lastComment = '';
+                    }
+                } elseif ($line[0] === '}') {
+                    $this->inBrace--;
+                    $this->lastComment = '';
+                    if ($this->inBrace < 0) {
+                        throw new \RuntimeException(
+                            'Line ' . ($this->rawPointer - 1) . ': An end brace is in excess.',
+                            1507645489
+                        );
+                    }
+                    break;
+                } else {
+                    $this->lastComment .= rtrim($line) . LF;
+                }
+            }
+            // Unset comment
+            if ($this->commentSet) {
+                if (strpos($line, '*/') === 0) {
+                    $this->commentSet = 0;
+                }
+            }
+        }
+    }
+
+    /**
+     * Parsing of TypoScript keys inside a curly brace where the key is composite of at least two keys,
+     * thus having to recursively call itself to get the value
+     *
+     * @param string $string The object sub-path, eg "thisprop.another_prot
+     * @param array $setup The local setup array from the function calling this function
+     */
+    protected function rollParseSub($string, array &$setup)
+    {
+        if ((string)$string === '') {
+            return;
+        }
+        list($key, $remainingKey) = $this->parseNextKeySegment($string);
+        $key .= '.';
+        if (!isset($setup[$key])) {
+            $setup[$key] = [];
+        }
+        $remainingKey === ''
+            ? $this->parseSub($setup[$key])
+            : $this->rollParseSub($remainingKey, $setup[$key]);
+    }
+
+    /**
+     * Setting a value/property of an object string in the setup array.
+     *
+     * @param string $string The object sub-path, eg "thisprop.another_prot
+     * @param array $setup The local setup array from the function calling this function.
+     * @param void
+     */
+    protected function setVal($string, array &$setup, $value)
+    {
+        if ((string)$string === '') {
+            return;
+        }
+
+        list($key, $remainingKey) = $this->parseNextKeySegment($string);
+        $subKey = $key . '.';
+        if ($remainingKey === '') {
+            if (isset($value[0])) {
+                $setup[$key] = $value[0];
+            }
+            if (isset($value[1])) {
+                $setup[$subKey] = $value[1];
+            }
+            if ($this->lastComment) {
+                $setup[$key . '..'] .= $this->lastComment;
+            }
+        } else {
+            if (!isset($setup[$subKey])) {
+                $setup[$subKey] = [];
+            }
+            $this->setVal($remainingKey, $setup[$subKey], $value);
+        }
+    }
+
+    /**
+     * Determines the first key segment of a TypoScript key by searching for the first
+     * unescaped dot in the given key string.
+     *
+     * Since the escape characters are only needed to correctly determine the key
+     * segment any escape characters before the first unescaped dot are
+     * stripped from the key.
+     *
+     * @param string $key The key, possibly consisting of multiple key segments separated by unescaped dots
+     * @return array Array with key segment and remaining part of $key
+     */
+    protected function parseNextKeySegment($key)
+    {
+        // if no dot is in the key, nothing to do
+        $dotPosition = strpos($key, '.');
+        if ($dotPosition === false) {
+            return [$key, ''];
+        }
+
+        if (strpos($key, '\\') !== false) {
+            // backslashes are in the key, so we do further parsing
+            while ($dotPosition !== false) {
+                if ($dotPosition > 0 && $key[$dotPosition - 1] !== '\\' || $dotPosition > 1 && $key[$dotPosition - 2] === '\\') {
+                    break;
+                }
+                // escaped dot found, continue
+                $dotPosition = strpos($key, '.', $dotPosition + 1);
+            }
+
+            if ($dotPosition === false) {
+                // no regular dot found
+                $keySegment = $key;
+                $remainingKey = '';
+            } else {
+                if ($dotPosition > 1 && $key[$dotPosition - 2] === '\\' && $key[$dotPosition - 1] === '\\') {
+                    $keySegment = substr($key, 0, $dotPosition - 1);
+                } else {
+                    $keySegment = substr($key, 0, $dotPosition);
+                }
+                $remainingKey = substr($key, $dotPosition + 1);
+            }
+
+            // fix key segment by removing escape sequences
+            $keySegment = str_replace('\\.', '.', $keySegment);
+        } else {
+            // no backslash in the key, we're fine off
+            list($keySegment, $remainingKey) = explode('.', $key, 2);
+        }
+        return [$keySegment, $remainingKey];
+    }
+
+    /**
+     * "Comments" from the "TypoScript" parser below are identified by two (!) dots at the end of array keys
+     * and all array keys have a single dot at the end, if they have sub arrays. This is cleaned here.
+     *
+     * Incoming array:
+     * [
+     *  'automaticInstallation' => '1',
+     *  'automaticInstallation..' => '# cat=basic/enabled; ...'
+     *  'FE.' => [
+     *      'enabled' = '1',
+     *      'enabled..' => '# cat=basic/enabled; ...'
+     *  ]
+     * ]
+     *
+     * Output array:
+     * [
+     *  'automaticInstallation' => '1',
+     *  'FE' => [
+     *      'enabled' => '1',
+     * ]
+     */
+    protected function removeCommentsAndDotsRecursive(array $config): array
+    {
+        $cleanedConfig = [];
+        foreach ($config as $key => $value) {
+            if (substr($key, -2) === '..') {
+                continue;
+            }
+            if (substr($key, -1) === '.') {
+                $cleanedConfig[rtrim($key, '.')] = $this->removeCommentsAndDotsRecursive($value);
+            } else {
+                $cleanedConfig[$key] = $value;
+            }
+        }
+        return $cleanedConfig;
+    }
+
+    /**
+     * This flattens a hierarchical TypoScript array to a dotted notation
+     *
+     * @param array $setupArray TypoScript array
+     * @param string $prefix Prefix to the object path. Used for recursive calls to this function.
+     * @return array
+     */
+    protected function flattenSetup($setupArray, $prefix = '')
+    {
+        $flatSetup = [];
+        if (is_array($setupArray)) {
+            foreach ($setupArray as $key => $val) {
+                if (is_array($val)) {
+                    $flatSetup = array_merge($flatSetup, $this->flattenSetup($val, $prefix . $key));
+                } else {
+                    $flatSetup[$prefix . $key] = $val;
+                }
+            }
+        }
+        return $flatSetup;
+    }
+
+    /**
+     * @return LanguageService
+     */
+    protected function getLanguageService()
+    {
+        return $GLOBALS['LANG'];
+    }
+}
diff --git a/typo3/sysext/install/Classes/ViewHelpers/Form/TypoScriptConstantsViewHelper.php b/typo3/sysext/install/Classes/ViewHelpers/Form/TypoScriptConstantsViewHelper.php
new file mode 100644 (file)
index 0000000..e7b054f
--- /dev/null
@@ -0,0 +1,328 @@
+<?php
+declare(strict_types=1);
+namespace TYPO3\CMS\Install\ViewHelpers\Form;
+
+/*
+ * 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\Localization\LanguageService;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper;
+use TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper;
+
+/**
+ * View Helper for rendering extension configuration forms
+ * @internal
+ */
+class TypoScriptConstantsViewHelper extends AbstractTagBasedViewHelper
+{
+    /**
+     * @var array
+     */
+    public $viewHelperMapping = [
+        'int' => 'renderIntegerField',
+        'int+' => 'renderPositiveIntegerField',
+        'integer' => 'renderIntegerField',
+        'color' => 'renderColorPicker',
+        'wrap' => 'renderWrapField',
+        'offset' => 'renderOffsetField',
+        'options' => 'renderOptionSelect',
+        'boolean' => 'renderCheckbox',
+        'user' => 'renderUserFunction',
+        'small' => 'renderSmallTextField',
+        'string' => 'renderTextField',
+        'input' => 'renderTextField',  // only for backwards compatibility, many extensions depend on that
+        'default' => 'renderTextField' // only for backwards compatibility, many extensions depend on that
+    ];
+
+    /**
+     * @var string
+     */
+    public $tagName = 'input';
+
+    /**
+     * Initialize arguments of this view helper
+     */
+    public function initializeArguments()
+    {
+        parent::initializeArguments();
+        $this->registerArgument('name', 'string', 'Name of input tag');
+        $this->registerArgument('value', 'mixed', 'Value of input tag');
+        $this->registerArgument('configuration', 'array', '', true);
+        $this->registerUniversalTagAttributes();
+    }
+
+    /**
+     * Render
+     *
+     * @return string the rendered tag
+     */
+    public function render(): string
+    {
+        /** @var array $configuration */
+        $configuration = $this->arguments['configuration'];
+        if (isset($this->viewHelperMapping[$configuration['type']]) && method_exists($this, $this->viewHelperMapping[$configuration['type']])) {
+            $input = $this->{$this->viewHelperMapping[$configuration['type']]}($configuration);
+        } else {
+            $input = $this->{$this->viewHelperMapping['default']}($configuration);
+        }
+
+        return $input;
+    }
+
+    /**
+     * Render field of type color picker
+     *
+     * @param array $configuration
+     * @return string
+     */
+    protected function renderColorPicker(array $configuration): string
+    {
+        $elementId = 'em-' . $configuration['name'];
+        $elementName = $this->getName($configuration);
+
+        // configure the field
+        $this->tag->setTagName('input');
+        $this->tag->addAttribute('type', 'text');
+        $this->tag->addAttribute('id', $elementId);
+        $this->tag->addAttribute('name', $elementName);
+        $this->tag->addAttribute('data-formengine-input-name', $elementName);
+        $this->tag->addAttribute('class', 'form-control');
+        if ($configuration['value'] !== null) {
+            $this->tag->addAttribute('value', $configuration['value']);
+        }
+
+        $output = '
+            <div class="form-wizards-element">
+                <input class="form-control t3js-color-input formengine-colorpickerelement t3js-color-picker" type="text"
+                  name="' . htmlspecialchars($elementName) . '" value="' . $this->tag->getAttribute('value') . '"/>
+                <script type="text/javascript">
+                    require([\'TYPO3/CMS/Backend/ColorPicker\'], function(ColorPicker){ColorPicker.initialize()});
+                </script>
+            </div>';
+
+        return $output;
+    }
+
+    /**
+     * Render field of type "offset"
+     *
+     * @param array $configuration
+     * @return string
+     */
+    protected function renderOffsetField(array $configuration): string
+    {
+        $this->tag->setTagName('input');
+        $this->tag->addAttribute('type', 'text');
+        $this->tag->addAttribute('id', 'em-' . $configuration['name']);
+        $this->tag->addAttribute('name', $this->getName($configuration));
+        $this->tag->addAttribute('class', 'form-control t3js-emconf-offset');
+        if ($configuration['value'] !== null) {
+            $this->tag->addAttribute('value', $configuration['value']);
+        }
+        return $this->tag->render();
+    }
+
+    /**
+     * Render field of type "wrap"
+     *
+     * @param array $configuration
+     * @return string
+     */
+    protected function renderWrapField(array $configuration): string
+    {
+        $this->tag->setTagName('input');
+        $this->tag->addAttribute('type', 'text');
+        $this->tag->addAttribute('id', 'em-' . $configuration['name']);
+        $this->tag->addAttribute('name', $this->getName($configuration));
+        $this->tag->addAttribute('class', 'form-control t3js-emconf-wrap');
+        if ($configuration['value'] !== null) {
+            $this->tag->addAttribute('value', $configuration['value']);
+        }
+        return $this->tag->render();
+    }
+
+    /**
+     * Render field of type "option"
+     *
+     * @param array $configuration
+     * @return string
+     */
+    protected function renderOptionSelect(array $configuration): string
+    {
+        $this->tag->setTagName('select');
+        $this->tag->addAttribute('id', 'em-' . $configuration['name']);
+        $this->tag->addAttribute('name', $this->getName($configuration));
+        $this->tag->addAttribute('class', 'form-control');
+        $optionValueArray = $configuration['generic'];
+        $output = '';
+        $languageService = $this->getLanguageService();
+        foreach ($optionValueArray as $label => $value) {
+            $output .= '<option value="' . htmlspecialchars($value) . '"';
+            if ($configuration['value'] == $value) {
+                $output .= ' selected="selected"';
+            }
+            $output .= '>' . htmlspecialchars($languageService->sL($label)) . '</option>';
+        }
+        $this->tag->setContent($output);
+        return $this->tag->render();
+    }
+
+    /**
+     * Render field of type "int+"
+     *
+     * @param array $configuration
+     * @return string
+     */
+    protected function renderPositiveIntegerField(array $configuration): string
+    {
+        $this->tag->setTagName('input');
+        $this->tag->addAttribute('type', 'number');
+        $this->tag->addAttribute('id', 'em-' . $configuration['name']);
+        $this->tag->addAttribute('name', $this->getName($configuration));
+        $this->tag->addAttribute('class', 'form-control');
+        $this->tag->addAttribute('min', '0');
+        if ($configuration['value'] !== null) {
+            $this->tag->addAttribute('value', $configuration['value']);
+        }
+        return $this->tag->render();
+    }
+
+    /**
+     * Render field of type "integer"
+     *
+     * @param array $configuration
+     * @return string
+     */
+    protected function renderIntegerField(array $configuration): string
+    {
+        $this->tag->setTagName('input');
+        $this->tag->addAttribute('type', 'number');
+        $this->tag->addAttribute('id', 'em-' . $configuration['name']);
+        $this->tag->addAttribute('name', $this->getName($configuration));
+        $this->tag->addAttribute('class', 'form-control');
+        if ($configuration['value'] !== null) {
+            $this->tag->addAttribute('value', $configuration['value']);
+        }
+        return $this->tag->render();
+    }
+
+    /**
+     * Render field of type "text"
+     *
+     * @param array $configuration
+     * @return string
+     */
+    protected function renderTextField(array $configuration): string
+    {
+        $this->tag->setTagName('input');
+        $this->tag->addAttribute('type', 'text');
+        $this->tag->addAttribute('id', 'em-' . $configuration['name']);
+        $this->tag->addAttribute('name', $this->getName($configuration));
+        $this->tag->addAttribute('class', 'form-control');
+        if ($configuration['value'] !== null) {
+            $this->tag->addAttribute('value', $configuration['value']);
+        }
+        return $this->tag->render();
+    }
+
+    /**
+     * Render field of type "small text"
+     *
+     * @param array $configuration
+     * @return string
+     */
+    protected function renderSmallTextField(array $configuration): string
+    {
+        return $this->renderTextField($configuration);
+    }
+
+    /**
+     * Render field of type "checkbox"
+     *
+     * @param array $configuration
+     * @return string
+     */
+    public function renderCheckbox(array $configuration): string
+    {
+        $this->tag->addAttribute('type', 'checkbox');
+        $this->tag->addAttribute('name', $this->getName($configuration));
+        $this->tag->addAttribute('value', 1);
+        $this->tag->addAttribute('id', 'em-' . $configuration['name']);
+        if ($configuration['value'] == 1) {
+            $this->tag->addAttribute('checked', 'checked');
+        }
+        $hiddenField = $this->renderHiddenFieldForEmptyValue($configuration);
+        return '<div class="checkbox">' . $hiddenField . '<label>' . $this->tag->render() . '</label></div>';
+    }
+
+    /**
+     * Render field of type "userFunc"
+     *
+     * @param array $configuration
+     * @return string
+     */
+    protected function renderUserFunction(array $configuration): string
+    {
+        $userFunction = $configuration['generic'];
+        $userFunctionParams = [
+            'fieldName' => $this->getName($configuration),
+            'fieldValue' => $configuration['value'],
+            'propertyName' => $configuration['name']
+        ];
+        return (string)GeneralUtility::callUserFunction($userFunction, $userFunctionParams, $this);
+    }
+
+    /**
+     * Get Field Name
+     *
+     * @param array $configuration
+     * @return string
+     */
+    protected function getName(array $configuration): string
+    {
+        return $configuration['name'];
+    }
+
+    /**
+     * Render a hidden field for empty values
+     *
+     * @param array $configuration
+     * @return string
+     */
+    protected function renderHiddenFieldForEmptyValue(array $configuration): string
+    {
+        $hiddenFieldNames = [];
+        if ($this->viewHelperVariableContainer->exists(FormViewHelper::class, 'renderedHiddenFields')) {
+            $hiddenFieldNames = $this->viewHelperVariableContainer->get(FormViewHelper::class, 'renderedHiddenFields');
+        }
+        $fieldName = $this->getName($configuration);
+        if (substr($fieldName, -2) === '[]') {
+            $fieldName = substr($fieldName, 0, -2);
+        }
+        if (!in_array($fieldName, $hiddenFieldNames)) {
+            $hiddenFieldNames[] = $fieldName;
+            $this->viewHelperVariableContainer->addOrUpdate(FormViewHelper::class, 'renderedHiddenFields', $hiddenFieldNames);
+            return '<input type="hidden" name="' . htmlspecialchars($fieldName) . '" value="0" />';
+        }
+        return '';
+    }
+
+    /**
+     * @return LanguageService|null Returns null if we are in the install tool standalone mode
+     */
+    protected function getLanguageService()
+    {
+        return $GLOBALS['LANG'];
+    }
+}
diff --git a/typo3/sysext/install/Classes/ViewHelpers/Format/NoSpaceViewHelper.php b/typo3/sysext/install/Classes/ViewHelpers/Format/NoSpaceViewHelper.php
new file mode 100644 (file)
index 0000000..93d9720
--- /dev/null
@@ -0,0 +1,52 @@
+<?php
+namespace TYPO3\CMS\Install\ViewHelpers\Format;
+
+/*
+ * 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\Fluid\Core\ViewHelper\AbstractViewHelper;
+use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
+use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
+
+/**
+ * Remove all spaces in a string
+ *
+ * @internal
+ */
+class NoSpaceViewHelper extends AbstractViewHelper
+{
+    use CompileWithRenderStatic;
+
+    /**
+     * Initialize arguments
+     */
+    public function initializeArguments()
+    {
+        parent::initializeArguments();
+        $this->registerArgument('value', 'string', '', true);
+    }
+
+    /**
+     * Render a string with whitespaces stripped
+     *
+     * @param array $arguments
+     * @param \Closure $renderChildrenClosure
+     * @param RenderingContextInterface $renderingContext
+     *
+     * @return string
+     */
+    public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext)
+    {
+        return str_replace(' ', '', $arguments['value']);
+    }
+}
diff --git a/typo3/sysext/install/Resources/Private/Partials/Settings/ExtensionConfiguration.html b/typo3/sysext/install/Resources/Private/Partials/Settings/ExtensionConfiguration.html
new file mode 100644 (file)
index 0000000..43e6372
--- /dev/null
@@ -0,0 +1 @@
+<div class="t3js-extensionConfiguration-content"></div>
diff --git a/typo3/sysext/install/Resources/Private/Partials/Settings/ExtensionConfiguration/ExtensionForm.html b/typo3/sysext/install/Resources/Private/Partials/Settings/ExtensionConfiguration/ExtensionForm.html
new file mode 100644 (file)
index 0000000..c206516
--- /dev/null
@@ -0,0 +1,71 @@
+{namespace i=TYPO3\CMS\Install\ViewHelpers}
+
+<div class="panel panel-default panel-flat searchhit">
+       <div class="panel-heading" role="tab" id="heading{extensionKey}">
+               <h3 class="panel-title">
+                       <a role="button" data-toggle="collapse" data-parent="#accordion" href="#collapse{extensionKey}"
+                               aria-expanded="true" aria-controls="collapse{extensionKey}" class="collapsed"
+                       >
+                               <span class="caret"></span>
+                               <strong>{extensionData.packageInfo.packageKey}</strong>
+                       </a>
+               </h3>
+       </div>
+       <div id="collapse{extensionKey}" class="panel-collapse collapse search-item" role="tabpanel" aria-labelledby="heading{extensionKey}">
+               <div class="panel-body">
+                       <div role="tabpanel">
+                               <ul class="nav nav-tabs" role="tablist">
+                                       <f:for each="{extensionData.configuration}" as="category" key="categoryName" iteration="iteration">
+                                               <f:if condition="{categoryName}">
+                                                       <li role="presentation" class="{f:if(condition:'{iteration.isFirst}', then:'active')}">
+                                                               <a class="text-capitalize" href="#{i:format.noSpace(value:'category-{extensionKey}-{categoryName}')}" aria-controls="category-{extensionKey}-{categoryName}" role="tab" data-toggle="tab">
+                                                                       {categoryName}
+                                                               </a>
+                                                       </li>
+                                               </f:if>
+                                       </f:for>
+                               </ul>
+
+                               <form action="#" name="configurationform" class="t3js-extensionConfiguration-form" data-extensionKey="{extensionKey}">
+                                       <div class="tab-content">
+                                               <f:for each="{extensionData.configuration}" as="subcategories" key="categoryName" iteration="iteration">
+                                                       <f:if condition="{categoryName}">
+                                                               <div role="tabpanel" class="tab-pane {f:if(condition:'{iteration.isFirst}', then:'active')}" id="{i:format.noSpace(value:'category-{extensionKey}-{categoryName}')}">
+                                                                       <f:for each="{subcategories}" as="subcategory">
+                                                                               <div class="form-section">
+                                                                                       <f:for each="{subcategory}" as="configurationItem" iteration="itemIterator">
+                                                                                               <f:if condition="{itemIterator.isFirst}">
+                                                                                                       <h2 class="h4 form-section-headline">{configurationItem.subcat_label}</h2>
+                                                                                               </f:if>
+                                                                                               <div class="form-group form-group-dashed">
+                                                                                                       <label for="em-{configurationItem.name}">
+                                                                                                               {configurationItem.labels.0}
+                                                                                                       </label>
+                                                                                                       <p>
+                                                                                                               {categoryName}.{configurationItem.name}
+                                                                                                               <f:if condition="{configurationItem.type} != 'user'">
+                                                                                                                       <f:if condition="{configurationItem.type}">
+                                                                                                                                       ({configurationItem.type})
+                                                                                                                               </f:if>
+                                                                                                               </f:if>
+                                                                                                       </p>
+                                                                                                       <div class="form-control-wrap">
+                                                                                                               <i:form.typoScriptConstants configuration="{configurationItem}" />
+                                                                                                       </div>
+                                                                                                       <f:if condition="{configurationItem.labels.1}">
+                                                                                                               <div class="help-block">{configurationItem.labels.1 -> f:format.nl2br()}</div>
+                                                                                                       </f:if>
+                                                                                               </div>
+                                                                                       </f:for>
+                                                                               </div>
+                                                                       </f:for>
+                                                               </div>
+                                                       </f:if>
+                                               </f:for>
+                                       </div>
+                                       <button type="submit" class="btn btn-default" name="mySubmit">Save "{extensionKey}" configuration</button>
+                               </form>
+                       </div>
+               </div>
+       </div>
+</div>
index f519ebd..8349582 100644 (file)
                title: 'Configure installation-wide options',
                category: 'Global Configuration',
                description: 'Modify settings to be written into TYPO3\'s LocalConfiguration.php.'
+       },
+       4: {partial: 'Settings/ExtensionConfiguration',
+               require: 'TYPO3/CMS/Install/ExtensionConfiguration',
+               baseClass: 't3js-extensionConfiguration',
+               title: 'Extension configuration',
+               category: 'Glocal Configuration',
+               description: 'Configure settings for extensions.'
        }
 }}">
        <div class="gridder">
diff --git a/typo3/sysext/install/Resources/Private/Templates/Settings/ExtensionConfigurationGetContent.html b/typo3/sysext/install/Resources/Private/Templates/Settings/ExtensionConfigurationGetContent.html
new file mode 100644 (file)
index 0000000..0caa9c9
--- /dev/null
@@ -0,0 +1,22 @@
+<p>
+       Select extension here
+</p>
+
+<div style="display:none;">
+       <div id="t3js-extensionConfiguration-write-token">{extensionConfigurationWriteToken}</div>
+</div>
+
+<div class="t3js-extensionConfiguration-output"></div>
+
+<div class="form-group">
+       <div class="input-group">
+               <span class="input-group-addon">Filter by:</span>
+               <input type="text" class="form-control t3js-extensionConfiguration-search" placeholder="search setting">
+       </div>
+</div>
+
+<div class="panel-group" role="tablist" aria-multiselectable="true">
+       <f:for each="{extensionsWithConfigurations}" as="extensionData" key="extensionKey">
+               <f:render partial="Settings/ExtensionConfiguration/ExtensionForm" arguments="{_all}"/>
+       </f:for>
+</div>
index fed178c..d98b7db 100644 (file)
@@ -10,4 +10,4 @@
  * 
  * The TYPO3 project - inspiring people to share!
  */
-@charset "UTF-8";html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}@font-face{font-family:'Source Sans Pro';font-weight:200;font-style:normal;font-stretch:normal;src:url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/EOT/SourceSansPro-ExtraLight.eot) format("embedded-opentype"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/WOFF2/TTF/SourceSansPro-ExtraLight.ttf.woff2) format("woff2"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/WOFF/OTF/SourceSansPro-ExtraLight.otf.woff) format("woff"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/OTF/SourceSansPro-ExtraLight.otf) format("opentype"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/TTF/SourceSansPro-ExtraLight.ttf) format("truetype")}@font-face{font-family:'Source Sans Pro';font-weight:200;font-style:italic;font-stretch:normal;src:url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/EOT/SourceSansPro-ExtraLightIt.eot) format("embedded-opentype"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/WOFF2/TTF/SourceSansPro-ExtraLightIt.ttf.woff2) format("woff2"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/WOFF/OTF/SourceSansPro-ExtraLightIt.otf.woff) format("woff"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/OTF/SourceSansPro-ExtraLightIt.otf) format("opentype"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/TTF/SourceSansPro-ExtraLightIt.ttf) format("truetype")}@font-face{font-family:'Source Sans Pro';font-weight:300;font-style:normal;font-stretch:normal;src:url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/EOT/SourceSansPro-Light.eot) format("embedded-opentype"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/WOFF2/TTF/SourceSansPro-Light.ttf.woff2) format("woff2"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/WOFF/OTF/SourceSansPro-Light.otf.woff) format("woff"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/OTF/SourceSansPro-Light.otf) format("opentype"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/TTF/SourceSansPro-Light.ttf) format("truetype")}@font-face{font-family:'Source Sans Pro';font-weight:300;font-style:italic;font-stretch:normal;src:url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/EOT/SourceSansPro-LightIt.eot) format("embedded-opentype"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/WOFF2/TTF/SourceSansPro-LightIt.ttf.woff2) format("woff2"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/WOFF/OTF/SourceSansPro-LightIt.otf.woff) format("woff"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/OTF/SourceSansPro-LightIt.otf) format("opentype"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/TTF/SourceSansPro-LightIt.ttf) format("truetype")}@font-face{font-family:'Source Sans Pro';font-weight:400;font-style:normal;font-stretch:normal;src:url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/EOT/SourceSansPro-Regular.eot) format("embedded-opentype"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/WOFF2/TTF/SourceSansPro-Regular.ttf.woff2) format("woff2"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/WOFF/OTF/SourceSansPro-Regular.otf.woff) format("woff"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/OTF/SourceSansPro-Regular.otf) format("opentype"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/TTF/SourceSansPro-Regular.ttf) format("truetype")}@font-face{font-family:'Source Sans Pro';font-weight:400;font-style:italic;font-stretch:normal;src:url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/EOT/SourceSansPro-It.eot) format("embedded-opentype"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/WOFF2/TTF/SourceSansPro-It.ttf.woff2) format("woff2"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/WOFF/OTF/SourceSansPro-It.otf.woff) format("woff"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/OTF/SourceSansPro-It.otf) format("opentype"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/TTF/SourceSansPro-It.ttf) format("truetype")}@font-face{font-family:'Source Sans Pro';font-weight:600;font-style:normal;font-stretch:normal;src:url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/EOT/SourceSansPro-Semibold.eot) format("embedded-opentype"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/WOFF2/TTF/SourceSansPro-Semibold.ttf.woff2) format("woff2"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/WOFF/OTF/SourceSansPro-Semibold.otf.woff) format("woff"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/OTF/SourceSansPro-Semibold.otf) format("opentype"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/TTF/SourceSansPro-Semibold.ttf) format("truetype")}@font-face{font-family:'Source Sans Pro';font-weight:600;font-style:italic;font-stretch:normal;src:url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/EOT/SourceSansPro-SemiboldIt.eot) format("embedded-opentype"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/WOFF2/TTF/SourceSansPro-SemiboldIt.ttf.woff2) format("woff2"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/WOFF/OTF/SourceSansPro-SemiboldIt.otf.woff) format("woff"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/OTF/SourceSansPro-SemiboldIt.otf) format("opentype"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/TTF/SourceSansPro-SemiboldIt.ttf) format("truetype")}@font-face{font-family:'Source Sans Pro';font-weight:700;font-style:normal;font-stretch:normal;src:url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/EOT/SourceSansPro-Bold.eot) format("embedded-opentype"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/WOFF2/TTF/SourceSansPro-Bold.ttf.woff2) format("woff2"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/WOFF/OTF/SourceSansPro-Bold.otf.woff) format("woff"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/OTF/SourceSansPro-Bold.otf) format("opentype"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/TTF/SourceSansPro-Bold.ttf) format("truetype")}@font-face{font-family:'Source Sans Pro';font-weight:700;font-style:italic;font-stretch:normal;src:url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/EOT/SourceSansPro-BoldIt.eot) format("embedded-opentype"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/WOFF2/TTF/SourceSansPro-BoldIt.ttf.woff2) format("woff2"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/WOFF/OTF/SourceSansPro-BoldIt.otf.woff) format("woff"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/OTF/SourceSansPro-BoldIt.otf) format("opentype"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/TTF/SourceSansPro-BoldIt.ttf) format("truetype")}@font-face{font-family:'Source Sans Pro';font-weight:900;font-style:normal;font-stretch:normal;src:url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/EOT/SourceSansPro-Black.eot) format("embedded-opentype"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/WOFF2/TTF/SourceSansPro-Black.ttf.woff2) format("woff2"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/WOFF/OTF/SourceSansPro-Black.otf.woff) format("woff"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/OTF/SourceSansPro-Black.otf) format("opentype"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/TTF/SourceSansPro-Black.ttf) format("truetype")}@font-face{font-family:'Source Sans Pro';font-weight:900;font-style:italic;font-stretch:normal;src:url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/EOT/SourceSansPro-BlackIt.eot) format("embedded-opentype"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/WOFF2/TTF/SourceSansPro-BlackIt.ttf.woff2) format("woff2"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/WOFF/OTF/SourceSansPro-BlackIt.otf.woff) format("woff"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/OTF/SourceSansPro-BlackIt.otf) format("opentype"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/SourceSansPro/TTF/SourceSansPro-BlackIt.ttf) format("truetype")}@font-face{font-family:FontAwesome;src:url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/FontAwesome/fontawesome-webfont.eot?v=4.7.0);src:url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/FontAwesome/fontawesome-webfont.eot?#iefix&v=4.7.0) format("embedded-opentype"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/FontAwesome/fontawesome-webfont.woff2?v=4.7.0) format("woff2"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/FontAwesome/fontawesome-webfont.woff?v=4.7.0) format("woff"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/FontAwesome/fontawesome-webfont.ttf?v=4.7.0) format("truetype"),url(../../../../../../typo3/sysext/backend/Resources/Public/Fonts/FontAwesome/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-webkit-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-webkit-transform:scale(-1,1);transform:scale(-1,1)}.fa-flip-vertical{-webkit-transform:scale(1,-1);transform:scale(1,-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-rotate-90{-webkit-filter:none;filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before{content:""}.fa-check-circle:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before{content:""}.fa-arrow-circle-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}*{box-sizing:border-box}:after,:before{box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:transparent}body{font-family:Verdana,Arial,Helvetica,sans-serif;font-size:12px;line-height:1.5;color:#000;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#212424;text-decoration:none}a:focus,a:hover{color:#000;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive{display:block;max-width:100%;height:auto}.img-rounded{border-radius:2px}.img-thumbnail{padding:4px;line-height:1.5;background-color:#fff;border:1px solid #ddd;border-radius:2px;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:18px;margin-bottom:18px;border:0;border-top:1px solid #7a7a7a}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#737373}.h1,.h2,.h3,h1,h2,h3{margin-top:18px;margin-bottom:9px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:9px;margin-bottom:9px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:27px}.h2,h2{font-size:19px}.h3,h3{font-size:17px}.h4,h4{font-size:15px}.h5,h5{font-size:12px}.h6,h6{font-size:11px}p{margin:0 0 9px}.lead{margin-bottom:18px;font-size:13px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:18px}}.small,small{font-size:91%}.mark,mark{background-color:#fbefdd;padding:.2em}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.initialism,.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#737373}.text-primary{color:#0078e6}a.text-primary:focus,a.text-primary:hover{color:#005db3}.text-success{color:#79a548}a.text-success:focus,a.text-success:hover{color:#5f8139}.text-info{color:#6daae0}a.text-info:focus,a.text-info:hover{color:#4392d7}.text-warning{color:#e8a33d}a.text-warning:focus,a.text-warning:hover{color:#d88b1a}.text-danger{color:#c83c3c}a.text-danger:focus,a.text-danger:hover{color:#a32e2e}.bg-primary{color:#fff}.bg-primary{background-color:#0078e6}a.bg-primary:focus,a.bg-primary:hover{background-color:#005db3}.bg-success{background-color:#d1e2bd}a.bg-success:focus,a.bg-success:hover{background-color:#b8d39a}.bg-info{background-color:#ebf3fb}a.bg-info:focus,a.bg-info:hover{background-color:#c1dbf2}.bg-warning{background-color:#fbefdd}a.bg-warning:focus,a.bg-warning:hover{background-color:#f6d9af}.bg-danger{background-color:#efc7c7}a.bg-danger:focus,a.bg-danger:hover{background-color:#e49f9f}.page-header{padding-bottom:8px;margin:36px 0 18px;border-bottom:1px solid #f5f5f5}ol,ul{margin-top:0;margin-bottom:9px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled,.modulemenu .modulemenu-group-container{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none;margin-left:-5px}.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px}dl{margin-top:0;margin-bottom:18px}dd,dt{line-height:1.5}dt{font-weight:700}dd{margin-left:0}.dl-horizontal dd:after{content:"";display:table;clear:both}@media (min-width:992px){.dl-horizontal dt{float:left;width:70px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:90px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #d7d7d7}.initialism{font-size:90%}blockquote{padding:9px 18px;margin:0 0 18px;font-size:15px;border-left:5px solid #f5f5f5}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.5;color:#d7d7d7}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #f5f5f5;border-left:0;text-align:right}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:18px;font-style:normal;line-height:1.5}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:2px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:2px;box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;box-shadow:none}pre{display:block;padding:8.5px;margin:0 0 9px;font-size:11px;line-height:1.5;word-break:break-all;word-wrap:break-word;color:#5a5a5a;background-color:#f5f5f5;border:1px solid #ccc;border-radius:2px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.container:after{content:"";display:table;clear:both}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.container-fluid:after{content:"";display:table;clear:both}.row{margin-left:-15px;margin-right:-15px}.row:after{content:"";display:table;clear:both}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-left:15px;padding-right:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-1{width:8.33333333%}.col-xs-2{width:16.66666667%}.col-xs-3{width:25%}.col-xs-4{width:33.33333333%}.col-xs-5{width:41.66666667%}.col-xs-6{width:50%}.col-xs-7{width:58.33333333%}.col-xs-8{width:66.66666667%}.col-xs-9{width:75%}.col-xs-10{width:83.33333333%}.col-xs-11{width:91.66666667%}.col-xs-12{width:100%}.col-xs-pull-0{right:auto}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-3{right:25%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-6{right:50%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-9{right:75%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-12{right:100%}.col-xs-push-0{left:auto}.col-xs-push-1{left:8.33333333%}.col-xs-push-2{left:16.66666667%}.col-xs-push-3{left:25%}.col-xs-push-4{left:33.33333333%}.col-xs-push-5{left:41.66666667%}.col-xs-push-6{left:50%}.col-xs-push-7{left:58.33333333%}.col-xs-push-8{left:66.66666667%}.col-xs-push-9{left:75%}.col-xs-push-10{left:83.33333333%}.col-xs-push-11{left:91.66666667%}.col-xs-push-12{left:100%}.col-xs-offset-0{margin-left:0}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-12{margin-left:100%}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-1{width:8.33333333%}.col-sm-2{width:16.66666667%}.col-sm-3{width:25%}.col-sm-4{width:33.33333333%}.col-sm-5{width:41.66666667%}.col-sm-6{width:50%}.col-sm-7{width:58.33333333%}.col-sm-8{width:66.66666667%}.col-sm-9{width:75%}.col-sm-10{width:83.33333333%}.col-sm-11{width:91.66666667%}.col-sm-12{width:100%}.col-sm-pull-0{right:auto}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-3{right:25%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-6{right:50%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-9{right:75%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-12{right:100%}.col-sm-push-0{left:auto}.col-sm-push-1{left:8.33333333%}.col-sm-push-2{left:16.66666667%}.col-sm-push-3{left:25%}.col-sm-push-4{left:33.33333333%}.col-sm-push-5{left:41.66666667%}.col-sm-push-6{left:50%}.col-sm-push-7{left:58.33333333%}.col-sm-push-8{left:66.66666667%}.col-sm-push-9{left:75%}.col-sm-push-10{left:83.33333333%}.col-sm-push-11{left:91.66666667%}.col-sm-push-12{left:100%}.col-sm-offset-0{margin-left:0}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-12{margin-left:100%}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-1{width:8.33333333%}.col-md-2{width:16.66666667%}.col-md-3{width:25%}.col-md-4{width:33.33333333%}.col-md-5{width:41.66666667%}.col-md-6{width:50%}.col-md-7{width:58.33333333%}.col-md-8{width:66.66666667%}.col-md-9{width:75%}.col-md-10{width:83.33333333%}.col-md-11{width:91.66666667%}.col-md-12{width:100%}.col-md-pull-0{right:auto}.col-md-pull-1{right:8.33333333%}.col-md-pull-2{right:16.66666667%}.col-md-pull-3{right:25%}.col-md-pull-4{right:33.33333333%}.col-md-pull-5{right:41.66666667%}.col-md-pull-6{right:50%}.col-md-pull-7{right:58.33333333%}.col-md-pull-8{right:66.66666667%}.col-md-pull-9{right:75%}.col-md-pull-10{right:83.33333333%}.col-md-pull-11{right:91.66666667%}.col-md-pull-12{right:100%}.col-md-push-0{left:auto}.col-md-push-1{left:8.33333333%}.col-md-push-2{left:16.66666667%}.col-md-push-3{left:25%}.col-md-push-4{left:33.33333333%}.col-md-push-5{left:41.66666667%}.col-md-push-6{left:50%}.col-md-push-7{left:58.33333333%}.col-md-push-8{left:66.66666667%}.col-md-push-9{left:75%}.col-md-push-10{left:83.33333333%}.col-md-push-11{left:91.66666667%}.col-md-push-12{left:100%}.col-md-offset-0{margin-left:0}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-3{margin-left:25%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-6{margin-left:50%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-9{margin-left:75%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-12{margin-left:100%}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-1{width:8.33333333%}.col-lg-2{width:16.66666667%}.col-lg-3{width:25%}.col-lg-4{width:33.33333333%}.col-lg-5{width:41.66666667%}.col-lg-6{width:50%}.col-lg-7{width:58.33333333%}.col-lg-8{width:66.66666667%}.col-lg-9{width:75%}.col-lg-10{width:83.33333333%}.col-lg-11{width:91.66666667%}.col-lg-12{width:100%}.col-lg-pull-0{right:auto}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-3{right:25%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-6{right:50%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-9{right:75%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-12{right:100%}.col-lg-push-0{left:auto}.col-lg-push-1{left:8.33333333%}.col-lg-push-2{left:16.66666667%}.col-lg-push-3{left:25%}.col-lg-push-4{left:33.33333333%}.col-lg-push-5{left:41.66666667%}.col-lg-push-6{left:50%}.col-lg-push-7{left:58.33333333%}.col-lg-push-8{left:66.66666667%}.col-lg-push-9{left:75%}.col-lg-push-10{left:83.33333333%}.col-lg-push-11{left:91.66666667%}.col-lg-push-12{left:100%}.col-lg-offset-0{margin-left:0}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-12{margin-left:100%}}table{background-color:#fafafa}caption{padding-top:6px;padding-bottom:6px;color:#737373;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:18px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:6px;line-height:1.5;vertical-align:top;border-top:1px solid #ccc}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ccc}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ccc}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:6px}.table-bordered{border:1px solid #ccc}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ccc}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f7f7f7}.table-hover>tbody>tr:hover{background-color:#ededed}table col[class*=col-]{position:static;float:none;display:table-column}table td[class*=col-],table th[class*=col-]{position:static;float:none;display:table-cell}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#ededed}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e1e0e0}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#d1e2bd}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#c4dbab}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#ebf3fb}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#d6e7f6}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fbefdd}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#f8e4c6}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#efc7c7}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#eab3b3}.table-responsive{overflow-x:auto;min-height:.01%}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:13.5px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ccc}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{padding:0;margin:0;border:0;min-width:0}legend{display:block;width:100%;padding:0;margin-bottom:18px;font-size:18px;line-height:inherit;color:#5a5a5a;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:12px;line-height:1.5;color:#333}.form-control{display:block;width:100%;height:32px;padding:6px 6px;font-size:12px;line-height:1.5;color:#333;background-color:#fefefe;background-image:none;border:1px solid #bbb;border-radius:2px;box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#aaa;outline:0;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(170,170,170,.6)}.form-control::-moz-placeholder{color:#d7d7d7;opacity:1}.form-control:-ms-input-placeholder{color:#d7d7d7}.form-control::-webkit-input-placeholder{color:#d7d7d7}.form-control::-ms-expand{border:0;background-color:transparent}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#f5f5f5;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=datetime-local].form-control,input[type=month].form-control,input[type=time].form-control{line-height:32px}.input-group-sm input[type=date],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],.input-group-sm input[type=time],.input-group-sm>.input-group-btn>input[type=date].btn,.input-group-sm>.input-group-btn>input[type=datetime-local].btn,.input-group-sm>.input-group-btn>input[type=month].btn,.input-group-sm>.input-group-btn>input[type=time].btn,.input-group-sm>input[type=date].form-control,.input-group-sm>input[type=date].input-group-addon,.input-group-sm>input[type=datetime-local].form-control,.input-group-sm>input[type=datetime-local].input-group-addon,.input-group-sm>input[type=month].form-control,.input-group-sm>input[type=month].input-group-addon,.input-group-sm>input[type=time].form-control,.input-group-sm>input[type=time].input-group-addon,input[type=date].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm,input[type=time].input-sm{line-height:26px}.input-group-lg input[type=date],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],.input-group-lg input[type=time],.input-group-lg>.input-group-btn>input[type=date].btn,.input-group-lg>.input-group-btn>input[type=datetime-local].btn,.input-group-lg>.input-group-btn>input[type=month].btn,.input-group-lg>.input-group-btn>input[type=time].btn,.input-group-lg>input[type=date].form-control,.input-group-lg>input[type=date].input-group-addon,.input-group-lg>input[type=datetime-local].form-control,.input-group-lg>input[type=datetime-local].input-group-addon,.input-group-lg>input[type=month].form-control,.input-group-lg>input[type=month].input-group-addon,.input-group-lg>input[type=time].form-control,.input-group-lg>input[type=time].input-group-addon,input[type=date].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg,input[type=time].input-lg{line-height:41.2px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:18px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:400;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{padding-top:7px;padding-bottom:7px;margin-bottom:0;min-height:30px}.form-control-static.input-lg,.form-control-static.input-sm,.input-group-lg>.form-control-static.form-control,.input-group-lg>.form-control-static.input-group-addon,.input-group-lg>.input-group-btn>.form-control-static.btn,.input-group-sm>.form-control-static.form-control,.input-group-sm>.form-control-static.input-group-addon,.input-group-sm>.input-group-btn>.form-control-static.btn{padding-left:0;padding-right:0}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn,.input-sm{height:26px;padding:4px 4px;font-size:11px;line-height:1.5;border-radius:2px}.input-group-sm>.input-group-btn>select.btn,.input-group-sm>select.form-control,.input-group-sm>select.input-group-addon,select.input-sm{height:26px;line-height:26px}.input-group-sm>.input-group-btn>select[multiple].btn,.input-group-sm>.input-group-btn>textarea.btn,.input-group-sm>select[multiple].form-control,.input-group-sm>select[multiple].input-group-addon,.input-group-sm>textarea.form-control,.input-group-sm>textarea.input-group-addon,select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:26px;padding:4px 4px;font-size:11px;line-height:1.5;border-radius:2px}.form-group-sm select.form-control{height:26px;line-height:26px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:26px;min-height:29px;padding:5px 4px;font-size:11px;line-height:1.5}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn,.input-lg{height:41.2px;padding:12px 12px;font-size:15px;line-height:1.3333333;border-radius:2px}.input-group-lg>.input-group-btn>select.btn,.input-group-lg>select.form-control,.input-group-lg>select.input-group-addon,select.input-lg{height:41.2px;line-height:41.2px}.input-group-lg>.input-group-btn>select[multiple].btn,.input-group-lg>.input-group-btn>textarea.btn,.input-group-lg>select[multiple].form-control,.input-group-lg>select[multiple].input-group-addon,.input-group-lg>textarea.form-control,.input-group-lg>textarea.input-group-addon,select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:41.2px;padding:12px 12px;font-size:15px;line-height:1.3333333;border-radius:2px}.form-group-lg select.form-control{height:41.2px;line-height:41.2px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:41.2px;min-height:33px;padding:13px 12px;font-size:15px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:40px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:32px;height:32px;line-height:32px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-group-lg>.form-control+.form-control-feedback,.input-group-lg>.input-group-addon+.form-control-feedback,.input-group-lg>.input-group-btn>.btn+.form-control-feedback,.input-lg+.form-control-feedback{width:41.2px;height:41.2px;line-height:41.2px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-group-sm>.form-control+.form-control-feedback,.input-group-sm>.input-group-addon+.form-control-feedback,.input-group-sm>.input-group-btn>.btn+.form-control-feedback,.input-sm+.form-control-feedback{width:26px;height:26px;line-height:26px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#79a548}.has-success .form-control{border-color:#79a548;box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#5f8139;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #abcb88}.has-success .input-group-addon{color:#79a548;border-color:#79a548;background-color:#d1e2bd}.has-success .form-control-feedback{color:#79a548}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#e8a33d}.has-warning .form-control{border-color:#e8a33d;box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#d88b1a;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #f3ce98}.has-warning .input-group-addon{color:#e8a33d;border-color:#e8a33d;background-color:#fbefdd}.has-warning .form-control-feedback{color:#e8a33d}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#c83c3c}.has-error .form-control{border-color:#c83c3c;box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#a32e2e;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #de8c8c}.has-error .input-group-addon{color:#c83c3c;border-color:#c83c3c;background-color:#efc7c7}.has-error .form-control-feedback{color:#c83c3c}.has-feedback label~.form-control-feedback{top:23px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#404040}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{margin-top:0;margin-bottom:0;padding-top:7px}.form-horizontal .checkbox,.form-horizontal .radio{min-height:25px}.form-horizontal .form-group{margin-left:-15px;margin-right:-15px}.form-horizontal .form-group:after{content:"";display:table;clear:both}@media (min-width:768px){.form-horizontal .control-label{text-align:right;margin-bottom:0;padding-top:7px}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:13px;font-size:15px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:5px;font-size:11px}}.btn{display:inline-block;margin-bottom:0;font-weight:400;text-align:center;vertical-align:middle;touch-action:manipulation;cursor:pointer;background-image:none;border:1px solid transparent;white-space:nowrap;padding:6px 6px;font-size:12px;line-height:1.5;border-radius:2px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{outline:0;background-image:none;box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;opacity:.65;box-shadow:none}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#eee;border-color:#bbb}.btn-default.focus,.btn-default:focus{color:#333;background-color:#d5d4d4;border-color:#7b7b7b}.btn-default:hover{color:#333;background-color:#d5d4d4;border-color:#9c9c9c}.btn-default.active,.btn-default:active,.open>.btn-default.dropdown-toggle{color:#333;background-color:#d5d4d4;border-color:#9c9c9c}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.btn-default.dropdown-toggle.focus,.open>.btn-default.dropdown-toggle:focus,.open>.btn-default.dropdown-toggle:hover{color:#333;background-color:#c3c3c3;border-color:#7b7b7b}.btn-default.active,.btn-default:active,.open>.btn-default.dropdown-toggle{background-image:none}.btn-default.disabled.focus,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled].focus,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#eee;border-color:#bbb}.btn-default .badge{color:#eee;background-color:#333}.btn-primary{color:#fff;background-color:#0078e6;border-color:#006bcd}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#005db3;border-color:#00284d}.btn-primary:hover{color:#fff;background-color:#005db3;border-color:#004b8f}.btn-primary.active,.btn-primary:active,.open>.btn-primary.dropdown-toggle{color:#fff;background-color:#005db3;border-color:#004b8f}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.btn-primary.dropdown-toggle.focus,.open>.btn-primary.dropdown-toggle:focus,.open>.btn-primary.dropdown-toggle:hover{color:#fff;background-color:#004b8f;border-color:#00284d}.btn-primary.active,.btn-primary:active,.open>.btn-primary.dropdown-toggle{background-image:none}.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled].focus,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#0078e6;border-color:#006bcd}.btn-primary .badge{color:#0078e6;background-color:#fff}.btn-success{color:#fff;background-color:#79a548;border-color:#6c9340}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#5f8139;border-color:#2b3a1a}.btn-success:hover{color:#fff;background-color:#5f8139;border-color:#4d692e}.btn-success.active,.btn-success:active,.open>.btn-success.dropdown-toggle{color:#fff;background-color:#5f8139;border-color:#4d692e}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.btn-success.dropdown-toggle.focus,.open>.btn-success.dropdown-toggle:focus,.open>.btn-success.dropdown-toggle:hover{color:#fff;background-color:#4d692e;border-color:#2b3a1a}.btn-success.active,.btn-success:active,.open>.btn-success.dropdown-toggle{background-image:none}.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled].focus,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#79a548;border-color:#6c9340}.btn-success .badge{color:#79a548;background-color:#fff}.btn-info{color:#fff;background-color:#6daae0;border-color:#589edc}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#4392d7;border-color:#205e94}.btn-info:hover{color:#fff;background-color:#4392d7;border-color:#2b80cb}.btn-info.active,.btn-info:active,.open>.btn-info.dropdown-toggle{color:#fff;background-color:#4392d7;border-color:#2b80cb}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.btn-info.dropdown-toggle.focus,.open>.btn-info.dropdown-toggle:focus,.open>.btn-info.dropdown-toggle:hover{color:#fff;background-color:#2b80cb;border-color:#205e94}.btn-info.active,.btn-info:active,.open>.btn-info.dropdown-toggle{background-image:none}.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled].focus,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#6daae0;border-color:#589edc}.btn-info .badge{color:#6daae0;background-color:#fff}.btn-warning{color:#fff;background-color:#e8a33d;border-color:#e59826}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#d88b1a;border-color:#7d510f}.btn-warning:hover{color:#fff;background-color:#d88b1a;border-color:#b87716}.btn-warning.active,.btn-warning:active,.open>.btn-warning.dropdown-toggle{color:#fff;background-color:#d88b1a;border-color:#b87716}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.btn-warning.dropdown-toggle.focus,.open>.btn-warning.dropdown-toggle:focus,.open>.btn-warning.dropdown-toggle:hover{color:#fff;background-color:#b87716;border-color:#7d510f}.btn-warning.active,.btn-warning:active,.open>.btn-warning.dropdown-toggle{background-image:none}.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled].focus,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#e8a33d;border-color:#e59826}.btn-warning .badge{color:#e8a33d;background-color:#fff}.btn-danger{color:#fff;background-color:#c83c3c;border-color:#b73434}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#a32e2e;border-color:#531818}.btn-danger:hover{color:#fff;background-color:#a32e2e;border-color:#872626}.btn-danger.active,.btn-danger:active,.open>.btn-danger.dropdown-toggle{color:#fff;background-color:#a32e2e;border-color:#872626}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.btn-danger.dropdown-toggle.focus,.open>.btn-danger.dropdown-toggle:focus,.open>.btn-danger.dropdown-toggle:hover{color:#fff;background-color:#872626;border-color:#531818}.btn-danger.active,.btn-danger:active,.open>.btn-danger.dropdown-toggle{background-image:none}.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled].focus,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#c83c3c;border-color:#b73434}.btn-danger .badge{color:#c83c3c;background-color:#fff}.btn-link{color:#212424;font-weight:400;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#000;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#d7d7d7;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:12px 12px;font-size:15px;line-height:1.3333333;border-radius:2px}.btn-group-sm>.btn,.btn-sm{padding:4px 4px;font-size:11px;line-height:1.5;border-radius:2px}.btn-group-xs>.btn,.btn-xs{padding:2px 4px;font-size:11px;line-height:1.5;border-radius:2px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-property:height,visibility;transition-property:height,visibility;-webkit-transition-duration:.35s;transition-duration:.35s;-webkit-transition-timing-function:ease;transition-timing-function:ease}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;font-size:12px;text-align:left;background-color:#292929;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:2px;box-shadow:0 6px 12px rgba(0,0,0,.175);background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:8px 0;overflow:hidden;background-color:#424242}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.5;color:#fff;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{text-decoration:none;color:#fff;background-color:#424242}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;outline:0;background-color:#424242}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#d7d7d7}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;background-color:transparent;background-image:none;cursor:not-allowed}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{left:auto;right:0}.dropdown-menu-left{left:0;right:auto}.dropdown-header{display:block;padding:3px 20px;font-size:11px;line-height:1.5;color:#d7d7d7;white-space:nowrap}.dropdown-backdrop{position:fixed;left:0;right:0;bottom:0;top:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px dashed;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:992px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{left:0;right:auto}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar:after{content:"";display:table;clear:both}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-bottom-left-radius:0;border-top-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px}.btn-group-lg.btn-group>.btn+.dropdown-toggle,.btn-group>.btn-lg+.dropdown-toggle{padding-left:12px;padding-right:12px}.btn-group.open .dropdown-toggle{box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{box-shadow:none}.btn .caret{margin-left:0}.btn-group-lg>.btn .caret,.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-group-lg>.btn .caret,.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group:after{content:"";display:table;clear:both}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:2px;border-top-left-radius:2px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-right-radius:0;border-top-left-radius:0;border-bottom-right-radius:2px;border-bottom-left-radius:2px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-right-radius:0;border-top-left-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{float:none;display:table-cell;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-left:0;padding-right:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 6px;font-size:12px;font-weight:400;line-height:1;color:#333;text-align:center;background-color:#f5f5f5;border:1px solid #bbb;border-radius:2px}.input-group-addon.input-sm,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.input-group-addon.btn{padding:4px 4px;font-size:11px;border-radius:2px}.input-group-addon.input-lg,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.input-group-addon.btn{padding:12px 12px;font-size:15px;border-radius:2px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-bottom-left-radius:0;border-top-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{margin-bottom:0;padding-left:0;list-style:none}.nav:after{content:"";display:table;clear:both}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:6px 12px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#f5f5f5}.nav>li.disabled>a{color:#d7d7d7}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#5a5a5a;text-decoration:none;background-color:transparent;cursor:not-allowed}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#f5f5f5;border-color:#212424}.nav .nav-divider{height:1px;margin:8px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ccc}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.5;border:1px solid transparent;border-radius:2px 2px 0 0}.nav-tabs>li>a:hover{border-color:#d7d7d7 #d7d7d7 #ccc}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#000;background-color:#ededed;border:1px solid #ccc;border-bottom-color:transparent;cursor:default}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:2px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#0078e6}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified,.nav-tabs.nav-justified{width:100%}.nav-justified>li,.nav-tabs.nav-justified>li{float:none}.nav-justified>li>a,.nav-tabs.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li,.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a,.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified,.nav-tabs.nav-justified{border-bottom:0}.nav-tabs-justified>li>a,.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:2px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ccc}@media (min-width:768px){.nav-tabs-justified>li>a,.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ccc;border-radius:2px 2px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#ccc}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0}.pagination{display:inline-block;padding-left:0;margin:18px 0;border-radius:2px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 6px;line-height:1.5;text-decoration:none;color:#212424;background-color:#fff;border:1px solid #ddd;margin-left:-1px}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:2px;border-top-left-radius:2px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-bottom-right-radius:2px;border-top-right-radius:2px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:2;color:#000;background-color:#f5f5f5;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:3;color:#fff;background-color:#0078e6;border-color:#0078e6;cursor:default}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#d7d7d7;background-color:#fff;border-color:#ddd;cursor:not-allowed}.pagination-lg>li>a,.pagination-lg>li>span{padding:12px 12px;font-size:15px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:2px;border-top-left-radius:2px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-bottom-right-radius:2px;border-top-right-radius:2px}.pagination-sm>li>a,.pagination-sm>li>span{padding:4px 4px;font-size:11px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:2px;border-top-left-radius:2px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-bottom-right-radius:2px;border-top-right-radius:2px}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}.label:empty{display:none}.btn .label{position:relative;top:-1px}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label-default{background-color:#d7d7d7}.label-default[href]:focus,.label-default[href]:hover{background-color:#bebdbd}.label-primary{background-color:#0078e6}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#005db3}.label-success{background-color:#79a548}.label-success[href]:focus,.label-success[href]:hover{background-color:#5f8139}.label-info{background-color:#6daae0}.label-info[href]:focus,.label-info[href]:hover{background-color:#4392d7}.label-warning{background-color:#e8a33d}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#d88b1a}.label-danger{background-color:#c83c3c}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#a32e2e}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:11px;font-weight:700;color:#fff;line-height:1;vertical-align:middle;white-space:nowrap;text-align:center;background-color:#737373;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#212424;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.thumbnail{display:block;padding:4px;margin-bottom:18px;line-height:1.5;background-color:#fff;border:1px solid #ddd;border-radius:2px;-webkit-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto;margin-left:auto;margin-right:auto}.thumbnail .caption{padding:9px;color:#000}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#212424}.alert{padding:11px;margin-bottom:18px;border:1px solid transparent;border-radius:2px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:31px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{background-color:#79a548;border-color:transparent;color:#fff}.alert-success hr{border-top-color:transparent}.alert-success .alert-link{color:#e6e5e5}.alert-info{background-color:#6daae0;border-color:transparent;color:#fff}.alert-info hr{border-top-color:transparent}.alert-info .alert-link{color:#e6e5e5}.alert-warning{background-color:#e8a33d;border-color:transparent;color:#fff}.alert-warning hr{border-top-color:transparent}.alert-warning .alert-link{color:#e6e5e5}.alert-danger{background-color:#c83c3c;border-color:transparent;color:#fff}.alert-danger hr{border-top-color:transparent}.alert-danger .alert-link{color:#e6e5e5}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{overflow:hidden;height:18px;margin-bottom:18px;background-color:#dedede;border-radius:2px;box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0%;height:100%;font-size:11px;line-height:18px;color:#fff;text-align:center;background-color:#0078e6;box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#79a548}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#6daae0}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#e8a33d}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#c83c3c}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{zoom:1;overflow:hidden}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{margin-bottom:20px;padding-left:0}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-right-radius:2px;border-top-left-radius:2px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:2px;border-bottom-left-radius:2px}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{text-decoration:none;color:#555;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{background-color:#f5f5f5;color:#d7d7d7;cursor:not-allowed}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#d7d7d7}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#0078e6;border-color:#0078e6}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#b3dbff}.list-group-item-success{color:#79a548;background-color:#d1e2bd}a.list-group-item-success,button.list-group-item-success{color:#79a548}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#79a548;background-color:#c4dbab}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#79a548;border-color:#79a548}.list-group-item-info{color:#6daae0;background-color:#ebf3fb}a.list-group-item-info,button.list-group-item-info{color:#6daae0}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#6daae0;background-color:#d6e7f6}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#6daae0;border-color:#6daae0}.list-group-item-warning{color:#e8a33d;background-color:#fbefdd}a.list-group-item-warning,button.list-group-item-warning{color:#e8a33d}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#e8a33d;background-color:#f8e4c6}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#e8a33d;border-color:#e8a33d}.list-group-item-danger{color:#c83c3c;background-color:#efc7c7}a.list-group-item-danger,button.list-group-item-danger{color:#c83c3c}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#c83c3c;background-color:#eab3b3}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#c83c3c;border-color:#c83c3c}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:18px;background-color:#fff;border:1px solid transparent;border-radius:2px;box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-body:after{content:"";display:table;clear:both}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:1px;border-top-left-radius:1px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:14px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:1px;border-bottom-left-radius:1px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-right-radius:1px;border-top-left-radius:1px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:1px;border-bottom-left-radius:1px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-right-radius:0;border-top-left-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-left:15px;padding-right:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-right-radius:1px;border-top-left-radius:1px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:1px;border-top-right-radius:1px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:1px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:1px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:1px;border-bottom-left-radius:1px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-left-radius:1px;border-bottom-right-radius:1px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:1px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:1px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ccc}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-respon