Commit 5768427f authored by Benni Mack's avatar Benni Mack Committed by Christian Kuhn
Browse files

[TASK] Migrate cHash configuration on silent upgrade wizard

The cHash calculation is done on every TYPO3 request, which is
really unnecessary overhead, as this could be taken care by the
SilentConfigurationUpgradeService once and store the new value.

Resolves: #81568
Releases: master
Change-Id: I30b562c0b9376d1044979cd7264c67a79209f6b6
Reviewed-on: https://review.typo3.org/53202

Reviewed-by: default avatarNathan Boiron <nathan.boiron@gmail.com>
Tested-by: default avatarNathan Boiron <nathan.boiron@gmail.com>
Tested-by: default avatarTYPO3com <no-reply@typo3.com>
Reviewed-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
parent 5b306d4a
......@@ -401,7 +401,6 @@ class Bootstrap
$this->initializeCachingFramework()
->initializePackageManagement($packageManagerClassName)
->initializeRuntimeActivatedPackagesFromConfiguration()
->setCacheHashOptions()
->setDefaultTimezone()
->initializeL10nLocales()
->setMemoryLimit();
......@@ -531,26 +530,6 @@ class Bootstrap
return $this;
}
/**
* Set cacheHash options
*
* @return Bootstrap
*/
protected function setCacheHashOptions()
{
$GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash'] = [
'cachedParametersWhiteList' => GeneralUtility::trimExplode(',', $GLOBALS['TYPO3_CONF_VARS']['FE']['cHashOnlyForParameters'], true),
'excludedParameters' => GeneralUtility::trimExplode(',', $GLOBALS['TYPO3_CONF_VARS']['FE']['cHashExcludedParameters'], true),
'requireCacheHashPresenceParameters' => GeneralUtility::trimExplode(',', $GLOBALS['TYPO3_CONF_VARS']['FE']['cHashRequiredParameters'], true),
];
if (trim($GLOBALS['TYPO3_CONF_VARS']['FE']['cHashExcludedParametersIfEmpty']) === '*') {
$GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash']['excludeAllEmptyParameters'] = true;
} else {
$GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash']['excludedParametersIfEmpty'] = GeneralUtility::trimExplode(',', $GLOBALS['TYPO3_CONF_VARS']['FE']['cHashExcludedParametersIfEmpty'], true);
}
return $this;
}
/**
* Set default timezone
*
......
......@@ -1038,11 +1038,13 @@ return [
'hidePagesIfNotTranslatedByDefault' => false,
'eID_include' => [], // Array of key/value pairs where key is "tx_[ext]_[optional suffix]" and value is relative filename of class to include. Key is used as "?eID=" for \TYPO3\CMS\Frontend\Http\RequestHandlerRequestHandler to include the code file which renders the page from that point. (Useful for functionality that requires a low initialization footprint, eg. frontend ajax applications)
'disableNoCacheParameter' => false,
'cacheHash' => [], // Array: Processed values of the cHash* parameters, handled by core bootstrap internally
'cHashExcludedParameters' => 'L, pk_campaign, pk_kwd, utm_source, utm_medium, utm_campaign, utm_term, utm_content',
'cHashOnlyForParameters' => '',
'cHashRequiredParameters' => '',
'cHashExcludedParametersIfEmpty' => '',
'cacheHash' => [
'cachedParametersWhiteList' => [],
'excludedParameters' => ['L', 'pk_campaign', 'pk_kwd', 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content'],
'requireCacheHashPresenceParameters' => [],
'excludeAllEmptyParameters' => false,
'excludedParametersIfEmpty' => []
],
'workspacePreviewLogoutTemplate' => '',
'versionNumberInFilename' => 'querystring',
'contentRenderingTemplates' => [], // Array to define the TypoScript parts that define the main content rendering. Extensions like "fluid_styled_content" provide content rendering templates. Other extensions like "felogin" or "indexed search" extend these templates and their TypoScript parts are added directly after the content templates. See EXT:fluid_styled_content/ext_localconf.php and EXT:frontend/Classes/TypoScript/TemplateService.php
......
......@@ -420,18 +420,24 @@ FE:
disableNoCacheParameter:
type: bool
description: 'If set, the no_cache request parameter will become ineffective. This is currently still an experimental feature and will require a website only with plugins that don''t use this parameter. However, using "&amp;no_cache=1" should be avoided anyway because there are better ways to disable caching for a certain part of the website (see COA_INT/USER_INT documentation in TSref).'
cHashExcludedParameters:
type: text
description: 'The the given parameters will be ignored in the cHash calculation. Example: L,tx_search_pi1[query]'
cHashOnlyForParameters:
type: text
description: 'Only the given parameters will be evaluated in the cHash calculation. Example: tx_news_pi1[uid]'
cHashRequiredParameters:
type: text
description: 'Optional: Configure Parameters that require a cHash. If no cHash is given but one of the parameters are set, then TYPO3 triggers the configured cHash Error behaviour'
cHashExcludedParametersIfEmpty:
type: text
description: 'Optional: Configure Parameters that are only relevant for the chash if there''s an associated value available. And asterisk "*" can be used to skip all empty parameters.'
cacheHash:
type: container
items:
cachedParametersWhiteList:
type: array
description: 'Only the given parameters will be evaluated in the cHash calculation. Example: tx_news_pi1[uid]'
requireCacheHashPresenceParameters:
type: array
description: 'Configure Parameters that require a cHash. If no cHash is given but one of the parameters are set, then TYPO3 triggers the configured cHash Error behaviour'
excludedParameters:
type: array
description: 'The the given parameters will be ignored in the cHash calculation. Example: L,tx_search_pi1[query]'
excludedParametersIfEmpty:
type: array
description: 'Configure Parameters that are only relevant for the cHash if there''s an associated value available. Set excludeAllEmptyParameters to true to skip all empty parameters.'
excludeAllEmptyParameters:
type: bool
description: 'If true, all parameters which are relevant for cHash are only considered if they are non-empty.'
workspacePreviewLogoutTemplate:
type: text
description: 'If set, points to an HTML file relative to the TYPO3_site root which will be read and outputted as template for this message. Example: <code>fileadmin/templates/template_workspace_preview_logout.html</code>. Inside you can put the marker %1$s to insert the URL to go back to. Use this in <code>&lt;a href="%1$s"&gt;Go back...&lt;/a&gt;</code> links.'
......
.. include:: ../../Includes.txt
===============================================
Important: #81568 - Migrate cHash configuration
===============================================
See :issue:`81568`
Description
===========
All cHash-related configuration options have been previously migrated on every TYPO3 request
into an array-structured form.
The following cHash-related configuration entries below have been migrated:
- :php:`$TYPO3_CONF_VARS['FE']['cHashExcludedParameters']` is now an array instead of a
comma-separated list, and migrated to :php:`$TYPO3_CONF_VARS['FE']['cacheHash']['excludedParameters']`
- :php:`$TYPO3_CONF_VARS['FE']['cHashOnlyForParameters']` is now an array instead of a
comma-separated list, and migrated to :php:`$TYPO3_CONF_VARS['FE']['cacheHash']['cachedParametersWhiteList']`
- :php:`$TYPO3_CONF_VARS['FE']['cHashRequiredParameters']` is now an array instead of a
comma-separated list, and migrated to :php:`$TYPO3_CONF_VARS['FE']['cacheHash']['requireCacheHashPresenceParameters']`
- :php:`$TYPO3_CONF_VARS['FE']['cHashExcludedParametersIfEmpty']`
* If the old value was ``*``, the following parameter is now set to true to the
option :php:`$TYPO3_CONF_VARS['FE']['cacheHash']['excludeAllEmptyParameters']`
* If the old values were a comma-separated list, they are now migrated as an array to
:php:`$TYPO3_CONF_VARS['FE']['cacheHash']['excludedParametersIfEmpty']`
These values are now migrated as a "silent upgrade wizard" via the Install Tool to the format
that TYPO3 uses internally since several versions.
Impact
======
Any values set in ``LocalConfiguration.php`` are migrated automatically. If any of the options were
overriden in ``AdditionalConfiguration.php`` these need to be adapted manually.
Changes within extensions are not affected.
.. index:: LocalConfiguration, Frontend
......@@ -180,8 +180,8 @@ class CacheHashCalculatorTest extends \TYPO3\TestingFramework\Core\Unit\UnitTest
}
/**
* In case the cHashOnlyForParameters is set, other parameters should not
* incluence the cHash (except the encryption key of course)
* In case the $TYPO3_CONF_VARS[FE][cacheHash][cachedParametersWhiteList] is set, other parameters should not
* influence the cHash (except the encryption key of course)
*
* @dataProvider canWhitelistParametersDataProvider
* @test
......
......@@ -38,51 +38,89 @@ class LocalConfigurationValueService
public function getCurrentConfigurationData(): array
{
$data = [];
$typo3ConfVars = array_keys($GLOBALS['TYPO3_CONF_VARS']);
sort($typo3ConfVars);
$commentArray = $this->getDefaultConfigArrayComments();
foreach ($typo3ConfVars as $sectionName) {
$data[$sectionName] = [];
foreach ($GLOBALS['TYPO3_CONF_VARS'][$sectionName] as $key => $value) {
$descriptionInfo = $commentArray[$sectionName]['items'][$key];
$descriptionType = $descriptionInfo['type'];
if (!is_array($value) && (!preg_match('/[' . LF . CR . ']/', (string)$value) || $descriptionType === 'multiline')) {
$itemData = [];
$itemData['key'] = $key;
$itemData['fieldType'] = $descriptionInfo['type'];
$itemData['description'] = $descriptionInfo['description'];
$itemData['allowedValues'] = $descriptionInfo['allowedValues'];
$itemData['key'] = $key;
switch ($descriptionType) {
case 'multiline':
$itemData['type'] = 'textarea';
$itemData['value'] = str_replace(['\' . LF . \'', '\' . LF . \''], [LF, LF], $value);
foreach ($GLOBALS['TYPO3_CONF_VARS'] as $sectionName => $section) {
if (isset($commentArray[$sectionName])) {
$data[$sectionName] = $this->recursiveConfigurationFetching($section, $commentArray[$sectionName]);
}
}
ksort($data);
return $data;
}
/**
* Because configuration entries can be at any sub-array level, we need
* to check entries recursively.
*
* @param array $sections
* @param array $descriptions
* @param array $path
* @return array
*/
protected function recursiveConfigurationFetching(array $sections, array $descriptions, array $path = []): array
{
$data = [];
foreach ($sections as $key => $value) {
if (!isset($descriptions['items'][$key])) {
// @todo should we do something here?
continue;
}
$descriptionInfo = $descriptions['items'][$key];
$descriptionType = $descriptionInfo['type'];
$newPath = $path;
$newPath[] = $key;
if ($descriptionType === 'container') {
$data = array_merge($data, $this->recursiveConfigurationFetching($value, $descriptionInfo, $newPath));
} elseif (!preg_match('/[' . LF . CR . ']/', (string)$value) || $descriptionType === 'multiline') {
$itemData = [];
$itemData['key'] = implode('/', $newPath);
$itemData['path'] = '[' . implode('][', $newPath) . ']';
$itemData['fieldType'] = $descriptionInfo['type'];
$itemData['description'] = $descriptionInfo['description'];
$itemData['allowedValues'] = $descriptionInfo['allowedValues'];
switch ($descriptionType) {
case 'multiline':
$itemData['type'] = 'textarea';
$itemData['value'] = str_replace(['\' . LF . \'', '\' . LF . \''], [LF, LF], $value);
break;
case 'bool':
$itemData['type'] = 'checkbox';
$itemData['value'] = $value ? '1' : '0';
$itemData['checked'] = (bool)$value;
case 'bool':
$itemData['type'] = 'checkbox';
$itemData['value'] = $value ? '1' : '0';
$itemData['checked'] = (bool)$value;
break;
case 'int':
$itemData['type'] = 'number';
$itemData['value'] = (int)$value;
case 'int':
$itemData['type'] = 'number';
$itemData['value'] = (int)$value;
break;
// Check if the setting is a PHP error code, will trigger a view helper in fluid
case 'errors':
$itemData['type'] = 'input';
$itemData['value'] = $value;
$itemData['phpErrorCode'] = true;
case 'array':
$itemData['type'] = 'input';
// @todo The line below should be improved when the array handling is introduced in the global settings manager.
$itemData['value'] = is_array($value)
? implode(',', $value)
: (string)$value;
break;
default:
$itemData['type'] = 'input';
$itemData['value'] = $value;
}
$data[$sectionName][] = $itemData;
// Check if the setting is a PHP error code, will trigger a view helper in fluid
case 'errors':
$itemData['type'] = 'input';
$itemData['value'] = $value;
$itemData['phpErrorCode'] = true;
break;
default:
$itemData['type'] = 'input';
$itemData['value'] = $value;
}
$data[] = $itemData;
}
}
return $data;
}
......@@ -101,7 +139,12 @@ class LocalConfigurationValueService
foreach ($valueList as $path => $value) {
$oldValue = $configurationManager->getConfigurationValueByPath($path);
$pathParts = explode('/', $path);
$descriptionData = $commentArray[$pathParts[0]]['items'][$pathParts[1]];
$descriptionData = $commentArray[$pathParts[0]];
while ($part = next($pathParts)) {
$descriptionData = $descriptionData['items'][$part];
}
$dataType = $descriptionData['type'];
if ($dataType === 'multiline') {
......@@ -120,6 +163,12 @@ class LocalConfigurationValueService
// Cast integer values to integers (but only for values that can not contain a string as well)
$value = (int)$value;
$valueHasChanged = (int)$oldValue !== $value;
} elseif ($dataType === 'array') {
$oldValueAsString = is_array($oldValue)
? implode(',', $oldValue)
: (string)$oldValue;
$valueHasChanged = $oldValueAsString !== $value;
$value = GeneralUtility::trimExplode(',', $value, true);
} else {
$valueHasChanged = (string)$oldValue !== (string)$value;
}
......@@ -127,11 +176,17 @@ class LocalConfigurationValueService
// Save if value changed
if ($valueHasChanged) {
$configurationPathValuePairs[$path] = $value;
if (is_bool($value)) {
$messageBody = 'New value = ' . ($value ? 'true' : 'false');
} elseif (empty($value)) {
$messageBody = 'New value = <i>none</i>';
} elseif (is_array($value)) {
$messageBody = "New value = ['" . implode("', '", $value) . "']";
} else {
$messageBody = 'New value = ' . $value;
}
$messageQueue->enqueue(new FlashMessage(
$messageBody,
$path
......
......@@ -127,6 +127,7 @@ class SilentConfigurationUpgradeService
$this->migrateDatabaseConnectionCharset();
$this->migrateDatabaseDriverOptions();
$this->migrateLangDebug();
$this->migrateCacheHashOptions();
// Should run at the end to prevent that obsolete settings are removed before migration
$this->removeObsoleteLocalConfigurationSettings();
......@@ -787,4 +788,59 @@ class SilentConfigurationUpgradeService
// no change inside the LocalConfiguration.php found, so nothing needs to be modified
}
}
/**
* Migrate single cache hash related options under "FE" into "FE/cacheHash"
*/
protected function migrateCacheHashOptions()
{
$confManager = $this->configurationManager;
$removeSettings = [];
$newSettings = [];
try {
$value = $confManager->getLocalConfigurationValueByPath('FE/cHashOnlyForParameters');
$removeSettings[] = 'FE/cHashOnlyForParameters';
$newSettings['FE/cacheHash/cachedParametersWhiteList'] = GeneralUtility::trimExplode(',', $value, true);
} catch (\RuntimeException $e) {
}
try {
$value = $confManager->getLocalConfigurationValueByPath('FE/cHashExcludedParameters');
$removeSettings[] = 'FE/cHashExcludedParameters';
$newSettings['FE/cacheHash/excludedParameters'] = GeneralUtility::trimExplode(',', $value, true);
} catch (\RuntimeException $e) {
}
try {
$value = $confManager->getLocalConfigurationValueByPath('FE/cHashRequiredParameters');
$removeSettings[] = 'FE/cHashRequiredParameters';
$newSettings['FE/cacheHash/requireCacheHashPresenceParameters'] = GeneralUtility::trimExplode(',', $value, true);
} catch (\RuntimeException $e) {
}
try {
$value = $confManager->getLocalConfigurationValueByPath('FE/cHashExcludedParametersIfEmpty');
$removeSettings[] = 'FE/cHashExcludedParametersIfEmpty';
if (trim($value) === '*') {
$newSettings['FE/cacheHash/excludeAllEmptyParameters'] = true;
} else {
$newSettings['FE/cacheHash/excludedParametersIfEmpty'] = GeneralUtility::trimExplode(',', $value, true);
}
} catch (\RuntimeException $e) {
}
// Add new settings and remove old ones
if (!empty($newSettings)) {
$confManager->setLocalConfigurationValuesByPathValuePairs($newSettings);
}
if (!empty($removeSettings)) {
$confManager->removeLocalConfigurationKeysByPath($removeSettings);
}
// Throw redirect if something was changed
if (!empty($newSettings) || !empty($removeSettings)) {
$this->throwRedirectException();
}
}
}
......@@ -17,7 +17,7 @@
<a id="{sectionName}-{item.key}"></a>
<div class="item">
<div class="item-heading">
<strong>[{sectionName}][{item.key}] </strong>
<strong>[{sectionName}]{item.path} </strong>
<f:if condition="{item.type} == 'checkbox'">
<f:then>
=
......
......@@ -14,6 +14,7 @@ namespace TYPO3\CMS\Install\Tests\Unit\Service;
* The TYPO3 project - inspiring people to share!
*/
use Prophecy\Prophecy\ObjectProphecy;
use TYPO3\CMS\Core\Configuration\ConfigurationManager;
use TYPO3\CMS\Core\Package\PackageManager;
use TYPO3\CMS\Core\Tests\Unit\Utility\AccessibleProxies\ExtensionManagementUtilityAccessibleProxy;
......@@ -669,4 +670,75 @@ class SilentConfigurationUpgradeServiceTest extends \TYPO3\TestingFramework\Core
$silentConfigurationUpgradeServiceInstance->_call('migrateLangDebug');
}
/**
* @test
*
* @param array $oldValues
* @param array $newValues
*
* @dataProvider migrateCacheHashOptionsDataProvider
*/
public function migrateCacheHashOptions(array $oldValues, array $newValues)
{
/** @var ConfigurationManager|ObjectProphecy $configurationManager */
$configurationManager = $this->prophesize(ConfigurationManager::class);
foreach ($oldValues as $key => $value) {
$configurationManager->getLocalConfigurationValueByPath($key)
->shouldBeCalled()
->willReturn($value);
}
$configurationManager->setLocalConfigurationValuesByPathValuePairs($newValues)
->shouldBeCalled();
$configurationManager->removeLocalConfigurationKeysByPath(array_keys($oldValues))
->shouldBeCalled();
$this->expectException(RedirectException::class);
/** @var $silentConfigurationUpgradeServiceInstance SilentConfigurationUpgradeService|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface */
$silentConfigurationUpgradeServiceInstance = $this->getAccessibleMock(
SilentConfigurationUpgradeService::class,
['dummy'],
[],
'',
false
);
$silentConfigurationUpgradeServiceInstance->_set('configurationManager', $configurationManager->reveal());
$silentConfigurationUpgradeServiceInstance->_call('migrateCacheHashOptions');
}
/**
* @return array
*/
public function migrateCacheHashOptionsDataProvider()
{
return [
[
'old' => [
'FE/cHashOnlyForParameters' => 'foo,bar',
'FE/cHashExcludedParameters' => 'bar,foo',
'FE/cHashRequiredParameters' => 'bar,baz',
'FE/cHashExcludedParametersIfEmpty' => '*'
],
'new' => [
'FE/cacheHash/cachedParametersWhiteList' => ['foo', 'bar'],
'FE/cacheHash/excludedParameters' => ['bar', 'foo'],
'FE/cacheHash/requireCacheHashPresenceParameters' => ['bar', 'baz'],
'FE/cacheHash/excludeAllEmptyParameters' => true
]
],
[
'old' => [
'FE/cHashExcludedParametersIfEmpty' => 'foo,bar'
],
'new' => [
'FE/cacheHash/excludedParametersIfEmpty' => ['foo', 'bar']
]
]
];
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment