Commit 00677ce4 authored by Christian Kuhn's avatar Christian Kuhn Committed by Benni Mack
Browse files

[!!!][TASK] Prepare richtext configuration

Configuring a richtext field in TCA with the "defaultExtras"
setting (which was formerly the 5th showitem parameter) has
been ugly ever since: The DataHandler needed a special _TRANSFORM_
field to trigger required RTE transformations, further config
depends on pageTs settings.

The patch gets rid of "defaultExtras" for richtext elements and
adds a new option "enableRichtext=true" within the config section
for type=text TCA fields.
This setting can be set via "columnsOverrides" for specific types, too.

As an intermediate solution, the configuration class "Richtext" is
added to fetch richtext configuration. This will be extended with
another patch to provide ckeditor configuration on TCA level.

Change-Id: I70f4cb26e2a45629b99680e532d376538afd2b90
Resolves: #79341
Releases: master
Reviewed-on: https://review.typo3.org/51311


Reviewed-by: Frans Saris's avatarFrans Saris <franssaris@gmail.com>
Tested-by: default avatarTYPO3com <no-reply@typo3.com>
Reviewed-by: Susanne Moog's avatarSusanne Moog <susanne.moog@typo3.org>
Tested-by: Susanne Moog's avatarSusanne Moog <susanne.moog@typo3.org>
Reviewed-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
Tested-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
parent 7e90dbcb
<?php
declare(strict_types=1);
namespace TYPO3\CMS\Backend\Form\FormDataProvider;
/*
* 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\Form\FormDataProviderInterface;
use TYPO3\CMS\Core\Configuration\Richtext;
use TYPO3\CMS\Core\Html\RteHtmlParser;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Resolve databaseRow field content for type=text, especially handle
* richtext transformations "from db to rte"
*/
class TcaText implements FormDataProviderInterface
{
/**
* Handle text field content, especially richtext transformation
*
* @param array $result Given result array
* @return array Modified result array
*/
public function addData(array $result)
{
foreach ($result['processedTca']['columns'] as $fieldName => $fieldConfig) {
if (empty($fieldConfig['config']['type']) || $fieldConfig['config']['type'] !== 'text') {
continue;
}
if (isset($fieldConfig['config']['enableRichtext']) && $fieldConfig['config']['enableRichtext'] === true) {
$richtextConfigurationProvider = GeneralUtility::makeInstance(Richtext::class);
$richtextConfiguration = $richtextConfigurationProvider->getConfiguration(
$result['tableName'],
$fieldName,
$result['effectivePid'],
(string)$result['recordTypeValue'],
$fieldConfig['config']
);
// Add final resolved configuration to TCA array
$result['processedTca']['columns'][$fieldName]['config']['richtextConfiguration'] = $richtextConfiguration;
// If eval=null is set for field, value might be null ... don't transform anything in this case.
if ($result['databaseRow'][$fieldName] !== null) {
// Process "from-db-to-rte" on current value
$parseHTML = GeneralUtility::makeInstance(RteHtmlParser::class);
$parseHTML->init($result['tableName'] . ':' . $fieldName, $result['effectivePid']);
$result['databaseRow'][$fieldName] = $parseHTML->RTE_transform(
$result['databaseRow'][$fieldName],
[],
'rte',
$richtextConfiguration
);
}
}
}
return $result;
}
}
......@@ -910,9 +910,11 @@ class BackendUtility
*
* @param array $pArr Array of "[key] = [value]" strings to convert.
* @return array
* @deprecated since TYPO3 v8, will be removed in TYPO3 v9
*/
public static function getSpecConfParametersFromArray($pArr)
{
GeneralUtility::logDeprecatedFunction();
$out = [];
if (is_array($pArr)) {
foreach ($pArr as $k => $v) {
......@@ -4059,9 +4061,11 @@ class BackendUtility
* @param string $type Type value of the current record (like from CType of tt_content)
* @return array Array with the configuration for the RTE
* @internal
* @deprecated since TYPO3 v8, will be removed in TYPO3 v9.
*/
public static function RTEsetup($RTEprop, $table, $field, $type = '')
{
GeneralUtility::logDeprecatedFunction();
$thisConfig = is_array($RTEprop['default.']) ? $RTEprop['default.'] : [];
$thisFieldConf = $RTEprop['config.'][$table . '.'][$field . '.'];
if (is_array($thisFieldConf)) {
......
<?php
declare(strict_types=1);
namespace TYPO3\CMS\Backend\Tests\Unit\Form\FormDataProvider;
/*
* 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\Form\FormDataProvider\TcaText;
use TYPO3\CMS\Core\Configuration\Richtext;
use TYPO3\CMS\Core\Html\RteHtmlParser;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Test case
*/
class TcaTextTest extends \TYPO3\CMS\Components\TestingFramework\Core\UnitTestCase
{
/**
* @test
*/
public function addDataSetsRichtextConfigurationAndTransformsContent()
{
$input = [
'tableName' => 'aTable',
'effectivePid' => 42,
'recordTypeValue' => 23,
'databaseRow' => [
'aField' => 'notProcessedContent',
],
'processedTca' => [
'columns' => [
'aField' => [
'config' => [
'type' => 'text',
'enableRichtext' => true,
],
],
],
],
];
$expected = [
'tableName' => 'aTable',
'effectivePid' => 42,
'recordTypeValue' => 23,
'databaseRow' => [
'aField' => 'processedContent',
],
'processedTca' => [
'columns' => [
'aField' => [
'config' => [
'type' => 'text',
'enableRichtext' => true,
'richtextConfiguration' => [
'aConfig' => 'option',
],
],
],
],
],
];
$richtextConfigurationProphecy = $this->prophesize(Richtext::class);
GeneralUtility::addInstance(Richtext::class, $richtextConfigurationProphecy->reveal());
$rteHtmlParserPropehy = $this->prophesize(RteHtmlParser::class);
GeneralUtility::addInstance(RteHtmlParser::class, $rteHtmlParserPropehy->reveal());
$richtextConfigurationProphecy
->getConfiguration(
'aTable',
'aField',
42,
23,
[
'type' => 'text',
'enableRichtext' => true,
]
)
->willReturn([ 'aConfig' => 'option' ]);
$rteHtmlParserPropehy->init('aTable:aField', 42)->shouldBeCalled();
$rteHtmlParserPropehy
->RTE_transform(
'notProcessedContent',
[],
'rte',
[ 'aConfig' => 'option']
)
->willReturn('processedContent');
$this->assertSame($expected, (new TcaText())->addData($input));
}
}
<?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\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Utility\ArrayUtility;
/**
* Prepare richtext configuration. Used in DataHandler and FormEngine
*
* @internal Internal class for the time being - may change / vanish any time
* @todo When I grow up, I want to become a data provider
*/
class Richtext
{
/**
* This is an intermediate class / method to retrieve RTE
* configuration until all core places use data providers to do that.
*
* @param string $table The table the field is in
* @param string $field Field name
* @param int $pid Real page id
* @param string $recordType Record type value
* @param array $tcaFieldConf ['config'] section of TCA field
* @return array
*/
public function getConfiguration(string $table, string $field, int $pid, string $recordType, array $tcaFieldConf): array
{
// if (isset($tcaFieldConf['richtextConfiguration'])) {
// @todo with yaml parser
// create instance of NodeFactory, ask for "text" element
//
// If text element is instanceof "old" rtehtmlarea, do nothing, or if rtehtml should support ,yml, too
// unpack extConf settings, see if "demo", "normal" or whatever is configured, let rtehtmlarea register
// these three as possible configuration options in typo3_conf_vars, then yaml parse config. if "richtextConfiguration"
// is already set for rtehtmlarea and is "default" then fetch the config that is selected in extConf, else pick
// configured one.
//
// If text element is instanceof "new" ckeditor, and richtextConfiguration is not set, the "default", else
// look up in typo3_conf_vars.
//
// As soon an the Data handler starts using FormDataProviders, this class can vanish again, and the hack to
// test for specific rich text instances can be dropped: Split the "TcaText" data provider into multiple parts, each
// RTE should register and own data provider that does the transformation / configuration providing. This way,
// the explicit check for different RTE classes is removed from core and "hooked in" by the RTE's.
// }
$rtePageTs = $this->getRtePageTsConfigOfPid($pid);
$configuration = $rtePageTs['properties'];
unset($configuration['default.']);
unset($configuration['config.']);
if (is_array($rtePageTs['properties']['default.'])) {
ArrayUtility::mergeRecursiveWithOverrule($configuration, $rtePageTs['properties']['default.']);
}
$rtePageTsField = $rtePageTs['properties']['config.'][$table . '.'][$field . '.'];
if (is_array($rtePageTsField)) {
unset($rtePageTsField['types.']);
ArrayUtility::mergeRecursiveWithOverrule($configuration, $rtePageTsField);
}
if ($recordType && is_array($rtePageTs['properties']['config.'][$table . '.'][$field . '.']['types.'][$recordType . '.'])) {
ArrayUtility::mergeRecursiveWithOverrule(
$configuration,
$rtePageTs['properties']['config.'][$table . '.'][$field . '.']['types.'][$recordType . '.']
);
}
// Handle "mode" / "transformation" config for RteHtmlParser
if (!isset($configuration['proc.']['overruleMode'])) {
// Fall back to 'default' transformations
$configuration['proc.']['overruleMode'] = 'default';
}
if ($configuration['proc.']['overruleMode'] === 'ts_css') {
// Change legacy 'ts_css' to 'default'
$configuration['proc.']['overruleMode'] = 'default';
}
return $configuration;
}
/**
* Return RTE section of page TS
*
* @param int $pid Page ts of given pid
* @return array RTE section of pageTs of given pid
*/
protected function getRtePageTsConfigOfPid(int $pid): array
{
// Override with pageTs if needed
$backendUser = $this->getBackendUser();
return $backendUser->getTSConfig('RTE', BackendUtility::getPagesTSconfig($pid));
}
/**
* @return BackendUserAuthentication
*/
protected function getBackendUser() : BackendUserAuthentication
{
return $GLOBALS['BE_USER'];
}
}
......@@ -22,6 +22,7 @@ use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Cache\CacheManager;
use TYPO3\CMS\Core\Cache\Frontend\VariableFrontend;
use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools;
use TYPO3\CMS\Core\Configuration\Richtext;
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
......@@ -1520,56 +1521,10 @@ class DataHandler
// If the field is set it would probably be because of an undo-operation - in which case we should not update the field of course...
$fieldArray[$GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField']] = serialize($originalLanguage_diffStorage);
}
// Checking for RTE-transformations of fields:
$types_fieldConfig = BackendUtility::getTCAtypes($table, $this->checkValue_currentRecord);
$theTypeString = null;
if (is_array($types_fieldConfig)) {
foreach ($types_fieldConfig as $vconf) {
// RTE transformations:
if ($this->dontProcessTransformations || !isset($fieldArray[$vconf['field']])) {
continue;
}
// Look for transformation flag:
if ((string)$incomingFieldArray['_TRANSFORM_' . $vconf['field']] === 'RTE') {
if ($theTypeString === null) {
$theTypeString = BackendUtility::getTCAtypeValue($table, $this->checkValue_currentRecord);
}
$RTEsetup = $this->BE_USER->getTSConfig('RTE', BackendUtility::getPagesTSconfig($tscPID));
$thisConfig = BackendUtility::RTEsetup($RTEsetup['properties'], $table, $vconf['field'], $theTypeString);
$fieldArray[$vconf['field']] = $this->transformRichtextContentToDatabase(
$fieldArray[$vconf['field']], $table, $vconf['field'], $vconf['spec'], $thisConfig, $this->checkValue_currentRecord['pid']
);
}
}
}
// Return fieldArray
return $fieldArray;
}
/**
* Performs transformation of content from richtext element to database.
*
* @param string $value Value to transform.
* @param string $table The table name
* @param string $field The field name
* @param array $defaultExtras Default extras configuration of this field - typically "richtext:rte_transform"
* @param array $thisConfig Configuration for RTEs; A mix between TSconfig and others. Configuration for additional transformation information
* @param int $pid PID value of record (true parent page id)
* @return string Transformed content
*/
protected function transformRichtextContentToDatabase($value, $table, $field, $defaultExtras, $thisConfig, $pid)
{
if ($defaultExtras['rte_transform']) {
// Initialize transformation:
$parseHTML = GeneralUtility::makeInstance(RteHtmlParser::class);
$parseHTML->init($table . ':' . $field, $pid);
// Perform transformation:
$value = $parseHTML->RTE_transform($value, $defaultExtras, 'db', $thisConfig);
}
return $value;
}
/*********************************************
*
* Evaluation of input values
......@@ -1677,7 +1632,7 @@ class DataHandler
switch ($tcaFieldConf['type']) {
case 'text':
$res = $this->checkValueForText($value, $tcaFieldConf);
$res = $this->checkValueForText($value, $tcaFieldConf, $table, $id, $realPid, $field);
break;
case 'passthrough':
case 'imageManipulation':
......@@ -1717,21 +1672,45 @@ class DataHandler
*
* @param string $value The value to set.
* @param array $tcaFieldConf Field configuration from TCA
* @param string $table Table name
* @param int $id UID of record
* @param int $realPid The real PID value of the record. For updates, this is just the pid of the record. For new records this is the PID of the page where it is inserted. If $realPid is -1 it means that a new version of the record is being inserted.
* @param string $field Field name
* @return array $res The result array. The processed value (if any!) is set in the "value" key.
*/
protected function checkValueForText($value, $tcaFieldConf)
protected function checkValueForText($value, $tcaFieldConf, $table, $id, $realPid, $field)
{
if (!isset($tcaFieldConf['eval']) || $tcaFieldConf['eval'] === '') {
return ['value' => $value];
}
$cacheId = $this->getFieldEvalCacheIdentifier($tcaFieldConf['eval']);
if ($this->runtimeCache->has($cacheId)) {
$evalCodesArray = $this->runtimeCache->get($cacheId);
if (isset($tcaFieldConf['eval']) && !$tcaFieldConf['eval'] === '') {
$cacheId = $this->getFieldEvalCacheIdentifier($tcaFieldConf['eval']);
if ($this->runtimeCache->has($cacheId)) {
$evalCodesArray = $this->runtimeCache->get($cacheId);
} else {
$evalCodesArray = GeneralUtility::trimExplode(',', $tcaFieldConf['eval'], true);
$this->runtimeCache->set($cacheId, $evalCodesArray);
}
$valueArray = $this->checkValue_text_Eval($value, $evalCodesArray, $tcaFieldConf['is_in']);
} else {
$evalCodesArray = GeneralUtility::trimExplode(',', $tcaFieldConf['eval'], true);
$this->runtimeCache->set($cacheId, $evalCodesArray);
$valueArray = ['value' => $value];
}
// Handle richtext transformations
if ($this->dontProcessTransformations) {
return $valueArray;
}
$recordType = BackendUtility::getTCAtypeValue($table, $this->checkValue_currentRecord);
$columnsOverridesConfigOfField = $GLOBALS['TCA'][$table]['types'][$recordType]['columnsOverrides'][$field]['config'] ?? null;
if ($columnsOverridesConfigOfField) {
ArrayUtility::mergeRecursiveWithOverrule($tcaFieldConf, $columnsOverridesConfigOfField);
}
if (isset($tcaFieldConf['enableRichtext']) && $tcaFieldConf['enableRichtext'] === true) {
$richtextConfigurationProvider = GeneralUtility::makeInstance(Richtext::class);
$richtextConfiguration = $richtextConfigurationProvider->getConfiguration($table, $field, $realPid, $recordType, $tcaFieldConf);
$parseHTML = GeneralUtility::makeInstance(RteHtmlParser::class);
$parseHTML->init($table . ':' . $field, $realPid);
$valueArray['value'] = $parseHTML->RTE_transform($value, [], 'db', $richtextConfiguration);
}
return $this->checkValue_text_Eval($value, $evalCodesArray, $tcaFieldConf['is_in']);
return $valueArray;
}
/**
......@@ -3019,23 +2998,6 @@ class DataHandler
];
$res = $this->checkValue_SW([], $dataValues[$key][$vKey], $dsConf['TCEforms']['config'], $CVtable, $CVid, $dataValues_current[$key][$vKey], $CVstatus, $CVrealPid, $CVrecFID, '', $uploadedFiles[$key][$vKey], $CVtscPID, $additionalData);
// Look for RTE transformation of field:
if ($dataValues[$key]['_TRANSFORM_' . $vKey] == 'RTE' && !$this->dontProcessTransformations) {
// Unsetting trigger field - we absolutely don't want that into the data storage!
unset($dataValues[$key]['_TRANSFORM_' . $vKey]);
if (isset($res['value'])) {
// Calculating/Retrieving some values here:
list(, , $recFieldName) = explode(':', $CVrecFID);
$theTypeString = BackendUtility::getTCAtypeValue($CVtable, $this->checkValue_currentRecord);
$specConf = BackendUtility::getSpecConfParts($dsConf['TCEforms']['defaultExtras']);
// Find, thisConfig:
$RTEsetup = $this->BE_USER->getTSConfig('RTE', BackendUtility::getPagesTSconfig($CVtscPID));
$thisConfig = BackendUtility::RTEsetup($RTEsetup['properties'], $CVtable, $recFieldName, $theTypeString);
$res['value'] = $this->transformRichtextContentToDatabase(
$res['value'], $CVtable, $recFieldName, $specConf, $thisConfig, $CVrealPid
);
}
}
}
// Adding the value:
if (isset($res['value'])) {
......
......@@ -154,12 +154,12 @@ class RteHtmlParser extends HtmlParser
* This is the main function called from DataHandler and transfer data classes
*
* @param string $value Input value
* @param array $specConf Special configuration for a field; This is coming from the types-configuration of the field in the TCA. In the types-configuration you can setup features for the field rendering and in particular the RTE takes al its major configuration options from there!
* @param array $specConf deprecated old "defaultExtras" parsed as array
* @param string $direction Direction of the transformation. Two keywords are allowed; "db" or "rte". If "db" it means the transformation will clean up content coming from the Rich Text Editor and goes into the database. The other direction, "rte", is of course when content is coming from database and must be transformed to fit the RTE.
* @param array $thisConfig Parsed TypoScript content configuring the RTE, probably coming from Page TSconfig.
* @return string Output value
*/
public function RTE_transform($value, $specConf, $direction = 'rte', $thisConfig = [])
public function RTE_transform($value, $specConf = [], $direction = 'rte', $thisConfig = [])
{
$this->tsConfig = $thisConfig;
$this->procOptions = (array)$thisConfig['proc.'];
......@@ -184,6 +184,10 @@ class RteHtmlParser extends HtmlParser
$modes = GeneralUtility::trimExplode(',', $this->procOptions['overruleMode']);
} else {
// Get parameters for rte_transformation:
// @deprecated since TYPO3 v8, will be removed in TYPO3 v9 - the else{} part can be removed in v9
GeneralUtility::deprecationLog(
'Argument 2 of RteHtmlParser::RTE_transform() is deprecated. Transformations should be given in $thisConfig[\'proc.\'][\'overruleMode\']'
);
$specialFieldConfiguration = BackendUtility::getSpecConfParametersFromArray($specConf['rte_transform']['parameters']);
$modes = GeneralUtility::trimExplode('-', $specialFieldConfiguration['mode']);
}
......@@ -274,7 +278,10 @@ class RteHtmlParser extends HtmlParser
{
$modeList = implode(',', $modes);
// Replace the shortcut "default" with all custom modes
$modeList = str_replace('default', 'detectbrokenlinks,css_transform,ts_images,ts_links', $modeList);
// Replace the shortcut "ts_css" with all custom modes
// @deprecated since TYPO3 v8, will be removed in TYPO3 v9 - NEXT line can be removed in v9
$modeList = str_replace('ts_css', 'detectbrokenlinks,css_transform,ts_images,ts_links', $modeList);
// Make list unique
......
......@@ -685,18 +685,21 @@ class TcaMigration
}
foreach ($tableDefinition['columns'] as $fieldName => &$fieldConfig) {
if (isset($fieldConfig['defaultExtras'])) {
$oldValue = $fieldConfig['defaultExtras'];
$fieldConfig['defaultExtras'] = preg_replace(
'/richtext(\[([^\]]*)\])*:rte_transform(\[([^\]]*)\])/',
'richtext${1}:rte_transform',
$fieldConfig['defaultExtras'],
-1,
$replacementCount
);
if ($replacementCount) {
$this->messages[] = 'rte_transform options are deprecated. String "' . $oldValue . '" in TCA'
. ' ' . $table . '[\'columns\'][\'' . $fieldName . '\'][\'defaultExtras\'] was changed to "'
. $fieldConfig['defaultExtras'] . '"';
$originalValue = $fieldConfig['defaultExtras'];
$defaultExtrasArray = GeneralUtility::trimExplode(':', $originalValue, true);
$isRichtextField = false;
foreach ($defaultExtrasArray as $defaultExtrasField) {
if (substr($defaultExtrasField, 0, 8) === 'richtext') {
$isRichtextField = true;
$fieldConfig['config']['enableRichtext'] = true;
$fieldConfig['config']['richtextConfiguration'] = 'default';
}
}
if ($isRichtextField) {
unset($fieldConfig['defaultExtras']);
$this->messages[] = 'rte configuration via \'defaultExtras\' options are deprecated. String "' . $originalValue . '" in TCA'
. ' ' . $table . '[\'columns\'][\'' . $fieldName . '\'][\'defaultExtras\'] was changed to'
. ' options in [\'config\']';
}
}
}
......@@ -712,21 +715,24 @@ class TcaMigration
}
foreach ($typeArray['columnsOverrides'] as $fieldName => &$fieldConfig) {
if (isset($fieldConfig['defaultExtras'])) {
$oldValue = $fieldConfig['defaultExtras'];
$fieldConfig['defaultExtras'] = preg_replace(
'/richtext(\[([^\]]*)\])*:rte_transform(\[([^\]]*)\])/',
'richtext${1}:rte_transform',
$fieldConfig['defaultExtras'],
-1,
$replacementCount
);
if ($replacementCount) {
$this->messages[] = 'rte_transform options are deprecated. String "'
. $oldValue . '" in TCA'
$originalValue = $fieldConfig['defaultExtras'];
$defaultExtrasArray = GeneralUtility::trimExplode(':', $originalValue, true);
$isRichtextField = false;
foreach ($defaultExtrasArray as $defaultExtrasField) {
if (substr($defaultExtrasField, 0, 8) === 'richtext') {
$isRichtextField = true;
$fieldConfig['config']['enableRichtext'] = true;
$fieldConfig['config']['richtextConfiguration'] = 'default';
}
}
if ($isRichtextField) {
unset($fieldConfig['defaultExtras']);
$this->messages[] = 'rte configuration via \'defaultExtras\' options are deprecated.. String "'
. $originalValue . '" in TCA'
. ' ' . $table . '[\'types\'][\'' . $typeName
. '\'][\'columnsOverrides\'][\'' . $fieldName
. '\'][\'defaultExtras\']' .
' was changed to "' . $fieldConfig['defaultExtras'] . '"';
' was changed to config options.';
}
}
}
......