Commit d421f7dc authored by Christian Kuhn's avatar Christian Kuhn Committed by Andreas Wolf
Browse files

[FEATURE] Allow TCA description property

When the site configuration module has been introduced, it came
with a custom functionality to show an additional help text
when editing site records between the field label and the field input.

This useful feature is now changed into a general TCA feature
available everywhere: A new field information node expansion / "wizard"
is added to all form elements, the inline and flex containers: If
the property "description" is set for a TCA column type (same array
level as "label", it will show the value as localized string between
the field label and the input section.

There are three available render types for "wizard a-like" output:
* Field information - text between label+field
* Field control - buttons next to input sections like the link popup button
* Field wizards - clickable stuff below the input section, for example
  the localization state selector
If a field has been set to readOnly=true in TCA, field control and field
wizards do not make sense to render since they are meant to act with the
field value.
The field information node however has only informational character
which is useful for readOnly fields, too. Thus, this node expansion
type is now the only one that is always rendered, even if a field has
been set to readOnly.

Note this patch is fully covered by ext:styleguide (master) to have
examples for all changed elements now using the description property.

Resolves: #85410
Releases: master
Change-Id: Idcfacafa19b8208614b653b8fac22ce47bca3b8f
Reviewed-on: https://review.typo3.org/57397

Tested-by: default avatarTYPO3com <no-reply@typo3.com>
Reviewed-by: default avatarJörg Bösche <typo3@joergboesche.de>
Tested-by: default avatarJörg Bösche <typo3@joergboesche.de>
Reviewed-by: Andreas Wolf's avatarAndreas Wolf <andreas.wolf@typo3.org>
Tested-by: Andreas Wolf's avatarAndreas Wolf <andreas.wolf@typo3.org>
parent 88b9368b
......@@ -82,6 +82,8 @@ class FlexFormContainerContainer extends AbstractContainer
$containerTitle = $languageService->sL(trim($flexFormDataStructureArray['title']));
}
$resultArray = $this->initializeResultArray();
$html = [];
$html[] = '<div class="t3-form-field-container-flexsections t3-flex-section t3js-flex-section">';
$html[] = '<input class="t3-flex-control t3js-flex-control-action" type="hidden" name="' . htmlspecialchars($actionFieldName) . '" value="" />';
......@@ -113,7 +115,6 @@ class FlexFormContainerContainer extends AbstractContainer
$html[] = '</div>';
$html[] = '</div>';
$resultArray = $this->initializeResultArray();
$resultArray['html'] = implode(LF, $html);
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $containerContentResult, false);
......
......@@ -82,6 +82,10 @@ class FlexFormElementContainer extends AbstractContainer
'label' => $parameterArray['label'],
];
if (isset($flexFormFieldArray['description']) && !empty($flexFormFieldArray['description'])) {
$fakeParameterArray['fieldConf']['description'] = $flexFormFieldArray['description'];
}
$alertMsgOnChange = '';
if (isset($fakeParameterArray['fieldConf']['onChange']) && $fakeParameterArray['fieldConf']['onChange'] === 'reload') {
if ($this->getBackendUserAuthentication()->jsConfirmation(JsConfirmation::TYPE_CHANGE)) {
......@@ -138,6 +142,7 @@ class FlexFormElementContainer extends AbstractContainer
// Possible line breaks in the label through xml: \n => <br/>, usage of nl2br() not possible, so it's done through str_replace (?!)
$processedTitle = str_replace('\\n', '<br />', htmlspecialchars($fakeParameterArray['fieldConf']['label']));
$html = [];
$html[] = '<div class="form-section">';
$html[] = '<div class="form-group t3js-formengine-palette-field t3js-formengine-validation-marker">';
......
......@@ -25,6 +25,17 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
*/
class FlexFormNoTabsContainer extends AbstractContainer
{
/**
* Default field information enabled for this element.
*
* @var array
*/
protected $defaultFieldInformation = [
'tcaDescription' => [
'renderType' => 'tcaDescription',
],
];
/**
* Entry method
*
......@@ -71,7 +82,14 @@ class FlexFormNoTabsContainer extends AbstractContainer
$options['flexFormFormPrefix'] = '[data][' . $sheetName . '][lDEF]';
$options['parameterArray'] = $parameterArray;
$resultArray = $this->initializeResultArray();
$fieldInformationResult = $this->renderFieldInformation();
$resultArray['html'] = '<div>' . $fieldInformationResult['html'] . '</div>';
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false);
$options['renderType'] = 'flexFormElementContainer';
return $this->nodeFactory->create($options)->render();
$childResult = $this->nodeFactory->create($options)->render();
return $this->mergeChildReturnIntoExistingResult($resultArray, $childResult, true);
}
}
......@@ -25,6 +25,17 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
*/
class FlexFormTabsContainer extends AbstractContainer
{
/**
* Default field information enabled for this element.
*
* @var array
*/
protected $defaultFieldInformation = [
'tcaDescription' => [
'renderType' => 'tcaDescription',
],
];
/**
* Entry method
*
......@@ -42,6 +53,7 @@ class FlexFormTabsContainer extends AbstractContainer
$flexFormRowData = $this->data['flexFormRowData'];
$resultArray = $this->initializeResultArray();
$resultArray['requireJsModules'][] = 'TYPO3/CMS/Backend/Tabs';
$domIdPrefix = 'DTM-' . GeneralUtility::shortMD5($this->data['parameterArray']['itemFormElName']);
......@@ -94,7 +106,11 @@ class FlexFormTabsContainer extends AbstractContainer
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $childReturn, false);
}
$resultArray['html'] = $this->renderTabMenu($tabElements, $domIdPrefix);
$fieldInformationResult = $this->renderFieldInformation();
$resultArray['html'] = '<div>' . $fieldInformationResult['html'] . '</div>';
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false);
$resultArray['html'] .= $this->renderTabMenu($tabElements, $domIdPrefix);
return $resultArray;
}
......
......@@ -62,6 +62,17 @@ class InlineControlContainer extends AbstractContainer
*/
protected $requireJsModules = [];
/**
* Default field information enabled for this element.
*
* @var array
*/
protected $defaultFieldInformation = [
'tcaDescription' => [
'renderType' => 'tcaDescription',
],
];
/**
* @var array Default wizards
*/
......@@ -270,6 +281,11 @@ class InlineControlContainer extends AbstractContainer
}
// Wrap all inline fields of a record with a <div> (like a container)
$html = '<div class="form-group" id="' . $nameObject . '">';
$fieldInformationResult = $this->renderFieldInformation();
$html .= $fieldInformationResult['html'];
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false);
// Add the level links before all child records:
if ($config['appearance']['levelLinksPosition'] === 'both' || $config['appearance']['levelLinksPosition'] === 'top') {
$html .= '<div class="form-group t3js-formengine-validation-marker">' . $levelLinks . $localizationLinks . '</div>';
......
......@@ -30,6 +30,17 @@ class CheckboxElement extends AbstractFormElement
*/
private $iconRegistry;
/**
* Default field information enabled for this element.
*
* @var array
*/
protected $defaultFieldInformation = [
'tcaDescription' => [
'renderType' => 'tcaDescription',
],
];
/**
* Default field wizards enabled for this element.
*
......@@ -130,9 +141,7 @@ class CheckboxElement extends AbstractFormElement
$html = [];
$html[] = '<div class="formengine-field-item t3js-formengine-field-item">';
if (!$disabled) {
$html[] = $fieldInformationHtml;
}
$html[] = $fieldInformationHtml;
$html[] = '<div class="form-wizards-wrap">';
$html[] = '<div class="form-wizards-element">';
$html[] = $elementHtml;
......
......@@ -29,6 +29,17 @@ class CheckboxLabeledToggleElement extends AbstractFormElement
*/
private $iconRegistry;
/**
* Default field information enabled for this element.
*
* @var array
*/
protected $defaultFieldInformation = [
'tcaDescription' => [
'renderType' => 'tcaDescription',
],
];
/**
* Default field wizards enabled for this element.
*
......@@ -129,9 +140,7 @@ class CheckboxLabeledToggleElement extends AbstractFormElement
$html = [];
$html[] = '<div class="formengine-field-item t3js-formengine-field-item">';
if (!$disabled) {
$html[] = $fieldInformationHtml;
}
$html[] = $fieldInformationHtml;
$html[] = '<div class="form-wizards-wrap">';
$html[] = '<div class="form-wizards-element">';
$html[] = $elementHtml;
......
......@@ -29,6 +29,17 @@ class CheckboxToggleElement extends AbstractFormElement
*/
private $iconRegistry;
/**
* Default field information enabled for this element.
*
* @var array
*/
protected $defaultFieldInformation = [
'tcaDescription' => [
'renderType' => 'tcaDescription',
],
];
/**
* Default field wizards enabled for this element.
*
......@@ -129,9 +140,7 @@ class CheckboxToggleElement extends AbstractFormElement
$html = [];
$html[] = '<div class="formengine-field-item t3js-formengine-field-item">';
if (!$disabled) {
$html[] = $fieldInformationHtml;
}
$html[] = $fieldInformationHtml;
$html[] = '<div class="form-wizards-wrap">';
$html[] = '<div class="form-wizards-element">';
$html[] = $elementHtml;
......
......@@ -27,6 +27,17 @@ use TYPO3\CMS\Core\Utility\StringUtility;
*/
class GroupElement extends AbstractFormElement
{
/**
* Default field information enabled for this element.
*
* @var array
*/
protected $defaultFieldInformation = [
'tcaDescription' => [
'renderType' => 'tcaDescription',
],
];
/**
* Default field controls for this element.
*
......@@ -175,10 +186,15 @@ class GroupElement extends AbstractFormElement
);
}
$fieldInformationResult = $this->renderFieldInformation();
$fieldInformationHtml = $fieldInformationResult['html'];
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false);
if (isset($config['readOnly']) && $config['readOnly']) {
// Return early if element is read only
$html = [];
$html[] = '<div class="formengine-field-item t3js-formengine-field-item">';
$html[] = $fieldInformationHtml;
$html[] = '<div class="form-wizards-wrap">';
$html[] = '<div class="form-wizards-element">';
$html[] = '<select';
......@@ -287,10 +303,6 @@ class GroupElement extends AbstractFormElement
$selectorAttributes['multiple'] = 'multiple';
}
$fieldInformationResult = $this->renderFieldInformation();
$fieldInformationHtml = $fieldInformationResult['html'];
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false);
$fieldControlResult = $this->renderFieldControl();
$fieldControlHtml = $fieldControlResult['html'];
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldControlResult, false);
......
......@@ -78,6 +78,17 @@ class ImageManipulationElement extends AbstractFormElement
]
];
/**
* Default field information enabled for this element.
*
* @var array
*/
protected $defaultFieldInformation = [
'tcaDescription' => [
'renderType' => 'tcaDescription',
],
];
/**
* Default field wizards enabled for this element.
*
......
......@@ -24,6 +24,17 @@ use TYPO3\CMS\Core\Utility\StringUtility;
*/
class InputColorPickerElement extends AbstractFormElement
{
/**
* Default field information enabled for this element.
*
* @var array
*/
protected $defaultFieldInformation = [
'tcaDescription' => [
'renderType' => 'tcaDescription',
],
];
/**
* Default field wizards enabled for this element.
*
......@@ -69,9 +80,14 @@ class InputColorPickerElement extends AbstractFormElement
$width = (int)$this->formMaxWidth($size);
$nullControlNameEscaped = htmlspecialchars('control[active][' . $table . '][' . $row['uid'] . '][' . $fieldName . ']');
$fieldInformationResult = $this->renderFieldInformation();
$fieldInformationHtml = $fieldInformationResult['html'];
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false);
if ($config['readOnly']) {
$html = [];
$html[] = '<div class="formengine-field-item t3js-formengine-field-item">';
$html[] = $fieldInformationHtml;
$html[] = '<div class="form-wizards-wrap">';
$html[] = '<div class="form-wizards-element">';
$html[] = '<div class="form-control-wrap" style="max-width: ' . $width . 'px">';
......@@ -149,10 +165,6 @@ class InputColorPickerElement extends AbstractFormElement
$valuePickerHtml[] = '</select>';
}
$fieldInformationResult = $this->renderFieldInformation();
$fieldInformationHtml = $fieldInformationResult['html'];
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false);
$fieldWizardResult = $this->renderFieldWizard();
$fieldWizardHtml = $fieldWizardResult['html'];
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldWizardResult, false);
......
......@@ -25,6 +25,17 @@ use TYPO3\CMS\Core\Utility\StringUtility;
*/
class InputDateTimeElement extends AbstractFormElement
{
/**
* Default field information enabled for this element.
*
* @var array
*/
protected $defaultFieldInformation = [
'tcaDescription' => [
'renderType' => 'tcaDescription',
],
];
/**
* Default field wizards enabled for this element.
*
......@@ -91,11 +102,16 @@ class InputDateTimeElement extends AbstractFormElement
$size = MathUtility::forceIntegerInRange($config['size'] ?? $defaultInputWidth, $this->minimumInputWidth, $this->maxInputWidth);
$width = (int)$this->formMaxWidth($size);
$fieldInformationResult = $this->renderFieldInformation();
$fieldInformationHtml = $fieldInformationResult['html'];
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false);
if (isset($config['readOnly']) && $config['readOnly']) {
// Early return for read only fields
$itemValue = $this->formatValue($format, $itemValue);
$html = [];
$html[] = '<div class="formengine-field-item t3js-formengine-field-item">';
$html[] = $fieldInformationHtml;
$html[] = '<div class="form-wizards-wrap">';
$html[] = '<div class="form-wizards-element">';
$html[] = '<div class="form-control-wrap" style="max-width: ' . $width . 'px">';
......@@ -160,10 +176,6 @@ class InputDateTimeElement extends AbstractFormElement
$itemValue = gmdate('c', (int)$itemValue);
}
$fieldInformationResult = $this->renderFieldInformation();
$fieldInformationHtml = $fieldInformationResult['html'];
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false);
$fieldWizardResult = $this->renderFieldWizard();
$fieldWizardHtml = $fieldWizardResult['html'];
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldWizardResult, false);
......
......@@ -36,6 +36,17 @@ use TYPO3\CMS\Frontend\Service\TypoLinkCodecService;
*/
class InputLinkElement extends AbstractFormElement
{
/**
* Default field information enabled for this element.
*
* @var array
*/
protected $defaultFieldInformation = [
'tcaDescription' => [
'renderType' => 'tcaDescription',
],
];
/**
* Default field controls render the link icon
*
......@@ -93,10 +104,15 @@ class InputLinkElement extends AbstractFormElement
$width = (int)$this->formMaxWidth($size);
$nullControlNameEscaped = htmlspecialchars('control[active][' . $table . '][' . $row['uid'] . '][' . $fieldName . ']');
$fieldInformationResult = $this->renderFieldInformation();
$fieldInformationHtml = $fieldInformationResult['html'];
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false);
if ($config['readOnly']) {
// Early return for read only fields
$html = [];
$html[] = '<div class="formengine-field-item t3js-formengine-field-item">';
$html[] = $fieldInformationHtml;
$html[] = '<div class="form-wizards-wrap">';
$html[] = '<div class="form-wizards-element">';
$html[] = '<div class="form-control-wrap" style="max-width: ' . $width . 'px">';
......@@ -186,10 +202,6 @@ class InputLinkElement extends AbstractFormElement
$valuePickerHtml[] = '</select>';
}
$fieldInformationResult = $this->renderFieldInformation();
$fieldInformationHtml = $fieldInformationResult['html'];
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false);
$fieldWizardResult = $this->renderFieldWizard();
$fieldWizardHtml = $fieldWizardResult['html'];
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldWizardResult, false);
......
......@@ -27,6 +27,17 @@ use TYPO3\CMS\Core\Utility\StringUtility;
*/
class InputTextElement extends AbstractFormElement
{
/**
* Default field information enabled for this element.
*
* @var array
*/
protected $defaultFieldInformation = [
'tcaDescription' => [
'renderType' => 'tcaDescription',
],
];
/**
* Default field wizards enabled for this element.
*
......@@ -72,6 +83,10 @@ class InputTextElement extends AbstractFormElement
$width = (int)$this->formMaxWidth($size);
$nullControlNameEscaped = htmlspecialchars('control[active][' . $table . '][' . $row['uid'] . '][' . $fieldName . ']');
$fieldInformationResult = $this->renderFieldInformation();
$fieldInformationHtml = $fieldInformationResult['html'];
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false);
if ($config['readOnly']) {
// Early return for read only fields
if (in_array('password', $evalList, true)) {
......@@ -79,6 +94,7 @@ class InputTextElement extends AbstractFormElement
}
$html = [];
$html[] = '<div class="formengine-field-item t3js-formengine-field-item">';
$html[] = $fieldInformationHtml;
$html[] = '<div class="form-wizards-wrap">';
$html[] = '<div class="form-wizards-element">';
$html[] = '<div class="form-control-wrap" style="max-width: ' . $width . 'px">';
......@@ -199,10 +215,6 @@ class InputTextElement extends AbstractFormElement
$valueSliderHtml[] = '</div>';
}
$fieldInformationResult = $this->renderFieldInformation();
$fieldInformationHtml = $fieldInformationResult['html'];
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false);
$fieldControlResult = $this->renderFieldControl();
$fieldControlHtml = $fieldControlResult['html'];
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldControlResult, false);
......
......@@ -21,12 +21,23 @@ use TYPO3\CMS\Core\Utility\MathUtility;
*/
class NoneElement extends AbstractFormElement
{
/**
* Default field information enabled for this element.
*
* @var array
*/
protected $defaultFieldInformation = [
'tcaDescription' => [
'renderType' => 'tcaDescription',
],
];
/**
* This will render a non-editable display of the content of the field.
*
* @return string The HTML code for the TCEform field
* @return array The HTML code for the TCEform field
*/
public function render()
public function render(): array
{
$resultArray = $this->initializeResultArray();
......
......@@ -19,6 +19,17 @@ namespace TYPO3\CMS\Backend\Form\Element;
*/
class RadioElement extends AbstractFormElement
{
/**
* Default field information enabled for this element.
*
* @var array
*/
protected $defaultFieldInformation = [
'tcaDescription' => [
'renderType' => 'tcaDescription',
],
];
/**
* Default field wizards enabled for this element.
*
......@@ -56,49 +67,47 @@ class RadioElement extends AbstractFormElement
$disabled = ' disabled';
}
$fieldInformationResult = $this->renderFieldInformation();
$fieldInformationHtml = $fieldInformationResult['html'];
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false);
$fieldWizardResult = $this->renderFieldWizard();
$fieldWizardHtml = $fieldWizardResult['html'];
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldWizardResult, false);
$html = [];
$html[] = '<div class="formengine-field-item t3js-formengine-field-item">';
$html[] = $fieldInformationHtml;
$html[] = '<div class="form-wizards-wrap">';
$html[] = '<div class="form-wizards-element">';
foreach ($this->data['parameterArray']['fieldConf']['config']['items'] as $itemNumber => $itemLabelAndValue) {
$label = $itemLabelAndValue[0];
$value = $itemLabelAndValue[1];
$radioId = htmlspecialchars($this->data['parameterArray']['itemFormElID'] . '_' . $itemNumber);
$radioChecked = (string)$value === (string)$this->data['parameterArray']['itemFormElValue'] ? ' checked="checked"' : '';
$fieldInformationResult = $this->renderFieldInformation();
$fieldInformationHtml = $fieldInformationResult['html'];
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false);
$fieldWizardResult = $this->renderFieldWizard();
$fieldWizardHtml = $fieldWizardResult['html'];
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldWizardResult, false);
$html[] = '<div class="formengine-field-item t3js-formengine-field-item">';
if (!$disabled) {
$html[] = $fieldInformationHtml;
}
$html[] = '<div class="form-wizards-wrap">';
$html[] = '<div class="form-wizards-element">';
$html[] = '<div class="radio' . $disabled . '">';
$html[] = '<label for="' . $radioId . '">';
$html[] = '<input type="radio"';
$html[] = ' name="' . htmlspecialchars($this->data['parameterArray']['itemFormElName']) . '"';
$html[] = ' id="' . $radioId . '"';
$html[] = ' value="' . htmlspecialchars($value) . '"';
$html[] = $radioChecked;
$html[] = $disabled;
$html[] = ' onclick="' . htmlspecialchars(implode('', $this->data['parameterArray']['fieldChangeFunc'])) . '"';
$html[] = '/>';
$html[] = htmlspecialchars($this->appendValueToLabelInDebugMode($label, $value));
$html[] = '</label>';
$html[] = '</div>';
$html[] = '</div>';
if (!$disabled) {
$html[] = '<div class="form-wizards-items-bottom">';
$html[] = $fieldWizardHtml;
$html[] = '</div>';
}
$html[] = '</div>';
$html[] = '<div class="radio' . $disabled . '">';
$html[] = '<label for="' . $radioId . '">';
$html[] = '<input type="radio"';
$html[] = ' name="' . htmlspecialchars($this->data['parameterArray']['itemFormElName']) . '"';
$html[] = ' id="' . $radioId . '"';
$html[] = ' value="' . htmlspecialchars($value) . '"';
$html[] = $radioChecked;
$html[] = $disabled;
$html[] = ' onclick="' . htmlspecialchars(implode('', $this->data['parameterArray']['fieldChangeFunc'])) . '"';
$html[] = '/>';
$html[] = htmlspecialchars($this->appendVa