Commit 0ee65b8f authored by Christian Kuhn's avatar Christian Kuhn Committed by Anja Leichsenring
Browse files

[!!!][FEATURE] FormEngine: The extendables

For details, see the ReST files with examples for new API
and TCA changes.

* Split TCA config "type" to "type" and "renderType":
  TCA config "type" is a technical debt since it both defines the
  database storage as well as the widget that is used to render
  a certain field in FormEngine. While "type" is kept, the
  render widget is now extracted to a "renderType".

* t3editor uses this "renderType" now. type=text with
  renderType=t3editor will call the new T3editorElement provided
  by ext:t3editor, and falls back to TextElement if t3editor is
  not loaded.

* t3editor is now enabled for "setup" and "constants" of
  sys_template records if opening the whole record.

* t3editor now works when configured in a flex form.

* Introduce an API in FormEngine NodeFactory to register new
  renderType, used by t3editor.

* Introduce a resolver API in FormEngine NodeFactory to change
  the class that renders a widget or container.

* Split TextElement into TextElement that only renders a textarea
  and RichTextElement provided by ext:rtehtmlarea that renders RTE.
  ext:rtehtmlarea uses the new resolver API to route rendering to
  its own class in case RTE is enabled and configured for a field.

* In TCA section "types" a new array "columnsOverrides" is
  introduced that allows overwriting some column configurations
  of fields. Currently, this works for some View/FormEngine related
  settings like renderType and defaultExtras.

* TCA Migration is introduced to dynamically rewrite TCA before
  it is put into cache.

* TCA migration is called a second time in ext:compatibility6 in
  case TCA is still registered via ext_tables.php. This has performance
  penalty since it is done on every frontend and backend call.

* TCA migration is also called dynamically for flex form definitions.

* TCA migration moves configured t3editor wizards to type=text with
  renderType=t3editor.

* TCA migration removes the 5th parameter "style pointer" from
  types showitem

* TCA migration moves the 4th showitem parameter "extra configuration"
  to "defaultExtras" of "columnsOverrides" of given TCA type.

Change-Id: Ia2c2bc16463a01021c7a6be765b4efa872a130fd
Resolves: #67229
Releases: master
Reviewed-on: http://review.typo3.org/39662


Reviewed-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: default avatarFrank Nägler <typo3@naegler.net>
Tested-by: default avatarFrank Nägler <typo3@naegler.net>
Reviewed-by: Markus Klein's avatarMarkus Klein <markus.klein@typo3.org>
Tested-by: Markus Klein's avatarMarkus Klein <markus.klein@typo3.org>
Reviewed-by: Anja Leichsenring's avatarAnja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Anja Leichsenring's avatarAnja Leichsenring <aleichsenring@ab-softlab.de>
parent 7231ac0f
......@@ -41,7 +41,7 @@ class InputElement extends AbstractFormElement {
$isDateField = FALSE;
$config = $parameterArray['fieldConf']['config'];
$specConf = BackendUtility::getSpecConfParts($parameterArray['extra'], $parameterArray['fieldConf']['defaultExtras']);
$specConf = BackendUtility::getSpecConfParts($parameterArray['fieldConf']['defaultExtras']);
$size = MathUtility::forceIntegerInRange($config['size'] ?: $this->defaultInputWidth, $this->minimumInputWidth, $this->maxInputWidth);
$evalList = GeneralUtility::trimExplode(',', $config['eval'], TRUE);
$classes = array();
......@@ -87,7 +87,7 @@ class InputElement extends AbstractFormElement {
),
'itemFormElValue' => $itemFormElValue,
);
$options['type'] = 'none';
$options['renderType'] = 'none';
/** @var NodeFactory $nodeFactory */
$nodeFactory = $this->globalOptions['nodeFactory'];
return $nodeFactory->create($options)->render();
......
......@@ -49,7 +49,7 @@ class SelectCheckBoxElement extends AbstractFormElement {
}
$this->resultArray = $this->initializeResultArray();
// "Extra" configuration; Returns configuration for the field based on settings found in the "types" fieldlist.
$specConf = BackendUtility::getSpecConfParts($parameterArray['extra'], $parameterArray['fieldConf']['defaultExtras']);
$specConf = BackendUtility::getSpecConfParts($parameterArray['fieldConf']['defaultExtras']);
$selItems = FormEngineUtility::getSelectItems($table, $field, $row, $parameterArray);
// Creating the label for the "No Matching Value" entry.
......
......@@ -51,7 +51,7 @@ class SelectMultipleSideBySideElement extends AbstractFormElement {
}
$this->resultArray = $this->initializeResultArray();
// "Extra" configuration; Returns configuration for the field based on settings found in the "types" fieldlist.
$specConf = BackendUtility::getSpecConfParts($parameterArray['extra'], $parameterArray['fieldConf']['defaultExtras']);
$specConf = BackendUtility::getSpecConfParts($parameterArray['fieldConf']['defaultExtras']);
$selItems = FormEngineUtility::getSelectItems($table, $field, $row, $parameterArray);
// Creating the label for the "No Matching Value" entry.
......
......@@ -50,7 +50,7 @@ class SelectSingleBoxElement extends AbstractFormElement {
}
$this->resultArray = $this->initializeResultArray();
// "Extra" configuration; Returns configuration for the field based on settings found in the "types" fieldlist.
$specConf = BackendUtility::getSpecConfParts($parameterArray['extra'], $parameterArray['fieldConf']['defaultExtras']);
$specConf = BackendUtility::getSpecConfParts($parameterArray['fieldConf']['defaultExtras']);
$selItems = FormEngineUtility::getSelectItems($table, $field, $row, $parameterArray);
// Creating the label for the "No Matching Value" entry.
......
......@@ -53,7 +53,7 @@ class SelectSingleElement extends AbstractFormElement {
$this->resultArray = $this->initializeResultArray();
// "Extra" configuration; Returns configuration for the field based on settings found in the "types" fieldlist.
$specConf = BackendUtility::getSpecConfParts($parameterArray['extra'], $parameterArray['fieldConf']['defaultExtras']);
$specConf = BackendUtility::getSpecConfParts($parameterArray['fieldConf']['defaultExtras']);
$selItems = FormEngineUtility::getSelectItems($table, $field, $row, $parameterArray);
// Creating the label for the "No Matching Value" entry.
......
......@@ -55,7 +55,7 @@ class SelectTreeElement extends AbstractFormElement {
$resultArray = $this->initializeResultArray();
// "Extra" configuration; Returns configuration for the field based on settings found in the "types" fieldlist.
$specConf = BackendUtility::getSpecConfParts($parameterArray['extra'], $parameterArray['fieldConf']['defaultExtras']);
$specConf = BackendUtility::getSpecConfParts($parameterArray['fieldConf']['defaultExtras']);
$selItems = FormEngineUtility::getSelectItems($table, $field, $row, $parameterArray);
$maxitems = (int)$config['maxitems'];
......
......@@ -17,7 +17,6 @@ namespace TYPO3\CMS\Backend\Form\Element;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\MathUtility;
use TYPO3\CMS\Backend\Form\FormEngine;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Backend\Form\NodeFactory;
......@@ -35,8 +34,7 @@ class TextElement extends AbstractFormElement {
protected $charactersPerRow = 40;
/**
* This will render a <textarea> OR RTE area form field,
* possibly with various control/validation features
* This will render a <textarea>
*
* @return array As defined in initializeResultArray() of AbstractNode
*/
......@@ -85,7 +83,7 @@ class TextElement extends AbstractFormElement {
),
'itemFormElValue' => $parameterArray['itemFormElValue'],
);
$options['type'] = 'none';
$options['renderType'] = 'none';
/** @var NodeFactory $nodeFactory */
$nodeFactory = $this->globalOptions['nodeFactory'];
return $nodeFactory->create($options)->render();
......@@ -103,77 +101,12 @@ class TextElement extends AbstractFormElement {
);
}
}
// Init RTE vars
// Set TRUE, if the RTE is loaded; If not a normal textarea is shown.
$rteWasLoaded = FALSE;
// Set TRUE, if the RTE would have been loaded if it wasn't for the disable-RTE flag in the bottom of the page...
$rteWouldHaveBeenLoaded = FALSE;
// "Extra" configuration; Returns configuration for the field based on settings found in the "types" fieldlist. Traditionally, this is where RTE configuration has been found.
$specialConfiguration = BackendUtility::getSpecConfParts($parameterArray['extra'], $parameterArray['fieldConf']['defaultExtras']);
$specialConfiguration = BackendUtility::getSpecConfParts($parameterArray['fieldConf']['defaultExtras']);
// Setting up the altItem form field, which is a hidden field containing the value
$altItem = '<input type="hidden" name="' . htmlspecialchars($parameterArray['itemFormElName']) . '" value="' . htmlspecialchars($parameterArray['itemFormElValue']) . '" />';
$html = '';
// If RTE is generally enabled (TYPO3_CONF_VARS and user settings)
if ($backendUser->isRTE()) {
$parameters = BackendUtility::getSpecConfParametersFromArray($specialConfiguration['rte_transform']['parameters']);
// If the field is configured for RTE and if any flag-field is not set to disable it.
if (isset($specialConfiguration['richtext']) && (!$parameters['flag'] || !$row[$parameters['flag']])) {
BackendUtility::fixVersioningPid($table, $row);
list($recordPid, $tsConfigPid) = BackendUtility::getTSCpidCached($table, $row['uid'], $row['pid']);
// If the pid-value is not negative (that is, a pid could NOT be fetched)
if ($tsConfigPid >= 0) {
$rteSetup = $backendUser->getTSConfig('RTE', BackendUtility::getPagesTSconfig($recordPid));
$rteTcaTypeValue = BackendUtility::getTCAtypeValue($table, $row);
$rteSetupConfiguration = BackendUtility::RTEsetup($rteSetup['properties'], $table, $fieldName, $rteTcaTypeValue);
if (!$rteSetupConfiguration['disabled']) {
// Get RTE object, draw form and set flag:
$rteObject = BackendUtility::RTEgetObj();
$dummyFormEngine = new FormEngine();
$rteResult = $rteObject->drawRTE(
$dummyFormEngine,
$table,
$fieldName,
$row,
$parameterArray,
$specialConfiguration,
$rteSetupConfiguration,
$rteTcaTypeValue,
'',
$tsConfigPid,
$this->globalOptions,
$this->initializeResultArray()
);
// This is a compat layer for "other" RTE's: If the result is not an array, it is the html string,
// otherwise it is a structure similar to our casual return array
// @todo: This interface needs a full re-definition, RTE should probably be its own type in the
// @todo: end, and other RTE implementations could then just override this.
if (is_array($rteResult)) {
$html = $rteResult['html'];
$rteResult['html'] = '';
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $rteResult);
} else {
$html = $rteResult;
}
// Wizard
$html = $this->renderWizards(
array($html, $altItem),
$config['wizards'],
$table,
$row,
$fieldName,
$parameterArray,
$parameterArray['itemFormElName'],
$specialConfiguration,
TRUE
);
$rteWasLoaded = TRUE;
}
}
}
}
// Display ordinary field if RTE was not loaded.
if (!$rteWasLoaded) {
// Show message, if no RTE (field can only be edited with RTE!)
if ($specialConfiguration['rte_only']) {
$html = '<p><em>' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.noRTEfound')) . '</em></p>';
......@@ -183,8 +116,7 @@ class TextElement extends AbstractFormElement {
if ($func === 'required') {
$resultArray['requiredFields'][$table . '_' . $row['uid'] . '_' . $fieldName] = $parameterArray['itemFormElName'];
} else {
// Pair hook to the one in \TYPO3\CMS\Core\DataHandling\DataHandler::checkValue_input_Eval()
// and \TYPO3\CMS\Core\DataHandling\DataHandler::checkValue_text_Eval()
// Hint: There is a similar hook for "evaluateFieldValue" in DataHandler
$evalObj = GeneralUtility::getUserObj($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tce']['formevals'][$func] . ':&' . $func);
if (is_object($evalObj) && method_exists($evalObj, 'deevaluateFieldValue')) {
$_params = array(
......@@ -258,13 +190,13 @@ class TextElement extends AbstractFormElement {
$parameterArray,
$parameterArray['itemFormElName'],
$specialConfiguration,
$rteWouldHaveBeenLoaded
FALSE
);
$maximumWidth = (int)$this->formMaxWidth($cols);
$html = '<div class="form-control-wrap"' . ($maximumWidth ? ' style="max-width: ' . $maximumWidth . 'px"' : '') . '>' . $html . '</div>';
}
}
$resultArray['html'] = $html;
return $resultArray;
}
......
......@@ -303,7 +303,7 @@ class FormEngine {
}
$options = $this->getConfigurationOptionsForChildElements();
$options['type'] = 'fullRecordContainer';
$options['renderType'] = 'fullRecordContainer';
$resultArray = $this->nodeFactory->create($options)->render();
$content = $resultArray['html'];
......@@ -345,7 +345,7 @@ class FormEngine {
$options = $this->getConfigurationOptionsForChildElements();
$options['singleFieldToRender'] = $theFieldToReturn;
$options['type'] = 'soloFieldContainer';
$options['renderType'] = 'soloFieldContainer';
$resultArray = $this->nodeFactory->create($options)->render();
$html = $resultArray['html'];
......@@ -383,7 +383,7 @@ class FormEngine {
$options = $this->getConfigurationOptionsForChildElements();
$options['fieldListToRender'] = $list;
$options['type'] = 'listOfFieldsContainer';
$options['renderType'] = 'listOfFieldsContainer';
$resultArray = $this->nodeFactory->create($options)->render();
$html = $resultArray['html'];
......@@ -430,6 +430,7 @@ class FormEngine {
'palettesCollapsed' => $this->palettesCollapsed,
'table' => $this->table,
'databaseRow' => $this->databaseRow,
'recordTypeValue' => '',
'additionalPreviewLanguages' => $this->additionalPreviewLanguages,
'localizationMode' => $this->localizationMode, // @todo: find out the details, Warning, this overlaps with inline behaviour localizationMode
'elementBaseName' => '',
......@@ -545,7 +546,7 @@ class FormEngine {
$options['inlineStructure'] = $this->inlineStackProcessor->getStructure();
$options['isAjaxContext'] = TRUE;
$options['type'] = 'inlineRecordContainer';
$options['renderType'] = 'inlineRecordContainer';
$childArray = $this->nodeFactory->create($options)->render();
if ($childArray === FALSE) {
......@@ -677,7 +678,7 @@ class FormEngine {
$options['inlineStructure'] = $this->inlineStackProcessor->getStructure();
$options['isAjaxContext'] = TRUE;
$options['type'] = 'inlineRecordContainer';
$options['renderType'] = 'inlineRecordContainer';
$childArray = $this->nodeFactory->create($options)->render();
if ($childArray === FALSE) {
......@@ -786,7 +787,7 @@ class FormEngine {
$options['inlineStructure'] = $this->inlineStackProcessor->getStructure();
$options['isAjaxContext'] = TRUE;
$options['type'] = 'inlineRecordContainer';
$options['renderType'] = 'inlineRecordContainer';
$childArray = $this->nodeFactory->create($options)->render();
$html .= $childArray['html'];
$childArray['html'] = '';
......
......@@ -20,16 +20,11 @@ use TYPO3\CMS\Backend\Form\Element;
/**
* Create an element object depending on type.
*
* @todo: This is currently just a straight ahead approach. A registry should be added allowing
* @todo: extensions to overwrite existing implementations and all Element and Container classes
* @todo: should be created through this factory. The factory itself could be added in the constructor
* @todo: of AbstractNode to have it always available.
*/
class NodeFactory {
/**
* Default registry of node-type to handling class
* Default registry of node name to handling class
*
* @var array
*/
......@@ -62,48 +57,86 @@ class NodeFactory {
'selectTree' => Element\SelectTreeElement::class,
'selectSingle' => Element\SelectSingleElement::class,
'selectSingleBox' => Element\SelectSingleBoxElement::class,
// t3editor is defined with a fallback so extensions can use it even if ext:t3editor is not loaded
't3editor' => Element\TextElement::class,
'text' => Element\TextElement::class,
'unknown' => Element\UnknownElement::class,
'user' => Element\UserElement::class,
);
/**
* Set up factory
* Node resolver classes
* Nested array with nodeName as key, (sorted) priority as sub key and class as value
*
* @var array
*/
protected $nodeResolver = array();
/**
* Set up factory. Initialize additionally registered nodes.
*/
public function __construct() {
// @todo: Add additional base types and override existing types
$this->registerAdditionalNodeTypesFromConfiguration();
$this->initializeNodeResolver();
}
/**
* Create an element depending on type
* Create a node depending on type
*
* @param array $globalOptions All information to decide which class should be instantiated and given down to sub nodes
* @return AbstractNode
* @throws Exception
*/
public function create(array $globalOptions) {
if (!is_string($globalOptions['type'])) {
throw new Exception('No type definition found', 1431452406);
if (empty($globalOptions['renderType'])) {
throw new Exception('No renderType definition found', 1431452406);
}
$type = $globalOptions['type'];
$type = $globalOptions['renderType'];
if ($type === 'select') {
$config = $globalOptions['parameterArray']['fieldConf']['config'];
$maxitems = (int)$config['maxitems'];
$maxItems = (int)$config['maxitems'];
if (isset($config['renderMode']) && $config['renderMode'] === 'tree') {
$type = 'selectTree';
} elseif ($maxitems <= 1) {
} elseif ($maxItems <= 1) {
$type = 'selectSingle';
} elseif (isset($config['renderMode']) && $config['renderMode'] === 'singlebox') {
$type = 'selectSingleBox';
} elseif (isset($config['renderMode']) && $config['renderMode'] === 'checkbox') {
$type = 'selectCheckBox';
} else {
// @todo: This "catch all" else should be removed to allow registration of own renderTypes for type=select
$type = 'selectMultipleSideBySide';
}
}
$className = isset($this->nodeTypes[$type]) ? $this->nodeTypes[$type] : $this->nodeTypes['unknown'];
if (!empty($this->nodeResolver[$type])) {
// Resolver with highest priority is called first. If it returns with a new class name,
// it will be taken and loop is aborted, otherwise resolver with next lower priority is called.
foreach ($this->nodeResolver[$type] as $priority => $resolverClassName) {
/** @var NodeResolverInterface $resolver */
$resolver = $this->instantiate($resolverClassName);
if (!$resolver instanceof NodeResolverInterface) {
throw new Exception(
'Node resolver for type ' . $type . ' at priority ' . $priority . ' must implement NodeResolverInterface',
1433157422
);
}
// Resolver classes do NOT receive the name of the already resolved class. Single
// resolvers should not have dependencies to each other or the default implementation,
// so they also shouldn't know the output of a different resolving class.
// Additionally, the globalOptions array is NOT given by reference here, changing config is a
// task of container classes alone and must not be abused here.
$newClassName = $resolver->setGlobalOptions($globalOptions)->resolve();
if ($newClassName !== NULL) {
$className = $newClassName;
break;
}
}
}
/** @var AbstractNode $nodeInstance */
$nodeInstance = $this->instantiate($className);
if (!$nodeInstance instanceof NodeInterface) {
......@@ -112,6 +145,88 @@ class NodeFactory {
return $nodeInstance->setGlobalOptions($globalOptions);
}
/**
* Add node types from nodeRegistry to $this->nodeTypes.
* This can be used to add new render types or to overwrite existing node types. The registered class must
* implement the NodeInterface and will be called if a node with this renderType is rendered.
*
* @throws Exception if configuration is incomplete or two nodes with identical priorities are registered
*/
protected function registerAdditionalNodeTypesFromConfiguration() {
// List of additional or override nodes
$registeredTypeOverrides = $GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['nodeRegistry'];
// Sanitize input array
$registeredPrioritiesForNodeNames = array();
foreach ($registeredTypeOverrides as $override) {
if (!isset($override['nodeName']) || !isset($override['class']) || !isset($override['priority'])) {
throw new Exception(
'Key class, nodeName or priority missing for an entry in $GLOBALS[\'TYPO3_CONF_VARS\'][\'SYS\'][\'formEngine\'][\'nodeRegistry\']',
1432207533
);
}
if ($override['priority'] < 0 || $override['priority'] > 100) {
throw new Exception(
'Priority of element ' . $override['nodeName'] . ' with class ' . $override['class'] . ' is ' . $override['priority'] . ', but must between 0 and 100',
1432223531
);
}
if (isset($registeredPrioritiesForNodeNames[$override['nodeName']][$override['priority']])) {
throw new Exception(
'Element ' . $override['nodeName'] . ' already has an override registered with priority ' . $override['priority'],
1432223893
);
}
$registeredPrioritiesForNodeNames[$override['nodeName']][$override['priority']] = '';
}
// Add element with highest priority to registry
$highestPriority = array();
foreach ($registeredTypeOverrides as $override) {
if (!isset($highestPriority[$override['nodeName']]) || $override['priority'] > $highestPriority[$override['nodeName']]) {
$highestPriority[$override['nodeName']] = $override['priority'];
$this->nodeTypes[$override['nodeName']] = $override['class'];
}
}
}
/**
* Add resolver and add them sorted to a local property.
* This can be used to manipulate the nodeName to class resolution with own code.
*
* @throws Exception if configuration is incomplete or two resolver with identical priorities are registered
*/
protected function initializeNodeResolver() {
// List of node resolver
$registeredNodeResolvers = $GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['nodeResolver'];
$resolversByType = array();
foreach ($registeredNodeResolvers as $nodeResolver) {
if (!isset($nodeResolver['nodeName']) || !isset($nodeResolver['class']) || !isset($nodeResolver['priority'])) {
throw new Exception(
'Key class, nodeName or priority missing for an entry in $GLOBALS[\'TYPO3_CONF_VARS\'][\'SYS\'][\'formEngine\'][\'nodeResolver\']',
1433155522
);
}
if ($nodeResolver['priority'] < 0 || $nodeResolver['priority'] > 100) {
throw new Exception(
'Priority of element ' . $nodeResolver['nodeName'] . ' with class ' . $nodeResolver['class'] . ' is ' . $nodeResolver['priority'] . ', but must between 0 and 100',
1433155563
);
}
if (isset($resolversByType[$nodeResolver['nodeName']][$nodeResolver['priority']])) {
throw new Exception(
'Element ' . $nodeResolver['nodeName'] . ' already has a resolver registered with priority ' . $nodeResolver['priority'],
1433155705
);
}
$resolversByType[$nodeResolver['nodeName']][$nodeResolver['priority']] = $nodeResolver['class'];
}
$sortedResolversByType = array();
foreach ($resolversByType as $nodeName => $prioritiesAndClasses) {
krsort($prioritiesAndClasses);
$sortedResolversByType[$nodeName] = $prioritiesAndClasses;
}
$this->nodeResolver = $sortedResolversByType;
}
/**
* Instantiate given class name
*
......
<?php
namespace TYPO3\CMS\Backend\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!
*/
/**
* Interface must be implemented by node resolver classes
*/
interface NodeResolverInterface {
/**
* Set global options from parent instance
*
* @param array $globalOptions Global options like 'readonly' for all elements
* @return $this
*/
public function setGlobalOptions(array $globalOptions);
/**
* Main resolver method
*
* @return string|void New class name or void if this resolver does not change current class name.
*/
public function resolve();
}
......@@ -768,9 +768,16 @@ class BackendUtility {
$altFieldList = array();
// Traverse fields in types config and parse the configuration into a nice array:
foreach ($fieldList as $k => $v) {
list($pFieldName, $pAltTitle, $pPalette, $pSpec) = GeneralUtility::trimExplode(';', $v);
$defaultExtras = is_array($GLOBALS['TCA'][$table]['columns'][$pFieldName]) ? $GLOBALS['TCA'][$table]['columns'][$pFieldName]['defaultExtras'] : '';
$specConfParts = self::getSpecConfParts($pSpec, $defaultExtras);
list($pFieldName, $pAltTitle, $pPalette) = GeneralUtility::trimExplode(';', $v);
$defaultExtras = '';
if (!empty($typesConf['columnsOverrides'][$pFieldName]['config']['defaultExtras'])) {
// Use defaultExtras from columnsOverrides if given
$defaultExtras = $typesConf['columnsOverrides'][$pFieldName]['config']['defaultExtras'];
} elseif (!empty($GLOBALS['TCA'][$table]['columns'][$pFieldName]['defaultExtras'])) {
// Use defaultExtras from columns if given
$defaultExtras = $GLOBALS['TCA'][$table]['columns'][$pFieldName]['defaultExtras'];
}
$specConfParts = self::getSpecConfParts($defaultExtras);
$fieldList[$k] = array(
'field' => $pFieldName,
'title' => $pAltTitle,
......@@ -859,19 +866,24 @@ class BackendUtility {
}
/**
* Parses a part of the field lists in the "types"-section of $GLOBALS['TCA'] arrays, namely the "special configuration" at index 3 (position 4)
* Parses "defaultExtras" of $GLOBALS['TCA'] columns config section to an array.
* Elements are splitted by ":" and within those parts, parameters are splitted by "|".
* Everything is returned in an array and you should rather see it visually than listen to me anymore now... Check out example in Inside TYPO3
*
* @param string $str Content from the "types" configuration of TCA (the special configuration) - see description of function
* @param string $defaultExtras The ['defaultExtras'] value from field configuration
* See unit tests for details.
*
* @param string $defaultExtrasString "defaultExtras" string from columns config
* @param string $_ @deprecated since TYPO3 CMS 7, will be removed with TYPO3 CMS 8
* @return array
*/
static public function getSpecConfParts($str, $defaultExtras) {
// Add defaultExtras:
$specConfParts = GeneralUtility::trimExplode(':', $defaultExtras . ':' . $str, TRUE);
static public function getSpecConfParts($defaultExtrasString, $_ = '') {
if (!empty($_)) {
GeneralUtility::deprecationLog('Second parameter of BackendUtility::getSpecConfParts() is deprecated. Will be removed with TYPO3 CMS 8');
// Prepend old parameter, can be overwritten by casual defaultExtras string, then.
$defaultExtrasString = $_ . ':' . $defaultExtrasString;
}
$specConfParts = GeneralUtility::trimExplode(':', $defaultExtrasString, TRUE);
$reg = array();
if (count($specConfParts)) {
if (!empty($specConfParts)) {
foreach ($specConfParts as $k2 => $v2) {
unset($specConfParts[$k2]);
if (preg_match('/(.*)\\[(.*)\\]/', $v2, $reg)) {
......
......@@ -18,6 +18,7 @@ use TYPO3\CMS\Backend\Form\Element;
use TYPO3\CMS\Core\Tests\UnitTestCase;
use TYPO3\CMS\Backend\Form\NodeFactory;
use TYPO3\CMS\Backend\Form\NodeInterface;
use TYPO3\CMS\Backend\Form\NodeResolverInterface;
/**
* Test case
......@@ -27,8 +28,224 @@ class NodeFactoryTest extends UnitTestCase {
/**
* @test
* @expectedException \TYPO3\CMS\Backend\Form\Exception
* @expectedExceptionCode 1432207533
*/
public function createThrowsExceptionIfTypeIsNotGiven() {
public function constructThrowsExceptionIfOverrideMissesNodeNameKey() {
$GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['nodeRegistry'] = array(
1433089391 => array(
'class' => 'foo',
'priority' => 23,
),
);
new NodeFactory();
}
/**
* @test