first commit
This commit is contained in:
commit
1218dd1517
12716 files changed
+989233
No files matched your search
@@ -0,0 +1,196 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
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!
|
||||
*/
|
||||
|
||||
use Psr\Log\LoggerAwareInterface;
|
||||
use Psr\Log\LoggerAwareTrait;
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Base class for container and single elements - their abstracts extend from here.
|
||||
*/
|
||||
abstract class AbstractNode implements NodeInterface, LoggerAwareInterface
|
||||
{
|
||||
use LoggerAwareTrait;
|
||||
|
||||
/**
|
||||
* Instance of the node factory to create sub elements, container and single element expansions.
|
||||
*
|
||||
* @var NodeFactory
|
||||
*/
|
||||
protected $nodeFactory;
|
||||
|
||||
/**
|
||||
* Main data array to work on, given from parent to child elements
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $data = [];
|
||||
|
||||
/**
|
||||
* A list of default field information added to the element / container.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldInformation = [];
|
||||
|
||||
/**
|
||||
* A list of default field controls added to the element / container.
|
||||
* This property is often reset by single elements.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldControl = [];
|
||||
|
||||
/**
|
||||
* A list of default field wizards added to the element / container.
|
||||
* This property is often reset by single elements.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldWizard = [];
|
||||
|
||||
/**
|
||||
* Set data to data array and register node factory to render sub elements
|
||||
*
|
||||
* @param NodeFactory $nodeFactory
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct(NodeFactory $nodeFactory, array $data)
|
||||
{
|
||||
$this->data = $data;
|
||||
$this->nodeFactory = $nodeFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for single nodes
|
||||
*
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
*/
|
||||
abstract public function render();
|
||||
|
||||
/**
|
||||
* Initialize the array that is returned to parent after calling. This structure
|
||||
* is identical for *all* nodes. Parent will merge the return of a child with its
|
||||
* own stuff and in itself return an array of the same structure.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function initializeResultArray(): array
|
||||
{
|
||||
return [
|
||||
'additionalJavaScriptPost' => [],
|
||||
'additionalJavaScriptSubmit' => [],
|
||||
'additionalHiddenFields' => [],
|
||||
'additionalInlineLanguageLabelFiles' => [],
|
||||
'stylesheetFiles' => [],
|
||||
// can hold strings or arrays,
|
||||
// string = requireJS module,
|
||||
// array = requireJS module + callback e.g. array('TYPO3/Foo/Bar', 'function() {}')
|
||||
'requireJsModules' => [],
|
||||
'inlineData' => [],
|
||||
'html' => '',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge existing data with a child return array.
|
||||
* The incoming $childReturn array should be initialized
|
||||
* using initializeResultArray() beforehand.
|
||||
*
|
||||
* @param array $existing Currently merged array
|
||||
* @param array $childReturn Array returned by child
|
||||
* @param bool $mergeHtml If false, the ['html'] section of $childReturn will NOT be added to $existing
|
||||
* @return array Result array
|
||||
*/
|
||||
protected function mergeChildReturnIntoExistingResult(array $existing, array $childReturn, bool $mergeHtml = true): array
|
||||
{
|
||||
if ($mergeHtml && !empty($childReturn['html'])) {
|
||||
$existing['html'] .= LF . $childReturn['html'];
|
||||
}
|
||||
foreach ($childReturn['additionalJavaScriptPost'] ?? [] as $value) {
|
||||
$existing['additionalJavaScriptPost'][] = $value;
|
||||
}
|
||||
foreach ($childReturn['additionalJavaScriptSubmit'] ?? [] as $value) {
|
||||
$existing['additionalJavaScriptSubmit'][] = $value;
|
||||
}
|
||||
foreach ($childReturn['additionalHiddenFields'] ?? [] as $value) {
|
||||
$existing['additionalHiddenFields'][] = $value;
|
||||
}
|
||||
foreach ($childReturn['stylesheetFiles'] ?? [] as $value) {
|
||||
$existing['stylesheetFiles'][] = $value;
|
||||
}
|
||||
foreach ($childReturn['requireJsModules'] ?? [] as $module) {
|
||||
$existing['requireJsModules'][] = $module;
|
||||
}
|
||||
foreach ($childReturn['additionalInlineLanguageLabelFiles'] ?? [] as $inlineLanguageLabelFile) {
|
||||
$existing['additionalInlineLanguageLabelFiles'][] = $inlineLanguageLabelFile;
|
||||
}
|
||||
if (!empty($childReturn['inlineData'])) {
|
||||
$existingInlineData = $existing['inlineData'];
|
||||
$childInlineData = $childReturn['inlineData'];
|
||||
ArrayUtility::mergeRecursiveWithOverrule($existingInlineData, $childInlineData);
|
||||
$existing['inlineData'] = $existingInlineData;
|
||||
}
|
||||
return $existing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build JSON string for validations rules.
|
||||
*
|
||||
* @param array $config
|
||||
* @return string
|
||||
*/
|
||||
protected function getValidationDataAsJsonString(array $config): string
|
||||
{
|
||||
$validationRules = [];
|
||||
if (!empty($config['eval'])) {
|
||||
$evalList = GeneralUtility::trimExplode(',', $config['eval'], true);
|
||||
foreach ($evalList as $evalType) {
|
||||
$validationRules[] = [
|
||||
'type' => $evalType,
|
||||
];
|
||||
}
|
||||
}
|
||||
if (!empty($config['range'])) {
|
||||
$newValidationRule = [
|
||||
'type' => 'range',
|
||||
];
|
||||
if (!empty($config['range']['lower'])) {
|
||||
$newValidationRule['lower'] = $config['range']['lower'];
|
||||
}
|
||||
if (!empty($config['range']['upper'])) {
|
||||
$newValidationRule['upper'] = $config['range']['upper'];
|
||||
}
|
||||
$validationRules[] = $newValidationRule;
|
||||
}
|
||||
if (!empty($config['maxitems']) || !empty($config['minitems'])) {
|
||||
$minItems = isset($config['minitems']) ? (int)$config['minitems'] : 0;
|
||||
$maxItems = isset($config['maxitems']) ? (int)$config['maxitems'] : 99999;
|
||||
$type = $config['type'] ?: 'range';
|
||||
$validationRules[] = [
|
||||
'type' => $type,
|
||||
'minItems' => $minItems,
|
||||
'maxItems' => $maxItems
|
||||
];
|
||||
}
|
||||
if (!empty($config['required'])) {
|
||||
$validationRules[] = ['type' => 'required'];
|
||||
}
|
||||
return json_encode($validationRules);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\Container;
|
||||
|
||||
/*
|
||||
* 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\AbstractNode;
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Fluid\View\StandaloneView;
|
||||
|
||||
/**
|
||||
* Abstract container has various methods used by the container classes
|
||||
*/
|
||||
abstract class AbstractContainer extends AbstractNode
|
||||
{
|
||||
/**
|
||||
* Merge field information configuration with default and render them.
|
||||
*
|
||||
* @return array Result array
|
||||
*/
|
||||
protected function renderFieldInformation(): array
|
||||
{
|
||||
$options = $this->data;
|
||||
$fieldInformation = $this->defaultFieldInformation;
|
||||
$currentRenderType = $this->data['renderType'];
|
||||
$fieldInformationFromTca = $options['processedTca']['ctrl']['container'][$currentRenderType]['fieldInformation'] ?? [];
|
||||
ArrayUtility::mergeRecursiveWithOverrule($fieldInformation, $fieldInformationFromTca);
|
||||
$options['renderType'] = 'fieldInformation';
|
||||
$options['renderData']['fieldInformation'] = $fieldInformation;
|
||||
return $this->nodeFactory->create($options)->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge field control configuration with default controls and render them.
|
||||
*
|
||||
* @return array Result array
|
||||
*/
|
||||
protected function renderFieldControl(): array
|
||||
{
|
||||
$options = $this->data;
|
||||
$fieldControl = $this->defaultFieldControl;
|
||||
$currentRenderType = $this->data['renderType'];
|
||||
$fieldControlFromTca = $options['processedTca']['ctrl']['container'][$currentRenderType]['fieldControl'] ?? [];
|
||||
ArrayUtility::mergeRecursiveWithOverrule($fieldControl, $fieldControlFromTca);
|
||||
$options['renderType'] = 'fieldControl';
|
||||
$options['renderData']['fieldControl'] = $fieldControl;
|
||||
return $this->nodeFactory->create($options)->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge field wizard configuration with default wizards and render them.
|
||||
*
|
||||
* @return array Result array
|
||||
*/
|
||||
protected function renderFieldWizard(): array
|
||||
{
|
||||
$options = $this->data;
|
||||
$fieldWizard = $this->defaultFieldWizard;
|
||||
$currentRenderType = $this->data['renderType'];
|
||||
$fieldWizardFromTca = $options['processedTca']['ctrl']['container'][$currentRenderType]['fieldWizard'] ?? [];
|
||||
ArrayUtility::mergeRecursiveWithOverrule($fieldWizard, $fieldWizardFromTca);
|
||||
$options['renderType'] = 'fieldWizard';
|
||||
$options['renderData']['fieldWizard'] = $fieldWizard;
|
||||
return $this->nodeFactory->create($options)->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* A single field of TCA 'types' 'showitem' can have three semicolon separated configuration options:
|
||||
* fieldName: Name of the field to be found in TCA 'columns' section
|
||||
* fieldLabel: An alternative field label
|
||||
* paletteName: Name of a palette to be found in TCA 'palettes' section that is rendered after this field
|
||||
*
|
||||
* @param string $field Semicolon separated field configuration
|
||||
* @throws \RuntimeException
|
||||
* @return array
|
||||
*/
|
||||
protected function explodeSingleFieldShowItemConfiguration($field)
|
||||
{
|
||||
$fieldArray = GeneralUtility::trimExplode(';', $field);
|
||||
if (empty($fieldArray[0])) {
|
||||
throw new \RuntimeException('Field must not be empty', 1426448465);
|
||||
}
|
||||
return [
|
||||
'fieldName' => $fieldArray[0],
|
||||
'fieldLabel' => !empty($fieldArray[1]) ? $fieldArray[1] : null,
|
||||
'paletteName' => !empty($fieldArray[2]) ? $fieldArray[2] : null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Render tabs with label and content. Used by TabsContainer and FlexFormTabsContainer.
|
||||
* Re-uses the template Tabs.html which is also used by ModuleTemplate.php.
|
||||
*
|
||||
* @param array $menuItems Tab elements, each element is an array with "label" and "content"
|
||||
* @param string $domId DOM id attribute, will be appended with an iteration number per tab.
|
||||
* @param int $defaultTabIndex
|
||||
* @return string
|
||||
*/
|
||||
protected function renderTabMenu(array $menuItems, $domId, $defaultTabIndex = 1)
|
||||
{
|
||||
$templatePath = ExtensionManagementUtility::extPath('backend')
|
||||
. 'Resources/Private/Templates/DocumentTemplate/';
|
||||
|
||||
$view = GeneralUtility::makeInstance(StandaloneView::class);
|
||||
$view->setTemplatePathAndFilename($templatePath . 'Tabs.html');
|
||||
$view->setPartialRootPaths([$templatePath . 'Partials']);
|
||||
$view->assignMultiple([
|
||||
'id' => $domId,
|
||||
'items' => $menuItems,
|
||||
'defaultTabIndex' => $defaultTabIndex,
|
||||
'wrapContent' => false,
|
||||
'storeLastActiveTab' => true,
|
||||
]);
|
||||
return $view->render();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\Container;
|
||||
|
||||
/*
|
||||
* 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\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Imaging\Icon;
|
||||
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Flex form container implementation
|
||||
* This one is called by FlexFormSectionContainer and renders HTML for a single container.
|
||||
* For processing of single elements FlexFormElementContainer is called
|
||||
*/
|
||||
class FlexFormContainerContainer extends AbstractContainer
|
||||
{
|
||||
/**
|
||||
* Entry method
|
||||
*
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
|
||||
$table = $this->data['tableName'];
|
||||
$row = $this->data['databaseRow'];
|
||||
$fieldName = $this->data['fieldName'];
|
||||
$flexFormFormPrefix = $this->data['flexFormFormPrefix'];
|
||||
$flexFormContainerElementCollapsed = $this->data['flexFormContainerElementCollapsed'];
|
||||
$flexFormDataStructureArray = $this->data['flexFormDataStructureArray'];
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
|
||||
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
|
||||
$toggleIcons = '<span class="t3js-flex-control-toggle-icon-open" style="' . ($flexFormContainerElementCollapsed ? 'display: none;' : '') . '">'
|
||||
. $iconFactory->getIcon('actions-view-list-collapse', Icon::SIZE_SMALL)->render()
|
||||
. '</span>';
|
||||
$toggleIcons .= '<span class="t3js-flex-control-toggle-icon-close" style="' . ($flexFormContainerElementCollapsed ? '' : 'display: none;') . '">'
|
||||
. $iconFactory->getIcon('actions-view-list-expand', Icon::SIZE_SMALL)->render()
|
||||
. '</span>';
|
||||
|
||||
$flexFormContainerIdentifier = $this->data['flexFormContainerIdentifier'];
|
||||
$actionFieldName = '_ACTION_FLEX_FORM'
|
||||
. $parameterArray['itemFormElName']
|
||||
. $this->data['flexFormFormPrefix']
|
||||
. '[_ACTION]'
|
||||
. '[' . $flexFormContainerIdentifier . ']';
|
||||
$toggleFieldName = 'data[' . $table . '][' . $row['uid'] . '][' . $fieldName . ']'
|
||||
. $flexFormFormPrefix
|
||||
. '[' . $flexFormContainerIdentifier . ']'
|
||||
. '[_TOGGLE]';
|
||||
|
||||
$moveAndDeleteContent = [];
|
||||
$userHasAccessToDefaultLanguage = $this->getBackendUserAuthentication()->checkLanguageAccess(0);
|
||||
if ($userHasAccessToDefaultLanguage) {
|
||||
$moveAndDeleteContent[] = '<span class="btn btn-default t3js-sortable-handle"><span title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:sortable.dragmove')) . '">' . $iconFactory->getIcon('actions-move-move', Icon::SIZE_SMALL)->render() . '</span></span>';
|
||||
$moveAndDeleteContent[] = '<span class="btn btn-default t3js-delete"><span title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:delete')) . '">' . $iconFactory->getIcon('actions-edit-delete', Icon::SIZE_SMALL)->render() . '</span></span>';
|
||||
}
|
||||
|
||||
$options = $this->data;
|
||||
// Append container specific stuff to field prefix
|
||||
$options['flexFormFormPrefix'] = $flexFormFormPrefix . '[' . $flexFormContainerIdentifier . '][' . $this->data['flexFormContainerName'] . '][el]';
|
||||
$options['flexFormDataStructureArray'] = $flexFormDataStructureArray['el'];
|
||||
$options['renderType'] = 'flexFormElementContainer';
|
||||
$containerContentResult = $this->nodeFactory->create($options)->render();
|
||||
|
||||
$containerTitle = '';
|
||||
if (!empty(trim($flexFormDataStructureArray['title']))) {
|
||||
$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="" />';
|
||||
$html[] = '<div class="panel panel-default panel-condensed">';
|
||||
$html[] = '<div class="panel-heading t3js-flex-section-header" data-toggle="formengine-flex">';
|
||||
$html[] = '<div class="form-irre-header">';
|
||||
$html[] = '<div class="form-irre-header-cell form-irre-header-icon">';
|
||||
$html[] = $toggleIcons;
|
||||
$html[] = '</div>';
|
||||
$html[] = '<div class="form-irre-header-cell form-irre-header-body">';
|
||||
$html[] = '<span class="t3js-record-title">' . htmlspecialchars($containerTitle) . '</span>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '<div class="form-irre-header-cell form-irre-header-control">';
|
||||
$html[] = '<div class="btn-group btn-group-sm">';
|
||||
$html[] = implode(LF, $moveAndDeleteContent);
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '<div class="panel-collapse t3js-flex-section-content"' . ($flexFormContainerElementCollapsed ? ' style="display:none;"' : '') . '>';
|
||||
$html[] = $containerContentResult['html'];
|
||||
$html[] = '</div>';
|
||||
$html[] = '<input';
|
||||
$html[] = 'class="t3-flex-control t3js-flex-control-toggle"';
|
||||
$html[] = 'type="hidden"';
|
||||
$html[] = 'name="' . htmlspecialchars($toggleFieldName) . '"';
|
||||
$html[] = 'value="' . ($flexFormContainerElementCollapsed ? '1' : '0') . '"';
|
||||
$html[] = '/>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
|
||||
$resultArray['html'] = implode(LF, $html);
|
||||
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $containerContentResult, false);
|
||||
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUserAuthentication()
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService()
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\Container;
|
||||
|
||||
/*
|
||||
* 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\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Type\Bitmask\JsConfirmation;
|
||||
|
||||
/**
|
||||
* The container handles single elements.
|
||||
*
|
||||
* This one is called by FlexFormTabsContainer, FlexFormNoTabsContainer or FlexFormContainerContainer.
|
||||
* For single fields, the code is similar to SingleFieldContainer, processing will end up in single
|
||||
* element classes depending on specific renderType of an element. Additionally, it determines if a
|
||||
* section is handled and hands over to FlexFormSectionContainer in this case.
|
||||
*/
|
||||
class FlexFormElementContainer extends AbstractContainer
|
||||
{
|
||||
/**
|
||||
* Entry method
|
||||
*
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$flexFormDataStructureArray = $this->data['flexFormDataStructureArray'];
|
||||
$flexFormRowData = $this->data['flexFormRowData'];
|
||||
$flexFormFormPrefix = $this->data['flexFormFormPrefix'];
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
|
||||
$languageService = $this->getLanguageService();
|
||||
$resultArray = $this->initializeResultArray();
|
||||
foreach ($flexFormDataStructureArray as $flexFormFieldName => $flexFormFieldArray) {
|
||||
if (
|
||||
// No item array found at all
|
||||
!is_array($flexFormFieldArray)
|
||||
// Not a section or container and not a list of single items
|
||||
|| (!isset($flexFormFieldArray['type']) && !is_array($flexFormFieldArray['config']))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($flexFormFieldArray['type'] === 'array') {
|
||||
// Section
|
||||
if (empty($flexFormFieldArray['section'])) {
|
||||
$resultArray['html'] = LF . 'Section expected at ' . $flexFormFieldName . ' but not found';
|
||||
continue;
|
||||
}
|
||||
|
||||
$options = $this->data;
|
||||
$options['flexFormDataStructureArray'] = $flexFormFieldArray;
|
||||
$options['flexFormRowData'] = $flexFormRowData[$flexFormFieldName]['el'] ?? [];
|
||||
$options['flexFormFieldName'] = $flexFormFieldName;
|
||||
$options['renderType'] = 'flexFormSectionContainer';
|
||||
$sectionContainerResult = $this->nodeFactory->create($options)->render();
|
||||
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $sectionContainerResult);
|
||||
} else {
|
||||
// Set up options for single element
|
||||
$fakeParameterArray = [
|
||||
'fieldConf' => [
|
||||
'label' => $languageService->sL(trim($flexFormFieldArray['label'])),
|
||||
'config' => $flexFormFieldArray['config'],
|
||||
'children' => $flexFormFieldArray['children'],
|
||||
'onChange' => $flexFormFieldArray['onChange'],
|
||||
],
|
||||
'fieldChangeFunc' => $parameterArray['fieldChangeFunc'],
|
||||
'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)) {
|
||||
$alertMsgOnChange = 'top.TYPO3.Modal.confirm('
|
||||
. 'TYPO3.lang["FormEngine.refreshRequiredTitle"],'
|
||||
. ' TYPO3.lang["FormEngine.refreshRequiredContent"]'
|
||||
. ')'
|
||||
. '.on('
|
||||
. '"button.clicked",'
|
||||
. ' function(e) { if (e.target.name == "ok" && TBE_EDITOR.checkSubmit(-1)) { TBE_EDITOR.submitForm() } top.TYPO3.Modal.dismiss(); }'
|
||||
. ');';
|
||||
} else {
|
||||
$alertMsgOnChange = 'if (TBE_EDITOR.checkSubmit(-1)){ TBE_EDITOR.submitForm();}';
|
||||
}
|
||||
}
|
||||
if ($alertMsgOnChange) {
|
||||
$fakeParameterArray['fieldChangeFunc']['alert'] = $alertMsgOnChange;
|
||||
}
|
||||
|
||||
$originalFieldName = $parameterArray['itemFormElName'];
|
||||
$fakeParameterArray['itemFormElName'] = $parameterArray['itemFormElName'] . $flexFormFormPrefix . '[' . $flexFormFieldName . '][vDEF]';
|
||||
if ($fakeParameterArray['itemFormElName'] !== $originalFieldName) {
|
||||
// If calculated itemFormElName is different from originalFieldName
|
||||
// change the originalFieldName in TBE_EDITOR_fieldChanged. This is
|
||||
// especially relevant for wizards writing their content back to hidden fields
|
||||
if (!empty($fakeParameterArray['fieldChangeFunc']['TBE_EDITOR_fieldChanged'])) {
|
||||
$fakeParameterArray['fieldChangeFunc']['TBE_EDITOR_fieldChanged'] = str_replace($originalFieldName, $fakeParameterArray['itemFormElName'], $fakeParameterArray['fieldChangeFunc']['TBE_EDITOR_fieldChanged']);
|
||||
}
|
||||
}
|
||||
$fakeParameterArray['itemFormElID'] = $parameterArray['itemFormElID'] . '_' . preg_replace('/[^a-zA-Z0-9_-]/', '_', $flexFormFieldName) . '_' . md5($fakeParameterArray['itemFormElName']);
|
||||
if (isset($flexFormRowData[$flexFormFieldName]['vDEF'])) {
|
||||
$fakeParameterArray['itemFormElValue'] = $flexFormRowData[$flexFormFieldName]['vDEF'];
|
||||
} else {
|
||||
$fakeParameterArray['itemFormElValue'] = $fakeParameterArray['fieldConf']['config']['default'];
|
||||
}
|
||||
|
||||
$options = $this->data;
|
||||
// Set either flexFormFieldName or flexFormContainerFieldName, depending on if we are a "regular" field or a flex container section field
|
||||
if (empty($options['flexFormFieldName'])) {
|
||||
$options['flexFormFieldName'] = $flexFormFieldName;
|
||||
} else {
|
||||
$options['flexFormContainerFieldName'] = $flexFormFieldName;
|
||||
}
|
||||
$options['parameterArray'] = $fakeParameterArray;
|
||||
$options['elementBaseName'] = $this->data['elementBaseName'] . $flexFormFormPrefix . '[' . $flexFormFieldName . '][vDEF]';
|
||||
|
||||
if (!empty($flexFormFieldArray['config']['renderType'])) {
|
||||
$options['renderType'] = $flexFormFieldArray['config']['renderType'];
|
||||
} else {
|
||||
// Fallback to type if no renderType is given
|
||||
$options['renderType'] = $flexFormFieldArray['config']['type'];
|
||||
}
|
||||
$childResult = $this->nodeFactory->create($options)->render();
|
||||
|
||||
if (!empty($childResult['html'])) {
|
||||
// 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">';
|
||||
$html[] = '<label class="t3js-formengine-label">';
|
||||
$html[] = BackendUtility::wrapInHelp($parameterArray['_cshKey'], $flexFormFieldName, $processedTitle);
|
||||
$html[] = '</label>';
|
||||
$html[] = '<div class="formengine-field-item t3js-formengine-field-item">';
|
||||
$html[] = $childResult['html'];
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$resultArray['html'] .= implode(LF, $html);
|
||||
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $childResult, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService()
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUserAuthentication()
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\Container;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Entry container to a flex form element. This container is created by
|
||||
* SingleFieldContainer if a type='flex' field is rendered.
|
||||
*
|
||||
* It either forks a FlexFormTabsContainer or a FlexFormNoTabsContainer.
|
||||
*/
|
||||
class FlexFormEntryContainer extends AbstractContainer
|
||||
{
|
||||
/**
|
||||
* Entry method
|
||||
*
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$flexFormDataStructureIdentifier = $this->data['parameterArray']['fieldConf']['config']['dataStructureIdentifier'];
|
||||
$flexFormDataStructureArray = $this->data['parameterArray']['fieldConf']['config']['ds'];
|
||||
|
||||
$options = $this->data;
|
||||
$options['flexFormDataStructureIdentifier'] = $flexFormDataStructureIdentifier;
|
||||
$options['flexFormDataStructureArray'] = $flexFormDataStructureArray;
|
||||
$options['flexFormRowData'] = $this->data['parameterArray']['itemFormElValue'];
|
||||
$options['renderType'] = 'flexFormNoTabsContainer';
|
||||
|
||||
// Enable tabs if there is more than one sheet
|
||||
if (count($flexFormDataStructureArray['sheets']) > 1) {
|
||||
$options['renderType'] = 'flexFormTabsContainer';
|
||||
}
|
||||
|
||||
$resultArray = $this->nodeFactory->create($options)->render();
|
||||
$resultArray['requireJsModules'][] = 'TYPO3/CMS/Backend/FormEngineFlexForm';
|
||||
|
||||
return $resultArray;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\Container;
|
||||
|
||||
/*
|
||||
* 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\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Handle a flex form that has no tabs.
|
||||
*
|
||||
* This container is called by FlexFormEntryContainer if only a default sheet
|
||||
* exists. It evaluates the display condition and hands over rendering of single
|
||||
* fields to FlexFormElementContainer.
|
||||
*/
|
||||
class FlexFormNoTabsContainer extends AbstractContainer
|
||||
{
|
||||
/**
|
||||
* Default field information enabled for this element.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldInformation = [
|
||||
'tcaDescription' => [
|
||||
'renderType' => 'tcaDescription',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Entry method
|
||||
*
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$table = $this->data['tableName'];
|
||||
$row = $this->data['databaseRow'];
|
||||
$fieldName = $this->data['fieldName']; // field name of the flex form field in DB
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
$flexFormDataStructureArray = $this->data['flexFormDataStructureArray'];
|
||||
$flexFormRowData = $this->data['flexFormRowData'];
|
||||
$resultArray = $this->initializeResultArray();
|
||||
|
||||
// Determine this single sheet name, most often it ends up with sDEF, except if only one sheet was defined
|
||||
$flexFormSheetNames = array_keys($flexFormDataStructureArray['sheets']);
|
||||
$sheetName = array_pop($flexFormSheetNames);
|
||||
$flexFormRowDataSubPart = $flexFormRowData['data'][$sheetName]['lDEF'] ?: [];
|
||||
|
||||
unset($flexFormDataStructureArray['meta']);
|
||||
|
||||
if (!is_array($flexFormDataStructureArray['sheets'][$sheetName]['ROOT']['el'])) {
|
||||
$resultArray['html'] = 'Data Structure ERROR: No [\'ROOT\'][\'el\'] element found in flex form definition.';
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
// Assemble key for loading the correct CSH file
|
||||
// @todo: what is that good for? That is for the title of single elements ... see FlexFormElementContainer!
|
||||
$dsPointerFields = GeneralUtility::trimExplode(',', $GLOBALS['TCA'][$table]['columns'][$fieldName]['config']['ds_pointerField'], true);
|
||||
$parameterArray['_cshKey'] = $table . '.' . $fieldName;
|
||||
foreach ($dsPointerFields as $key) {
|
||||
if (is_string($row[$key]) && $row[$key] !== '') {
|
||||
$parameterArray['_cshKey'] .= '.' . $row[$key];
|
||||
} elseif (is_array($row[$key]) && isset($row[$key][0]) && is_string($row[$key][0]) && $row[$key][0] !== '') {
|
||||
$parameterArray['_cshKey'] .= '.' . $row[$key][0];
|
||||
}
|
||||
}
|
||||
|
||||
$options = $this->data;
|
||||
$options['flexFormDataStructureArray'] = $flexFormDataStructureArray['sheets'][$sheetName]['ROOT']['el'];
|
||||
$options['flexFormRowData'] = $flexFormRowDataSubPart;
|
||||
$options['flexFormSheetName'] = $sheetName;
|
||||
$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';
|
||||
$childResult = $this->nodeFactory->create($options)->render();
|
||||
return $this->mergeChildReturnIntoExistingResult($resultArray, $childResult, true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\Container;
|
||||
|
||||
/*
|
||||
* 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\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Imaging\Icon;
|
||||
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Handle flex form sections.
|
||||
*
|
||||
* This container is created by FlexFormElementContainer if a "single" element is in
|
||||
* fact a section. For each existing section container it creates as FlexFormContainerContainer
|
||||
* to render its inner fields.
|
||||
* Additionally, a button for each possible container is rendered with information for the
|
||||
* ajax controller that fetches one on click.
|
||||
*/
|
||||
class FlexFormSectionContainer extends AbstractContainer
|
||||
{
|
||||
/**
|
||||
* Entry method
|
||||
*
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
|
||||
$languageService = $this->getLanguageService();
|
||||
|
||||
$flexFormDataStructureIdentifier = $this->data['flexFormDataStructureIdentifier'];
|
||||
$flexFormDataStructureArray = $this->data['flexFormDataStructureArray'];
|
||||
$flexFormRowData = $this->data['flexFormRowData'];
|
||||
$flexFormFieldName = $this->data['flexFormFieldName'];
|
||||
$flexFormSheetName = $this->data['flexFormSheetName'];
|
||||
|
||||
$userHasAccessToDefaultLanguage = $this->getBackendUserAuthentication()->checkLanguageAccess(0);
|
||||
|
||||
$resultArray = $this->initializeResultArray();
|
||||
|
||||
// Render each existing container
|
||||
foreach ($flexFormDataStructureArray['children'] as $flexFormContainerIdentifier => $containerDataStructure) {
|
||||
$existingContainerData = $flexFormRowData[$flexFormContainerIdentifier];
|
||||
// @todo: This relies on the fact that "_TOGGLE" is *below* the real data in the saved xml structure
|
||||
$existingSectionContainerDataStructureType = key($existingContainerData);
|
||||
$existingContainerData = $existingContainerData[$existingSectionContainerDataStructureType];
|
||||
$options = $this->data;
|
||||
$options['flexFormRowData'] = $existingContainerData['el'];
|
||||
$options['flexFormDataStructureArray'] = $containerDataStructure;
|
||||
$options['flexFormFormPrefix'] = $this->data['flexFormFormPrefix'] . '[' . $flexFormFieldName . ']' . '[el]';
|
||||
$options['flexFormContainerName'] = $existingSectionContainerDataStructureType;
|
||||
$options['flexFormContainerIdentifier'] = $flexFormContainerIdentifier;
|
||||
$options['flexFormContainerElementCollapsed'] = (bool)$flexFormRowData[$flexFormContainerIdentifier]['_TOGGLE'];
|
||||
$options['renderType'] = 'flexFormContainerContainer';
|
||||
$flexFormContainerContainerResult = $this->nodeFactory->create($options)->render();
|
||||
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $flexFormContainerContainerResult);
|
||||
}
|
||||
|
||||
// "New container" handling: Creates buttons for each possible container with all relevant information for the ajax call.
|
||||
$containerTemplatesHtml = [];
|
||||
foreach ($flexFormDataStructureArray['el'] as $flexFormContainerName => $flexFormFieldDefinition) {
|
||||
$containerTitle = '';
|
||||
if (!empty(trim($flexFormFieldDefinition['title']))) {
|
||||
$containerTitle = $languageService->sL(trim($flexFormFieldDefinition['title']));
|
||||
}
|
||||
$containerTemplateHtml = [];
|
||||
$containerTemplateHtml[] = '<a';
|
||||
$containerTemplateHtml[] = 'href="#"';
|
||||
$containerTemplateHtml[] = 'class="btn btn-default t3js-flex-container-add"';
|
||||
$containerTemplateHtml[] = 'data-vanillauid="' . (int)$this->data['vanillaUid'] . '"';
|
||||
// no int cast for databaseRow uid, this can be "NEW1234..."
|
||||
$containerTemplateHtml[] = 'data-databaserowuid="' . htmlspecialchars($this->data['databaseRow']['uid']) . '"';
|
||||
$containerTemplateHtml[] = 'data-command="' . htmlspecialchars($this->data['command']) . '"';
|
||||
$containerTemplateHtml[] = 'data-tablename="' . htmlspecialchars($this->data['tableName']) . '"';
|
||||
$containerTemplateHtml[] = 'data-fieldname="' . htmlspecialchars($this->data['fieldName']) . '"';
|
||||
$containerTemplateHtml[] = 'data-recordtypevalue="' . $this->data['recordTypeValue'] . '"';
|
||||
$containerTemplateHtml[] = 'data-datastructureidentifier="' . htmlspecialchars($flexFormDataStructureIdentifier) . '"';
|
||||
$containerTemplateHtml[] = 'data-flexformsheetname="' . htmlspecialchars($flexFormSheetName) . '"';
|
||||
$containerTemplateHtml[] = 'data-flexformfieldname="' . htmlspecialchars($flexFormFieldName) . '"';
|
||||
$containerTemplateHtml[] = 'data-flexformcontainername="' . htmlspecialchars($flexFormContainerName) . '"';
|
||||
$containerTemplateHtml[] = '>';
|
||||
$containerTemplateHtml[] = $iconFactory->getIcon('actions-document-new', Icon::SIZE_SMALL)->render();
|
||||
$containerTemplateHtml[] = htmlspecialchars(GeneralUtility::fixed_lgd_cs($containerTitle, 30));
|
||||
$containerTemplateHtml[] = '</a>';
|
||||
$containerTemplatesHtml[] = implode(LF, $containerTemplateHtml);
|
||||
}
|
||||
// Create new elements links
|
||||
$createElementsHtml = [];
|
||||
if ($userHasAccessToDefaultLanguage) {
|
||||
$createElementsHtml[] = '<div class="t3-form-field-add-flexsection">';
|
||||
$createElementsHtml[] = '<div class="btn-group">';
|
||||
$createElementsHtml[] = implode('', $containerTemplatesHtml);
|
||||
$createElementsHtml[] = '</div>';
|
||||
$createElementsHtml[] = '</div>';
|
||||
}
|
||||
|
||||
$sectionTitle = '';
|
||||
if (!empty(trim($flexFormDataStructureArray['title']))) {
|
||||
$sectionTitle = $languageService->sL(trim($flexFormDataStructureArray['title']));
|
||||
}
|
||||
|
||||
// Wrap child stuff
|
||||
$toggleAll = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.toggleall'));
|
||||
$html = [];
|
||||
$html[] = '<div class="panel panel-tab">';
|
||||
$html[] = '<div class="panel-body">';
|
||||
$html[] = '<div class="t3-form-field-container t3-form-flex">';
|
||||
$html[] = '<div class="t3-form-field-label-flexsection">';
|
||||
$html[] = '<h4>';
|
||||
$html[] = htmlspecialchars($sectionTitle);
|
||||
$html[] = '</h4>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '<div class="t3js-form-field-toggle-flexsection t3-form-flexsection-toggle">';
|
||||
$html[] = '<a class="btn btn-default" href="#" title="' . $toggleAll . '">';
|
||||
$html[] = $iconFactory->getIcon('actions-move-right', Icon::SIZE_SMALL)->render() . $toggleAll;
|
||||
$html[] = '</a>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '<div';
|
||||
$html[] = 'class="panel-group panel-hover t3-form-field-container-flexsection t3-flex-container"';
|
||||
$html[] = 'data-t3-flex-allow-restructure="' . ($userHasAccessToDefaultLanguage ? '1' : '0') . '"';
|
||||
$html[] = '>';
|
||||
$html[] = $resultArray['html'];
|
||||
$html[] = '</div>';
|
||||
$html[] = implode(LF, $createElementsHtml);
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
|
||||
$resultArray['html'] = implode(LF, $html);
|
||||
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUserAuthentication()
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService()
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\Container;
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Handle flex forms that have tabs (multiple "sheets").
|
||||
*
|
||||
* This container is called by FlexFormEntryContainer. It resolves each
|
||||
* sheet and hands rendering of single sheet content over to FlexFormElementContainer.
|
||||
*/
|
||||
class FlexFormTabsContainer extends AbstractContainer
|
||||
{
|
||||
/**
|
||||
* Default field information enabled for this element.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldInformation = [
|
||||
'tcaDescription' => [
|
||||
'renderType' => 'tcaDescription',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Entry method
|
||||
*
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
|
||||
$table = $this->data['tableName'];
|
||||
$row = $this->data['databaseRow'];
|
||||
$fieldName = $this->data['fieldName']; // field name of the flex form field in DB
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
$flexFormDataStructureArray = $this->data['flexFormDataStructureArray'];
|
||||
$flexFormRowData = $this->data['flexFormRowData'];
|
||||
|
||||
$resultArray = $this->initializeResultArray();
|
||||
|
||||
$resultArray['requireJsModules'][] = 'TYPO3/CMS/Backend/Tabs';
|
||||
|
||||
$domIdPrefix = 'DTM-' . GeneralUtility::shortMD5($this->data['parameterArray']['itemFormElName']);
|
||||
$tabCounter = 0;
|
||||
$tabElements = [];
|
||||
foreach ($flexFormDataStructureArray['sheets'] as $sheetName => $sheetDataStructure) {
|
||||
$flexFormRowSheetDataSubPart = $flexFormRowData['data'][$sheetName]['lDEF'] ?: [];
|
||||
|
||||
if (!is_array($sheetDataStructure['ROOT']['el'])) {
|
||||
$resultArray['html'] .= LF . 'No Data Structure ERROR: No [\'ROOT\'][\'el\'] found for sheet "' . $sheetName . '".';
|
||||
continue;
|
||||
}
|
||||
|
||||
$tabCounter++;
|
||||
|
||||
// Assemble key for loading the correct CSH file
|
||||
// @todo: what is that good for? That is for the title of single elements ... see FlexFormElementContainer!
|
||||
$dsPointerFields = GeneralUtility::trimExplode(',', $GLOBALS['TCA'][$table]['columns'][$fieldName]['config']['ds_pointerField'], true);
|
||||
$parameterArray['_cshKey'] = $table . '.' . $fieldName;
|
||||
foreach ($dsPointerFields as $key) {
|
||||
if (is_string($row[$key]) && $row[$key] !== '') {
|
||||
$parameterArray['_cshKey'] .= '.' . $row[$key];
|
||||
} elseif (is_array($row[$key]) && isset($row[$key][0]) && is_string($row[$key][0]) && $row[$key][0] !== '') {
|
||||
$parameterArray['_cshKey'] .= '.' . $row[$key][0];
|
||||
}
|
||||
}
|
||||
|
||||
$options = $this->data;
|
||||
$options['flexFormDataStructureArray'] = $sheetDataStructure['ROOT']['el'];
|
||||
$options['flexFormRowData'] = $flexFormRowSheetDataSubPart;
|
||||
$options['flexFormSheetName'] = $sheetName;
|
||||
$options['flexFormFormPrefix'] = '[data][' . $sheetName . '][lDEF]';
|
||||
$options['parameterArray'] = $parameterArray;
|
||||
// Merge elements of this tab into a single list again and hand over to
|
||||
// palette and single field container to render this group
|
||||
$options['tabAndInlineStack'][] = [
|
||||
'tab',
|
||||
$domIdPrefix . '-' . $tabCounter,
|
||||
];
|
||||
$options['renderType'] = 'flexFormElementContainer';
|
||||
$childReturn = $this->nodeFactory->create($options)->render();
|
||||
|
||||
if ($childReturn['html'] !== '') {
|
||||
$tabElements[] = [
|
||||
'label' => !empty(trim($sheetDataStructure['ROOT']['sheetTitle'])) ? $languageService->sL(trim($sheetDataStructure['ROOT']['sheetTitle'])) : $sheetName,
|
||||
'content' => $childReturn['html'],
|
||||
'description' => trim($sheetDataStructure['ROOT']['sheetDescription']) ? $languageService->sL(trim($sheetDataStructure['ROOT']['sheetDescription'])) : '',
|
||||
'linkTitle' => trim($sheetDataStructure['ROOT']['sheetShortDescr']) ? $languageService->sL(trim($sheetDataStructure['ROOT']['sheetShortDescr'])) : '',
|
||||
];
|
||||
}
|
||||
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $childReturn, false);
|
||||
}
|
||||
|
||||
$fieldInformationResult = $this->renderFieldInformation();
|
||||
$resultArray['html'] = '<div>' . $fieldInformationResult['html'] . '</div>';
|
||||
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false);
|
||||
|
||||
$resultArray['html'] .= $this->renderTabMenu($tabElements, $domIdPrefix);
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService()
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\Container;
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* A container rendering a "full record". This is an entry container used as first
|
||||
* step into the rendering tree..
|
||||
*
|
||||
* This container determines the to be rendered fields depending on the record type,
|
||||
* initializes possible language base data, finds out if tabs should be rendered and
|
||||
* then calls either TabsContainer or a NoTabsContainer for further processing.
|
||||
*/
|
||||
class FullRecordContainer extends AbstractContainer
|
||||
{
|
||||
/**
|
||||
* Entry method
|
||||
*
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$table = $this->data['tableName'];
|
||||
$recordTypeValue = $this->data['recordTypeValue'];
|
||||
|
||||
// Load the description content for the table if requested
|
||||
if (!empty($this->data['processedTca']['interface']['always_description'])) {
|
||||
$languageService = $this->getLanguageService();
|
||||
$languageService->loadSingleTableDescription($table);
|
||||
}
|
||||
|
||||
// List of items to be rendered
|
||||
$itemList = $this->data['processedTca']['types'][$recordTypeValue]['showitem'];
|
||||
|
||||
$fieldsArray = GeneralUtility::trimExplode(',', $itemList, true);
|
||||
|
||||
// Streamline the fields array
|
||||
// First, make sure there is always a --div-- definition for the first element
|
||||
if (strpos($fieldsArray[0], '--div--') !== 0) {
|
||||
array_unshift($fieldsArray, '--div--;LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.generalTab');
|
||||
}
|
||||
// If first tab has no label definition, add "general" label
|
||||
$firstTabHasLabel = count(GeneralUtility::trimExplode(';', $fieldsArray[0])) > 1;
|
||||
if (!$firstTabHasLabel) {
|
||||
$fieldsArray[0] = '--div--;LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.generalTab';
|
||||
}
|
||||
// If there are at least two --div-- definitions, inner container will be a TabContainer, else a NoTabContainer
|
||||
$tabCount = 0;
|
||||
foreach ($fieldsArray as $field) {
|
||||
if (strpos($field, '--div--') === 0) {
|
||||
$tabCount++;
|
||||
}
|
||||
}
|
||||
$hasTabs = true;
|
||||
if ($tabCount < 2) {
|
||||
// Remove first tab definition again if there is only one tab defined
|
||||
array_shift($fieldsArray);
|
||||
$hasTabs = false;
|
||||
}
|
||||
|
||||
$data = $this->data;
|
||||
$data['fieldsArray'] = $fieldsArray;
|
||||
if ($hasTabs) {
|
||||
$data['renderType'] = 'tabsContainer';
|
||||
} else {
|
||||
$data['renderType'] = 'noTabsContainer';
|
||||
}
|
||||
|
||||
return $this->nodeFactory->create($data)->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService()
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,683 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\Container;
|
||||
|
||||
/*
|
||||
* 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\InlineStackProcessor;
|
||||
use TYPO3\CMS\Backend\Form\NodeFactory;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Imaging\Icon;
|
||||
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Resource\Folder;
|
||||
use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperRegistry;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
use TYPO3\CMS\Core\Utility\StringUtility;
|
||||
|
||||
/**
|
||||
* Inline element entry container.
|
||||
*
|
||||
* This container is the entry step to rendering an inline element. It is created by SingleFieldContainer.
|
||||
*
|
||||
* The code creates the main structure for the single inline elements, initializes
|
||||
* the inlineData array, that is manipulated and also returned back in its manipulated state.
|
||||
* The "control" stuff of inline elements is rendered here, for example the "create new" button.
|
||||
*
|
||||
* For each existing inline relation an InlineRecordContainer is called for further processing.
|
||||
*/
|
||||
class InlineControlContainer extends AbstractContainer
|
||||
{
|
||||
/**
|
||||
* Inline data array used in JS, returned as JSON object to frontend
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $inlineData = [];
|
||||
|
||||
/**
|
||||
* @var InlineStackProcessor
|
||||
*/
|
||||
protected $inlineStackProcessor;
|
||||
|
||||
/**
|
||||
* @var IconFactory
|
||||
*/
|
||||
protected $iconFactory;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $requireJsModules = [];
|
||||
|
||||
/**
|
||||
* Default field information enabled for this element.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldInformation = [
|
||||
'tcaDescription' => [
|
||||
'renderType' => 'tcaDescription',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array Default wizards
|
||||
*/
|
||||
protected $defaultFieldWizard = [
|
||||
'localizationStateSelector' => [
|
||||
'renderType' => 'localizationStateSelector',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Container objects give $nodeFactory down to other containers.
|
||||
*
|
||||
* @param NodeFactory $nodeFactory
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct(NodeFactory $nodeFactory, array $data)
|
||||
{
|
||||
parent::__construct($nodeFactory, $data);
|
||||
$this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry method
|
||||
*
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
|
||||
$this->inlineData = $this->data['inlineData'];
|
||||
|
||||
/** @var InlineStackProcessor $inlineStackProcessor */
|
||||
$inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
|
||||
$this->inlineStackProcessor = $inlineStackProcessor;
|
||||
$inlineStackProcessor->initializeByGivenStructure($this->data['inlineStructure']);
|
||||
|
||||
$table = $this->data['tableName'];
|
||||
$row = $this->data['databaseRow'];
|
||||
$field = $this->data['fieldName'];
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
|
||||
$resultArray = $this->initializeResultArray();
|
||||
|
||||
$config = $parameterArray['fieldConf']['config'];
|
||||
$foreign_table = $config['foreign_table'];
|
||||
$isReadOnly = isset($config['readOnly']) && $config['readOnly'];
|
||||
$language = 0;
|
||||
$languageFieldName = $GLOBALS['TCA'][$table]['ctrl']['languageField'];
|
||||
if (BackendUtility::isTableLocalizable($table)) {
|
||||
$language = isset($row[$languageFieldName][0]) ? (int)$row[$languageFieldName][0] : (int)$row[$languageFieldName];
|
||||
}
|
||||
|
||||
// Add the current inline job to the structure stack
|
||||
$newStructureItem = [
|
||||
'table' => $table,
|
||||
'uid' => $row['uid'],
|
||||
'field' => $field,
|
||||
'config' => $config,
|
||||
];
|
||||
// Extract FlexForm parts (if any) from element name, e.g. array('vDEF', 'lDEF', 'FlexField', 'vDEF')
|
||||
if (!empty($parameterArray['itemFormElName'])) {
|
||||
$flexFormParts = $this->extractFlexFormParts($parameterArray['itemFormElName']);
|
||||
if ($flexFormParts !== null) {
|
||||
$newStructureItem['flexform'] = $flexFormParts;
|
||||
}
|
||||
}
|
||||
$inlineStackProcessor->pushStableStructureItem($newStructureItem);
|
||||
|
||||
// Transport the flexform DS identifier fields to the FormInlineAjaxController
|
||||
if (!empty($newStructureItem['flexform'])
|
||||
&& isset($this->data['processedTca']['columns'][$field]['config']['dataStructureIdentifier'])
|
||||
) {
|
||||
$config['dataStructureIdentifier'] = $this->data['processedTca']['columns'][$field]['config']['dataStructureIdentifier'];
|
||||
}
|
||||
|
||||
// Hand over original returnUrl to FormInlineAjaxController. Needed if opening for instance a
|
||||
// nested element in a new view to then go back to the original returnUrl and not the url of
|
||||
// the inline ajax controller
|
||||
$config['originalReturnUrl'] = $this->data['returnUrl'];
|
||||
|
||||
// e.g. data[<table>][<uid>][<field>]
|
||||
$nameForm = $inlineStackProcessor->getCurrentStructureFormPrefix();
|
||||
// e.g. data-<pid>-<table1>-<uid1>-<field1>-<table2>-<uid2>-<field2>
|
||||
$nameObject = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']);
|
||||
|
||||
$config['inline']['first'] = false;
|
||||
$firstChild = reset($this->data['parameterArray']['fieldConf']['children']);
|
||||
if (isset($firstChild['databaseRow']['uid'])) {
|
||||
$config['inline']['first'] = $firstChild['databaseRow']['uid'];
|
||||
}
|
||||
$config['inline']['last'] = false;
|
||||
$lastChild = end($this->data['parameterArray']['fieldConf']['children']);
|
||||
if (isset($lastChild['databaseRow']['uid'])) {
|
||||
$config['inline']['last'] = $lastChild['databaseRow']['uid'];
|
||||
}
|
||||
|
||||
$top = $inlineStackProcessor->getStructureLevel(0);
|
||||
|
||||
$this->inlineData['config'][$nameObject] = [
|
||||
'table' => $foreign_table,
|
||||
'md5' => md5($nameObject)
|
||||
];
|
||||
$configJson = json_encode($config);
|
||||
$this->inlineData['config'][$nameObject . '-' . $foreign_table] = [
|
||||
'min' => $config['minitems'],
|
||||
'max' => $config['maxitems'],
|
||||
'sortable' => $config['appearance']['useSortable'],
|
||||
'top' => [
|
||||
'table' => $top['table'],
|
||||
'uid' => $top['uid']
|
||||
],
|
||||
'context' => [
|
||||
'config' => $configJson,
|
||||
'hmac' => GeneralUtility::hmac($configJson, 'InlineContext'),
|
||||
],
|
||||
];
|
||||
$this->inlineData['nested'][$nameObject] = $this->data['tabAndInlineStack'];
|
||||
|
||||
$uniqueMax = 0;
|
||||
$uniqueIds = [];
|
||||
|
||||
if ($config['foreign_unique']) {
|
||||
// Add inlineData['unique'] with JS unique configuration
|
||||
// @todo: Improve validation and throw an exception if type is neither select nor group here
|
||||
$type = $config['selectorOrUniqueConfiguration']['config']['type'] === 'select' ? 'select' : 'groupdb';
|
||||
foreach ($parameterArray['fieldConf']['children'] as $child) {
|
||||
// Determine used unique ids, skip not localized records
|
||||
if (!$child['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
|
||||
$value = $child['databaseRow'][$config['foreign_unique']];
|
||||
// We're assuming there is only one connected value here for both select and group
|
||||
if ($type === 'select') {
|
||||
// A select field is an array of uids. See TcaSelectItems data provider for details.
|
||||
// Pick first entry, ends up as eg. $value = 42.
|
||||
$value = $value['0'];
|
||||
} else {
|
||||
// A group field is an array of arrays containing uid + table + title + row.
|
||||
// See TcaGroup data provider for details.
|
||||
// Pick the first one (always on 0), and use uid + table only. Exclude title + row
|
||||
// since the entire inlineData['unique'] array ends up in JavaScript in the end
|
||||
// and we don't need and want the title and the entire row data in the frontend.
|
||||
// Ends up as $value = [ 'uid' => '42', 'table' => 'tx_my_table' ]
|
||||
$value = [
|
||||
'uid' => $value[0]['uid'],
|
||||
'table' => $value[0]['table'],
|
||||
];
|
||||
}
|
||||
// Note structure of $value is different in select vs. group: It's a uid for select, but an
|
||||
// array with uid + table for group. This is handled differently on JavaScript side, search
|
||||
// for 'groupdb' in jsfunc.inline.js for details.
|
||||
$uniqueIds[$child['databaseRow']['uid']] = $value;
|
||||
}
|
||||
}
|
||||
$possibleRecords = $config['selectorOrUniquePossibleRecords'];
|
||||
$possibleRecordsUidToTitle = [];
|
||||
foreach ($possibleRecords as $possibleRecord) {
|
||||
$possibleRecordsUidToTitle[$possibleRecord[1]] = $possibleRecord[0];
|
||||
}
|
||||
$uniqueMax = $config['appearance']['useCombination'] || empty($possibleRecords) ? -1 : count($possibleRecords);
|
||||
$this->inlineData['unique'][$nameObject . '-' . $foreign_table] = [
|
||||
'max' => $uniqueMax,
|
||||
'used' => $uniqueIds,
|
||||
'type' => $type,
|
||||
'table' => $foreign_table,
|
||||
'elTable' => $config['selectorOrUniqueConfiguration']['foreignTable'],
|
||||
'field' => $config['foreign_unique'],
|
||||
'selector' => $config['selectorOrUniqueConfiguration']['isSelector'] ? $type : false,
|
||||
'possible' => $possibleRecordsUidToTitle,
|
||||
];
|
||||
}
|
||||
|
||||
$resultArray['inlineData'] = $this->inlineData;
|
||||
|
||||
// @todo: It might be a good idea to have something like "isLocalizedRecord" or similar set by a data provider
|
||||
$uidOfDefaultRecord = $row[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']];
|
||||
$isLocalizedParent = $language > 0
|
||||
&& ($uidOfDefaultRecord[0] ?? $uidOfDefaultRecord) > 0
|
||||
&& MathUtility::canBeInterpretedAsInteger($row['uid']);
|
||||
$numberOfFullLocalizedChildren = 0;
|
||||
$numberOfNotYetLocalizedChildren = 0;
|
||||
foreach ($this->data['parameterArray']['fieldConf']['children'] as $child) {
|
||||
if (!$child['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
|
||||
$numberOfFullLocalizedChildren++;
|
||||
}
|
||||
if ($isLocalizedParent && $child['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
|
||||
$numberOfNotYetLocalizedChildren++;
|
||||
}
|
||||
}
|
||||
|
||||
// Render the localization links if needed
|
||||
$localizationLinks = '';
|
||||
if ($numberOfNotYetLocalizedChildren) {
|
||||
// Add the "Localize all records" link before all child records:
|
||||
if (isset($config['appearance']['showAllLocalizationLink']) && $config['appearance']['showAllLocalizationLink']) {
|
||||
$localizationLinks = ' ' . $this->getLevelInteractionLink('localize', $nameObject . '-' . $foreign_table, $config);
|
||||
}
|
||||
// Add the "Synchronize with default language" link before all child records:
|
||||
if (isset($config['appearance']['showSynchronizationLink']) && $config['appearance']['showSynchronizationLink']) {
|
||||
$localizationLinks .= ' ' . $this->getLevelInteractionLink('synchronize', $nameObject . '-' . $foreign_table, $config);
|
||||
}
|
||||
}
|
||||
|
||||
// Define how to show the "Create new record" link - if there are more than maxitems, hide it
|
||||
if ($isReadOnly || $numberOfFullLocalizedChildren >= $config['maxitems'] || ($uniqueMax > 0 && $numberOfFullLocalizedChildren >= $uniqueMax)) {
|
||||
$config['inline']['inlineNewButtonStyle'] = 'display: none;';
|
||||
$config['inline']['inlineNewRelationButtonStyle'] = 'display: none;';
|
||||
$config['inline']['inlineOnlineMediaAddButtonStyle'] = 'display: none;';
|
||||
}
|
||||
|
||||
// Render the level links (create new record):
|
||||
$levelLinks = '';
|
||||
if (!empty($config['appearance']['enabledControls']['new'])) {
|
||||
$levelLinks = $this->getLevelInteractionLink('newRecord', $nameObject . '-' . $foreign_table, $config);
|
||||
}
|
||||
// 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>';
|
||||
}
|
||||
|
||||
// If it's required to select from possible child records (reusable children), add a selector box
|
||||
if (!$isReadOnly && $config['foreign_selector'] && $config['appearance']['showPossibleRecordsSelector'] !== false) {
|
||||
if ($config['selectorOrUniqueConfiguration']['config']['type'] === 'select') {
|
||||
$selectorBox = $this->renderPossibleRecordsSelectorTypeSelect($config, $uniqueIds);
|
||||
} else {
|
||||
$selectorBox = $this->renderPossibleRecordsSelectorTypeGroupDB($config);
|
||||
}
|
||||
$html .= $selectorBox . $localizationLinks;
|
||||
}
|
||||
|
||||
$title = $languageService->sL(trim($parameterArray['fieldConf']['label']));
|
||||
$html .= '<div class="panel-group panel-hover" data-title="' . htmlspecialchars($title) . '" id="' . $nameObject . '_records">';
|
||||
|
||||
$sortableRecordUids = [];
|
||||
foreach ($this->data['parameterArray']['fieldConf']['children'] as $options) {
|
||||
$options['inlineParentUid'] = $row['uid'];
|
||||
$options['inlineFirstPid'] = $this->data['inlineFirstPid'];
|
||||
// @todo: this can be removed if this container no longer sets additional info to $config
|
||||
$options['inlineParentConfig'] = $config;
|
||||
$options['inlineData'] = $this->inlineData;
|
||||
$options['inlineStructure'] = $inlineStackProcessor->getStructure();
|
||||
$options['inlineExpandCollapseStateArray'] = $this->data['inlineExpandCollapseStateArray'];
|
||||
$options['renderType'] = 'inlineRecordContainer';
|
||||
$childResult = $this->nodeFactory->create($options)->render();
|
||||
$html .= $childResult['html'];
|
||||
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $childResult, false);
|
||||
if (!$options['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
|
||||
// Don't add record to list of "valid" uids if it is only the default
|
||||
// language record of a not yet localized child
|
||||
$sortableRecordUids[] = $options['databaseRow']['uid'];
|
||||
}
|
||||
}
|
||||
|
||||
$html .= '</div>';
|
||||
|
||||
$fieldWizardResult = $this->renderFieldWizard();
|
||||
$fieldWizardHtml = $fieldWizardResult['html'];
|
||||
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldWizardResult, false);
|
||||
$html .= $fieldWizardHtml;
|
||||
|
||||
// Add the level links after all child records:
|
||||
if (!$isReadOnly && ($config['appearance']['levelLinksPosition'] === 'both' || $config['appearance']['levelLinksPosition'] === 'bottom')) {
|
||||
$html .= $levelLinks . $localizationLinks;
|
||||
}
|
||||
if (is_array($config['customControls'])) {
|
||||
$html .= '<div id="' . $nameObject . '_customControls">';
|
||||
foreach ($config['customControls'] as $customControlConfig) {
|
||||
if (!isset($customControlConfig['userFunc'])) {
|
||||
trigger_error('Support for customControl without a userFunc key will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
|
||||
$customControlConfig = [
|
||||
'userFunc' => $customControlConfig
|
||||
];
|
||||
}
|
||||
$parameters = [
|
||||
'table' => $table,
|
||||
'field' => $field,
|
||||
'row' => $row,
|
||||
'nameObject' => $nameObject,
|
||||
'nameForm' => $nameForm,
|
||||
'config' => $config,
|
||||
'customControlConfig' => $customControlConfig,
|
||||
];
|
||||
$html .= GeneralUtility::callUserFunction($customControlConfig['userFunc'], $parameters, $this);
|
||||
}
|
||||
$html .= '</div>';
|
||||
}
|
||||
// Add Drag&Drop functions for sorting to FormEngine::$additionalJS_post
|
||||
if (count($sortableRecordUids) > 1 && $config['appearance']['useSortable']) {
|
||||
$resultArray['additionalJavaScriptPost'][] = 'inline.createDragAndDropSorting("' . $nameObject . '_records' . '");';
|
||||
}
|
||||
$resultArray['requireJsModules'] = array_merge($resultArray['requireJsModules'], $this->requireJsModules);
|
||||
|
||||
// Publish the uids of the child records in the given order to the browser
|
||||
$html .= '<input type="hidden" name="' . $nameForm . '" value="' . implode(',', $sortableRecordUids) . '" '
|
||||
. ' data-formengine-validation-rules="' . htmlspecialchars($this->getValidationDataAsJsonString(['type' => 'inline', 'minitems' => $config['minitems'], 'maxitems' => $config['maxitems']])) . '"'
|
||||
. ' class="inlineRecord" />';
|
||||
// Close the wrap for all inline fields (container)
|
||||
$html .= '</div>';
|
||||
|
||||
$resultArray['html'] = $html;
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the HTML code of a general link to be used on a level of inline children.
|
||||
* The possible keys for the parameter $type are 'newRecord', 'localize' and 'synchronize'.
|
||||
*
|
||||
* @param string $type The link type, values are 'newRecord', 'localize' and 'synchronize'.
|
||||
* @param string $objectPrefix The "path" to the child record to create (e.g. 'data-parentPageId-partenTable-parentUid-parentField-childTable]')
|
||||
* @param array $conf TCA configuration of the parent(!) field
|
||||
* @return string The HTML code of the new link, wrapped in a div
|
||||
*/
|
||||
protected function getLevelInteractionLink($type, $objectPrefix, $conf = [])
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
$nameObject = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']);
|
||||
$attributes = [];
|
||||
switch ($type) {
|
||||
case 'newRecord':
|
||||
$title = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.createnew'));
|
||||
$icon = 'actions-add';
|
||||
$className = 'typo3-newRecordLink';
|
||||
$attributes['class'] = 'btn btn-default inlineNewButton ' . $this->inlineData['config'][$nameObject]['md5'];
|
||||
$attributes['onclick'] = 'return inline.createNewRecord(' . GeneralUtility::quoteJSvalue($objectPrefix) . ')';
|
||||
if (!empty($conf['inline']['inlineNewButtonStyle'])) {
|
||||
$attributes['style'] = $conf['inline']['inlineNewButtonStyle'];
|
||||
}
|
||||
if (!empty($conf['appearance']['newRecordLinkAddTitle'])) {
|
||||
$title = htmlspecialchars(sprintf(
|
||||
$languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.createnew.link'),
|
||||
$languageService->sL($GLOBALS['TCA'][$conf['foreign_table']]['ctrl']['title'])
|
||||
));
|
||||
} elseif (isset($conf['appearance']['newRecordLinkTitle']) && $conf['appearance']['newRecordLinkTitle'] !== '') {
|
||||
$title = htmlspecialchars($languageService->sL($conf['appearance']['newRecordLinkTitle']));
|
||||
}
|
||||
break;
|
||||
case 'localize':
|
||||
$title = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:localizeAllRecords'));
|
||||
$icon = 'actions-document-localize';
|
||||
$className = 'typo3-localizationLink';
|
||||
$attributes['class'] = 'btn btn-default';
|
||||
$attributes['onclick'] = 'return inline.synchronizeLocalizeRecords(' . GeneralUtility::quoteJSvalue($objectPrefix) . ', \'localize\')';
|
||||
break;
|
||||
case 'synchronize':
|
||||
$title = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:synchronizeWithOriginalLanguage'));
|
||||
$icon = 'actions-document-synchronize';
|
||||
$className = 'typo3-synchronizationLink';
|
||||
$attributes['class'] = 'btn btn-default inlineNewButton ' . $this->inlineData['config'][$nameObject]['md5'];
|
||||
$attributes['onclick'] = 'return inline.synchronizeLocalizeRecords(' . GeneralUtility::quoteJSvalue($objectPrefix) . ', \'synchronize\')';
|
||||
break;
|
||||
default:
|
||||
$title = '';
|
||||
$icon = '';
|
||||
$className = '';
|
||||
}
|
||||
// Create the link:
|
||||
$icon = $icon ? $this->iconFactory->getIcon($icon, Icon::SIZE_SMALL)->render() : '';
|
||||
$link = $this->wrapWithAnchor($icon . ' ' . $title, '#', $attributes);
|
||||
return '<div' . ($className ? ' class="' . $className . '"' : '') . 'title="' . $title . '">' . $link . '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a text with an anchor and returns the HTML representation.
|
||||
*
|
||||
* @param string $text The text to be wrapped by an anchor
|
||||
* @param string $link The link to be used in the anchor
|
||||
* @param array $attributes Array of attributes to be used in the anchor
|
||||
* @return string The wrapped text as HTML representation
|
||||
*/
|
||||
protected function wrapWithAnchor($text, $link, $attributes = [])
|
||||
{
|
||||
$attributes['href'] = trim($link ?: '#');
|
||||
return '<a ' . GeneralUtility::implodeAttributes($attributes, true, true) . '>' . $text . '</a>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a link that opens an element browser in a new window.
|
||||
* For group/db there is no way to use a "selector" like a <select>|</select>-box.
|
||||
*
|
||||
* @param array $inlineConfiguration TCA inline configuration of the parent(!) field
|
||||
* @return string A HTML link that opens an element browser in a new window
|
||||
*/
|
||||
protected function renderPossibleRecordsSelectorTypeGroupDB(array $inlineConfiguration)
|
||||
{
|
||||
$backendUser = $this->getBackendUserAuthentication();
|
||||
$languageService = $this->getLanguageService();
|
||||
|
||||
$groupFieldConfiguration = $inlineConfiguration['selectorOrUniqueConfiguration']['config'];
|
||||
|
||||
$foreign_table = $inlineConfiguration['foreign_table'];
|
||||
$allowed = $groupFieldConfiguration['allowed'];
|
||||
$currentStructureDomObjectIdPrefix = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']);
|
||||
$objectPrefix = $currentStructureDomObjectIdPrefix . '-' . $foreign_table;
|
||||
$nameObject = $currentStructureDomObjectIdPrefix;
|
||||
$mode = 'db';
|
||||
$showUpload = false;
|
||||
$elementBrowserEnabled = true;
|
||||
if (!empty($inlineConfiguration['appearance']['createNewRelationLinkTitle'])) {
|
||||
$createNewRelationText = htmlspecialchars($languageService->sL($inlineConfiguration['appearance']['createNewRelationLinkTitle']));
|
||||
} else {
|
||||
$createNewRelationText = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.createNewRelation'));
|
||||
}
|
||||
if (is_array($groupFieldConfiguration['appearance'])) {
|
||||
if (isset($groupFieldConfiguration['appearance']['elementBrowserType'])) {
|
||||
$mode = $groupFieldConfiguration['appearance']['elementBrowserType'];
|
||||
}
|
||||
if ($mode === 'file') {
|
||||
$showUpload = true;
|
||||
}
|
||||
if (isset($inlineConfiguration['appearance']['fileUploadAllowed'])) {
|
||||
$showUpload = (bool)$inlineConfiguration['appearance']['fileUploadAllowed'];
|
||||
}
|
||||
if (isset($groupFieldConfiguration['appearance']['elementBrowserAllowed'])) {
|
||||
$allowed = $groupFieldConfiguration['appearance']['elementBrowserAllowed'];
|
||||
}
|
||||
if (isset($inlineConfiguration['appearance']['elementBrowserEnabled'])) {
|
||||
$elementBrowserEnabled = (bool)$inlineConfiguration['appearance']['elementBrowserEnabled'];
|
||||
}
|
||||
}
|
||||
// Remove any white-spaces from the allowed extension lists
|
||||
$allowed = implode(',', GeneralUtility::trimExplode(',', $allowed, true));
|
||||
$browserParams = '|||' . $allowed . '|' . $objectPrefix . '|inline.checkUniqueElement||inline.importElement';
|
||||
$onClick = 'setFormValueOpenBrowser(' . GeneralUtility::quoteJSvalue($mode) . ', ' . GeneralUtility::quoteJSvalue($browserParams) . '); return false;';
|
||||
|
||||
$buttonStyle = '';
|
||||
if (isset($inlineConfiguration['inline']['inlineNewRelationButtonStyle'])) {
|
||||
$buttonStyle = ' style="' . $inlineConfiguration['inline']['inlineNewRelationButtonStyle'] . '"';
|
||||
}
|
||||
$item = '';
|
||||
if ($elementBrowserEnabled) {
|
||||
$item .= '
|
||||
<a href="#" class="btn btn-default inlineNewRelationButton ' . $this->inlineData['config'][$nameObject]['md5'] . '"
|
||||
' . $buttonStyle . ' onclick="' . htmlspecialchars($onClick) . '" title="' . $createNewRelationText . '">
|
||||
' . $this->iconFactory->getIcon('actions-insert-record', Icon::SIZE_SMALL)->render() . '
|
||||
' . $createNewRelationText . '
|
||||
</a>';
|
||||
}
|
||||
|
||||
$isDirectFileUploadEnabled = (bool)$backendUser->uc['edit_docModuleUpload'];
|
||||
$allowedArray = GeneralUtility::trimExplode(',', $allowed, true);
|
||||
$onlineMediaAllowed = OnlineMediaHelperRegistry::getInstance()->getSupportedFileExtensions();
|
||||
if (!empty($allowedArray)) {
|
||||
$onlineMediaAllowed = array_intersect($allowedArray, $onlineMediaAllowed);
|
||||
}
|
||||
if ($showUpload && $isDirectFileUploadEnabled) {
|
||||
$folder = $backendUser->getDefaultUploadFolder(
|
||||
$this->data['tableName'] === 'pages' ? $this->data['vanillaUid'] : $this->data['parentPageRow']['uid'],
|
||||
$this->data['tableName'],
|
||||
$this->data['fieldName']
|
||||
);
|
||||
if (
|
||||
$folder instanceof Folder
|
||||
&& $folder->getStorage()->checkUserActionPermission('add', 'File')
|
||||
) {
|
||||
$maxFileSize = GeneralUtility::getMaxUploadFileSize() * 1024;
|
||||
$item .= ' <a href="#" class="btn btn-default t3js-drag-uploader inlineNewFileUploadButton ' . $this->inlineData['config'][$nameObject]['md5'] . '"
|
||||
' . $buttonStyle . '
|
||||
data-dropzone-target="#' . htmlspecialchars(StringUtility::escapeCssSelector($currentStructureDomObjectIdPrefix)) . '"
|
||||
data-insert-dropzone-before="1"
|
||||
data-file-irre-object="' . htmlspecialchars($objectPrefix) . '"
|
||||
data-file-allowed="' . htmlspecialchars($allowed) . '"
|
||||
data-target-folder="' . htmlspecialchars($folder->getCombinedIdentifier()) . '"
|
||||
data-max-file-size="' . htmlspecialchars($maxFileSize) . '"
|
||||
>';
|
||||
$item .= $this->iconFactory->getIcon('actions-upload', Icon::SIZE_SMALL)->render() . ' ';
|
||||
$item .= htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:file_upload.select-and-submit'));
|
||||
$item .= '</a>';
|
||||
|
||||
$this->requireJsModules[] = ['TYPO3/CMS/Backend/DragUploader' => 'function(dragUploader){dragUploader.initialize()}'];
|
||||
if (!empty($onlineMediaAllowed)) {
|
||||
$buttonStyle = '';
|
||||
if (isset($inlineConfiguration['inline']['inlineOnlineMediaAddButtonStyle'])) {
|
||||
$buttonStyle = ' style="' . $inlineConfiguration['inline']['inlineOnlineMediaAddButtonStyle'] . '"';
|
||||
}
|
||||
$this->requireJsModules[] = 'TYPO3/CMS/Backend/OnlineMedia';
|
||||
$buttonText = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:online_media.new_media.button'));
|
||||
$placeholder = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:online_media.new_media.placeholder'));
|
||||
$buttonSubmit = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:online_media.new_media.submit'));
|
||||
$allowedMediaUrl = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.allowEmbedSources'));
|
||||
$item .= '
|
||||
<span class="btn btn-default t3js-online-media-add-btn ' . $this->inlineData['config'][$nameObject]['md5'] . '"
|
||||
' . $buttonStyle . '
|
||||
data-file-irre-object="' . htmlspecialchars($objectPrefix) . '"
|
||||
data-online-media-allowed="' . htmlspecialchars(implode(',', $onlineMediaAllowed)) . '"
|
||||
data-online-media-allowed-help-text="' . $allowedMediaUrl . '"
|
||||
data-target-folder="' . htmlspecialchars($folder->getCombinedIdentifier()) . '"
|
||||
title="' . $buttonText . '"
|
||||
data-btn-submit="' . $buttonSubmit . '"
|
||||
data-placeholder="' . $placeholder . '"
|
||||
>
|
||||
' . $this->iconFactory->getIcon('actions-online-media-add', Icon::SIZE_SMALL)->render() . '
|
||||
' . $buttonText . '</span>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$item = '<div class="form-control-wrap">' . $item . '</div>';
|
||||
$allowedList = '';
|
||||
$allowedLabelKey = ($mode === 'file') ? 'allowedFileExtensions' : 'allowedRelations';
|
||||
$allowedLabel = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.' . $allowedLabelKey));
|
||||
foreach ($allowedArray as $allowedItem) {
|
||||
$allowedList .= '<span class="label label-success">' . strtoupper($allowedItem) . '</span> ';
|
||||
}
|
||||
if (!empty($allowedList)) {
|
||||
$item .= '<div class="help-block">' . $allowedLabel . '<br>' . $allowedList . '</div>';
|
||||
}
|
||||
$item = '<div class="form-group t3js-formengine-validation-marker">' . $item . '</div>';
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a selector as used for the select type, to select from all available
|
||||
* records and to create a relation to the embedding record (e.g. like MM).
|
||||
*
|
||||
* @param array $config TCA inline configuration of the parent(!) field
|
||||
* @param array $uniqueIds The uids that have already been used and should be unique
|
||||
* @return string A HTML <select> box with all possible records
|
||||
*/
|
||||
protected function renderPossibleRecordsSelectorTypeSelect(array $config, array $uniqueIds)
|
||||
{
|
||||
$possibleRecords = $config['selectorOrUniquePossibleRecords'];
|
||||
$nameObject = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']);
|
||||
// Create option tags:
|
||||
$opt = [];
|
||||
foreach ($possibleRecords as $p) {
|
||||
if (!in_array($p[1], $uniqueIds)) {
|
||||
$opt[] = '<option value="' . htmlspecialchars($p[1]) . '">' . htmlspecialchars($p[0]) . '</option>';
|
||||
}
|
||||
}
|
||||
// Put together the selector box:
|
||||
$size = (int)$config['size'];
|
||||
$size = $config['autoSizeMax'] ? MathUtility::forceIntegerInRange(count($possibleRecords) + 1, MathUtility::forceIntegerInRange($size, 1), $config['autoSizeMax']) : $size;
|
||||
$onChange = 'return inline.importNewRecord(' . GeneralUtility::quoteJSvalue($nameObject . '-' . $config['foreign_table']) . ')';
|
||||
$item = '
|
||||
<select id="' . $nameObject . '-' . $config['foreign_table'] . '_selector" class="form-control"' . ($size ? ' size="' . $size . '"' : '')
|
||||
. ' onchange="' . htmlspecialchars($onChange) . '"' . ($config['foreign_unique'] ? ' isunique="isunique"' : '') . '>
|
||||
' . implode('', $opt) . '
|
||||
</select>';
|
||||
|
||||
if ($size <= 1) {
|
||||
// Add a "Create new relation" link for adding new relations
|
||||
// This is necessary, if the size of the selector is "1" or if
|
||||
// there is only one record item in the select-box, that is selected by default
|
||||
// The selector-box creates a new relation on using an onChange event (see some line above)
|
||||
if (!empty($config['appearance']['createNewRelationLinkTitle'])) {
|
||||
$createNewRelationText = htmlspecialchars($this->getLanguageService()->sL($config['appearance']['createNewRelationLinkTitle']));
|
||||
} else {
|
||||
$createNewRelationText = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.createNewRelation'));
|
||||
}
|
||||
$item .= '
|
||||
<span class="input-group-btn">
|
||||
<a href="#" class="btn btn-default" onclick="' . htmlspecialchars($onChange) . '" title="' . $createNewRelationText . '">
|
||||
' . $this->iconFactory->getIcon('actions-add', Icon::SIZE_SMALL)->render() . $createNewRelationText . '
|
||||
</a>
|
||||
</span>';
|
||||
} else {
|
||||
$item .= '
|
||||
<span class="input-group-btn btn"></span>';
|
||||
}
|
||||
|
||||
// Wrap the selector and add a spacer to the bottom
|
||||
$item = '<div class="input-group form-group t3js-formengine-validation-marker ' . $this->inlineData['config'][$nameObject]['md5'] . '">' . $item . '</div>';
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts FlexForm parts of a form element name like
|
||||
* data[table][uid][field][sDEF][lDEF][FlexForm][vDEF]
|
||||
* Helper method used in inline
|
||||
*
|
||||
* @param string $formElementName The form element name
|
||||
* @return array|null
|
||||
*/
|
||||
protected function extractFlexFormParts($formElementName)
|
||||
{
|
||||
$flexFormParts = null;
|
||||
$matches = [];
|
||||
if (preg_match('#^data(?:\[[^]]+\]){3}(\[data\](?:\[[^]]+\]){4,})$#', $formElementName, $matches)) {
|
||||
$flexFormParts = GeneralUtility::trimExplode(
|
||||
'][',
|
||||
trim($matches[1], '[]')
|
||||
);
|
||||
}
|
||||
return $flexFormParts;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUserAuthentication()
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService()
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,674 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\Container;
|
||||
|
||||
/*
|
||||
* 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\Element\InlineElementHookInterface;
|
||||
use TYPO3\CMS\Backend\Form\Exception\AccessDeniedContentEditException;
|
||||
use TYPO3\CMS\Backend\Form\InlineStackProcessor;
|
||||
use TYPO3\CMS\Backend\Form\NodeFactory;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Imaging\Icon;
|
||||
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Resource\ProcessedFile;
|
||||
use TYPO3\CMS\Core\Resource\ResourceFactory;
|
||||
use TYPO3\CMS\Core\Type\Bitmask\Permission;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
|
||||
/**
|
||||
* Render a single inline record relation.
|
||||
*
|
||||
* This container is called by InlineControlContainer to render single existing records.
|
||||
* Furthermore it is called by FormEngine for an incoming ajax request to expand an existing record
|
||||
* or to create a new one.
|
||||
*
|
||||
* This container creates the outer HTML of single inline records - eg. drag and drop and delete buttons.
|
||||
* For rendering of the record itself processing is handed over to FullRecordContainer.
|
||||
*/
|
||||
class InlineRecordContainer extends AbstractContainer
|
||||
{
|
||||
/**
|
||||
* Inline data array used for JSON output
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $inlineData = [];
|
||||
|
||||
/**
|
||||
* @var InlineStackProcessor
|
||||
*/
|
||||
protected $inlineStackProcessor;
|
||||
|
||||
/**
|
||||
* Array containing instances of hook classes called once for IRRE objects
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $hookObjects = [];
|
||||
|
||||
/**
|
||||
* @var IconFactory
|
||||
*/
|
||||
protected $iconFactory;
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*
|
||||
* @param NodeFactory $nodeFactory
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct(NodeFactory $nodeFactory, array $data)
|
||||
{
|
||||
parent::__construct($nodeFactory, $data);
|
||||
$this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
|
||||
$this->inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
|
||||
$this->initHookObjects();
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry method
|
||||
*
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
* @throws AccessDeniedContentEditException
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$data = $this->data;
|
||||
$this->inlineData = $data['inlineData'];
|
||||
|
||||
$inlineStackProcessor = $this->inlineStackProcessor;
|
||||
$inlineStackProcessor->initializeByGivenStructure($data['inlineStructure']);
|
||||
|
||||
$record = $data['databaseRow'];
|
||||
$inlineConfig = $data['inlineParentConfig'];
|
||||
$foreignTable = $inlineConfig['foreign_table'];
|
||||
|
||||
$resultArray = $this->initializeResultArray();
|
||||
|
||||
// Send a mapping information to the browser via JSON:
|
||||
// e.g. data[<curTable>][<curId>][<curField>] => data-<pid>-<parentTable>-<parentId>-<parentField>-<curTable>-<curId>-<curField>
|
||||
$formPrefix = $inlineStackProcessor->getCurrentStructureFormPrefix();
|
||||
$domObjectId = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($data['inlineFirstPid']);
|
||||
$this->inlineData['map'][$formPrefix] = $domObjectId;
|
||||
|
||||
$resultArray['inlineData'] = $this->inlineData;
|
||||
|
||||
// If there is a selector field, normalize it:
|
||||
if (!empty($inlineConfig['foreign_selector'])) {
|
||||
$foreign_selector = $inlineConfig['foreign_selector'];
|
||||
$valueToNormalize = $record[$foreign_selector];
|
||||
if (is_array($record[$foreign_selector])) {
|
||||
// @todo: this can be kicked again if always prepared rows are handled here
|
||||
$valueToNormalize = implode(',', $record[$foreign_selector]);
|
||||
}
|
||||
$record[$foreign_selector] = $this->normalizeUid($valueToNormalize);
|
||||
}
|
||||
|
||||
// Get the current naming scheme for DOM name/id attributes:
|
||||
$appendFormFieldNames = '[' . $foreignTable . '][' . $record['uid'] . ']';
|
||||
$objectId = $domObjectId . '-' . $foreignTable . '-' . $record['uid'];
|
||||
$class = '';
|
||||
$html = '';
|
||||
$combinationHtml = '';
|
||||
$isNewRecord = $data['command'] === 'new';
|
||||
$hiddenField = '';
|
||||
if (isset($data['processedTca']['ctrl']['enablecolumns']['disabled'])) {
|
||||
$hiddenField = $data['processedTca']['ctrl']['enablecolumns']['disabled'];
|
||||
}
|
||||
if (!$data['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
|
||||
if ($isNewRecord || $data['isInlineChildExpanded']) {
|
||||
// Render full content ONLY IF this is an AJAX request, a new record, or the record is not collapsed
|
||||
$combinationHtml = '';
|
||||
if (isset($data['combinationChild'])) {
|
||||
$combinationChild = $this->renderCombinationChild($data, $appendFormFieldNames);
|
||||
$combinationHtml = $combinationChild['html'];
|
||||
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $combinationChild, false);
|
||||
}
|
||||
$childArray = $this->renderChild($data);
|
||||
$html = $childArray['html'];
|
||||
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $childArray, false);
|
||||
} else {
|
||||
// This string is the marker for the JS-function to check if the full content has already been loaded
|
||||
$html = '<!--notloaded-->';
|
||||
}
|
||||
if ($isNewRecord) {
|
||||
// Add pid of record as hidden field
|
||||
$html .= '<input type="hidden" name="data' . htmlspecialchars($appendFormFieldNames)
|
||||
. '[pid]" value="' . htmlspecialchars($record['pid']) . '"/>';
|
||||
// Tell DataHandler this record is expanded
|
||||
$ucFieldName = 'uc[inlineView]'
|
||||
. '[' . $data['inlineTopMostParentTableName'] . ']'
|
||||
. '[' . $data['inlineTopMostParentUid'] . ']'
|
||||
. $appendFormFieldNames;
|
||||
$html .= '<input type="hidden" name="' . htmlspecialchars($ucFieldName)
|
||||
. '" value="' . (int)$data['isInlineChildExpanded'] . '" />';
|
||||
} else {
|
||||
// Set additional field for processing for saving
|
||||
$html .= '<input type="hidden" name="cmd' . htmlspecialchars($appendFormFieldNames)
|
||||
. '[delete]" value="1" disabled="disabled" />';
|
||||
if (!$data['isInlineChildExpanded'] && !empty($hiddenField)) {
|
||||
$checked = !empty($record[$hiddenField]) ? ' checked="checked"' : '';
|
||||
$html .= '<input type="checkbox" data-formengine-input-name="data'
|
||||
. htmlspecialchars($appendFormFieldNames)
|
||||
. '[' . htmlspecialchars($hiddenField) . ']" value="1"' . $checked . ' />';
|
||||
$html .= '<input type="input" name="data' . htmlspecialchars($appendFormFieldNames)
|
||||
. '[' . htmlspecialchars($hiddenField) . ']" value="' . htmlspecialchars($record[$hiddenField]) . '" />';
|
||||
}
|
||||
}
|
||||
// If this record should be shown collapsed
|
||||
$class = $data['isInlineChildExpanded'] ? 'panel-visible' : 'panel-collapsed';
|
||||
}
|
||||
$hiddenFieldHtml = implode(LF, $resultArray['additionalHiddenFields'] ?? []);
|
||||
|
||||
if ($inlineConfig['renderFieldsOnly']) {
|
||||
// Render "body" part only
|
||||
$html = $html . $combinationHtml;
|
||||
} else {
|
||||
// Render header row and content (if expanded)
|
||||
if ($data['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
|
||||
$class .= ' t3-form-field-container-inline-placeHolder';
|
||||
}
|
||||
if (!empty($hiddenField) && isset($record[$hiddenField]) && (int)$record[$hiddenField]) {
|
||||
$class .= ' t3-form-field-container-inline-hidden';
|
||||
}
|
||||
$class .= ($isNewRecord ? ' inlineIsNewRecord' : '');
|
||||
$tableUniqueOriginalValue = '';
|
||||
if (isset($data['inlineData']['unique'][$domObjectId . '-' . $foreignTable]['used'][$record['uid']])) {
|
||||
$uniqueValueValues = $data['inlineData']['unique'][$domObjectId . '-' . $foreignTable]['used'][$record['uid']];
|
||||
// in case of site_language we don't have the full form engine options, so fallbacks need to be taken into account
|
||||
$tableUniqueOriginalValue = ($uniqueValueValues['table'] ?? $foreignTable) . '_' . ($uniqueValueValues['uid'] ?? $uniqueValueValues);
|
||||
}
|
||||
$html = '
|
||||
<div class="panel panel-default panel-condensed ' . trim($class) . '" id="' . htmlspecialchars($objectId) . '_div" data-table-unique-original-value="' . htmlspecialchars($tableUniqueOriginalValue) . '">
|
||||
<div class="panel-heading" data-toggle="formengine-inline" id="' . htmlspecialchars($objectId) . '_header" data-expandSingle="' . ($inlineConfig['appearance']['expandSingle'] ? 1 : 0) . '">
|
||||
<div class="form-irre-header">
|
||||
<div class="form-irre-header-cell form-irre-header-icon">
|
||||
<span class="caret"></span>
|
||||
</div>
|
||||
' . $this->renderForeignRecordHeader($data) . '
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-collapse" id="' . htmlspecialchars($objectId) . '_fields">' . $html . $hiddenFieldHtml . $combinationHtml . '</div>
|
||||
</div>';
|
||||
}
|
||||
|
||||
$resultArray['html'] = $html;
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render inner child
|
||||
*
|
||||
* @param array $data
|
||||
* @return array Result array
|
||||
*/
|
||||
protected function renderChild(array $data)
|
||||
{
|
||||
$domObjectId = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($data['inlineFirstPid']);
|
||||
$data['tabAndInlineStack'][] = [
|
||||
'inline',
|
||||
$domObjectId . '-' . $data['tableName'] . '-' . $data['databaseRow']['uid'],
|
||||
];
|
||||
// @todo: ugly construct ...
|
||||
$data['inlineData'] = $this->inlineData;
|
||||
$data['renderType'] = 'fullRecordContainer';
|
||||
return $this->nodeFactory->create($data)->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render child child
|
||||
*
|
||||
* Render a table with FormEngine, that occurs on an intermediate table but should be editable directly,
|
||||
* so two tables are combined (the intermediate table with attributes and the sub-embedded table).
|
||||
* -> This is a direct embedding over two levels!
|
||||
*
|
||||
* @param array $data
|
||||
* @param string $appendFormFieldNames The [<table>][<uid>] of the parent record (the intermediate table)
|
||||
* @return array Result array
|
||||
*/
|
||||
protected function renderCombinationChild(array $data, $appendFormFieldNames)
|
||||
{
|
||||
$childData = $data['combinationChild'];
|
||||
$parentConfig = $data['inlineParentConfig'];
|
||||
|
||||
// If field is set to readOnly, set all fields of the relation to readOnly as well
|
||||
if (isset($parentConfig['readOnly']) && $parentConfig['readOnly']) {
|
||||
foreach ($childData['processedTca']['columns'] as $columnName => $columnConfiguration) {
|
||||
$childData['processedTca']['columns'][$columnName]['config']['readOnly'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$resultArray = $this->initializeResultArray();
|
||||
|
||||
// Display Warning FlashMessage if it is not suppressed
|
||||
if (!isset($parentConfig['appearance']['suppressCombinationWarning']) || empty($parentConfig['appearance']['suppressCombinationWarning'])) {
|
||||
$combinationWarningMessage = 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.inline_use_combination';
|
||||
if (!empty($parentConfig['appearance']['overwriteCombinationWarningMessage'])) {
|
||||
$combinationWarningMessage = $parentConfig['appearance']['overwriteCombinationWarningMessage'];
|
||||
}
|
||||
$message = $this->getLanguageService()->sL($combinationWarningMessage);
|
||||
$markup = [];
|
||||
// @TODO: This is not a FlashMessage! The markup must be changed and special CSS
|
||||
// @TODO: should be created, in order to prevent confusion.
|
||||
$markup[] = '<div class="alert alert-warning">';
|
||||
$markup[] = ' <div class="media">';
|
||||
$markup[] = ' <div class="media-left">';
|
||||
$markup[] = ' <span class="fa-stack fa-lg">';
|
||||
$markup[] = ' <i class="fa fa-circle fa-stack-2x"></i>';
|
||||
$markup[] = ' <i class="fa fa-exclamation fa-stack-1x"></i>';
|
||||
$markup[] = ' </span>';
|
||||
$markup[] = ' </div>';
|
||||
$markup[] = ' <div class="media-body">';
|
||||
$markup[] = ' <div class="alert-message">' . htmlspecialchars($message) . '</div>';
|
||||
$markup[] = ' </div>';
|
||||
$markup[] = ' </div>';
|
||||
$markup[] = '</div>';
|
||||
$resultArray['html'] = implode(LF, $markup);
|
||||
}
|
||||
|
||||
$childArray = $this->renderChild($childData);
|
||||
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $childArray);
|
||||
|
||||
// If this is a new record, add a pid value to store this record and the pointer value for the intermediate table
|
||||
if ($childData['command'] === 'new') {
|
||||
$comboFormFieldName = 'data[' . $childData['tableName'] . '][' . $childData['databaseRow']['uid'] . '][pid]';
|
||||
$resultArray['html'] .= '<input type="hidden" name="' . htmlspecialchars($comboFormFieldName) . '" value="' . htmlspecialchars($childData['databaseRow']['pid']) . '" />';
|
||||
}
|
||||
// If the foreign_selector field is also responsible for uniqueness, tell the browser the uid of the "other" side of the relation
|
||||
if ($childData['command'] === 'new' || $parentConfig['foreign_unique'] === $parentConfig['foreign_selector']) {
|
||||
$parentFormFieldName = 'data' . $appendFormFieldNames . '[' . $parentConfig['foreign_selector'] . ']';
|
||||
$resultArray['html'] .= '<input type="hidden" name="' . htmlspecialchars($parentFormFieldName) . '" value="' . htmlspecialchars($childData['databaseRow']['uid']) . '" />';
|
||||
}
|
||||
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the HTML header for a foreign record, such as the title, toggle-function, drag'n'drop, etc.
|
||||
* Later on the command-icons are inserted here.
|
||||
*
|
||||
* @param array $data Current data
|
||||
* @return string The HTML code of the header
|
||||
*/
|
||||
protected function renderForeignRecordHeader(array $data)
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
$inlineConfig = $data['inlineParentConfig'];
|
||||
$foreignTable = $inlineConfig['foreign_table'];
|
||||
$rec = $data['databaseRow'];
|
||||
// Init:
|
||||
$domObjectId = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($data['inlineFirstPid']);
|
||||
$objectId = $domObjectId . '-' . $foreignTable . '-' . $rec['uid'];
|
||||
|
||||
$recordTitle = $data['recordTitle'];
|
||||
if (!empty($recordTitle)) {
|
||||
// The user function may return HTML, therefore we can't escape it
|
||||
if (empty($data['processedTca']['ctrl']['formattedLabel_userFunc'])) {
|
||||
$recordTitle = BackendUtility::getRecordTitlePrep($recordTitle);
|
||||
}
|
||||
} else {
|
||||
$recordTitle = '<em>[' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.no_title')) . ']</em>';
|
||||
}
|
||||
|
||||
$altText = BackendUtility::getRecordIconAltText($rec, $foreignTable);
|
||||
|
||||
$iconImg = '<span title="' . $altText . '" id="' . htmlspecialchars($objectId) . '_icon' . '">' . $this->iconFactory->getIconForRecord($foreignTable, $rec, Icon::SIZE_SMALL)->render() . '</span>';
|
||||
$label = '<span id="' . $objectId . '_label">' . $recordTitle . '</span>';
|
||||
$ctrl = $this->renderForeignRecordHeaderControl($data);
|
||||
$thumbnail = false;
|
||||
|
||||
// Renders a thumbnail for the header
|
||||
if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['thumbnails'] && !empty($inlineConfig['appearance']['headerThumbnail']['field'])) {
|
||||
$fieldValue = $rec[$inlineConfig['appearance']['headerThumbnail']['field']];
|
||||
$fileUid = $fieldValue[0]['uid'];
|
||||
|
||||
if (!empty($fileUid)) {
|
||||
try {
|
||||
$fileObject = ResourceFactory::getInstance()->getFileObject($fileUid);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
$fileObject = null;
|
||||
}
|
||||
if ($fileObject && $fileObject->isMissing()) {
|
||||
$thumbnail .= '<span class="label label-danger">'
|
||||
. htmlspecialchars(static::getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.file_missing'))
|
||||
. '</span> ' . htmlspecialchars($fileObject->getName()) . '<br />';
|
||||
} elseif ($fileObject) {
|
||||
$imageSetup = $inlineConfig['appearance']['headerThumbnail'];
|
||||
unset($imageSetup['field']);
|
||||
if (!empty($rec['crop'])) {
|
||||
$imageSetup['crop'] = $rec['crop'];
|
||||
}
|
||||
$imageSetup = array_merge(['width' => '45', 'height' => '45c'], $imageSetup);
|
||||
|
||||
if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['thumbnails']
|
||||
&& GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'], $fileObject->getExtension())) {
|
||||
$processedImage = $fileObject->process(ProcessedFile::CONTEXT_IMAGEPREVIEW, $imageSetup);
|
||||
// Only use a thumbnail if the processing process was successful by checking if image width is set
|
||||
if ($processedImage->getProperty('width')) {
|
||||
$imageUrl = $processedImage->getPublicUrl(true);
|
||||
$thumbnail = '<img src="' . $imageUrl . '" ' .
|
||||
'width="' . $processedImage->getProperty('width') . '" ' .
|
||||
'height="' . $processedImage->getProperty('height') . '" ' .
|
||||
'alt="' . htmlspecialchars($altText) . '" ' .
|
||||
'title="' . htmlspecialchars($altText) . '">';
|
||||
}
|
||||
} else {
|
||||
$thumbnail = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($inlineConfig['appearance']['headerThumbnail']['field']) && $thumbnail) {
|
||||
$mediaContainer = '<div class="form-irre-header-cell form-irre-header-thumbnail" id="' . $objectId . '_thumbnailcontainer">' . $thumbnail . '</div>';
|
||||
} else {
|
||||
$mediaContainer = '<div class="form-irre-header-cell form-irre-header-icon" id="' . $objectId . '_iconcontainer">' . $iconImg . '</div>';
|
||||
}
|
||||
$header = $mediaContainer . '
|
||||
<div class="form-irre-header-cell form-irre-header-body">' . $label . '</div>
|
||||
<div class="form-irre-header-cell form-irre-header-control t3js-formengine-irre-control">' . $ctrl . '</div>';
|
||||
|
||||
return $header;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the control-icons for a record header (create new, sorting, delete, disable/enable).
|
||||
* Most of the parts are copy&paste from TYPO3\CMS\Recordlist\RecordList\DatabaseRecordList and
|
||||
* modified for the JavaScript calls here
|
||||
*
|
||||
* @param array $data Current data
|
||||
* @return string The HTML code with the control-icons
|
||||
*/
|
||||
protected function renderForeignRecordHeaderControl(array $data)
|
||||
{
|
||||
$rec = $data['databaseRow'];
|
||||
$inlineConfig = $data['inlineParentConfig'];
|
||||
$foreignTable = $inlineConfig['foreign_table'];
|
||||
$languageService = $this->getLanguageService();
|
||||
$backendUser = $this->getBackendUserAuthentication();
|
||||
// Initialize:
|
||||
$cells = [
|
||||
'edit' => '',
|
||||
'hide' => '',
|
||||
'delete' => '',
|
||||
'info' => '',
|
||||
'new' => '',
|
||||
'sort.up' => '',
|
||||
'sort.down' => '',
|
||||
'dragdrop' => '',
|
||||
'localize' => '',
|
||||
'locked' => '',
|
||||
];
|
||||
$isNewItem = strpos($rec['uid'], 'NEW') === 0;
|
||||
$isParentReadOnly = isset($inlineConfig['readOnly']) && $inlineConfig['readOnly'];
|
||||
$isParentExisting = MathUtility::canBeInterpretedAsInteger($data['inlineParentUid']);
|
||||
$tcaTableCtrl = $GLOBALS['TCA'][$foreignTable]['ctrl'];
|
||||
$tcaTableCols = $GLOBALS['TCA'][$foreignTable]['columns'];
|
||||
$isPagesTable = $foreignTable === 'pages';
|
||||
$isSysFileReferenceTable = $foreignTable === 'sys_file_reference';
|
||||
$enableManualSorting = $tcaTableCtrl['sortby'] || $inlineConfig['MM'] || !$data['isOnSymmetricSide']
|
||||
&& $inlineConfig['foreign_sortby'] || $data['isOnSymmetricSide'] && $inlineConfig['symmetric_sortby'];
|
||||
$nameObject = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($data['inlineFirstPid']);
|
||||
$nameObjectFt = $nameObject . '-' . $foreignTable;
|
||||
$nameObjectFtId = $nameObjectFt . '-' . $rec['uid'];
|
||||
$calcPerms = $backendUser->calcPerms(BackendUtility::readPageAccess($rec['pid'], $backendUser->getPagePermsClause(Permission::PAGE_SHOW)));
|
||||
// If the listed table is 'pages' we have to request the permission settings for each page:
|
||||
$localCalcPerms = false;
|
||||
if ($isPagesTable) {
|
||||
$localCalcPerms = $backendUser->calcPerms(BackendUtility::getRecord('pages', $rec['uid']));
|
||||
}
|
||||
// This expresses the edit permissions for this particular element:
|
||||
$permsEdit = $isPagesTable && $localCalcPerms & Permission::PAGE_EDIT || !$isPagesTable && $calcPerms & Permission::CONTENT_EDIT;
|
||||
// Controls: Defines which controls should be shown
|
||||
$enabledControls = $inlineConfig['appearance']['enabledControls'];
|
||||
// Hook: Can disable/enable single controls for specific child records:
|
||||
foreach ($this->hookObjects as $hookObj) {
|
||||
/** @var InlineElementHookInterface $hookObj */
|
||||
$hookObj->renderForeignRecordHeaderControl_preProcess($data['inlineParentUid'], $foreignTable, $rec, $inlineConfig, $data['isInlineDefaultLanguageRecordInLocalizedParentContext'], $enabledControls);
|
||||
}
|
||||
if ($data['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
|
||||
$cells['localize'] = '<span title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:localize.isLocalizable')) . '">
|
||||
' . $this->iconFactory->getIcon('actions-edit-localize-status-low', Icon::SIZE_SMALL)->render() . '
|
||||
</span>';
|
||||
}
|
||||
// "Info": (All records)
|
||||
// @todo: hardcoded sys_file!
|
||||
if ($rec['table_local'] === 'sys_file') {
|
||||
$uid = $rec['uid_local'][0]['uid'];
|
||||
$table = '_FILE';
|
||||
} else {
|
||||
$uid = $rec['uid'];
|
||||
$table = $foreignTable;
|
||||
}
|
||||
if ($enabledControls['info']) {
|
||||
if ($isNewItem) {
|
||||
$cells['info'] = '<span class="btn btn-default disabled">' . $this->iconFactory->getIcon('empty-empty', Icon::SIZE_SMALL)->render() . '</span>';
|
||||
} else {
|
||||
$cells['info'] = '
|
||||
<a class="btn btn-default" href="#" onclick="' . htmlspecialchars('top.TYPO3.InfoWindow.showItem(' . GeneralUtility::quoteJSvalue($table) . ', ' . GeneralUtility::quoteJSvalue($uid) . '); return false;') . '" title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:showInfo')) . '">
|
||||
' . $this->iconFactory->getIcon('actions-document-info', Icon::SIZE_SMALL)->render() . '
|
||||
</a>';
|
||||
}
|
||||
}
|
||||
// If the table is NOT a read-only table, then show these links:
|
||||
if (!$isParentReadOnly && !$tcaTableCtrl['readOnly'] && !$data['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
|
||||
// "New record after" link (ONLY if the records in the table are sorted by a "sortby"-row or if default values can depend on previous record):
|
||||
if ($enabledControls['new'] && ($enableManualSorting || $tcaTableCtrl['useColumnsForDefaultValues'])) {
|
||||
if (!$isPagesTable && $calcPerms & Permission::CONTENT_EDIT || $isPagesTable && $calcPerms & Permission::PAGE_NEW) {
|
||||
$onClick = 'return inline.createNewRecord(' . GeneralUtility::quoteJSvalue($nameObjectFt) . ',' . GeneralUtility::quoteJSvalue($rec['uid']) . ')';
|
||||
$style = '';
|
||||
if ($inlineConfig['inline']['inlineNewButtonStyle']) {
|
||||
$style = ' style="' . $inlineConfig['inline']['inlineNewButtonStyle'] . '"';
|
||||
}
|
||||
$cells['new'] = '
|
||||
<a class="btn btn-default inlineNewButton ' . $this->inlineData['config'][$nameObject]['md5'] . '" href="#" onclick="' . htmlspecialchars($onClick) . '" title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:new' . ($isPagesTable ? 'Page' : 'Record'))) . '" ' . $style . '>
|
||||
' . $this->iconFactory->getIcon('actions-' . ($isPagesTable ? 'page-new' : 'add'), Icon::SIZE_SMALL)->render() . '
|
||||
</a>';
|
||||
}
|
||||
}
|
||||
// "Up/Down" links
|
||||
if ($enabledControls['sort'] && $permsEdit && $enableManualSorting) {
|
||||
// Up
|
||||
$onClick = 'return inline.changeSorting(' . GeneralUtility::quoteJSvalue($nameObjectFtId) . ', \'1\')';
|
||||
$icon = 'actions-move-up';
|
||||
$class = '';
|
||||
if ($inlineConfig['inline']['first'] == $rec['uid']) {
|
||||
$class = ' disabled';
|
||||
$icon = 'empty-empty';
|
||||
}
|
||||
$cells['sort.up'] = '
|
||||
<a class="btn btn-default sortingUp' . $class . '" href="#" onclick="' . htmlspecialchars($onClick) . '" title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:moveUp')) . '">
|
||||
' . $this->iconFactory->getIcon($icon, Icon::SIZE_SMALL)->render() . '
|
||||
</a>';
|
||||
// Down
|
||||
$onClick = 'return inline.changeSorting(' . GeneralUtility::quoteJSvalue($nameObjectFtId) . ', \'-1\')';
|
||||
$icon = 'actions-move-down';
|
||||
$class = '';
|
||||
if ($inlineConfig['inline']['last'] == $rec['uid']) {
|
||||
$class = ' disabled';
|
||||
$icon = 'empty-empty';
|
||||
}
|
||||
|
||||
$cells['sort.down'] = '
|
||||
<a class="btn btn-default sortingDown' . $class . '" href="#" onclick="' . htmlspecialchars($onClick) . '" title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:moveDown')) . '">
|
||||
' . $this->iconFactory->getIcon($icon, Icon::SIZE_SMALL)->render() . '
|
||||
</a>';
|
||||
}
|
||||
// "Edit" link:
|
||||
if (($rec['table_local'] === 'sys_file') && !$isNewItem && $backendUser->check('tables_modify', 'sys_file_metadata')) {
|
||||
$sys_language_uid = 0;
|
||||
if (!empty($rec['sys_language_uid'])) {
|
||||
$sys_language_uid = $rec['sys_language_uid'][0];
|
||||
}
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('sys_file_metadata');
|
||||
$recordInDatabase = $queryBuilder
|
||||
->select('uid')
|
||||
->from('sys_file_metadata')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'file',
|
||||
$queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
|
||||
),
|
||||
$queryBuilder->expr()->eq(
|
||||
'sys_language_uid',
|
||||
$queryBuilder->createNamedParameter($sys_language_uid, \PDO::PARAM_INT)
|
||||
)
|
||||
)
|
||||
->setMaxResults(1)
|
||||
->execute()
|
||||
->fetch();
|
||||
if (!empty($recordInDatabase)) {
|
||||
$uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
|
||||
$url = (string)$uriBuilder->buildUriFromRoute('record_edit', [
|
||||
'edit[sys_file_metadata][' . (int)$recordInDatabase['uid'] . ']' => 'edit',
|
||||
'returnUrl' => $this->data['returnUrl']
|
||||
]);
|
||||
$title = $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.editMetadata');
|
||||
$cells['edit'] = '
|
||||
<a class="btn btn-default" href="' . htmlspecialchars($url) . '" title="' . htmlspecialchars($title) . '">
|
||||
' . $this->iconFactory->getIcon('actions-open', Icon::SIZE_SMALL)->render() . '
|
||||
</a>';
|
||||
}
|
||||
}
|
||||
// "Delete" link:
|
||||
if ($enabledControls['delete'] && ($isPagesTable && $localCalcPerms & Permission::PAGE_DELETE
|
||||
|| !$isPagesTable && $calcPerms & Permission::CONTENT_EDIT
|
||||
|| $isSysFileReferenceTable && $calcPerms & Permission::PAGE_EDIT)
|
||||
) {
|
||||
$title = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:delete'));
|
||||
$icon = $this->iconFactory->getIcon('actions-edit-delete', Icon::SIZE_SMALL)->render();
|
||||
$cells['delete'] = '<a href="#" class="btn btn-default t3js-editform-delete-inline-record" data-objectid="' . htmlspecialchars($nameObjectFtId) . '" title="' . $title . '">' . $icon . '</a>';
|
||||
}
|
||||
|
||||
// "Hide/Unhide" links:
|
||||
$hiddenField = $tcaTableCtrl['enablecolumns']['disabled'];
|
||||
if ($enabledControls['hide'] && $permsEdit && $hiddenField && $tcaTableCols[$hiddenField] && (!$tcaTableCols[$hiddenField]['exclude'] || $backendUser->check('non_exclude_fields', $foreignTable . ':' . $hiddenField))) {
|
||||
$onClick = 'return inline.enableDisableRecord(' . GeneralUtility::quoteJSvalue($nameObjectFtId) . ',' .
|
||||
GeneralUtility::quoteJSvalue($hiddenField) . ')';
|
||||
$className = 't3js-' . $nameObjectFtId . '_disabled';
|
||||
if ($rec[$hiddenField]) {
|
||||
$title = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:unHide' . ($isPagesTable ? 'Page' : '')));
|
||||
$cells['hide'] = '
|
||||
<a class="btn btn-default hiddenHandle ' . $className . '" href="#" onclick="
|
||||
' . htmlspecialchars($onClick) . '"' . 'title="' . $title . '">
|
||||
' . $this->iconFactory->getIcon('actions-edit-unhide', Icon::SIZE_SMALL)->render() . '
|
||||
</a>';
|
||||
} else {
|
||||
$title = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:hide' . ($isPagesTable ? 'Page' : '')));
|
||||
$cells['hide'] = '
|
||||
<a class="btn btn-default hiddenHandle ' . $className . '" href="#" onclick="
|
||||
' . htmlspecialchars($onClick) . '"' . 'title="' . $title . '">
|
||||
' . $this->iconFactory->getIcon('actions-edit-hide', Icon::SIZE_SMALL)->render() . '
|
||||
</a>';
|
||||
}
|
||||
}
|
||||
// Drag&Drop Sorting: Sortable handler for script.aculo.us
|
||||
if ($enabledControls['dragdrop'] && $permsEdit && $enableManualSorting && $inlineConfig['appearance']['useSortable']) {
|
||||
$cells['dragdrop'] = '
|
||||
<span class="btn btn-default sortableHandle" data-id="' . htmlspecialchars($rec['uid']) . '" title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.move')) . '">
|
||||
' . $this->iconFactory->getIcon('actions-move-move', Icon::SIZE_SMALL)->render() . '
|
||||
</span>';
|
||||
}
|
||||
} elseif ($data['isInlineDefaultLanguageRecordInLocalizedParentContext'] && $isParentExisting) {
|
||||
if ($enabledControls['localize'] && $data['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
|
||||
$onClick = 'inline.synchronizeLocalizeRecords(' . GeneralUtility::quoteJSvalue($nameObjectFt) . ', ' . GeneralUtility::quoteJSvalue($rec['uid']) . ');';
|
||||
$cells['localize'] = '
|
||||
<a class="btn btn-default" href="#" onclick="' . htmlspecialchars($onClick) . '" title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:localize')) . '">
|
||||
' . $this->iconFactory->getIcon('actions-document-localize', Icon::SIZE_SMALL)->render() . '
|
||||
</a>';
|
||||
}
|
||||
}
|
||||
// If the record is edit-locked by another user, we will show a little warning sign:
|
||||
if ($lockInfo = BackendUtility::isRecordLocked($foreignTable, $rec['uid'])) {
|
||||
$cells['locked'] = '
|
||||
<a class="btn btn-default" href="#" data-toggle="tooltip" data-title="' . htmlspecialchars($lockInfo['msg']) . '">
|
||||
' . '<span title="' . htmlspecialchars($lockInfo['msg']) . '">' . $this->iconFactory->getIcon('warning-in-use', Icon::SIZE_SMALL)->render() . '</span>' . '
|
||||
</a>';
|
||||
}
|
||||
// Hook: Post-processing of single controls for specific child records:
|
||||
foreach ($this->hookObjects as $hookObj) {
|
||||
$hookObj->renderForeignRecordHeaderControl_postProcess($data['inlineParentUid'], $foreignTable, $rec, $inlineConfig, $data['isInlineDefaultLanguageRecordInLocalizedParentContext'], $cells);
|
||||
}
|
||||
|
||||
$out = '';
|
||||
if (!empty($cells['edit']) || !empty($cells['hide']) || !empty($cells['delete'])) {
|
||||
$out .= '<div class="btn-group btn-group-sm" role="group">' . $cells['edit'] . $cells['hide'] . $cells['delete'] . '</div>';
|
||||
unset($cells['edit'], $cells['hide'], $cells['delete']);
|
||||
}
|
||||
if (!empty($cells['info']) || !empty($cells['new']) || !empty($cells['sort.up']) || !empty($cells['sort.down']) || !empty($cells['dragdrop'])) {
|
||||
$out .= '<div class="btn-group btn-group-sm" role="group">' . $cells['info'] . $cells['new'] . $cells['sort.up'] . $cells['sort.down'] . $cells['dragdrop'] . '</div>';
|
||||
unset($cells['info'], $cells['new'], $cells['sort.up'], $cells['sort.down'], $cells['dragdrop']);
|
||||
}
|
||||
if (!empty($cells['localize'])) {
|
||||
$out .= '<div class="btn-group btn-group-sm" role="group">' . $cells['localize'] . '</div>';
|
||||
unset($cells['localize']);
|
||||
}
|
||||
if (!empty($cells)) {
|
||||
$out .= ' <div class="btn-group btn-group-sm" role="group">' . implode('', $cells) . '</div>';
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a relation "uid" published by transferData, like "1|Company%201"
|
||||
*
|
||||
* @param string $string A transferData reference string, containing the uid
|
||||
* @return string The normalized uid
|
||||
*/
|
||||
protected function normalizeUid($string)
|
||||
{
|
||||
$parts = explode('|', $string);
|
||||
return $parts[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialized the hook objects for this class.
|
||||
* Each hook object has to implement the interface
|
||||
* \TYPO3\CMS\Backend\Form\Element\InlineElementHookInterface
|
||||
*
|
||||
* @throws \UnexpectedValueException
|
||||
*/
|
||||
protected function initHookObjects()
|
||||
{
|
||||
$this->hookObjects = [];
|
||||
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tceforms_inline.php']['tceformsInlineHook'] ?? [] as $className) {
|
||||
$processObject = GeneralUtility::makeInstance($className);
|
||||
if (!$processObject instanceof InlineElementHookInterface) {
|
||||
throw new \UnexpectedValueException($className . ' must implement interface ' . InlineElementHookInterface::class, 1202072000);
|
||||
}
|
||||
$this->hookObjects[] = $processObject;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUserAuthentication()
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService()
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\Container;
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Render a given list of field of a TCA table.
|
||||
*
|
||||
* This is an entry container called from FormEngine to handle a
|
||||
* list of specific fields. Access rights are checked here and globalOption array
|
||||
* is prepared for further processing of single fields by PaletteAndSingleContainer.
|
||||
*/
|
||||
class ListOfFieldsContainer extends AbstractContainer
|
||||
{
|
||||
/**
|
||||
* Entry method
|
||||
*
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$table = $this->data['tableName'];
|
||||
$fieldListToRender = $this->data['fieldListToRender'];
|
||||
$recordTypeValue = $this->data['recordTypeValue'];
|
||||
|
||||
// Load the description content for the table if requested
|
||||
if (!empty($GLOBALS['TCA'][$table]['interface']['always_description'])) {
|
||||
$languageService = $this->getLanguageService();
|
||||
$languageService->loadSingleTableDescription($table);
|
||||
}
|
||||
|
||||
$fieldListToRender = array_unique(GeneralUtility::trimExplode(',', $fieldListToRender, true));
|
||||
|
||||
$fieldsByShowitem = $this->data['processedTca']['types'][$recordTypeValue]['showitem'];
|
||||
$fieldsByShowitem = GeneralUtility::trimExplode(',', $fieldsByShowitem, true);
|
||||
|
||||
$finalFieldsList = [];
|
||||
foreach ($fieldListToRender as $fieldName) {
|
||||
foreach ($fieldsByShowitem as $fieldByShowitem) {
|
||||
$fieldByShowitemArray = $this->explodeSingleFieldShowItemConfiguration($fieldByShowitem);
|
||||
if ($fieldByShowitemArray['fieldName'] === $fieldName) {
|
||||
$finalFieldsList[] = implode(';', $fieldByShowitemArray);
|
||||
break;
|
||||
}
|
||||
if ($fieldByShowitemArray['fieldName'] === '--palette--'
|
||||
&& isset($this->data['processedTca']['palettes'][$fieldByShowitemArray['paletteName']]['showitem'])
|
||||
&& is_string($this->data['processedTca']['palettes'][$fieldByShowitemArray['paletteName']]['showitem'])
|
||||
) {
|
||||
$paletteName = $fieldByShowitemArray['paletteName'];
|
||||
$paletteFields = GeneralUtility::trimExplode(',', $this->data['processedTca']['palettes'][$paletteName]['showitem'], true);
|
||||
foreach ($paletteFields as $paletteField) {
|
||||
$paletteFieldArray = $this->explodeSingleFieldShowItemConfiguration($paletteField);
|
||||
if ($paletteFieldArray['fieldName'] === $fieldName) {
|
||||
$finalFieldsList[] = implode(';', $paletteFieldArray);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$options = $this->data;
|
||||
$options['fieldsArray'] = $finalFieldsList;
|
||||
$options['renderType'] = 'paletteAndSingleContainer';
|
||||
return $this->nodeFactory->create($options)->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService()
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\Container;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Handle a record that has no tabs.
|
||||
*
|
||||
* This container is called by FullRecordContainer and just wraps the output
|
||||
* of PaletteAndSingleContainer in some HTML.
|
||||
*/
|
||||
class NoTabsContainer extends AbstractContainer
|
||||
{
|
||||
/**
|
||||
* Entry method
|
||||
*
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$options = $this->data;
|
||||
$options['renderType'] = 'paletteAndSingleContainer';
|
||||
$resultArray = $this->nodeFactory->create($options)->render();
|
||||
$resultArray['html'] = '<div class="tab-content">' . $resultArray['html'] . '</div>';
|
||||
return $resultArray;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\Container;
|
||||
|
||||
/*
|
||||
* 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\Imaging\Icon;
|
||||
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Type\Bitmask\Permission;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Fluid\View\StandaloneView;
|
||||
|
||||
/**
|
||||
* Render header and footer row.
|
||||
*
|
||||
* This is an entry container called from controllers.
|
||||
* It wraps the title and a footer around the main html.
|
||||
* It either calls a FullRecordContainer or ListOfFieldsContainer to render
|
||||
* a full record or only some fields from a full record.
|
||||
*/
|
||||
class OuterWrapContainer extends AbstractContainer
|
||||
{
|
||||
/**
|
||||
* Entry method
|
||||
*
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
$backendUser = $this->getBackendUserAuthentication();
|
||||
|
||||
$table = $this->data['tableName'];
|
||||
$row = $this->data['databaseRow'];
|
||||
|
||||
$options = $this->data;
|
||||
if (empty($this->data['fieldListToRender'])) {
|
||||
$options['renderType'] = 'fullRecordContainer';
|
||||
} else {
|
||||
$options['renderType'] = 'listOfFieldsContainer';
|
||||
}
|
||||
$result = $this->nodeFactory->create($options)->render();
|
||||
|
||||
$childHtml = $result['html'];
|
||||
|
||||
$recordPath = '';
|
||||
// @todo: what is this >= 0 check for? wsol cases?!
|
||||
if ($this->data['effectivePid'] >= 0) {
|
||||
$permissionsClause = $backendUser->getPagePermsClause(Permission::PAGE_SHOW);
|
||||
$recordPath = BackendUtility::getRecordPath($this->data['effectivePid'], $permissionsClause, 15);
|
||||
}
|
||||
|
||||
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
|
||||
$icon = '<span title="' . htmlspecialchars($recordPath) . '">' . $iconFactory->getIconForRecord($table, $row, Icon::SIZE_SMALL)->render() . '</span>';
|
||||
|
||||
// @todo: Could this be done in a more clever way? Does it work at all?
|
||||
$tableTitle = $languageService->sL($this->data['processedTca']['ctrl']['title']);
|
||||
|
||||
if ($this->data['command'] === 'new') {
|
||||
$newOrUid = ' <span class="typo3-TCEforms-newToken">' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.new')) . '</span>';
|
||||
|
||||
// @todo: There is quite some stuff do to for WS overlays ...
|
||||
$workspacedPageRecord = BackendUtility::getRecordWSOL('pages', $this->data['effectivePid'], 'title');
|
||||
$pageTitle = BackendUtility::getRecordTitle('pages', $workspacedPageRecord, true, false);
|
||||
if ($table === 'pages') {
|
||||
$label = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.createNewPage'));
|
||||
$pageTitle = sprintf($label, $tableTitle);
|
||||
} else {
|
||||
$label = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.createNewRecord'));
|
||||
if ($this->data['effectivePid'] === 0) {
|
||||
$label = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.createNewRecordRootLevel'));
|
||||
}
|
||||
$pageTitle = sprintf($label, $tableTitle, $pageTitle);
|
||||
}
|
||||
} else {
|
||||
$icon = BackendUtility::wrapClickMenuOnIcon($icon, $table, $row['uid']);
|
||||
$newOrUid = ' <span class="typo3-TCEforms-recUid">[' . htmlspecialchars($row['uid']) . ']</span>';
|
||||
|
||||
// @todo: getRecordTitlePrep applies an htmlspecialchars here
|
||||
$recordLabel = BackendUtility::getRecordTitlePrep($this->data['recordTitle']);
|
||||
if ($table === 'pages') {
|
||||
$label = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.editPage'));
|
||||
$pageTitle = sprintf($label, $tableTitle, $recordLabel);
|
||||
} else {
|
||||
$label = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.editRecord'));
|
||||
$workspacedPageRecord = BackendUtility::getRecordWSOL('pages', $row['pid'], 'uid,title');
|
||||
$pageTitle = BackendUtility::getRecordTitle('pages', $workspacedPageRecord, true, false);
|
||||
if (empty($recordLabel)) {
|
||||
$label = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.editRecordNoTitle'));
|
||||
}
|
||||
if ($this->data['effectivePid'] === 0) {
|
||||
$label = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.editRecordRootLevel'));
|
||||
}
|
||||
if (!empty($recordLabel)) {
|
||||
// Use record title and prepend an edit label.
|
||||
$pageTitle = sprintf($label, $tableTitle, $recordLabel, $pageTitle);
|
||||
} else {
|
||||
// Leave out the record title since it is not set.
|
||||
$pageTitle = sprintf($label, $tableTitle, $pageTitle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$view = GeneralUtility::makeInstance(StandaloneView::class);
|
||||
$view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName(
|
||||
'EXT:backend/Resources/Private/Templates/OuterWrapContainer.html'
|
||||
));
|
||||
|
||||
$descriptionColumn = !empty($this->data['processedTca']['ctrl']['descriptionColumn'])
|
||||
? $this->data['processedTca']['ctrl']['descriptionColumn'] : null;
|
||||
if ($descriptionColumn !== null) {
|
||||
$view->assign('recordDescription', $this->data['databaseRow'][$descriptionColumn]);
|
||||
}
|
||||
$readOnlyRecord = !empty($this->data['processedTca']['ctrl']['readOnly'])
|
||||
? (bool)$this->data['processedTca']['ctrl']['readOnly'] : null;
|
||||
if ($readOnlyRecord === true) {
|
||||
$view->assign('recordReadonly', true);
|
||||
}
|
||||
$fieldInformationResult = $this->renderFieldInformation();
|
||||
$fieldInformationHtml = $fieldInformationResult['html'];
|
||||
$result = $this->mergeChildReturnIntoExistingResult($result, $fieldInformationResult, false);
|
||||
|
||||
$fieldWizardResult = $this->renderFieldWizard();
|
||||
$fieldWizardHtml = $fieldWizardResult['html'];
|
||||
$result = $this->mergeChildReturnIntoExistingResult($result, $fieldWizardResult, false);
|
||||
|
||||
$view->assignMultiple([
|
||||
'pageTitle' => $pageTitle,
|
||||
'fieldInformationHtml' => $fieldInformationHtml,
|
||||
'fieldWizardHtml' => $fieldWizardHtml,
|
||||
'childHtml' => $childHtml,
|
||||
'icon' => $icon,
|
||||
'tableTitle' => $tableTitle,
|
||||
'newOrUid' => $newOrUid,
|
||||
'isNewRecord' => $this->data['command'] === 'new'
|
||||
]);
|
||||
$result['html'] = $view->render();
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService()
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUserAuthentication()
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,431 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\Container;
|
||||
|
||||
/*
|
||||
* 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\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Handle palettes and single fields.
|
||||
*
|
||||
* This container is called by TabsContainer, NoTabsContainer and ListOfFieldsContainer.
|
||||
*
|
||||
* This container mostly operates on TCA showItem of a specific type - the value is
|
||||
* coming in from upper containers as "fieldArray". It handles palettes with all its
|
||||
* different options and prepares rendering of single fields for the SingleFieldContainer.
|
||||
*/
|
||||
class PaletteAndSingleContainer extends AbstractContainer
|
||||
{
|
||||
/**
|
||||
* Final result array accumulating results from children and final HTML
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $resultArray = [];
|
||||
|
||||
/**
|
||||
* Entry method
|
||||
*
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
|
||||
/*
|
||||
* The first code block creates a target structure array to later create the final
|
||||
* HTML string. The single fields and sub containers are rendered here already and
|
||||
* other parts of the return array from children except html are accumulated in
|
||||
* $this->resultArray
|
||||
*
|
||||
$targetStructure = array(
|
||||
0 => array(
|
||||
'type' => 'palette',
|
||||
'fieldName' => 'palette1',
|
||||
'fieldLabel' => 'palette1',
|
||||
'elements' => array(
|
||||
0 => array(
|
||||
'type' => 'single',
|
||||
'fieldName' => 'paletteName',
|
||||
'fieldLabel' => 'element1',
|
||||
'fieldHtml' => 'element1',
|
||||
),
|
||||
1 => array(
|
||||
'type' => 'linebreak',
|
||||
),
|
||||
2 => array(
|
||||
'type' => 'single',
|
||||
'fieldName' => 'paletteName',
|
||||
'fieldLabel' => 'element2',
|
||||
'fieldHtml' => 'element2',
|
||||
),
|
||||
),
|
||||
),
|
||||
1 => array(
|
||||
'type' => 'single',
|
||||
'fieldName' => 'element3',
|
||||
'fieldLabel' => 'element3',
|
||||
'fieldHtml' => 'element3',
|
||||
),
|
||||
2 => array(
|
||||
'type' => 'palette2',
|
||||
'fieldName' => 'palette2',
|
||||
'fieldLabel' => '', // Palettes may not have a label
|
||||
'elements' => array(
|
||||
0 => array(
|
||||
'type' => 'single',
|
||||
'fieldName' => 'element4',
|
||||
'fieldLabel' => 'element4',
|
||||
'fieldHtml' => 'element4',
|
||||
),
|
||||
1 => array(
|
||||
'type' => 'linebreak',
|
||||
),
|
||||
2 => array(
|
||||
'type' => 'single',
|
||||
'fieldName' => 'element5',
|
||||
'fieldLabel' => 'element5',
|
||||
'fieldHtml' => 'element5',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
*/
|
||||
|
||||
// Create an intermediate structure of rendered sub elements and elements nested in palettes
|
||||
$targetStructure = [];
|
||||
$mainStructureCounter = -1;
|
||||
$fieldsArray = $this->data['fieldsArray'];
|
||||
$this->resultArray = $this->initializeResultArray();
|
||||
foreach ($fieldsArray as $fieldString) {
|
||||
$fieldConfiguration = $this->explodeSingleFieldShowItemConfiguration($fieldString);
|
||||
$fieldName = $fieldConfiguration['fieldName'];
|
||||
if ($fieldName === '--palette--') {
|
||||
$paletteElementArray = $this->createPaletteContentArray($fieldConfiguration['paletteName']);
|
||||
if (!empty($paletteElementArray)) {
|
||||
$mainStructureCounter++;
|
||||
$paletteLabel = $fieldConfiguration['fieldLabel'];
|
||||
if ($paletteLabel === null
|
||||
&& !empty($this->data['processedTca']['palettes'][$fieldConfiguration['paletteName']]['label'])
|
||||
) {
|
||||
// If there is no label in ['types']['aType']['showitem'] for this palette: "--palette--;;aPalette" but
|
||||
// not "--palette--;LLL:aLabelReference;aPalette", then use ['palettes']['aPalette']['label'] if given.
|
||||
$paletteLabel = $this->data['processedTca']['palettes'][$fieldConfiguration['paletteName']]['label'];
|
||||
}
|
||||
$targetStructure[$mainStructureCounter] = [
|
||||
'type' => 'palette',
|
||||
'fieldName' => $fieldConfiguration['paletteName'],
|
||||
'fieldLabel' => $languageService->sL($paletteLabel),
|
||||
'elements' => $paletteElementArray,
|
||||
];
|
||||
}
|
||||
} else {
|
||||
if (!is_array($this->data['processedTca']['columns'][$fieldName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$options = $this->data;
|
||||
$options['fieldName'] = $fieldName;
|
||||
|
||||
$options['renderType'] = 'singleFieldContainer';
|
||||
$childResultArray = $this->nodeFactory->create($options)->render();
|
||||
|
||||
if (!empty($childResultArray['html'])) {
|
||||
$mainStructureCounter++;
|
||||
$fieldLabel = '';
|
||||
if (!empty($this->data['processedTca']['columns'][$fieldName]['label'])) {
|
||||
$fieldLabel = $this->data['processedTca']['columns'][$fieldName]['label'];
|
||||
}
|
||||
$targetStructure[$mainStructureCounter] = [
|
||||
'type' => 'single',
|
||||
'fieldName' => $fieldConfiguration['fieldName'],
|
||||
'fieldLabel' => $fieldLabel,
|
||||
'fieldHtml' => $childResultArray['html'],
|
||||
];
|
||||
}
|
||||
|
||||
$this->resultArray = $this->mergeChildReturnIntoExistingResult($this->resultArray, $childResultArray, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Compile final content
|
||||
$content = [];
|
||||
foreach ($targetStructure as $element) {
|
||||
if ($element['type'] === 'palette') {
|
||||
$paletteName = $element['fieldName'];
|
||||
$paletteElementsHtml = $this->renderInnerPaletteContent($element);
|
||||
|
||||
$isHiddenPalette = !empty($this->data['processedTca']['palettes'][$paletteName]['isHiddenPalette']);
|
||||
|
||||
$paletteElementsHtml = '<div class="row">' . $paletteElementsHtml . '</div>';
|
||||
|
||||
$content[] = $this->fieldSetWrap($paletteElementsHtml, $isHiddenPalette, $element['fieldLabel']);
|
||||
} else {
|
||||
// Return raw HTML only in case of user element with no wrapping requested
|
||||
if ($this->isUserNoTableWrappingField($element)) {
|
||||
$content[] = $element['fieldHtml'];
|
||||
} else {
|
||||
$content[] = $this->fieldSetWrap($this->wrapSingleFieldContentWithLabelAndOuterDiv($element));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$finalResultArray = $this->resultArray;
|
||||
$finalResultArray['html'] = implode(LF, $content);
|
||||
return $finalResultArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render single fields of a given palette
|
||||
*
|
||||
* @param string $paletteName The palette to render
|
||||
* @return array
|
||||
*/
|
||||
protected function createPaletteContentArray($paletteName)
|
||||
{
|
||||
// palette needs a palette name reference, otherwise it does not make sense to try rendering of it
|
||||
if (empty($paletteName) || empty($this->data['processedTca']['palettes'][$paletteName]['showitem'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$resultStructure = [];
|
||||
$foundRealElement = false; // Set to true if not only line breaks were rendered
|
||||
$fieldsArray = GeneralUtility::trimExplode(',', $this->data['processedTca']['palettes'][$paletteName]['showitem'], true);
|
||||
foreach ($fieldsArray as $fieldString) {
|
||||
$fieldArray = $this->explodeSingleFieldShowItemConfiguration($fieldString);
|
||||
$fieldName = $fieldArray['fieldName'];
|
||||
if ($fieldName === '--linebreak--') {
|
||||
$resultStructure[] = [
|
||||
'type' => 'linebreak',
|
||||
];
|
||||
} else {
|
||||
if (!is_array($this->data['processedTca']['columns'][$fieldName])) {
|
||||
continue;
|
||||
}
|
||||
$options = $this->data;
|
||||
$options['fieldName'] = $fieldName;
|
||||
|
||||
$options['renderType'] = 'singleFieldContainer';
|
||||
$singleFieldContentArray = $this->nodeFactory->create($options)->render();
|
||||
|
||||
if (!empty($singleFieldContentArray['html'])) {
|
||||
$foundRealElement = true;
|
||||
$fieldLabel = '';
|
||||
if (!empty($this->data['processedTca']['columns'][$fieldName]['label'])) {
|
||||
$fieldLabel = $this->data['processedTca']['columns'][$fieldName]['label'];
|
||||
}
|
||||
$resultStructure[] = [
|
||||
'type' => 'single',
|
||||
'fieldName' => $fieldName,
|
||||
'fieldLabel' => $fieldLabel,
|
||||
'fieldHtml' => $singleFieldContentArray['html'],
|
||||
];
|
||||
}
|
||||
$this->resultArray = $this->mergeChildReturnIntoExistingResult($this->resultArray, $singleFieldContentArray, false);
|
||||
}
|
||||
}
|
||||
|
||||
if ($foundRealElement) {
|
||||
return $resultStructure;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders inner content of single elements of a palette and wrap it as needed
|
||||
*
|
||||
* @param array $elementArray Array of elements
|
||||
* @return string Wrapped content
|
||||
*/
|
||||
protected function renderInnerPaletteContent(array $elementArray)
|
||||
{
|
||||
// Group fields
|
||||
$groupedFields = [];
|
||||
$row = 0;
|
||||
$lastLineWasLinebreak = true;
|
||||
foreach ($elementArray['elements'] as $element) {
|
||||
if ($element['type'] === 'linebreak') {
|
||||
if (!$lastLineWasLinebreak) {
|
||||
$row++;
|
||||
$groupedFields[$row][] = $element;
|
||||
$row++;
|
||||
$lastLineWasLinebreak = true;
|
||||
}
|
||||
} else {
|
||||
$lastLineWasLinebreak = false;
|
||||
$groupedFields[$row][] = $element;
|
||||
}
|
||||
}
|
||||
|
||||
$result = [];
|
||||
// Process fields
|
||||
foreach ($groupedFields as $fields) {
|
||||
$numberOfItems = count($fields);
|
||||
$colWidth = (int)floor(12 / $numberOfItems);
|
||||
// Column class calculation
|
||||
$colClass = 'col-md-12';
|
||||
$colClear = [];
|
||||
if ($colWidth == 6) {
|
||||
$colClass = 'col-sm-6';
|
||||
$colClear = [
|
||||
2 => 'visible-sm-block visible-md-block visible-lg-block',
|
||||
];
|
||||
} elseif ($colWidth === 4) {
|
||||
$colClass = 'col-sm-4';
|
||||
$colClear = [
|
||||
3 => 'visible-sm-block visible-md-block visible-lg-block',
|
||||
];
|
||||
} elseif ($colWidth === 3) {
|
||||
$colClass = 'col-sm-6 col-md-3';
|
||||
$colClear = [
|
||||
2 => 'visible-sm-block',
|
||||
4 => 'visible-md-block visible-lg-block',
|
||||
];
|
||||
} elseif ($colWidth <= 2) {
|
||||
$colClass = 'checkbox-column col-sm-6 col-md-3 col-lg-2';
|
||||
$colClear = [
|
||||
2 => 'visible-sm-block',
|
||||
4 => 'visible-md-block',
|
||||
6 => 'visible-lg-block'
|
||||
];
|
||||
}
|
||||
|
||||
// Render fields
|
||||
for ($counter = 0; $counter < $numberOfItems; $counter++) {
|
||||
$element = $fields[$counter];
|
||||
if ($element['type'] === 'linebreak') {
|
||||
if ($counter !== $numberOfItems) {
|
||||
$result[] = '<div class="clearfix"></div>';
|
||||
}
|
||||
} else {
|
||||
$result[] = $this->wrapSingleFieldContentWithLabelAndOuterDiv($element, [$colClass]);
|
||||
|
||||
// Breakpoints
|
||||
if ($counter + 1 < $numberOfItems && !empty($colClear)) {
|
||||
foreach ($colClear as $rowBreakAfter => $clearClass) {
|
||||
if (($counter + 1) % $rowBreakAfter === 0) {
|
||||
$result[] = '<div class="clearfix ' . $clearClass . '"></div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return implode(LF, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap content in a field set
|
||||
*
|
||||
* @param string $content Incoming content
|
||||
* @param bool $paletteHidden TRUE if the palette is hidden
|
||||
* @param string $label Given label
|
||||
* @return string Wrapped content
|
||||
*/
|
||||
protected function fieldSetWrap($content, $paletteHidden = false, $label = '')
|
||||
{
|
||||
$fieldSetClass = 'form-section';
|
||||
if ($paletteHidden) {
|
||||
$fieldSetClass .= ' hide';
|
||||
}
|
||||
|
||||
$result = [];
|
||||
$result[] = '<fieldset class="' . $fieldSetClass . '">';
|
||||
|
||||
if (!empty($label)) {
|
||||
$result[] = '<h4 class="form-section-headline">' . htmlspecialchars($label) . '</h4>';
|
||||
}
|
||||
|
||||
$result[] = $content;
|
||||
$result[] = '</fieldset>';
|
||||
return implode(LF, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap a single element
|
||||
*
|
||||
* @param array $element Given element as documented above
|
||||
* @param array $additionalPaletteClasses Additional classes to be added to HTML
|
||||
* @return string Wrapped element
|
||||
*/
|
||||
protected function wrapSingleFieldContentWithLabelAndOuterDiv(array $element, array $additionalPaletteClasses = [])
|
||||
{
|
||||
$fieldName = $element['fieldName'];
|
||||
|
||||
$paletteFieldClasses = [
|
||||
'form-group',
|
||||
't3js-formengine-validation-marker',
|
||||
't3js-formengine-palette-field',
|
||||
];
|
||||
foreach ($additionalPaletteClasses as $class) {
|
||||
$paletteFieldClasses[] = $class;
|
||||
}
|
||||
|
||||
$label = BackendUtility::wrapInHelp($this->data['tableName'], $fieldName, htmlspecialchars($element['fieldLabel']));
|
||||
|
||||
if ($GLOBALS['TYPO3_CONF_VARS']['BE']['debug'] && $this->getBackendUser()->isAdmin()) {
|
||||
$label .= '<code>[' . htmlspecialchars($fieldName) . ']</code>';
|
||||
}
|
||||
|
||||
$content = [];
|
||||
$content[] = '<div class="' . implode(' ', $paletteFieldClasses) . '">';
|
||||
$content[] = '<label class="t3js-formengine-label">';
|
||||
$content[] = $label;
|
||||
$content[] = '</label>';
|
||||
$content[] = $element['fieldHtml'];
|
||||
$content[] = '</div>';
|
||||
|
||||
return implode(LF, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* TRUE if field is of type user and to wrapping is requested
|
||||
*
|
||||
* @param array $element Current element from "target structure" array
|
||||
* @return bool TRUE if user and noTableWrapping is set
|
||||
*/
|
||||
protected function isUserNoTableWrappingField($element)
|
||||
{
|
||||
$fieldName = $element['fieldName'];
|
||||
if (
|
||||
$this->data['processedTca']['columns'][$fieldName]['config']['type'] === 'user'
|
||||
&& !empty($this->data['processedTca']['columns'][$fieldName]['config']['noTableWrapping'])
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService()
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current BE user.
|
||||
*
|
||||
* @return BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUser()
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,373 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\Container;
|
||||
|
||||
/*
|
||||
* 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\InlineStackProcessor;
|
||||
use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Type\Bitmask\JsConfirmation;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
|
||||
/**
|
||||
* Container around a "single field".
|
||||
*
|
||||
* This container is the last one in the chain before processing is handed over to single element classes.
|
||||
* If a single field is of type flex or inline, it however creates FlexFormEntryContainer or InlineControlContainer.
|
||||
*
|
||||
* The container does various checks and processing for a given single fields.
|
||||
*/
|
||||
class SingleFieldContainer extends AbstractContainer
|
||||
{
|
||||
/**
|
||||
* Entry method
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$backendUser = $this->getBackendUserAuthentication();
|
||||
$resultArray = $this->initializeResultArray();
|
||||
|
||||
$table = $this->data['tableName'];
|
||||
$row = $this->data['databaseRow'];
|
||||
$fieldName = $this->data['fieldName'];
|
||||
|
||||
$parameterArray = [];
|
||||
$parameterArray['fieldConf'] = $this->data['processedTca']['columns'][$fieldName];
|
||||
|
||||
$isOverlay = false;
|
||||
|
||||
// This field decides whether the current record is an overlay (as opposed to being a standalone record)
|
||||
// Based on this decision we need to trigger field exclusion or special rendering (like readOnly)
|
||||
if (isset($this->data['processedTca']['ctrl']['transOrigPointerField'])
|
||||
&& is_array($this->data['processedTca']['columns'][$this->data['processedTca']['ctrl']['transOrigPointerField']])
|
||||
) {
|
||||
$parentValue = $row[$this->data['processedTca']['ctrl']['transOrigPointerField']];
|
||||
if (MathUtility::canBeInterpretedAsInteger($parentValue)) {
|
||||
$isOverlay = (bool)$parentValue;
|
||||
} elseif (is_array($parentValue)) {
|
||||
// This case may apply if the value has been converted to an array by the select or group data provider
|
||||
$isOverlay = !empty($parentValue) ? (bool)$parentValue[0] : false;
|
||||
} else {
|
||||
throw new \InvalidArgumentException(
|
||||
'The given value "' . $parentValue . '" for the original language field ' . $this->data['processedTca']['ctrl']['transOrigPointerField']
|
||||
. ' of table ' . $table . ' is invalid.',
|
||||
1470742770
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// A couple of early returns in case the field should not be rendered
|
||||
// Check if this field is configured and editable according to exclude fields and other configuration
|
||||
if (// Return if BE-user has no access rights to this field, @todo: another user access rights check!
|
||||
$parameterArray['fieldConf']['exclude'] && !$backendUser->check('non_exclude_fields', $table . ':' . $fieldName)
|
||||
// Return if field should not be rendered in translated records
|
||||
|| $isOverlay && empty($parameterArray['fieldConf']['l10n_display']) && $parameterArray['fieldConf']['l10n_mode'] === 'exclude'
|
||||
|| $this->inlineFieldShouldBeSkipped()
|
||||
) {
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
$parameterArray['fieldTSConfig'] = [];
|
||||
if (isset($this->data['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.'])
|
||||
&& is_array($this->data['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.'])
|
||||
) {
|
||||
$parameterArray['fieldTSConfig'] = $this->data['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.'];
|
||||
}
|
||||
if ($parameterArray['fieldTSConfig']['disabled']) {
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
// Override fieldConf by fieldTSconfig:
|
||||
$parameterArray['fieldConf']['config'] = FormEngineUtility::overrideFieldConf($parameterArray['fieldConf']['config'], $parameterArray['fieldTSConfig']);
|
||||
$parameterArray['itemFormElName'] = 'data[' . $table . '][' . $row['uid'] . '][' . $fieldName . ']';
|
||||
$parameterArray['itemFormElID'] = 'data_' . $table . '_' . $row['uid'] . '_' . $fieldName;
|
||||
$newElementBaseName = $this->data['elementBaseName'] . '[' . $table . '][' . $row['uid'] . '][' . $fieldName . ']';
|
||||
|
||||
// The value to show in the form field.
|
||||
$parameterArray['itemFormElValue'] = $row[$fieldName];
|
||||
// Set field to read-only if configured for translated records to show default language content as readonly
|
||||
if ($parameterArray['fieldConf']['l10n_display']
|
||||
&& GeneralUtility::inList($parameterArray['fieldConf']['l10n_display'], 'defaultAsReadonly')
|
||||
&& $isOverlay
|
||||
) {
|
||||
$parameterArray['fieldConf']['config']['readOnly'] = true;
|
||||
$parameterArray['itemFormElValue'] = $this->data['defaultLanguageRow'][$fieldName];
|
||||
}
|
||||
|
||||
if (strpos($this->data['processedTca']['ctrl']['type'], ':') === false) {
|
||||
$typeField = $this->data['processedTca']['ctrl']['type'];
|
||||
} else {
|
||||
$typeField = substr($this->data['processedTca']['ctrl']['type'], 0, strpos($this->data['processedTca']['ctrl']['type'], ':'));
|
||||
}
|
||||
// Create a JavaScript code line which will ask the user to save/update the form due to changing the element.
|
||||
// This is used for eg. "type" fields and others configured with "onChange"
|
||||
if (!empty($this->data['processedTca']['ctrl']['type']) && $fieldName === $typeField
|
||||
|| isset($parameterArray['fieldConf']['onChange']) && $parameterArray['fieldConf']['onChange'] === 'reload'
|
||||
) {
|
||||
if ($backendUser->jsConfirmation(JsConfirmation::TYPE_CHANGE)) {
|
||||
$alertMsgOnChange = 'top.TYPO3.Modal.confirm('
|
||||
. 'TYPO3.lang["FormEngine.refreshRequiredTitle"],'
|
||||
. ' TYPO3.lang["FormEngine.refreshRequiredContent"]'
|
||||
. ')'
|
||||
. '.on('
|
||||
. '"button.clicked",'
|
||||
. ' function(e) { if (e.target.name == "ok" && TBE_EDITOR.checkSubmit(-1)) { TBE_EDITOR.submitForm() } top.TYPO3.Modal.dismiss(); }'
|
||||
. ');';
|
||||
} else {
|
||||
$alertMsgOnChange = 'if (TBE_EDITOR.checkSubmit(-1)){ TBE_EDITOR.submitForm() };';
|
||||
}
|
||||
} else {
|
||||
$alertMsgOnChange = '';
|
||||
}
|
||||
|
||||
// JavaScript code for event handlers:
|
||||
$parameterArray['fieldChangeFunc'] = [];
|
||||
$parameterArray['fieldChangeFunc']['TBE_EDITOR_fieldChanged'] = 'TBE_EDITOR.fieldChanged('
|
||||
. GeneralUtility::quoteJSvalue($table) . ','
|
||||
. GeneralUtility::quoteJSvalue($row['uid']) . ','
|
||||
. GeneralUtility::quoteJSvalue($fieldName) . ','
|
||||
. GeneralUtility::quoteJSvalue($parameterArray['itemFormElName'])
|
||||
. ');';
|
||||
if ($alertMsgOnChange) {
|
||||
$parameterArray['fieldChangeFunc']['alert'] = $alertMsgOnChange;
|
||||
}
|
||||
|
||||
// If this is the child of an inline type and it is the field creating the label
|
||||
if ($this->isInlineChildAndLabelField($table, $fieldName)) {
|
||||
$inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
|
||||
$inlineStackProcessor->initializeByGivenStructure($this->data['inlineStructure']);
|
||||
$inlineDomObjectId = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']);
|
||||
$inlineObjectId = implode(
|
||||
'-',
|
||||
[
|
||||
$inlineDomObjectId,
|
||||
$table,
|
||||
$row['uid']
|
||||
]
|
||||
);
|
||||
$parameterArray['fieldChangeFunc']['inline'] = 'inline.handleChangedField('
|
||||
. GeneralUtility::quoteJSvalue($parameterArray['itemFormElName']) . ','
|
||||
. GeneralUtility::quoteJSvalue($inlineObjectId)
|
||||
. ');';
|
||||
}
|
||||
|
||||
// Based on the type of the item, call a render function on a child element
|
||||
$options = $this->data;
|
||||
$options['parameterArray'] = $parameterArray;
|
||||
$options['elementBaseName'] = $newElementBaseName;
|
||||
if (!empty($parameterArray['fieldConf']['config']['renderType'])) {
|
||||
$options['renderType'] = $parameterArray['fieldConf']['config']['renderType'];
|
||||
} else {
|
||||
// Fallback to type if no renderType is given
|
||||
$options['renderType'] = $parameterArray['fieldConf']['config']['type'];
|
||||
}
|
||||
$resultArray = $this->nodeFactory->create($options)->render();
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the $table is the child of an inline type AND the $field is the label field of this table.
|
||||
* This function is used to dynamically update the label while editing. This has no effect on labels,
|
||||
* that were processed by a FormEngine-hook on saving.
|
||||
*
|
||||
* @param string $table The table to check
|
||||
* @param string $field The field on this table to check
|
||||
* @return bool Is inline child and field is responsible for the label
|
||||
*/
|
||||
protected function isInlineChildAndLabelField($table, $field)
|
||||
{
|
||||
/** @var InlineStackProcessor $inlineStackProcessor */
|
||||
$inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
|
||||
$inlineStackProcessor->initializeByGivenStructure($this->data['inlineStructure']);
|
||||
$level = $inlineStackProcessor->getStructureLevel(-1);
|
||||
if ($level['config']['foreign_label']) {
|
||||
$label = $level['config']['foreign_label'];
|
||||
} else {
|
||||
$label = $this->data['processedTca']['ctrl']['label'];
|
||||
}
|
||||
return $level['config']['foreign_table'] === $table && $label === $field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendering of inline fields should be skipped under certain circumstances
|
||||
*
|
||||
* @return bool TRUE if field should be skipped based on inline configuration
|
||||
*/
|
||||
protected function inlineFieldShouldBeSkipped()
|
||||
{
|
||||
$table = $this->data['tableName'];
|
||||
$fieldName = $this->data['fieldName'];
|
||||
$fieldConfig = $this->data['processedTca']['columns'][$fieldName]['config'];
|
||||
|
||||
/** @var InlineStackProcessor $inlineStackProcessor */
|
||||
$inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
|
||||
$inlineStackProcessor->initializeByGivenStructure($this->data['inlineStructure']);
|
||||
$structureDepth = $inlineStackProcessor->getStructureDepth();
|
||||
|
||||
$skipThisField = false;
|
||||
if ($structureDepth > 0) {
|
||||
$searchArray = [
|
||||
'%OR' => [
|
||||
'config' => [
|
||||
0 => [
|
||||
'%AND' => [
|
||||
'foreign_table' => $table,
|
||||
'%OR' => [
|
||||
'%AND' => [
|
||||
'appearance' => ['useCombination' => true],
|
||||
'foreign_selector' => $fieldName
|
||||
],
|
||||
'MM' => $fieldConfig['MM']
|
||||
]
|
||||
]
|
||||
],
|
||||
1 => [
|
||||
'%AND' => [
|
||||
'foreign_table' => $fieldConfig['foreign_table'],
|
||||
'foreign_selector' => $fieldConfig['foreign_field']
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
// Get the parent record from structure stack
|
||||
$level = $inlineStackProcessor->getStructureLevel(-1);
|
||||
// If we have symmetric fields, check on which side we are and hide fields, that are set automatically:
|
||||
if ($this->data['isOnSymmetricSide']) {
|
||||
$searchArray['%OR']['config'][0]['%AND']['%OR']['symmetric_field'] = $fieldName;
|
||||
$searchArray['%OR']['config'][0]['%AND']['%OR']['symmetric_sortby'] = $fieldName;
|
||||
} else {
|
||||
$searchArray['%OR']['config'][0]['%AND']['%OR']['foreign_field'] = $fieldName;
|
||||
$searchArray['%OR']['config'][0]['%AND']['%OR']['foreign_sortby'] = $fieldName;
|
||||
}
|
||||
$skipThisField = $this->arrayCompareComplex($level, $searchArray);
|
||||
}
|
||||
return $skipThisField;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles complex comparison requests on an array.
|
||||
* A request could look like the following:
|
||||
*
|
||||
* $searchArray = array(
|
||||
* '%AND' => array(
|
||||
* 'key1' => 'value1',
|
||||
* 'key2' => 'value2',
|
||||
* '%OR' => array(
|
||||
* 'subarray' => array(
|
||||
* 'subkey' => 'subvalue'
|
||||
* ),
|
||||
* 'key3' => 'value3',
|
||||
* 'key4' => 'value4'
|
||||
* )
|
||||
* )
|
||||
* );
|
||||
*
|
||||
* It is possible to use the array keys '%AND.1', '%AND.2', etc. to prevent
|
||||
* overwriting the sub-array. It could be necessary, if you use complex comparisons.
|
||||
*
|
||||
* The example above means, key1 *AND* key2 (and their values) have to match with
|
||||
* the $subjectArray and additional one *OR* key3 or key4 have to meet the same
|
||||
* condition.
|
||||
* It is also possible to compare parts of a sub-array (e.g. "subarray"), so this
|
||||
* function recurses down one level in that sub-array.
|
||||
*
|
||||
* @param array $subjectArray The array to search in
|
||||
* @param array $searchArray The array with keys and values to search for
|
||||
* @param string $type Use '%AND' or '%OR' for comparison
|
||||
* @return bool The result of the comparison
|
||||
*/
|
||||
protected function arrayCompareComplex($subjectArray, $searchArray, $type = '')
|
||||
{
|
||||
$localMatches = 0;
|
||||
$localEntries = 0;
|
||||
if (is_array($searchArray) && !empty($searchArray)) {
|
||||
// If no type was passed, try to determine
|
||||
if (!$type) {
|
||||
reset($searchArray);
|
||||
$type = key($searchArray);
|
||||
$searchArray = current($searchArray);
|
||||
}
|
||||
// We use '%AND' and '%OR' in uppercase
|
||||
$type = strtoupper($type);
|
||||
// Split regular elements from sub elements
|
||||
foreach ($searchArray as $key => $value) {
|
||||
$localEntries++;
|
||||
// Process a sub-group of OR-conditions
|
||||
if ($key === '%OR') {
|
||||
$localMatches += $this->arrayCompareComplex($subjectArray, $value, '%OR') ? 1 : 0;
|
||||
} elseif ($key === '%AND') {
|
||||
$localMatches += $this->arrayCompareComplex($subjectArray, $value, '%AND') ? 1 : 0;
|
||||
} elseif (is_array($value) && $this->isAssociativeArray($searchArray)) {
|
||||
$localMatches += $this->arrayCompareComplex($subjectArray[$key], $value, $type) ? 1 : 0;
|
||||
} elseif (is_array($value)) {
|
||||
$localMatches += $this->arrayCompareComplex($subjectArray, $value, $type) ? 1 : 0;
|
||||
} else {
|
||||
if (isset($subjectArray[$key]) && isset($value)) {
|
||||
// Boolean match:
|
||||
if (is_bool($value)) {
|
||||
$localMatches += !($subjectArray[$key] xor $value) ? 1 : 0;
|
||||
} elseif (is_numeric($subjectArray[$key]) && is_numeric($value)) {
|
||||
$localMatches += $subjectArray[$key] == $value ? 1 : 0;
|
||||
} else {
|
||||
$localMatches += $subjectArray[$key] === $value ? 1 : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
// If one or more matches are required ('OR'), return TRUE after the first successful match
|
||||
if ($type === '%OR' && $localMatches > 0) {
|
||||
return true;
|
||||
}
|
||||
// If all matches are required ('AND') and we have no result after the first run, return FALSE
|
||||
if ($type === '%AND' && $localMatches == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Return the result for '%AND' (if nothing was checked, TRUE is returned)
|
||||
return $localEntries === $localMatches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether an object is an associative array.
|
||||
*
|
||||
* @param mixed $object The object to be checked
|
||||
* @return bool Returns TRUE, if the object is an associative array
|
||||
*/
|
||||
protected function isAssociativeArray($object)
|
||||
{
|
||||
return is_array($object) && !empty($object) && array_keys($object) !== range(0, count($object) - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUserAuthentication()
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService()
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\Container;
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Render all tabs of a record that has tabs.
|
||||
*
|
||||
* This container is called from FullRecordContainer and resolves the --div-- structure,
|
||||
* operates on given fieldArrays and calls a PaletteAndSingleContainer for each single tab.
|
||||
*/
|
||||
class TabsContainer extends AbstractContainer
|
||||
{
|
||||
/**
|
||||
* Entry method
|
||||
*
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
|
||||
// All the fields to handle in a flat list
|
||||
$fieldsArray = $this->data['fieldsArray'];
|
||||
|
||||
// Create a nested array from flat fieldArray list
|
||||
$tabsArray = [];
|
||||
// First element will be a --div--, so it is safe to start -1 here to trigger 0 as first array index
|
||||
$currentTabIndex = -1;
|
||||
foreach ($fieldsArray as $fieldString) {
|
||||
$fieldArray = $this->explodeSingleFieldShowItemConfiguration($fieldString);
|
||||
if ($fieldArray['fieldName'] === '--div--') {
|
||||
$currentTabIndex++;
|
||||
if (empty($fieldArray['fieldLabel'])) {
|
||||
throw new \RuntimeException(
|
||||
'A --div-- has no label (--div--;fieldLabel) in showitem of ' . implode(',', $fieldsArray),
|
||||
1426454001
|
||||
);
|
||||
}
|
||||
$tabsArray[$currentTabIndex] = [
|
||||
'label' => $languageService->sL($fieldArray['fieldLabel']),
|
||||
'elements' => [],
|
||||
];
|
||||
} else {
|
||||
$tabsArray[$currentTabIndex]['elements'][] = $fieldArray;
|
||||
}
|
||||
}
|
||||
|
||||
$resultArray = $this->initializeResultArray();
|
||||
$resultArray['requireJsModules'][] = 'TYPO3/CMS/Backend/Tabs';
|
||||
|
||||
$domIdPrefix = 'DTM-' . GeneralUtility::shortMD5($this->data['tableName'] . $this->data['databaseRow']['uid']);
|
||||
$tabCounter = 0;
|
||||
$tabElements = [];
|
||||
foreach ($tabsArray as $tabWithLabelAndElements) {
|
||||
$tabCounter++;
|
||||
$elements = $tabWithLabelAndElements['elements'];
|
||||
|
||||
// Merge elements of this tab into a single list again and hand over to
|
||||
// palette and single field container to render this group
|
||||
$options = $this->data;
|
||||
$options['tabAndInlineStack'][] = [
|
||||
'tab',
|
||||
$domIdPrefix . '-' . $tabCounter,
|
||||
];
|
||||
$options['fieldsArray'] = [];
|
||||
foreach ($elements as $element) {
|
||||
$options['fieldsArray'][] = implode(';', $element);
|
||||
}
|
||||
$options['renderType'] = 'paletteAndSingleContainer';
|
||||
$childArray = $this->nodeFactory->create($options)->render();
|
||||
|
||||
if ($childArray['html'] !== '') {
|
||||
$tabElements[] = [
|
||||
'label' => $tabWithLabelAndElements['label'],
|
||||
'content' => $childArray['html'],
|
||||
];
|
||||
}
|
||||
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $childArray, false);
|
||||
}
|
||||
|
||||
$resultArray['html'] = $this->renderTabMenu($tabElements, $domIdPrefix);
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService()
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,407 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\Element;
|
||||
|
||||
/*
|
||||
* 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\AbstractNode;
|
||||
use TYPO3\CMS\Backend\Form\NodeFactory;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
|
||||
/**
|
||||
* Base class for form elements of FormEngine. Contains several helper methods used by single elements.
|
||||
*/
|
||||
abstract class AbstractFormElement extends AbstractNode
|
||||
{
|
||||
/**
|
||||
* Default width value for a couple of elements like text
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $defaultInputWidth = 30;
|
||||
|
||||
/**
|
||||
* Minimum width value for a couple of elements like text
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $minimumInputWidth = 10;
|
||||
|
||||
/**
|
||||
* Maximum width value for a couple of elements like text
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $maxInputWidth = 50;
|
||||
|
||||
/**
|
||||
* @var IconFactory
|
||||
*/
|
||||
protected $iconFactory;
|
||||
|
||||
/**
|
||||
* Container objects give $nodeFactory down to other containers.
|
||||
*
|
||||
* @param NodeFactory $nodeFactory
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct(NodeFactory $nodeFactory, array $data)
|
||||
{
|
||||
parent::__construct($nodeFactory, $data);
|
||||
$this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge field information configuration with default and render them.
|
||||
*
|
||||
* @return array Result array
|
||||
*/
|
||||
protected function renderFieldInformation(): array
|
||||
{
|
||||
$options = $this->data;
|
||||
$fieldInformation = $this->defaultFieldInformation;
|
||||
$fieldInformationFromTca = $options['parameterArray']['fieldConf']['config']['fieldInformation'] ?? [];
|
||||
ArrayUtility::mergeRecursiveWithOverrule($fieldInformation, $fieldInformationFromTca);
|
||||
$options['renderType'] = 'fieldInformation';
|
||||
$options['renderData']['fieldInformation'] = $fieldInformation;
|
||||
return $this->nodeFactory->create($options)->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge field control configuration with default controls and render them.
|
||||
*
|
||||
* @return array Result array
|
||||
*/
|
||||
protected function renderFieldControl(): array
|
||||
{
|
||||
$options = $this->data;
|
||||
$fieldControl = $this->defaultFieldControl;
|
||||
$fieldControlFromTca = $options['parameterArray']['fieldConf']['config']['fieldControl'] ?? [];
|
||||
ArrayUtility::mergeRecursiveWithOverrule($fieldControl, $fieldControlFromTca);
|
||||
$options['renderType'] = 'fieldControl';
|
||||
$options['renderData']['fieldControl'] = $fieldControl;
|
||||
return $this->nodeFactory->create($options)->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge field wizard configuration with default wizards and render them.
|
||||
*
|
||||
* @return array Result array
|
||||
*/
|
||||
protected function renderFieldWizard(): array
|
||||
{
|
||||
$options = $this->data;
|
||||
$fieldWizard = $this->defaultFieldWizard;
|
||||
$fieldWizardFromTca = $options['parameterArray']['fieldConf']['config']['fieldWizard'] ?? [];
|
||||
ArrayUtility::mergeRecursiveWithOverrule($fieldWizard, $fieldWizardFromTca);
|
||||
$options['renderType'] = 'fieldWizard';
|
||||
$options['renderData']['fieldWizard'] = $fieldWizard;
|
||||
return $this->nodeFactory->create($options)->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the "null value" checkbox should be rendered. This is used in some
|
||||
* "text" based types like "text" and "input" for some renderType's.
|
||||
*
|
||||
* A field has eval=null set, but has no useOverridePlaceholder defined.
|
||||
* Goal is to have a field that can distinct between NULL and empty string in the database.
|
||||
* A checkbox and an additional hidden field will be created, both with the same name
|
||||
* and prefixed with "control[active]". If the checkbox is set (value 1), the value from the casual
|
||||
* input field will be written to the database. If the checkbox is not set, the hidden field
|
||||
* transfers value=0 to DataHandler, the value of the input field will then be reset to NULL by the
|
||||
* DataHandler at an early point in processing, so NULL will be written to DB as field value.
|
||||
*
|
||||
* All that only works if the field is not within flex form scope since flex forms
|
||||
* can not store a "null" value or distinct it from "empty string".
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasNullCheckboxButNoPlaceholder(): bool
|
||||
{
|
||||
$hasNullCheckboxNoPlaceholder = false;
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
$mode = $parameterArray['fieldConf']['config']['mode'] ?? '';
|
||||
if (empty($this->data['flexFormDataStructureIdentifier'])
|
||||
&& !empty($parameterArray['fieldConf']['config']['eval'])
|
||||
&& GeneralUtility::inList($parameterArray['fieldConf']['config']['eval'], 'null')
|
||||
&& ($mode !== 'useOrOverridePlaceholder')
|
||||
) {
|
||||
$hasNullCheckboxNoPlaceholder = true;
|
||||
}
|
||||
return $hasNullCheckboxNoPlaceholder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the "null value" checkbox should be rendered and the placeholder
|
||||
* handling is enabled. This is used in some "text" based types like "text" and
|
||||
* "input" for some renderType's.
|
||||
*
|
||||
* A field has useOverridePlaceholder set and null in eval and is not within a flex form.
|
||||
* Here, a value from a deeper DB structure can be "fetched up" as value, and can also be overridden by a
|
||||
* local value. This is used in FAL, where eg. the "title" field can have the default value from sys_file_metadata,
|
||||
* the title field of sys_file_reference is then set to NULL. Or the "override" checkbox is set, and a string
|
||||
* or an empty string is then written to the field of sys_file_reference.
|
||||
* The situation is similar to hasNullCheckboxButNoPlaceholder(), but additionally a "default" value should be shown.
|
||||
* To achieve this, again a hidden control[hidden] field is added together with a checkbox with the same name
|
||||
* to transfer the information whether the default value should be used or not: Checkbox checked transfers 1 as
|
||||
* value in control[active], meaning the overridden value should be used.
|
||||
* Additionally to the casual input field, a second field is added containing the "placeholder" value. This
|
||||
* field has no name attribute and is not transferred at all. Those two are then hidden / shown depending
|
||||
* on the state of the above checkbox in via JS.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasNullCheckboxWithPlaceholder(): bool
|
||||
{
|
||||
$hasNullCheckboxWithPlaceholder = false;
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
$mode = $parameterArray['fieldConf']['config']['mode'] ?? '';
|
||||
if (empty($this->data['flexFormDataStructureIdentifier'])
|
||||
&& !empty($parameterArray['fieldConf']['config']['eval'])
|
||||
&& GeneralUtility::inList($parameterArray['fieldConf']['config']['eval'], 'null')
|
||||
&& ($mode === 'useOrOverridePlaceholder')
|
||||
) {
|
||||
$hasNullCheckboxWithPlaceholder = true;
|
||||
}
|
||||
return $hasNullCheckboxWithPlaceholder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format field content if 'format' is set to date, filesize, ..., user
|
||||
*
|
||||
* @param string $format Configuration for the display.
|
||||
* @param string $itemValue The value to display
|
||||
* @param array $formatOptions Format options
|
||||
* @return string Formatted field value
|
||||
*/
|
||||
protected function formatValue($format, $itemValue, $formatOptions = [])
|
||||
{
|
||||
switch ($format) {
|
||||
case 'date':
|
||||
if ($itemValue) {
|
||||
$option = isset($formatOptions['option']) ? trim($formatOptions['option']) : '';
|
||||
if ($option) {
|
||||
if (isset($formatOptions['strftime']) && $formatOptions['strftime']) {
|
||||
$value = strftime($option, $itemValue);
|
||||
} else {
|
||||
$value = date($option, $itemValue);
|
||||
}
|
||||
} else {
|
||||
$value = date('d-m-Y', $itemValue);
|
||||
}
|
||||
} else {
|
||||
$value = '';
|
||||
}
|
||||
if (isset($formatOptions['appendAge']) && $formatOptions['appendAge']) {
|
||||
$age = BackendUtility::calcAge(
|
||||
$GLOBALS['EXEC_TIME'] - $itemValue,
|
||||
$this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.minutesHoursDaysYears')
|
||||
);
|
||||
$value .= ' (' . $age . ')';
|
||||
}
|
||||
$itemValue = $value;
|
||||
break;
|
||||
case 'datetime':
|
||||
// compatibility with "eval" (type "input")
|
||||
if ($itemValue !== '' && $itemValue !== null) {
|
||||
$itemValue = BackendUtility::datetime((int)$itemValue);
|
||||
}
|
||||
break;
|
||||
case 'time':
|
||||
// compatibility with "eval" (type "input")
|
||||
if ($itemValue !== '' && $itemValue !== null) {
|
||||
$itemValue = BackendUtility::time((int)$itemValue, false);
|
||||
}
|
||||
break;
|
||||
case 'timesec':
|
||||
// compatibility with "eval" (type "input")
|
||||
if ($itemValue !== '' && $itemValue !== null) {
|
||||
$itemValue = BackendUtility::time((int)$itemValue);
|
||||
}
|
||||
break;
|
||||
case 'year':
|
||||
// compatibility with "eval" (type "input")
|
||||
if ($itemValue !== '' && $itemValue !== null) {
|
||||
$itemValue = date('Y', (int)$itemValue);
|
||||
}
|
||||
break;
|
||||
case 'int':
|
||||
$baseArr = ['dec' => 'd', 'hex' => 'x', 'HEX' => 'X', 'oct' => 'o', 'bin' => 'b'];
|
||||
$base = isset($formatOptions['base']) ? trim($formatOptions['base']) : '';
|
||||
$format = $baseArr[$base] ?? 'd';
|
||||
$itemValue = sprintf('%' . $format, $itemValue);
|
||||
break;
|
||||
case 'float':
|
||||
// default precision
|
||||
$precision = 2;
|
||||
if (isset($formatOptions['precision'])) {
|
||||
$precision = MathUtility::forceIntegerInRange($formatOptions['precision'], 1, 10, $precision);
|
||||
}
|
||||
$itemValue = sprintf('%.' . $precision . 'f', $itemValue);
|
||||
break;
|
||||
case 'number':
|
||||
$format = isset($formatOptions['option']) ? trim($formatOptions['option']) : '';
|
||||
$itemValue = sprintf('%' . $format, $itemValue);
|
||||
break;
|
||||
case 'md5':
|
||||
$itemValue = md5($itemValue);
|
||||
break;
|
||||
case 'filesize':
|
||||
// We need to cast to int here, otherwise empty values result in empty output,
|
||||
// but we expect zero.
|
||||
$value = GeneralUtility::formatSize((int)$itemValue);
|
||||
if (!empty($formatOptions['appendByteSize'])) {
|
||||
$value .= ' (' . $itemValue . ')';
|
||||
}
|
||||
$itemValue = $value;
|
||||
break;
|
||||
case 'user':
|
||||
$func = trim($formatOptions['userFunc']);
|
||||
if ($func) {
|
||||
$params = [
|
||||
'value' => $itemValue,
|
||||
'args' => $formatOptions['userFunc'],
|
||||
'config' => [
|
||||
'type' => 'none',
|
||||
'format' => $format,
|
||||
'format.' => $formatOptions,
|
||||
],
|
||||
];
|
||||
$itemValue = GeneralUtility::callUserFunction($func, $params, $this);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Do nothing e.g. when $format === ''
|
||||
}
|
||||
return $itemValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the max width in pixels for an elements like input and text
|
||||
*
|
||||
* @param int $size The abstract size value (1-48)
|
||||
* @return int Maximum width in pixels
|
||||
*/
|
||||
protected function formMaxWidth($size = 48)
|
||||
{
|
||||
$compensationForLargeDocuments = 1.33;
|
||||
$compensationForFormFields = 12;
|
||||
|
||||
$size = round($size * $compensationForLargeDocuments);
|
||||
return ceil($size * $compensationForFormFields);
|
||||
}
|
||||
|
||||
/***********************************************
|
||||
* CheckboxElement related methods
|
||||
***********************************************/
|
||||
|
||||
/**
|
||||
* Creates checkbox parameters
|
||||
*
|
||||
* @param string $itemName Form element name
|
||||
* @param int $formElementValue The value of the checkbox (representing checkboxes with the bits)
|
||||
* @param int $checkbox Checkbox # (0-9?)
|
||||
* @param int $checkboxesCount Total number of checkboxes in the array.
|
||||
* @param string $additionalJavaScript Additional JavaScript for the onclick handler.
|
||||
* @return string The onclick attribute + possibly the checked-option set.
|
||||
* @internal
|
||||
*/
|
||||
protected function checkBoxParams($itemName, $formElementValue, $checkbox, $checkboxesCount, $additionalJavaScript = ''): string
|
||||
{
|
||||
$elementName = 'document.editform[' . GeneralUtility::quoteJSvalue($itemName) . ']';
|
||||
$checkboxPow = 2 ** $checkbox;
|
||||
$onClick = $elementName . '.value=this.checked?(' . $elementName . '.value|' . $checkboxPow . '):('
|
||||
. $elementName . '.value&' . ((2 ** $checkboxesCount) - 1 - $checkboxPow) . ');' . $additionalJavaScript;
|
||||
return ' onclick="' . htmlspecialchars($onClick) . '"' . ($formElementValue & $checkboxPow ? ' checked="checked"' : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the bootstrap grid classes based on the amount of columns
|
||||
* defined in the checkbox item TCA
|
||||
*
|
||||
* @param $cols
|
||||
* @return array
|
||||
* @internal
|
||||
*/
|
||||
protected function calculateColumnMarkup(int $cols): array
|
||||
{
|
||||
$colWidth = (int)floor(12 / $cols);
|
||||
$colClass = 'col-md-12';
|
||||
$colClear = [];
|
||||
if ($colWidth === 6) {
|
||||
$colClass = 'col-sm-6';
|
||||
$colClear = [
|
||||
2 => 'visible-sm-block visible-md-block visible-lg-block',
|
||||
];
|
||||
} elseif ($colWidth === 4) {
|
||||
$colClass = 'col-sm-4';
|
||||
$colClear = [
|
||||
3 => 'visible-sm-block visible-md-block visible-lg-block',
|
||||
];
|
||||
} elseif ($colWidth === 3) {
|
||||
$colClass = 'col-sm-6 col-md-3';
|
||||
$colClear = [
|
||||
2 => 'visible-sm-block',
|
||||
4 => 'visible-md-block visible-lg-block',
|
||||
];
|
||||
} elseif ($colWidth <= 2) {
|
||||
$colClass = 'checkbox-column col-sm-6 col-md-3 col-lg-2';
|
||||
$colClear = [
|
||||
2 => 'visible-sm-block',
|
||||
4 => 'visible-md-block',
|
||||
6 => 'visible-lg-block'
|
||||
];
|
||||
}
|
||||
return [$colClass, $colClear];
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the value of a form field to its label
|
||||
*
|
||||
* @param string|int $label The label which can also be an integer
|
||||
* @param string|int $value The value which can also be an integer
|
||||
* @return string|int
|
||||
*/
|
||||
protected function appendValueToLabelInDebugMode($label, $value)
|
||||
{
|
||||
if ($value !== '' && $GLOBALS['TYPO3_CONF_VARS']['BE']['debug'] && $this->getBackendUser()->isAdmin()) {
|
||||
return $label . ' [' . $value . ']';
|
||||
}
|
||||
|
||||
return $label;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService()
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current BE user.
|
||||
*
|
||||
* @return BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUser()
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Form\Element;
|
||||
|
||||
/*
|
||||
* 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\NodeFactory;
|
||||
use TYPO3\CMS\Core\Imaging\Icon;
|
||||
use TYPO3\CMS\Core\Imaging\IconRegistry;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\StringUtility;
|
||||
|
||||
/**
|
||||
* Generation of TCEform elements of the type "check"
|
||||
*/
|
||||
class CheckboxElement extends AbstractFormElement
|
||||
{
|
||||
/**
|
||||
* @var IconRegistry
|
||||
*/
|
||||
private $iconRegistry;
|
||||
|
||||
/**
|
||||
* Default field information enabled for this element.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldInformation = [
|
||||
'tcaDescription' => [
|
||||
'renderType' => 'tcaDescription',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Default field wizards enabled for this element.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldWizard = [
|
||||
'localizationStateSelector' => [
|
||||
'renderType' => 'localizationStateSelector',
|
||||
],
|
||||
'otherLanguageContent' => [
|
||||
'renderType' => 'otherLanguageContent',
|
||||
'after' => [
|
||||
'localizationStateSelector'
|
||||
],
|
||||
],
|
||||
'defaultLanguageDifferences' => [
|
||||
'renderType' => 'defaultLanguageDifferences',
|
||||
'after' => [
|
||||
'otherLanguageContent',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* @param NodeFactory $nodeFactory
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct(NodeFactory $nodeFactory, array $data)
|
||||
{
|
||||
parent::__construct($nodeFactory, $data);
|
||||
$this->iconRegistry = GeneralUtility::makeInstance(IconRegistry::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will render a checkbox or an array of checkboxes
|
||||
*
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
*/
|
||||
public function render(): array
|
||||
{
|
||||
$resultArray = $this->initializeResultArray();
|
||||
|
||||
$elementHtml = '';
|
||||
$disabled = false;
|
||||
if ($this->data['parameterArray']['fieldConf']['config']['readOnly']) {
|
||||
$disabled = true;
|
||||
}
|
||||
// Traversing the array of items
|
||||
$items = $this->data['parameterArray']['fieldConf']['config']['items'];
|
||||
|
||||
$numberOfItems = \count($items);
|
||||
if ($numberOfItems === 0) {
|
||||
$items[] = ['', ''];
|
||||
$numberOfItems = 1;
|
||||
}
|
||||
$formElementValue = (int)$this->data['parameterArray']['itemFormElValue'];
|
||||
$cols = (int)$this->data['parameterArray']['fieldConf']['config']['cols'];
|
||||
if ($cols > 1) {
|
||||
[$colClass, $colClear] = $this->calculateColumnMarkup($cols);
|
||||
$elementHtml .= '<div class="checkbox-row row">';
|
||||
$counter = 0;
|
||||
// $itemKey is important here, because items could have been removed via TSConfig
|
||||
foreach ($items as $itemKey => $itemDefinition) {
|
||||
$label = $itemDefinition[0];
|
||||
$elementHtml .=
|
||||
'<div class="checkbox-column ' . $colClass . '">'
|
||||
. $this->renderSingleCheckboxElement($label, $itemKey, $formElementValue, $numberOfItems, $this->data['parameterArray'], $disabled) .
|
||||
'</div>';
|
||||
++$counter;
|
||||
if ($counter < $numberOfItems && !empty($colClear)) {
|
||||
foreach ($colClear as $rowBreakAfter => $clearClass) {
|
||||
if ($counter % $rowBreakAfter === 0) {
|
||||
$elementHtml .= '<div class="clearfix ' . $clearClass . '"></div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$elementHtml .= '</div>';
|
||||
} else {
|
||||
$counter = 0;
|
||||
foreach ($items as $itemKey => $itemDefinition) {
|
||||
$label = $itemDefinition[0];
|
||||
$elementHtml .= $this->renderSingleCheckboxElement($label, $counter, $formElementValue, $numberOfItems, $this->data['parameterArray'], $disabled);
|
||||
++$counter;
|
||||
}
|
||||
}
|
||||
if (!$disabled) {
|
||||
$elementHtml .= '<input type="hidden" name="' . htmlspecialchars($this->data['parameterArray']['itemFormElName']) . '" value="' . htmlspecialchars((string)$formElementValue) . '" />';
|
||||
}
|
||||
|
||||
$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">';
|
||||
$html[] = $elementHtml;
|
||||
$html[] = '</div>';
|
||||
if (!$disabled && !empty($fieldWizardHtml)) {
|
||||
$html[] = '<div class="form-wizards-items-bottom">';
|
||||
$html[] = $fieldWizardHtml;
|
||||
$html[] = '</div>';
|
||||
}
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
|
||||
$resultArray['html'] = implode(LF, $html);
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* This functions builds the HTML output for the checkbox
|
||||
*
|
||||
* @param string $label Label of this item
|
||||
* @param int $itemCounter Number of this element in the list of all elements
|
||||
* @param int $formElementValue Value of this element
|
||||
* @param int $numberOfItems Full number of items
|
||||
* @param array $additionalInformation Information with additional configuration options.
|
||||
* @param bool $disabled TRUE if form element is disabled
|
||||
* @return string Single element HTML
|
||||
*/
|
||||
protected function renderSingleCheckboxElement($label, $itemCounter, $formElementValue, $numberOfItems, $additionalInformation, $disabled): string
|
||||
{
|
||||
$config = $additionalInformation['fieldConf']['config'];
|
||||
$inline = !empty($config['cols']) && $config['cols'] === 'inline';
|
||||
$invert = isset($config['items'][$itemCounter]['invertStateDisplay']) && $config['items'][$itemCounter]['invertStateDisplay'] === true;
|
||||
$checkboxParameters = $this->checkBoxParams(
|
||||
$additionalInformation['itemFormElName'],
|
||||
$formElementValue,
|
||||
$itemCounter,
|
||||
$numberOfItems,
|
||||
implode('', $additionalInformation['fieldChangeFunc'])
|
||||
);
|
||||
$uniqueId = StringUtility::getUniqueId('_');
|
||||
$checkboxId = $additionalInformation['itemFormElID'] . '_' . $itemCounter . $uniqueId;
|
||||
|
||||
$iconIdentifierChecked = !empty($config['items'][$itemCounter]['iconIdentifierChecked']) ? $config['items'][$itemCounter]['iconIdentifierChecked'] : 'actions-check';
|
||||
if (!$this->iconRegistry->isRegistered($iconIdentifierChecked)) {
|
||||
$iconIdentifierChecked = 'actions-check';
|
||||
}
|
||||
$iconIdentifierUnchecked = !empty($config['items'][$itemCounter]['iconIdentifierUnchecked']) ? $config['items'][$itemCounter]['iconIdentifierUnchecked'] : 'empty-empty';
|
||||
if (!$this->iconRegistry->isRegistered($iconIdentifierUnchecked)) {
|
||||
$iconIdentifierUnchecked = 'empty-empty';
|
||||
}
|
||||
$iconChecked = $this->iconFactory->getIcon($iconIdentifierChecked, Icon::SIZE_SMALL)->render('inline');
|
||||
$iconUnchecked = $this->iconFactory->getIcon($iconIdentifierUnchecked, Icon::SIZE_SMALL)->render('inline');
|
||||
|
||||
return '
|
||||
<div class="checkbox checkbox-type-icon-toggle' . ($invert ? ' checkbox-invert' : '') . ($inline ? ' checkbox-inline' : '') . (!$disabled ? '' : ' disabled') . '">
|
||||
<input type="checkbox"
|
||||
class="checkbox-input"
|
||||
value="1"
|
||||
data-formengine-input-name="' . htmlspecialchars($additionalInformation['itemFormElName']) . '"
|
||||
' . $checkboxParameters . '
|
||||
' . ($disabled ? ' disabled="disabled"' : '') . '
|
||||
id="' . $checkboxId . '" />
|
||||
<label class="checkbox-label" for="' . $checkboxId . '">
|
||||
<span class="checkbox-label-icon">
|
||||
<span class="checkbox-label-icon-checked">' . ($invert ? $iconUnchecked : $iconChecked) . '</span>
|
||||
<span class="checkbox-label-icon-unchecked">' . ($invert ? $iconChecked : $iconUnchecked) . '</span>
|
||||
</span>
|
||||
<span class="checkbox-label-text">' . $this->appendValueToLabelInDebugMode(($label ? htmlspecialchars($label) : ' '), $formElementValue) . '</span>
|
||||
</label>
|
||||
</div>';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Form\Element;
|
||||
|
||||
/*
|
||||
* 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\NodeFactory;
|
||||
use TYPO3\CMS\Core\Imaging\IconRegistry;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\StringUtility;
|
||||
|
||||
/**
|
||||
* Generation of TCEform elements of the type "check"
|
||||
*/
|
||||
class CheckboxLabeledToggleElement extends AbstractFormElement
|
||||
{
|
||||
/**
|
||||
* @var IconRegistry
|
||||
*/
|
||||
private $iconRegistry;
|
||||
|
||||
/**
|
||||
* Default field information enabled for this element.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldInformation = [
|
||||
'tcaDescription' => [
|
||||
'renderType' => 'tcaDescription',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Default field wizards enabled for this element.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldWizard = [
|
||||
'localizationStateSelector' => [
|
||||
'renderType' => 'localizationStateSelector',
|
||||
],
|
||||
'otherLanguageContent' => [
|
||||
'renderType' => 'otherLanguageContent',
|
||||
'after' => [
|
||||
'localizationStateSelector'
|
||||
],
|
||||
],
|
||||
'defaultLanguageDifferences' => [
|
||||
'renderType' => 'defaultLanguageDifferences',
|
||||
'after' => [
|
||||
'otherLanguageContent',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* @param NodeFactory $nodeFactory
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct(NodeFactory $nodeFactory, array $data)
|
||||
{
|
||||
parent::__construct($nodeFactory, $data);
|
||||
$this->iconRegistry = GeneralUtility::makeInstance(IconRegistry::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will render a checkbox or an array of checkboxes
|
||||
*
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
*/
|
||||
public function render(): array
|
||||
{
|
||||
$resultArray = $this->initializeResultArray();
|
||||
|
||||
$elementHtml = '';
|
||||
$disabled = false;
|
||||
if ($this->data['parameterArray']['fieldConf']['config']['readOnly']) {
|
||||
$disabled = true;
|
||||
}
|
||||
// Traversing the array of items
|
||||
$items = $this->data['parameterArray']['fieldConf']['config']['items'];
|
||||
|
||||
$numberOfItems = \count($items);
|
||||
if ($numberOfItems === 0) {
|
||||
$items[] = ['', ''];
|
||||
$numberOfItems = 1;
|
||||
}
|
||||
$formElementValue = (int)$this->data['parameterArray']['itemFormElValue'];
|
||||
$cols = (int)$this->data['parameterArray']['fieldConf']['config']['cols'];
|
||||
if ($cols > 1) {
|
||||
[$colClass, $colClear] = $this->calculateColumnMarkup($cols);
|
||||
$elementHtml .= '<div class="checkbox-row row">';
|
||||
$counter = 0;
|
||||
// $itemKey is important here, because items could have been removed via TSConfig
|
||||
foreach ($items as $itemKey => $itemDefinition) {
|
||||
$label = $itemDefinition[0];
|
||||
$elementHtml .=
|
||||
'<div class="checkbox-column ' . $colClass . '">'
|
||||
. $this->renderSingleCheckboxElement($label, $itemKey, $formElementValue, $numberOfItems, $this->data['parameterArray'], $disabled) .
|
||||
'</div>';
|
||||
++$counter;
|
||||
if ($counter < $numberOfItems && !empty($colClear)) {
|
||||
foreach ($colClear as $rowBreakAfter => $clearClass) {
|
||||
if ($counter % $rowBreakAfter === 0) {
|
||||
$elementHtml .= '<div class="clearfix ' . $clearClass . '"></div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$elementHtml .= '</div>';
|
||||
} else {
|
||||
$counter = 0;
|
||||
foreach ($items as $itemKey => $itemDefinition) {
|
||||
$label = $itemDefinition[0];
|
||||
$elementHtml .= $this->renderSingleCheckboxElement($label, $counter, $formElementValue, $numberOfItems, $this->data['parameterArray'], $disabled);
|
||||
++$counter;
|
||||
}
|
||||
}
|
||||
if (!$disabled) {
|
||||
$elementHtml .= '<input type="hidden" name="' . htmlspecialchars($this->data['parameterArray']['itemFormElName']) . '" value="' . htmlspecialchars((string)$formElementValue) . '" />';
|
||||
}
|
||||
|
||||
$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">';
|
||||
$html[] = $elementHtml;
|
||||
$html[] = '</div>';
|
||||
if (!$disabled && !empty($fieldWizardHtml)) {
|
||||
$html[] = '<div class="form-wizards-items-bottom">';
|
||||
$html[] = $fieldWizardHtml;
|
||||
$html[] = '</div>';
|
||||
}
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
|
||||
$resultArray['html'] = implode(LF, $html);
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* This functions builds the HTML output for the checkbox
|
||||
*
|
||||
* @param string $label Label of this item
|
||||
* @param int $itemCounter Number of this element in the list of all elements
|
||||
* @param int $formElementValue Value of this element
|
||||
* @param int $numberOfItems Full number of items
|
||||
* @param array $additionalInformation Information with additional configuration options.
|
||||
* @param bool $disabled TRUE if form element is disabled
|
||||
* @return string Single element HTML
|
||||
*/
|
||||
protected function renderSingleCheckboxElement($label, $itemCounter, $formElementValue, $numberOfItems, $additionalInformation, $disabled): string
|
||||
{
|
||||
$config = $additionalInformation['fieldConf']['config'];
|
||||
$inline = !empty($config['cols']) && $config['cols'] === 'inline';
|
||||
$invert = isset($config['items'][$itemCounter]['invertStateDisplay']) && $config['items'][$itemCounter]['invertStateDisplay'] === true;
|
||||
$checkboxParameters = $this->checkBoxParams(
|
||||
$additionalInformation['itemFormElName'],
|
||||
$formElementValue,
|
||||
$itemCounter,
|
||||
$numberOfItems,
|
||||
implode('', $additionalInformation['fieldChangeFunc'])
|
||||
);
|
||||
$uniqueId = StringUtility::getUniqueId('_');
|
||||
$checkboxId = $additionalInformation['itemFormElID'] . '_' . $itemCounter . $uniqueId;
|
||||
return '
|
||||
<div class="checkbox checkbox-type-labeled-toggle' . ($invert ? ' checkbox-invert' : '') . ($inline ? ' checkbox-inline' : '') . (!$disabled ? '' : ' disabled') . '">
|
||||
<input type="checkbox"
|
||||
class="checkbox-input"
|
||||
value="1"
|
||||
data-formengine-input-name="' . htmlspecialchars($additionalInformation['itemFormElName']) . '"
|
||||
' . $checkboxParameters . '
|
||||
' . (!$disabled ?: ' disabled="disabled"') . '
|
||||
id="' . $checkboxId . '" />
|
||||
<label class="checkbox-label" for="' . $checkboxId . '">
|
||||
<span class="checkbox-label-switch">
|
||||
<span class="checkbox-label-switch-checked">
|
||||
' . $config['items'][$itemCounter]['labelChecked'] . '
|
||||
</span>
|
||||
<span class="checkbox-label-switch-unchecked">
|
||||
' . $config['items'][$itemCounter]['labelUnchecked'] . '
|
||||
</span>
|
||||
</span>
|
||||
<span class="checkbox-label-text">' . $this->appendValueToLabelInDebugMode(($label ? htmlspecialchars($label) : ' '), $formElementValue) . '</span>
|
||||
</label>
|
||||
</div>';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Form\Element;
|
||||
|
||||
/*
|
||||
* 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\NodeFactory;
|
||||
use TYPO3\CMS\Core\Imaging\IconRegistry;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\StringUtility;
|
||||
|
||||
/**
|
||||
* Generation of TCEform elements of the type "check"
|
||||
*/
|
||||
class CheckboxToggleElement extends AbstractFormElement
|
||||
{
|
||||
/**
|
||||
* @var IconRegistry
|
||||
*/
|
||||
private $iconRegistry;
|
||||
|
||||
/**
|
||||
* Default field information enabled for this element.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldInformation = [
|
||||
'tcaDescription' => [
|
||||
'renderType' => 'tcaDescription',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Default field wizards enabled for this element.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldWizard = [
|
||||
'localizationStateSelector' => [
|
||||
'renderType' => 'localizationStateSelector',
|
||||
],
|
||||
'otherLanguageContent' => [
|
||||
'renderType' => 'otherLanguageContent',
|
||||
'after' => [
|
||||
'localizationStateSelector'
|
||||
],
|
||||
],
|
||||
'defaultLanguageDifferences' => [
|
||||
'renderType' => 'defaultLanguageDifferences',
|
||||
'after' => [
|
||||
'otherLanguageContent',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* @param NodeFactory $nodeFactory
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct(NodeFactory $nodeFactory, array $data)
|
||||
{
|
||||
parent::__construct($nodeFactory, $data);
|
||||
$this->iconRegistry = GeneralUtility::makeInstance(IconRegistry::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will render a checkbox or an array of checkboxes
|
||||
*
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
*/
|
||||
public function render(): array
|
||||
{
|
||||
$resultArray = $this->initializeResultArray();
|
||||
|
||||
$elementHtml = '';
|
||||
$disabled = false;
|
||||
if ($this->data['parameterArray']['fieldConf']['config']['readOnly']) {
|
||||
$disabled = true;
|
||||
}
|
||||
// Traversing the array of items
|
||||
$items = $this->data['parameterArray']['fieldConf']['config']['items'];
|
||||
|
||||
$numberOfItems = count($items);
|
||||
if ($numberOfItems === 0) {
|
||||
$items[] = ['', ''];
|
||||
$numberOfItems = 1;
|
||||
}
|
||||
$formElementValue = (int)$this->data['parameterArray']['itemFormElValue'];
|
||||
$cols = (int)$this->data['parameterArray']['fieldConf']['config']['cols'];
|
||||
if ($cols > 1) {
|
||||
[$colClass, $colClear] = $this->calculateColumnMarkup($cols);
|
||||
$elementHtml .= '<div class="checkbox-row row">';
|
||||
$counter = 0;
|
||||
// $itemKey is important here, because items could have been removed via TSConfig
|
||||
foreach ($items as $itemKey => $itemDefinition) {
|
||||
$label = $itemDefinition[0];
|
||||
$elementHtml .=
|
||||
'<div class="checkbox-column ' . $colClass . '">'
|
||||
. $this->renderSingleCheckboxElement($label, $itemKey, $formElementValue, $numberOfItems, $this->data['parameterArray'], $disabled) .
|
||||
'</div>';
|
||||
++$counter;
|
||||
if ($counter < $numberOfItems && !empty($colClear)) {
|
||||
foreach ($colClear as $rowBreakAfter => $clearClass) {
|
||||
if ($counter % $rowBreakAfter === 0) {
|
||||
$elementHtml .= '<div class="clearfix ' . $clearClass . '"></div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$elementHtml .= '</div>';
|
||||
} else {
|
||||
$counter = 0;
|
||||
foreach ($items as $itemKey => $itemDefinition) {
|
||||
$label = $itemDefinition[0];
|
||||
$elementHtml .= $this->renderSingleCheckboxElement($label, $counter, $formElementValue, $numberOfItems, $this->data['parameterArray'], $disabled);
|
||||
++$counter;
|
||||
}
|
||||
}
|
||||
if (!$disabled) {
|
||||
$elementHtml .= '<input type="hidden" name="' . htmlspecialchars($this->data['parameterArray']['itemFormElName']) . '" value="' . htmlspecialchars((string)$formElementValue) . '" />';
|
||||
}
|
||||
|
||||
$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">';
|
||||
$html[] = $elementHtml;
|
||||
$html[] = '</div>';
|
||||
if (!$disabled && !empty($fieldWizardHtml)) {
|
||||
$html[] = '<div class="form-wizards-items-bottom">';
|
||||
$html[] = $fieldWizardHtml;
|
||||
$html[] = '</div>';
|
||||
}
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
|
||||
$resultArray['html'] = implode(LF, $html);
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* This functions builds the HTML output for the checkbox
|
||||
*
|
||||
* @param string $label Label of this item
|
||||
* @param int $itemCounter Number of this element in the list of all elements
|
||||
* @param int $formElementValue Value of this element
|
||||
* @param int $numberOfItems Full number of items
|
||||
* @param array $additionalInformation Information with additional configuration options.
|
||||
* @param bool $disabled TRUE if form element is disabled
|
||||
* @return string Single element HTML
|
||||
*/
|
||||
protected function renderSingleCheckboxElement($label, $itemCounter, $formElementValue, $numberOfItems, $additionalInformation, $disabled): string
|
||||
{
|
||||
$config = $additionalInformation['fieldConf']['config'];
|
||||
$inline = !empty($config['cols']) && $config['cols'] === 'inline';
|
||||
$invert = isset($config['items'][0]['invertStateDisplay']) && $config['items'][0]['invertStateDisplay'] === true;
|
||||
$checkboxParameters = $this->checkBoxParams(
|
||||
$additionalInformation['itemFormElName'],
|
||||
$formElementValue,
|
||||
$itemCounter,
|
||||
$numberOfItems,
|
||||
implode('', $additionalInformation['fieldChangeFunc'])
|
||||
);
|
||||
$uniqueId = StringUtility::getUniqueId('_');
|
||||
$checkboxId = $additionalInformation['itemFormElID'] . '_' . $itemCounter . $uniqueId;
|
||||
return '
|
||||
<div class="checkbox checkbox-type-toggle' . ($invert ? ' checkbox-invert' : '') . ($inline ? ' checkbox-inline' : '') . (!$disabled ? '' : ' disabled') . '">
|
||||
<input type="checkbox"
|
||||
class="checkbox-input"
|
||||
value="1"
|
||||
data-formengine-input-name="' . htmlspecialchars($additionalInformation['itemFormElName']) . '"
|
||||
' . $checkboxParameters . '
|
||||
' . (!$disabled ?: ' disabled="disabled"') . '
|
||||
id="' . $checkboxId . '" />
|
||||
<label class="checkbox-label" for="' . $checkboxId . '">
|
||||
<span class="checkbox-label-text">' . $this->appendValueToLabelInDebugMode(($label ? htmlspecialchars($label) : ' '), $formElementValue) . '</span>
|
||||
</label>
|
||||
</div>';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Form\Element;
|
||||
|
||||
/*
|
||||
* 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\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Resource\File;
|
||||
use TYPO3\CMS\Core\Resource\ProcessedFile;
|
||||
use TYPO3\CMS\Core\Resource\ResourceFactory;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* This renderType is used with type=user in FAL for table sys_file and
|
||||
* sys_file_metadata, for field fileinfo and renders an informational
|
||||
* element with image preview, filename, size and similar.
|
||||
*/
|
||||
class FileInfoElement extends AbstractFormElement
|
||||
{
|
||||
|
||||
/**
|
||||
* Handler for single nodes
|
||||
*
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
*/
|
||||
public function render(): array
|
||||
{
|
||||
$resultArray = $this->initializeResultArray();
|
||||
|
||||
$fileUid = 0;
|
||||
if ($this->data['tableName'] === 'sys_file') {
|
||||
$fileUid = (int)$this->data['databaseRow']['uid'];
|
||||
} elseif ($this->data['tableName'] === 'sys_file_metadata') {
|
||||
$fileUid = (int)$this->data['databaseRow']['file'][0];
|
||||
}
|
||||
|
||||
$fileObject = null;
|
||||
if ($fileUid > 0) {
|
||||
$fileObject = ResourceFactory::getInstance()->getFileObject($fileUid);
|
||||
}
|
||||
$resultArray['html'] = $this->renderFileInformationContent($fileObject);
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a HTML Block with file information
|
||||
*
|
||||
* @param File $file
|
||||
* @return string
|
||||
*/
|
||||
protected function renderFileInformationContent(File $file = null): string
|
||||
{
|
||||
/** @var LanguageService $lang */
|
||||
$lang = $GLOBALS['LANG'];
|
||||
|
||||
if ($file !== null) {
|
||||
$content = '';
|
||||
if ($file->isMissing()) {
|
||||
$content .= '<span class="label label-danger label-space-right">'
|
||||
. htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.file_missing'))
|
||||
. '</span>';
|
||||
}
|
||||
if (GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'], $file->getExtension())) {
|
||||
$processedFile = $file->process(ProcessedFile::CONTEXT_IMAGEPREVIEW, ['width' => 150, 'height' => 150]);
|
||||
$previewImage = $processedFile->getPublicUrl(true);
|
||||
if ($previewImage) {
|
||||
$content .= '<img src="' . htmlspecialchars($previewImage) . '" ' .
|
||||
'width="' . $processedFile->getProperty('width') . '" ' .
|
||||
'height="' . $processedFile->getProperty('height') . '" ' .
|
||||
'alt="" class="t3-tceforms-sysfile-imagepreview" />';
|
||||
}
|
||||
}
|
||||
$content .= '<strong>' . htmlspecialchars($file->getName()) . '</strong>';
|
||||
$content .= ' (' . htmlspecialchars(GeneralUtility::formatSize($file->getSize())) . 'bytes)<br />';
|
||||
$content .= BackendUtility::getProcessedValue('sys_file', 'type', $file->getType()) . ' (' . $file->getMimeType() . ')<br />';
|
||||
$content .= htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:fileMetaDataLocation')) . ': ';
|
||||
$content .= htmlspecialchars($file->getStorage()->getName()) . ' - ' . htmlspecialchars($file->getIdentifier()) . '<br />';
|
||||
$content .= '<br />';
|
||||
} else {
|
||||
$content = '<h2>' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:fileMetaErrorInvalidRecord')) . '</h2>';
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,425 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\Element;
|
||||
|
||||
/*
|
||||
* 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\InlineStackProcessor;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Imaging\Icon;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
use TYPO3\CMS\Core\Utility\StringUtility;
|
||||
|
||||
/**
|
||||
* Generation of elements of the type "group"
|
||||
*/
|
||||
class GroupElement extends AbstractFormElement
|
||||
{
|
||||
/**
|
||||
* Default field information enabled for this element.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldInformation = [
|
||||
'tcaDescription' => [
|
||||
'renderType' => 'tcaDescription',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Default field controls for this element.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldControl = [
|
||||
'elementBrowser' => [
|
||||
'renderType' => 'elementBrowser',
|
||||
],
|
||||
'insertClipboard' => [
|
||||
'renderType' => 'insertClipboard',
|
||||
'after' => [ 'elementBrowser' ],
|
||||
],
|
||||
'editPopup' => [
|
||||
'renderType' => 'editPopup',
|
||||
'disabled' => true,
|
||||
'after' => [ 'insertClipboard' ],
|
||||
],
|
||||
'addRecord' => [
|
||||
'renderType' => 'addRecord',
|
||||
'disabled' => true,
|
||||
'after' => [ 'editPopup' ],
|
||||
],
|
||||
'listModule' => [
|
||||
'renderType' => 'listModule',
|
||||
'disabled' => true,
|
||||
'after' => [ 'addRecord' ],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Default field wizards for this element
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldWizard = [
|
||||
'tableList' => [
|
||||
'renderType' => 'tableList',
|
||||
],
|
||||
'fileTypeList' => [
|
||||
'renderType' => 'fileTypeList',
|
||||
'after' => [ 'tableList' ],
|
||||
],
|
||||
'fileThumbnails' => [
|
||||
'renderType' => 'fileThumbnails',
|
||||
'after' => [ 'fileTypeList' ],
|
||||
],
|
||||
'recordsOverview' => [
|
||||
'renderType' => 'recordsOverview',
|
||||
'after' => [ 'fileThumbnails' ],
|
||||
],
|
||||
'fileUpload' => [
|
||||
'renderType' => 'fileUpload',
|
||||
'after' => [ 'recordsOverview' ],
|
||||
],
|
||||
'localizationStateSelector' => [
|
||||
'renderType' => 'localizationStateSelector',
|
||||
'after' => [ 'fileUpload' ],
|
||||
],
|
||||
'otherLanguageContent' => [
|
||||
'renderType' => 'otherLanguageContent',
|
||||
'after' => [ 'localizationStateSelector' ],
|
||||
],
|
||||
'defaultLanguageDifferences' => [
|
||||
'renderType' => 'defaultLanguageDifferences',
|
||||
'after' => [ 'otherLanguageContent' ],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* This will render a selector box into which elements from either
|
||||
* the file system or database can be inserted. Relations.
|
||||
*
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
$backendUser = $this->getBackendUserAuthentication();
|
||||
$resultArray = $this->initializeResultArray();
|
||||
|
||||
$table = $this->data['tableName'];
|
||||
$fieldName = $this->data['fieldName'];
|
||||
$row = $this->data['databaseRow'];
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
$config = $parameterArray['fieldConf']['config'];
|
||||
$elementName = $parameterArray['itemFormElName'];
|
||||
|
||||
$selectedItems = $parameterArray['itemFormElValue'];
|
||||
$selectedItemsCount = count($selectedItems);
|
||||
|
||||
$maxItems = $config['maxitems'];
|
||||
$autoSizeMax = MathUtility::forceIntegerInRange($config['autoSizeMax'], 0);
|
||||
$size = 5;
|
||||
if (isset($config['size'])) {
|
||||
$size = (int)$config['size'];
|
||||
}
|
||||
if ($autoSizeMax >= 1) {
|
||||
$size = MathUtility::forceIntegerInRange($selectedItemsCount + 1, MathUtility::forceIntegerInRange($size, 1), $autoSizeMax);
|
||||
}
|
||||
|
||||
$internalType = (string)$config['internal_type'];
|
||||
$maxTitleLength = $backendUser->uc['titleLen'];
|
||||
|
||||
$listOfSelectedValues = [];
|
||||
$selectorOptionsHtml = [];
|
||||
if ($internalType === 'file_reference' || $internalType === 'file') {
|
||||
// @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0. Deprecation logged by TcaMigration class.
|
||||
foreach ($selectedItems as $selectedItem) {
|
||||
$uidOrPath = $selectedItem['uidOrPath'];
|
||||
$listOfSelectedValues[] = $uidOrPath;
|
||||
$title = $selectedItem['title'];
|
||||
$shortenedTitle = GeneralUtility::fixed_lgd_cs($title, $maxTitleLength);
|
||||
$selectorOptionsHtml[] =
|
||||
'<option value="' . htmlspecialchars($uidOrPath) . '" title="' . htmlspecialchars($title) . '">'
|
||||
. htmlspecialchars($this->appendValueToLabelInDebugMode($shortenedTitle, $uidOrPath))
|
||||
. '</option>';
|
||||
}
|
||||
} elseif ($internalType === 'folder') {
|
||||
foreach ($selectedItems as $selectedItem) {
|
||||
$folder = $selectedItem['folder'];
|
||||
$listOfSelectedValues[] = $folder;
|
||||
$selectorOptionsHtml[] =
|
||||
'<option value="' . htmlspecialchars($folder) . '" title="' . htmlspecialchars($folder) . '">'
|
||||
. htmlspecialchars($folder)
|
||||
. '</option>';
|
||||
}
|
||||
} elseif ($internalType === 'db') {
|
||||
foreach ($selectedItems as $selectedItem) {
|
||||
$tableWithUid = $selectedItem['table'] . '_' . $selectedItem['uid'];
|
||||
$listOfSelectedValues[] = $tableWithUid;
|
||||
$title = $selectedItem['title'];
|
||||
if (empty($title)) {
|
||||
$title = '[' . $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.no_title') . ']';
|
||||
}
|
||||
$shortenedTitle = GeneralUtility::fixed_lgd_cs($title, $maxTitleLength);
|
||||
$selectorOptionsHtml[] =
|
||||
'<option value="' . htmlspecialchars($tableWithUid) . '" title="' . htmlspecialchars($title) . '">'
|
||||
. htmlspecialchars($this->appendValueToLabelInDebugMode($shortenedTitle, $tableWithUid))
|
||||
. '</option>';
|
||||
}
|
||||
} else {
|
||||
throw new \RuntimeException(
|
||||
'internal_type missing on type="group" field',
|
||||
1485007097
|
||||
);
|
||||
}
|
||||
|
||||
$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';
|
||||
$html[] = ' size="' . $size . '"';
|
||||
$html[] = ' disabled="disabled"';
|
||||
$html[] = ' class="form-control tceforms-multiselect"';
|
||||
$html[] = ($maxItems !== 1 && $size !== 1) ? ' multiple="multiple"' : '';
|
||||
$html[] = '>';
|
||||
$html[] = implode(LF, $selectorOptionsHtml);
|
||||
$html[] = '</select>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '<div class="form-wizards-items-aside">';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$resultArray['html'] = implode(LF, $html);
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
// Need some information if in flex form scope for the suggest element
|
||||
$dataStructureIdentifier = '';
|
||||
$flexFormSheetName = '';
|
||||
$flexFormFieldName = '';
|
||||
$flexFormContainerName = '';
|
||||
$flexFormContainerFieldName = '';
|
||||
if ($this->data['processedTca']['columns'][$fieldName]['config']['type'] === 'flex') {
|
||||
$flexFormConfig = $this->data['processedTca']['columns'][$fieldName];
|
||||
$dataStructureIdentifier = $flexFormConfig['config']['dataStructureIdentifier'];
|
||||
if (!isset($flexFormConfig['config']['dataStructureIdentifier'])) {
|
||||
throw new \RuntimeException(
|
||||
'A data structure identifier must be set in [\'config\'] part of a flex form.'
|
||||
. ' This is usually added by TcaFlexPrepare data processor',
|
||||
1485206970
|
||||
);
|
||||
}
|
||||
if (isset($this->data['flexFormSheetName'])) {
|
||||
$flexFormSheetName = $this->data['flexFormSheetName'];
|
||||
}
|
||||
if (isset($this->data['flexFormFieldName'])) {
|
||||
$flexFormFieldName = $this->data['flexFormFieldName'];
|
||||
}
|
||||
if (isset($this->data['flexFormContainerName'])) {
|
||||
$flexFormContainerName = $this->data['flexFormContainerName'];
|
||||
}
|
||||
if (isset($this->data['flexFormContainerFieldName'])) {
|
||||
$flexFormContainerFieldName = $this->data['flexFormContainerFieldName'];
|
||||
}
|
||||
}
|
||||
// Get minimum characters for suggest from TCA and override by TsConfig
|
||||
$suggestMinimumCharacters = 0;
|
||||
if (isset($config['suggestOptions']['default']['minimumCharacters'])) {
|
||||
$suggestMinimumCharacters = (int)$config['suggestOptions']['default']['minimumCharacters'];
|
||||
}
|
||||
if (isset($parameterArray['fieldTSConfig']['suggest.']['default.']['minimumCharacters'])) {
|
||||
$suggestMinimumCharacters = (int)$parameterArray['fieldTSConfig']['suggest.']['default.']['minimumCharacters'];
|
||||
}
|
||||
$suggestMinimumCharacters = $suggestMinimumCharacters > 0 ? $suggestMinimumCharacters : 2;
|
||||
|
||||
$itemCanBeSelectedMoreThanOnce = !empty($config['multiple']);
|
||||
|
||||
$showMoveIcons = true;
|
||||
if (isset($config['hideMoveIcons']) && $config['hideMoveIcons']) {
|
||||
$showMoveIcons = false;
|
||||
}
|
||||
$showDeleteControl = true;
|
||||
if (isset($config['hideDeleteIcon']) && $config['hideDeleteIcon']) {
|
||||
$showDeleteControl = false;
|
||||
}
|
||||
|
||||
// Check against inline uniqueness - Create some onclick js for delete control and element browser
|
||||
// to override record selection in some FAL scenarios - See 'appearance' docs of group element
|
||||
$inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
|
||||
$inlineStackProcessor->initializeByGivenStructure($this->data['inlineStructure']);
|
||||
$deleteControlOnClick = '';
|
||||
if ($this->data['isInlineChild']
|
||||
&& $this->data['inlineParentUid']
|
||||
&& $this->data['inlineParentConfig']['foreign_table'] === $table
|
||||
&& $this->data['inlineParentConfig']['foreign_unique'] === $fieldName
|
||||
) {
|
||||
$objectPrefix = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']) . '-' . $table;
|
||||
$deleteControlOnClick = 'inline.revertUnique(' . GeneralUtility::quoteJSvalue($objectPrefix) . ',null,' . GeneralUtility::quoteJSvalue($row['uid']) . ');';
|
||||
}
|
||||
|
||||
$selectorAttributes = [
|
||||
'id' => StringUtility::getUniqueId('tceforms-multiselect-'),
|
||||
'data-formengine-input-name' => htmlspecialchars($elementName),
|
||||
'data-formengine-validation-rules' => $this->getValidationDataAsJsonString($config),
|
||||
'size' => $size,
|
||||
];
|
||||
$selectorClasses = [
|
||||
'form-control',
|
||||
'tceforms-multiselect',
|
||||
];
|
||||
if ($maxItems === 1) {
|
||||
$selectorClasses[] = 'form-select-no-siblings';
|
||||
}
|
||||
$selectorAttributes['class'] = implode(' ', $selectorClasses);
|
||||
if ($maxItems !== 1 && $size !== 1) {
|
||||
$selectorAttributes['multiple'] = 'multiple';
|
||||
}
|
||||
|
||||
$fieldControlResult = $this->renderFieldControl();
|
||||
$fieldControlHtml = $fieldControlResult['html'];
|
||||
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldControlResult, 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">';
|
||||
if ($internalType === 'db' && (!isset($config['hideSuggest']) || (bool)$config['hideSuggest'] !== true)) {
|
||||
$html[] = '<div class="form-wizards-items-top">';
|
||||
$html[] = '<div class="autocomplete t3-form-suggest-container">';
|
||||
$html[] = '<div class="input-group">';
|
||||
$html[] = '<span class="input-group-addon">';
|
||||
$html[] = $this->iconFactory->getIcon('actions-search', Icon::SIZE_SMALL)->render();
|
||||
$html[] = '</span>';
|
||||
$html[] = '<input type="search" class="t3-form-suggest form-control"';
|
||||
$html[] = ' placeholder="' . $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.findRecord') . '"';
|
||||
$html[] = ' data-fieldname="' . htmlspecialchars($fieldName) . '"';
|
||||
$html[] = ' data-tablename="' . htmlspecialchars($table) . '"';
|
||||
$html[] = ' data-field="' . htmlspecialchars($elementName) . '"';
|
||||
$html[] = ' data-uid="' . htmlspecialchars($this->data['databaseRow']['uid']) . '"';
|
||||
$html[] = ' data-pid="' . htmlspecialchars($this->data['parentPageRow']['uid'] ?? 0) . '"';
|
||||
$html[] = ' data-fieldtype="' . htmlspecialchars($config['type']) . '"';
|
||||
$html[] = ' data-minchars="' . htmlspecialchars($suggestMinimumCharacters) . '"';
|
||||
$html[] = ' data-datastructureidentifier="' . htmlspecialchars($dataStructureIdentifier) . '"';
|
||||
$html[] = ' data-flexformsheetname="' . htmlspecialchars($flexFormSheetName) . '"';
|
||||
$html[] = ' data-flexformfieldname="' . htmlspecialchars($flexFormFieldName) . '"';
|
||||
$html[] = ' data-flexformcontainername="' . htmlspecialchars($flexFormContainerName) . '"';
|
||||
$html[] = ' data-flexformcontainerfieldname="' . htmlspecialchars($flexFormContainerFieldName) . '"';
|
||||
$html[] = '/>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
}
|
||||
$html[] = '<div class="form-wizards-element">';
|
||||
$html[] = '<input type="hidden" class="t3js-group-hidden-field" data-formengine-input-name="' . htmlspecialchars($elementName) . '" value="' . $itemCanBeSelectedMoreThanOnce . '" />';
|
||||
$html[] = '<select ' . GeneralUtility::implodeAttributes($selectorAttributes, true) . '>';
|
||||
$html[] = implode(LF, $selectorOptionsHtml);
|
||||
$html[] = '</select>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '<div class="form-wizards-items-aside">';
|
||||
$html[] = '<div class="btn-group-vertical">';
|
||||
if ($maxItems > 1 && $size >=5 && $showMoveIcons) {
|
||||
$html[] = '<a href="#"';
|
||||
$html[] = ' class="btn btn-default t3js-btn-moveoption-top"';
|
||||
$html[] = ' data-fieldname="' . htmlspecialchars($elementName) . '"';
|
||||
$html[] = ' title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.move_to_top')) . '"';
|
||||
$html[] = '>';
|
||||
$html[] = $this->iconFactory->getIcon('actions-move-to-top', Icon::SIZE_SMALL)->render();
|
||||
$html[] = '</a>';
|
||||
}
|
||||
if ($maxItems > 1 && $size > 1 && $showMoveIcons) {
|
||||
$html[] = '<a href="#"';
|
||||
$html[] = ' class="btn btn-default t3js-btn-moveoption-up"';
|
||||
$html[] = ' data-fieldname="' . htmlspecialchars($elementName) . '"';
|
||||
$html[] = ' title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.move_up')) . '"';
|
||||
$html[] = '>';
|
||||
$html[] = $this->iconFactory->getIcon('actions-move-up', Icon::SIZE_SMALL)->render();
|
||||
$html[] = '</a>';
|
||||
$html[] = '<a href="#"';
|
||||
$html[] = ' class="btn btn-default t3js-btn-moveoption-down"';
|
||||
$html[] = ' data-fieldname="' . htmlspecialchars($elementName) . '"';
|
||||
$html[] = ' title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.move_down')) . '"';
|
||||
$html[] = '>';
|
||||
$html[] = $this->iconFactory->getIcon('actions-move-down', Icon::SIZE_SMALL)->render();
|
||||
$html[] = '</a>';
|
||||
}
|
||||
if ($maxItems > 1 && $size >= 5 && $showMoveIcons) {
|
||||
$html[] = '<a href="#"';
|
||||
$html[] = ' class="btn btn-default t3js-btn-moveoption-bottom"';
|
||||
$html[] = ' data-fieldname="' . htmlspecialchars($elementName) . '"';
|
||||
$html[] = ' title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.move_to_bottom')) . '"';
|
||||
$html[] = '>';
|
||||
$html[] = $this->iconFactory->getIcon('actions-move-to-bottom', Icon::SIZE_SMALL)->render();
|
||||
$html[] = '</a>';
|
||||
}
|
||||
if ($showDeleteControl) {
|
||||
$html[] = '<a href="#"';
|
||||
$html[] = ' class="btn btn-default t3js-btn-removeoption"';
|
||||
$html[] = ' data-fieldname="' . htmlspecialchars($elementName) . '"';
|
||||
$html[] = ' title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.remove_selected')) . '"';
|
||||
$html[] = ' onClick="' . $deleteControlOnClick . '"';
|
||||
$html[] = '>';
|
||||
$html[] = $this->iconFactory->getIcon('actions-selection-delete', Icon::SIZE_SMALL)->render();
|
||||
$html[] = '</a>';
|
||||
}
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '<div class="form-wizards-items-aside">';
|
||||
$html[] = '<div class="btn-group-vertical">';
|
||||
$html[] = $fieldControlHtml;
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
if (!empty($fieldWizardHtml)) {
|
||||
$html[] = '<div class="form-wizards-items-bottom">';
|
||||
$html[] = $fieldWizardHtml;
|
||||
$html[] = '</div>';
|
||||
}
|
||||
$html[] = '</div>';
|
||||
$html[] = '<input type="hidden" name="' . htmlspecialchars($elementName) . '" value="' . htmlspecialchars(implode(',', $listOfSelectedValues)) . '" />';
|
||||
$html[] = '</div>';
|
||||
|
||||
$resultArray['html'] = implode(LF, $html);
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUserAuthentication()
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService()
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Form\Element;
|
||||
|
||||
/*
|
||||
* 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\NodeFactory;
|
||||
use TYPO3\CMS\Backend\Routing\UriBuilder;
|
||||
use TYPO3\CMS\Core\Imaging\ImageManipulation\Area;
|
||||
use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
|
||||
use TYPO3\CMS\Core\Imaging\ImageManipulation\InvalidConfigurationException;
|
||||
use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException;
|
||||
use TYPO3\CMS\Core\Resource\File;
|
||||
use TYPO3\CMS\Core\Resource\ResourceFactory;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
use TYPO3\CMS\Core\Utility\StringUtility;
|
||||
use TYPO3\CMS\Fluid\View\StandaloneView;
|
||||
|
||||
/**
|
||||
* Generation of image manipulation FormEngine element.
|
||||
* This is typically used in FAL relations to cut images.
|
||||
*/
|
||||
class ImageManipulationElement extends AbstractFormElement
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $wizardRouteName = 'ajax_wizard_image_manipulation';
|
||||
|
||||
/**
|
||||
* Default element configuration
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $defaultConfig = [
|
||||
'file_field' => 'uid_local',
|
||||
'allowedExtensions' => null, // default: $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext']
|
||||
'cropVariants' => [
|
||||
'default' => [
|
||||
'title' => 'LLL:EXT:core/Resources/Private/Language/locallang_wizards.xlf:imwizard.crop_variant.default',
|
||||
'allowedAspectRatios' => [
|
||||
'16:9' => [
|
||||
'title' => 'LLL:EXT:core/Resources/Private/Language/locallang_wizards.xlf:imwizard.ratio.16_9',
|
||||
'value' => 16 / 9
|
||||
],
|
||||
'3:2' => [
|
||||
'title' => 'LLL:EXT:core/Resources/Private/Language/locallang_wizards.xlf:imwizard.ratio.3_2',
|
||||
'value' => 3 / 2
|
||||
],
|
||||
'4:3' => [
|
||||
'title' => 'LLL:EXT:core/Resources/Private/Language/locallang_wizards.xlf:imwizard.ratio.4_3',
|
||||
'value' => 4 / 3
|
||||
],
|
||||
'1:1' => [
|
||||
'title' => 'LLL:EXT:core/Resources/Private/Language/locallang_wizards.xlf:imwizard.ratio.1_1',
|
||||
'value' => 1.0
|
||||
],
|
||||
'NaN' => [
|
||||
'title' => 'LLL:EXT:core/Resources/Private/Language/locallang_wizards.xlf:imwizard.ratio.free',
|
||||
'value' => 0.0
|
||||
],
|
||||
],
|
||||
'selectedRatio' => 'NaN',
|
||||
'cropArea' => [
|
||||
'x' => 0.0,
|
||||
'y' => 0.0,
|
||||
'width' => 1.0,
|
||||
'height' => 1.0,
|
||||
],
|
||||
],
|
||||
]
|
||||
];
|
||||
|
||||
/**
|
||||
* Default field information enabled for this element.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldInformation = [
|
||||
'tcaDescription' => [
|
||||
'renderType' => 'tcaDescription',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Default field wizards enabled for this element.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldWizard = [
|
||||
'localizationStateSelector' => [
|
||||
'renderType' => 'localizationStateSelector',
|
||||
],
|
||||
'otherLanguageContent' => [
|
||||
'renderType' => 'otherLanguageContent',
|
||||
'after' => [
|
||||
'localizationStateSelector'
|
||||
],
|
||||
],
|
||||
'defaultLanguageDifferences' => [
|
||||
'renderType' => 'defaultLanguageDifferences',
|
||||
'after' => [
|
||||
'otherLanguageContent',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* @var StandaloneView
|
||||
*/
|
||||
protected $templateView;
|
||||
|
||||
/**
|
||||
* @var UriBuilder
|
||||
*/
|
||||
protected $uriBuilder;
|
||||
|
||||
/**
|
||||
* @param NodeFactory $nodeFactory
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct(NodeFactory $nodeFactory, array $data)
|
||||
{
|
||||
parent::__construct($nodeFactory, $data);
|
||||
// Would be great, if we could inject the view here, but since the constructor is in the interface, we can't
|
||||
$this->templateView = GeneralUtility::makeInstance(StandaloneView::class);
|
||||
$this->templateView->setLayoutRootPaths([GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Layouts/')]);
|
||||
$this->templateView->setPartialRootPaths([GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Partials/ImageManipulation/')]);
|
||||
$this->templateView->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Templates/ImageManipulation/ImageManipulationElement.html'));
|
||||
$this->uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will render an imageManipulation field
|
||||
*
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
* @throws \TYPO3\CMS\Core\Imaging\ImageManipulation\InvalidConfigurationException
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$resultArray = $this->initializeResultArray();
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
$config = $this->populateConfiguration($parameterArray['fieldConf']['config']);
|
||||
|
||||
$file = $this->getFile($this->data['databaseRow'], $config['file_field']);
|
||||
if (!$file) {
|
||||
// Early return in case we do not find a file
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
$config = $this->processConfiguration($config, $parameterArray['itemFormElValue'], $file);
|
||||
|
||||
$fieldInformationResult = $this->renderFieldInformation();
|
||||
$fieldInformationHtml = $fieldInformationResult['html'];
|
||||
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false);
|
||||
|
||||
$fieldControlResult = $this->renderFieldControl();
|
||||
$fieldControlHtml = $fieldControlResult['html'];
|
||||
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldControlResult, false);
|
||||
|
||||
$fieldWizardResult = $this->renderFieldWizard();
|
||||
$fieldWizardHtml = $fieldWizardResult['html'];
|
||||
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldWizardResult, false);
|
||||
|
||||
$arguments = [
|
||||
'fieldInformation' => $fieldInformationHtml,
|
||||
'fieldControl' => $fieldControlHtml,
|
||||
'fieldWizard' => $fieldWizardHtml,
|
||||
'isAllowedFileExtension' => in_array(strtolower($file->getExtension()), GeneralUtility::trimExplode(',', strtolower($config['allowedExtensions'])), true),
|
||||
'image' => $file,
|
||||
'formEngine' => [
|
||||
'field' => [
|
||||
'value' => $parameterArray['itemFormElValue'],
|
||||
'name' => $parameterArray['itemFormElName']
|
||||
],
|
||||
'validation' => '[]'
|
||||
],
|
||||
'config' => $config,
|
||||
'wizardUri' => $this->getWizardUri(),
|
||||
'wizardPayload' => json_encode($this->getWizardPayload($config['cropVariants'], $file)),
|
||||
'previewUrl' => $this->getPreviewUrl($this->data['databaseRow'], $file),
|
||||
];
|
||||
|
||||
if ($arguments['isAllowedFileExtension']) {
|
||||
$resultArray['requireJsModules'][] = [
|
||||
'TYPO3/CMS/Backend/ImageManipulation' => 'function (ImageManipulation) {top.require(["cropper"], function() { ImageManipulation.initializeTrigger(); }); }'
|
||||
];
|
||||
$arguments['formEngine']['field']['id'] = StringUtility::getUniqueId('formengine-image-manipulation-');
|
||||
if (GeneralUtility::inList($config['eval'], 'required')) {
|
||||
$arguments['formEngine']['validation'] = $this->getValidationDataAsJsonString(['required' => true]);
|
||||
}
|
||||
}
|
||||
$this->templateView->assignMultiple($arguments);
|
||||
$resultArray['html'] = $this->templateView->render();
|
||||
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file object
|
||||
*
|
||||
* @param array $row
|
||||
* @param string $fieldName
|
||||
* @return File|null
|
||||
*/
|
||||
protected function getFile(array $row, $fieldName)
|
||||
{
|
||||
$file = null;
|
||||
$fileUid = !empty($row[$fieldName]) ? $row[$fieldName] : null;
|
||||
if (is_array($fileUid) && isset($fileUid[0]['uid'])) {
|
||||
$fileUid = $fileUid[0]['uid'];
|
||||
}
|
||||
if (MathUtility::canBeInterpretedAsInteger($fileUid)) {
|
||||
try {
|
||||
$file = ResourceFactory::getInstance()->getFileObject($fileUid);
|
||||
} catch (FileDoesNotExistException $e) {
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
}
|
||||
}
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $databaseRow
|
||||
* @param File $file
|
||||
* @return string
|
||||
*/
|
||||
protected function getPreviewUrl(array $databaseRow, File $file): string
|
||||
{
|
||||
$previewUrl = '';
|
||||
// Hook to generate a preview URL
|
||||
$hookParameters = [
|
||||
'databaseRow' => $databaseRow,
|
||||
'file' => $file,
|
||||
'previewUrl' => $previewUrl,
|
||||
];
|
||||
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['Backend/Form/Element/ImageManipulationElement']['previewUrl'] ?? [] as $listener) {
|
||||
$previewUrl = GeneralUtility::callUserFunction($listener, $hookParameters, $this);
|
||||
}
|
||||
return $previewUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $baseConfiguration
|
||||
* @return array
|
||||
* @throws InvalidConfigurationException
|
||||
*/
|
||||
protected function populateConfiguration(array $baseConfiguration)
|
||||
{
|
||||
$defaultConfig = self::$defaultConfig;
|
||||
|
||||
// If ratios are set do not add default options
|
||||
if (isset($baseConfiguration['cropVariants'])) {
|
||||
unset($defaultConfig['cropVariants']);
|
||||
}
|
||||
|
||||
$config = array_replace_recursive($defaultConfig, $baseConfiguration);
|
||||
|
||||
if (!is_array($config['cropVariants'])) {
|
||||
throw new InvalidConfigurationException('Crop variants configuration must be an array', 1485377267);
|
||||
}
|
||||
|
||||
$cropVariants = [];
|
||||
foreach ($config['cropVariants'] as $id => $cropVariant) {
|
||||
// Ignore disabled crop variants
|
||||
if (!empty($cropVariant['disabled'])) {
|
||||
continue;
|
||||
}
|
||||
// Enforce a crop area (default is full image)
|
||||
if (empty($cropVariant['cropArea'])) {
|
||||
$cropVariant['cropArea'] = Area::createEmpty()->asArray();
|
||||
}
|
||||
$cropVariants[$id] = $cropVariant;
|
||||
}
|
||||
|
||||
$config['cropVariants'] = $cropVariants;
|
||||
|
||||
// By default we allow all image extensions that can be handled by the GFX functionality
|
||||
if ($config['allowedExtensions'] === null) {
|
||||
$config['allowedExtensions'] = $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'];
|
||||
}
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $config
|
||||
* @param string $elementValue
|
||||
* @param File $file
|
||||
* @return array
|
||||
* @throws \TYPO3\CMS\Core\Imaging\ImageManipulation\InvalidConfigurationException
|
||||
*/
|
||||
protected function processConfiguration(array $config, string &$elementValue, File $file)
|
||||
{
|
||||
$cropVariantCollection = CropVariantCollection::create($elementValue, $config['cropVariants']);
|
||||
if (empty($config['readOnly']) && !empty($file->getProperty('width'))) {
|
||||
$cropVariantCollection = $cropVariantCollection->applyRatioRestrictionToSelectedCropArea($file);
|
||||
$elementValue = (string)$cropVariantCollection;
|
||||
}
|
||||
$config['cropVariants'] = $cropVariantCollection->asArray();
|
||||
$config['allowedExtensions'] = implode(', ', GeneralUtility::trimExplode(',', $config['allowedExtensions'], true));
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getWizardUri(): string
|
||||
{
|
||||
return (string)$this->uriBuilder->buildUriFromRoute($this->wizardRouteName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $cropVariants
|
||||
* @param File $image
|
||||
* @return array
|
||||
*/
|
||||
protected function getWizardPayload(array $cropVariants, File $image): array
|
||||
{
|
||||
$arguments = [
|
||||
'cropVariants' => $cropVariants,
|
||||
'image' => $image->getUid(),
|
||||
];
|
||||
$uriArguments['arguments'] = json_encode($arguments);
|
||||
$uriArguments['signature'] = GeneralUtility::hmac($uriArguments['arguments'], $this->wizardRouteName);
|
||||
|
||||
return $uriArguments;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\Element;
|
||||
|
||||
/*
|
||||
* 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 for classes which hook into inline element handling
|
||||
*/
|
||||
interface InlineElementHookInterface
|
||||
{
|
||||
/**
|
||||
* Pre-processing to define which control items are enabled or disabled.
|
||||
*
|
||||
* @param string $parentUid The uid of the parent (embedding) record (uid or NEW...)
|
||||
* @param string $foreignTable The table (foreign_table) we create control-icons for
|
||||
* @param array $childRecord The current record of that foreign_table
|
||||
* @param array $childConfig TCA configuration of the current field of the child record
|
||||
* @param bool $isVirtual Defines whether the current records is only virtually shown and not physically part of the parent record
|
||||
* @param array &$enabledControls (reference) Associative array with the enabled control items
|
||||
*/
|
||||
public function renderForeignRecordHeaderControl_preProcess($parentUid, $foreignTable, array $childRecord, array $childConfig, $isVirtual, array &$enabledControls);
|
||||
|
||||
/**
|
||||
* Post-processing to define which control items to show. Possibly own icons can be added here.
|
||||
*
|
||||
* @param string $parentUid The uid of the parent (embedding) record (uid or NEW...)
|
||||
* @param string $foreignTable The table (foreign_table) we create control-icons for
|
||||
* @param array $childRecord The current record of that foreign_table
|
||||
* @param array $childConfig TCA configuration of the current field of the child record
|
||||
* @param bool $isVirtual Defines whether the current records is only virtually shown and not physically part of the parent record
|
||||
* @param array &$controlItems (reference) Associative array with the currently available control items
|
||||
*/
|
||||
public function renderForeignRecordHeaderControl_postProcess($parentUid, $foreignTable, array $childRecord, array $childConfig, $isVirtual, array &$controlItems);
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\Element;
|
||||
|
||||
/*
|
||||
* 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\Core\Utility\MathUtility;
|
||||
use TYPO3\CMS\Core\Utility\StringUtility;
|
||||
|
||||
/**
|
||||
* Render an input field with a color picker
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldWizard = [
|
||||
'localizationStateSelector' => [
|
||||
'renderType' => 'localizationStateSelector',
|
||||
],
|
||||
'otherLanguageContent' => [
|
||||
'renderType' => 'otherLanguageContent',
|
||||
'after' => [
|
||||
'localizationStateSelector'
|
||||
],
|
||||
],
|
||||
'defaultLanguageDifferences' => [
|
||||
'renderType' => 'defaultLanguageDifferences',
|
||||
'after' => [
|
||||
'otherLanguageContent',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* This will render a single-line input form field, possibly with various control/validation features
|
||||
*
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
|
||||
$table = $this->data['tableName'];
|
||||
$fieldName = $this->data['fieldName'];
|
||||
$row = $this->data['databaseRow'];
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
$resultArray = $this->initializeResultArray();
|
||||
|
||||
$itemValue = $parameterArray['itemFormElValue'];
|
||||
$config = $parameterArray['fieldConf']['config'];
|
||||
$size = MathUtility::forceIntegerInRange($config['size'] ?: $this->defaultInputWidth, $this->minimumInputWidth, $this->maxInputWidth);
|
||||
$evalList = GeneralUtility::trimExplode(',', $config['eval'], true);
|
||||
$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">';
|
||||
$html[] = '<input class="form-control" value="' . htmlspecialchars($itemValue) . '" type="text" disabled>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$resultArray['html'] = implode(LF, $html);
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
// @todo: The whole eval handling is a mess and needs refactoring
|
||||
foreach ($evalList as $func) {
|
||||
// @todo: This is ugly: The code should find out on it's own whether a eval definition is a
|
||||
// @todo: keyword like "date", or a class reference. The global registration could be dropped then
|
||||
// Pair hook to the one in \TYPO3\CMS\Core\DataHandling\DataHandler::checkValue_input_Eval()
|
||||
if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tce']['formevals'][$func])) {
|
||||
if (class_exists($func)) {
|
||||
$evalObj = GeneralUtility::makeInstance($func);
|
||||
if (method_exists($evalObj, 'deevaluateFieldValue')) {
|
||||
$_params = [
|
||||
'value' => $itemValue
|
||||
];
|
||||
$itemValue = $evalObj->deevaluateFieldValue($_params);
|
||||
}
|
||||
if (method_exists($evalObj, 'returnFieldJS')) {
|
||||
$resultArray['additionalJavaScriptPost'][] = 'TBE_EDITOR.customEvalFunctions[' . GeneralUtility::quoteJSvalue($evalData) . '] = function(value) {' . $evalObj->returnFieldJS() . '};';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load needed js library
|
||||
$resultArray['requireJsModules'][] = [
|
||||
'TYPO3/CMS/Backend/ColorPicker' => 'function(ColorPicker){ColorPicker.initialize()}'
|
||||
];
|
||||
|
||||
$attributes = [
|
||||
'value' => $itemValue,
|
||||
'id' => StringUtility::getUniqueId('formengine-input-'),
|
||||
'class' => implode(' ', [
|
||||
'form-control',
|
||||
'hasDefaultValue',
|
||||
't3js-clearable',
|
||||
't3js-color-picker',
|
||||
'formengine-colorpickerelement',
|
||||
]),
|
||||
'data-formengine-validation-rules' => $this->getValidationDataAsJsonString($config),
|
||||
'data-formengine-input-params' => json_encode([
|
||||
'field' => $parameterArray['itemFormElName'],
|
||||
'evalList' => implode(',', $evalList),
|
||||
'is_in' => trim($config['is_in']),
|
||||
]),
|
||||
'data-formengine-input-name' => $parameterArray['itemFormElName'],
|
||||
];
|
||||
|
||||
if (isset($config['max']) && (int)$config['max'] > 0) {
|
||||
$attributes['maxlength'] = (int)$config['max'];
|
||||
}
|
||||
if (!empty($config['placeholder'])) {
|
||||
$attributes['placeholder'] = trim($config['placeholder']);
|
||||
}
|
||||
if (isset($config['autocomplete'])) {
|
||||
$attributes['autocomplete'] = empty($config['autocomplete']) ? 'new-' . $fieldName : 'on';
|
||||
}
|
||||
|
||||
$valuePickerHtml = [];
|
||||
if (isset($config['valuePicker']['items']) && is_array($config['valuePicker']['items'])) {
|
||||
$valuePickerHtml[] = '<select class="t3js-colorpicker-value-trigger form-control tceforms-select tceforms-wizardselect">';
|
||||
$valuePickerHtml[] = '<option></option>';
|
||||
foreach ($config['valuePicker']['items'] as $item) {
|
||||
$valuePickerHtml[] = '<option value="' . htmlspecialchars($item[1]) . '">' . htmlspecialchars($languageService->sL($item[0])) . '</option>';
|
||||
}
|
||||
$valuePickerHtml[] = '</select>';
|
||||
}
|
||||
|
||||
$fieldWizardResult = $this->renderFieldWizard();
|
||||
$fieldWizardHtml = $fieldWizardResult['html'];
|
||||
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldWizardResult, false);
|
||||
|
||||
$fieldControlResult = $this->renderFieldControl();
|
||||
$fieldControlHtml = $fieldControlResult['html'];
|
||||
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldControlResult, false);
|
||||
|
||||
$mainFieldHtml = [];
|
||||
$mainFieldHtml[] = '<div class="form-control-wrap" style="max-width: ' . $width . 'px">';
|
||||
$mainFieldHtml[] = '<div class="form-wizards-wrap">';
|
||||
$mainFieldHtml[] = '<div class="form-wizards-element">';
|
||||
$mainFieldHtml[] = '<input type="text" ' . GeneralUtility::implodeAttributes($attributes, true) . ' />';
|
||||
$mainFieldHtml[] = '<input type="hidden" name="' . $parameterArray['itemFormElName'] . '" value="' . htmlspecialchars($itemValue) . '" />';
|
||||
$mainFieldHtml[] = '</div>';
|
||||
$mainFieldHtml[] = '<div class="form-wizards-items-aside">';
|
||||
$mainFieldHtml[] = '<div class="btn-group">';
|
||||
$mainFieldHtml[] = $fieldControlHtml;
|
||||
$mainFieldHtml[] = implode(LF, $valuePickerHtml);
|
||||
$mainFieldHtml[] = '</div>';
|
||||
$mainFieldHtml[] = '</div>';
|
||||
if (!empty($fieldWizardHtml)) {
|
||||
$mainFieldHtml[] = '<div class="form-wizards-items-bottom">';
|
||||
$mainFieldHtml[] = $fieldWizardHtml;
|
||||
$mainFieldHtml[] = '</div>';
|
||||
}
|
||||
$mainFieldHtml[] = '</div>';
|
||||
$mainFieldHtml[] = '</div>';
|
||||
$mainFieldHtml = implode(LF, $mainFieldHtml);
|
||||
|
||||
$fullElement = $mainFieldHtml;
|
||||
if ($this->hasNullCheckboxButNoPlaceholder()) {
|
||||
$checked = $itemValue !== null ? ' checked="checked"' : '';
|
||||
$fullElement = [];
|
||||
$fullElement[] = '<div class="t3-form-field-disable"></div>';
|
||||
$fullElement[] = '<div class="checkbox t3-form-field-eval-null-checkbox">';
|
||||
$fullElement[] = '<label for="' . $nullControlNameEscaped . '">';
|
||||
$fullElement[] = '<input type="hidden" name="' . $nullControlNameEscaped . '" value="0" />';
|
||||
$fullElement[] = '<input type="checkbox" name="' . $nullControlNameEscaped . '" id="' . $nullControlNameEscaped . '" value="1"' . $checked . ' />';
|
||||
$fullElement[] = $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.nullCheckbox');
|
||||
$fullElement[] = '</label>';
|
||||
$fullElement[] = '</div>';
|
||||
$fullElement[] = $mainFieldHtml;
|
||||
$fullElement = implode(LF, $fullElement);
|
||||
} elseif ($this->hasNullCheckboxWithPlaceholder()) {
|
||||
$checked = $itemValue !== null ? ' checked="checked"' : '';
|
||||
$placeholder = $shortenedPlaceholder = $config['placeholder'] ?? '';
|
||||
$disabled = '';
|
||||
$fallbackValue = 0;
|
||||
if (strlen($placeholder) > 0) {
|
||||
$shortenedPlaceholder = GeneralUtility::fixed_lgd_cs($placeholder, 20);
|
||||
if ($placeholder !== $shortenedPlaceholder) {
|
||||
$overrideLabel = sprintf(
|
||||
$languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.placeholder.override'),
|
||||
'<span title="' . htmlspecialchars($placeholder) . '">' . htmlspecialchars($shortenedPlaceholder) . '</span>'
|
||||
);
|
||||
} else {
|
||||
$overrideLabel = sprintf(
|
||||
$languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.placeholder.override'),
|
||||
htmlspecialchars($placeholder)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$overrideLabel = $languageService->sL(
|
||||
'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.placeholder.override_not_available'
|
||||
);
|
||||
}
|
||||
$fullElement = [];
|
||||
$fullElement[] = '<div class="checkbox t3js-form-field-eval-null-placeholder-checkbox">';
|
||||
$fullElement[] = '<label for="' . $nullControlNameEscaped . '">';
|
||||
$fullElement[] = '<input type="hidden" name="' . $nullControlNameEscaped . '" value="' . $fallbackValue . '" />';
|
||||
$fullElement[] = '<input type="checkbox" name="' . $nullControlNameEscaped . '" id="' . $nullControlNameEscaped . '" value="1"' . $checked . $disabled . ' />';
|
||||
$fullElement[] = $overrideLabel;
|
||||
$fullElement[] = '</label>';
|
||||
$fullElement[] = '</div>';
|
||||
$fullElement[] = '<div class="t3js-formengine-placeholder-placeholder">';
|
||||
$fullElement[] = '<div class="form-control-wrap" style="max-width:' . $width . 'px">';
|
||||
$fullElement[] = '<input type="text" class="form-control" disabled="disabled" value="' . htmlspecialchars($shortenedPlaceholder) . '" />';
|
||||
$fullElement[] = '</div>';
|
||||
$fullElement[] = '</div>';
|
||||
$fullElement[] = '<div class="t3js-formengine-placeholder-formfield">';
|
||||
$fullElement[] = $mainFieldHtml;
|
||||
$fullElement[] = '</div>';
|
||||
$fullElement = implode(LF, $fullElement);
|
||||
}
|
||||
|
||||
$resultArray['html'] = '<div class="formengine-field-item t3js-formengine-field-item">' . $fieldInformationHtml . $fullElement . '</div>';
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService()
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\Element;
|
||||
|
||||
/*
|
||||
* 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\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
use TYPO3\CMS\Core\Utility\StringUtility;
|
||||
|
||||
/**
|
||||
* Generation of TCEform elements of the type "input type=text"
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldWizard = [
|
||||
'localizationStateSelector' => [
|
||||
'renderType' => 'localizationStateSelector',
|
||||
],
|
||||
'otherLanguageContent' => [
|
||||
'renderType' => 'otherLanguageContent',
|
||||
'after' => [
|
||||
'localizationStateSelector'
|
||||
],
|
||||
],
|
||||
'defaultLanguageDifferences' => [
|
||||
'renderType' => 'defaultLanguageDifferences',
|
||||
'after' => [
|
||||
'otherLanguageContent',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* This will render a single-line input form field, possibly with various control/validation features
|
||||
*
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
* @throws \RuntimeException with invalid configuration
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
|
||||
$table = $this->data['tableName'];
|
||||
$fieldName = $this->data['fieldName'];
|
||||
$row = $this->data['databaseRow'];
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
$resultArray = $this->initializeResultArray();
|
||||
$config = $parameterArray['fieldConf']['config'];
|
||||
|
||||
$itemValue = $parameterArray['itemFormElValue'];
|
||||
$defaultInputWidth = 10;
|
||||
$evalList = GeneralUtility::trimExplode(',', $config['eval'], true);
|
||||
$nullControlNameEscaped = htmlspecialchars('control[active][' . $table . '][' . $row['uid'] . '][' . $fieldName . ']');
|
||||
|
||||
if (in_array('date', $evalList, true)) {
|
||||
$format = 'date';
|
||||
$defaultInputWidth = 13;
|
||||
} elseif (in_array('datetime', $evalList, true)) {
|
||||
$format = 'datetime';
|
||||
$defaultInputWidth = 13;
|
||||
} elseif (in_array('time', $evalList, true)) {
|
||||
$format = 'time';
|
||||
} elseif (in_array('timesec', $evalList, true)) {
|
||||
$format = 'timesec';
|
||||
} else {
|
||||
throw new \RuntimeException(
|
||||
'Field "' . $fieldName . '" in table "' . $table . '" with renderType "inputDataTime" needs'
|
||||
. '"eval" set to either "date", "datetime", "time" or "timesec"',
|
||||
1483823746
|
||||
);
|
||||
}
|
||||
|
||||
$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);
|
||||
|
||||
// Early return for read only fields
|
||||
if (isset($config['readOnly']) && $config['readOnly']) {
|
||||
// Ensure dbType values (see DatabaseRowDateTimeFields) are converted to a UNIX timestamp before rendering read-only
|
||||
if (!empty($itemValue) && !MathUtility::canBeInterpretedAsInteger($itemValue)) {
|
||||
$itemValue = (new \DateTime($itemValue))->getTimestamp();
|
||||
}
|
||||
// Format the unix-timestamp to the defined format (date/year etc)
|
||||
$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">';
|
||||
$html[] = '<input class="form-control" value="' . htmlspecialchars($itemValue) . '" type="text" disabled>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$resultArray['html'] = implode(LF, $html);
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
$attributes = [
|
||||
'value' => '',
|
||||
'id' => StringUtility::getUniqueId('formengine-input-'),
|
||||
'class' => implode(' ', [
|
||||
't3js-datetimepicker',
|
||||
'form-control',
|
||||
't3js-clearable',
|
||||
'hasDefaultValue',
|
||||
]),
|
||||
'data-date-type' => $format,
|
||||
'data-formengine-validation-rules' => $this->getValidationDataAsJsonString($config),
|
||||
'data-formengine-input-params' => json_encode([
|
||||
'field' => $parameterArray['itemFormElName'],
|
||||
'evalList' => implode(',', $evalList)
|
||||
]),
|
||||
'data-formengine-input-name' => $parameterArray['itemFormElName'],
|
||||
];
|
||||
|
||||
$maxLength = $config['max'] ?? 0;
|
||||
if ((int)$maxLength > 0) {
|
||||
$attributes['maxlength'] = (int)$maxLength;
|
||||
}
|
||||
if (!empty($config['placeholder'])) {
|
||||
$attributes['placeholder'] = trim($config['placeholder']);
|
||||
}
|
||||
|
||||
if ($format === 'datetime' || $format === 'date') {
|
||||
// This only handles integer timestamps; if the field is a SQL native date(time), it was already converted
|
||||
// to an ISO-8601 date by the DatabaseRowDateTimeFields class. (those dates are stored as server local time)
|
||||
if (MathUtility::canBeInterpretedAsInteger($itemValue) && $itemValue != 0) {
|
||||
// We store UTC timestamps in the database.
|
||||
// Convert the timestamp to a proper ISO-8601 date so we get rid of timezone issues on the client.
|
||||
// Details: As the JS side is not capable of handling dates in the server's timezone
|
||||
// (moment.js can only handle UTC or browser's local timezone), we need to offset the value
|
||||
// to eliminate the timezone. JS will receive all dates as if they were UTC, which we undo on save in DataHandler
|
||||
$adjustedValue = $itemValue + date('Z', (int)$itemValue);
|
||||
// output date as a ISO-8601 date
|
||||
$itemValue = gmdate('c', $adjustedValue);
|
||||
}
|
||||
if (isset($config['range']['lower'])) {
|
||||
$attributes['data-date-min-date'] = (int)$config['range']['lower'] * 1000;
|
||||
}
|
||||
if (isset($config['range']['upper'])) {
|
||||
$attributes['data-date-max-date'] = (int)$config['range']['upper'] * 1000;
|
||||
}
|
||||
}
|
||||
if (($format === 'time' || $format === 'timesec') && MathUtility::canBeInterpretedAsInteger($itemValue) && $itemValue != 0) {
|
||||
// time(sec) is stored as elapsed seconds in DB, hence we interpret it as UTC time on 1970-01-01
|
||||
// and pass on the ISO format to JS.
|
||||
$itemValue = gmdate('c', (int)$itemValue);
|
||||
}
|
||||
|
||||
$fieldWizardResult = $this->renderFieldWizard();
|
||||
$fieldWizardHtml = $fieldWizardResult['html'];
|
||||
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldWizardResult, false);
|
||||
|
||||
$fieldControlResult = $this->renderFieldControl();
|
||||
$fieldControlHtml = $fieldControlResult['html'];
|
||||
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldControlResult, false);
|
||||
|
||||
$expansionHtml = [];
|
||||
$expansionHtml[] = '<div class="form-control-wrap" style="max-width: ' . $width . 'px">';
|
||||
$expansionHtml[] = '<div class="form-wizards-wrap">';
|
||||
$expansionHtml[] = '<div class="form-wizards-element">';
|
||||
$expansionHtml[] = '<div class="input-group">';
|
||||
$expansionHtml[] = '<input type="text" ' . GeneralUtility::implodeAttributes($attributes, true) . ' />';
|
||||
$expansionHtml[] = '<input type="hidden" name="' . $parameterArray['itemFormElName'] . '" value="' . htmlspecialchars($itemValue) . '" />';
|
||||
$expansionHtml[] = '<span class="input-group-btn">';
|
||||
$expansionHtml[] = '<label class="btn btn-default" for="' . $attributes['id'] . '">';
|
||||
$expansionHtml[] = $this->iconFactory->getIcon('actions-edit-pick-date', Icon::SIZE_SMALL)->render();
|
||||
$expansionHtml[] = '</label>';
|
||||
$expansionHtml[] = '</span>';
|
||||
$expansionHtml[] = '</div>';
|
||||
$expansionHtml[] = '</div>';
|
||||
if (!empty($fieldControlHtml)) {
|
||||
$expansionHtml[] = '<div class="form-wizards-items-aside">';
|
||||
$expansionHtml[] = '<div class="btn-group">';
|
||||
$expansionHtml[] = $fieldControlHtml;
|
||||
$expansionHtml[] = '</div>';
|
||||
$expansionHtml[] = '</div>';
|
||||
}
|
||||
if (!empty($fieldWizardHtml)) {
|
||||
$expansionHtml[] = '<div class="form-wizards-items-bottom">';
|
||||
$expansionHtml[] = $fieldWizardHtml;
|
||||
$expansionHtml[] = '</div>';
|
||||
}
|
||||
$expansionHtml[] = '</div>';
|
||||
$expansionHtml[] = '</div>';
|
||||
$expansionHtml = implode(LF, $expansionHtml);
|
||||
|
||||
$fullElement = $expansionHtml;
|
||||
if ($this->hasNullCheckboxButNoPlaceholder()) {
|
||||
$checked = $itemValue !== null ? ' checked="checked"' : '';
|
||||
$fullElement = [];
|
||||
$fullElement[] = '<div class="t3-form-field-disable"></div>';
|
||||
$fullElement[] = '<div class="checkbox t3-form-field-eval-null-checkbox">';
|
||||
$fullElement[] = '<label for="' . $nullControlNameEscaped . '">';
|
||||
$fullElement[] = '<input type="hidden" name="' . $nullControlNameEscaped . '" value="0" />';
|
||||
$fullElement[] = '<input type="checkbox" name="' . $nullControlNameEscaped . '" id="' . $nullControlNameEscaped . '" value="1"' . $checked . ' />';
|
||||
$fullElement[] = $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.nullCheckbox');
|
||||
$fullElement[] = '</label>';
|
||||
$fullElement[] = '</div>';
|
||||
$fullElement[] = $expansionHtml;
|
||||
$fullElement = implode(LF, $fullElement);
|
||||
} elseif ($this->hasNullCheckboxWithPlaceholder()) {
|
||||
$checked = $itemValue !== null ? ' checked="checked"' : '';
|
||||
$placeholder = $shortenedPlaceholder = $config['placeholder'] ?? '';
|
||||
$disabled = '';
|
||||
$fallbackValue = 0;
|
||||
if (strlen($placeholder) > 0) {
|
||||
$shortenedPlaceholder = GeneralUtility::fixed_lgd_cs($placeholder, 20);
|
||||
if ($placeholder !== $shortenedPlaceholder) {
|
||||
$overrideLabel = sprintf(
|
||||
$languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.placeholder.override'),
|
||||
'<span title="' . htmlspecialchars($placeholder) . '">' . htmlspecialchars($shortenedPlaceholder) . '</span>'
|
||||
);
|
||||
} else {
|
||||
$overrideLabel = sprintf(
|
||||
$languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.placeholder.override'),
|
||||
htmlspecialchars($placeholder)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$overrideLabel = $languageService->sL(
|
||||
'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.placeholder.override_not_available'
|
||||
);
|
||||
}
|
||||
$fullElement = [];
|
||||
$fullElement[] = '<div class="checkbox t3js-form-field-eval-null-placeholder-checkbox">';
|
||||
$fullElement[] = '<label for="' . $nullControlNameEscaped . '">';
|
||||
$fullElement[] = '<input type="hidden" name="' . $nullControlNameEscaped . '" value="' . $fallbackValue . '" />';
|
||||
$fullElement[] = '<input type="checkbox" name="' . $nullControlNameEscaped . '" id="' . $nullControlNameEscaped . '" value="1"' . $checked . $disabled . ' />';
|
||||
$fullElement[] = $overrideLabel;
|
||||
$fullElement[] = '</label>';
|
||||
$fullElement[] = '</div>';
|
||||
$fullElement[] = '<div class="t3js-formengine-placeholder-placeholder">';
|
||||
$fullElement[] = '<div class="form-control-wrap" style="max-width:' . $width . 'px">';
|
||||
$fullElement[] = '<input type="text" class="form-control" disabled="disabled" value="' . htmlspecialchars($shortenedPlaceholder) . '" />';
|
||||
$fullElement[] = '</div>';
|
||||
$fullElement[] = '</div>';
|
||||
$fullElement[] = '<div class="t3js-formengine-placeholder-formfield">';
|
||||
$fullElement[] = $expansionHtml;
|
||||
$fullElement[] = '</div>';
|
||||
$fullElement = implode(LF, $fullElement);
|
||||
}
|
||||
|
||||
$resultArray['html'] = '<div class="formengine-field-item t3js-formengine-field-item">' . $fieldInformationHtml . $fullElement . '</div>';
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\Element;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Generation of TCEform elements of the type "input type=hidden"
|
||||
*/
|
||||
class InputHiddenElement extends AbstractFormElement
|
||||
{
|
||||
/**
|
||||
* This will render an input type="hidden" form field
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
$resultArray = $this->initializeResultArray();
|
||||
|
||||
$resultArray['additionalHiddenFields'][] = '<input type="hidden" name="' . $parameterArray['itemFormElName'] . '" value="' . htmlspecialchars($parameterArray['itemFormElValue']) . '" />';
|
||||
|
||||
return $resultArray;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,458 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\Element;
|
||||
|
||||
/*
|
||||
* 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\Imaging\Icon;
|
||||
use TYPO3\CMS\Core\LinkHandling\Exception\UnknownLinkHandlerException;
|
||||
use TYPO3\CMS\Core\LinkHandling\LinkService;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException;
|
||||
use TYPO3\CMS\Core\Resource\Exception\FolderDoesNotExistException;
|
||||
use TYPO3\CMS\Core\Resource\Exception\InvalidPathException;
|
||||
use TYPO3\CMS\Core\Resource\File;
|
||||
use TYPO3\CMS\Core\Resource\Folder;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
use TYPO3\CMS\Core\Utility\StringUtility;
|
||||
use TYPO3\CMS\Frontend\Service\TypoLinkCodecService;
|
||||
|
||||
/**
|
||||
* Link input element.
|
||||
*
|
||||
* Shows current link and the link popup.
|
||||
*/
|
||||
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
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldControl = [
|
||||
'linkPopup' => [
|
||||
'renderType' => 'linkPopup',
|
||||
'options' => []
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Default field wizards enabled for this element.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldWizard = [
|
||||
'localizationStateSelector' => [
|
||||
'renderType' => 'localizationStateSelector',
|
||||
],
|
||||
'otherLanguageContent' => [
|
||||
'renderType' => 'otherLanguageContent',
|
||||
'after' => [
|
||||
'localizationStateSelector'
|
||||
],
|
||||
],
|
||||
'defaultLanguageDifferences' => [
|
||||
'renderType' => 'defaultLanguageDifferences',
|
||||
'after' => [
|
||||
'otherLanguageContent',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* This will render a single-line input form field, possibly with various control/validation features
|
||||
*
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
|
||||
$table = $this->data['tableName'];
|
||||
$fieldName = $this->data['fieldName'];
|
||||
$row = $this->data['databaseRow'];
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
$resultArray = $this->initializeResultArray();
|
||||
$config = $parameterArray['fieldConf']['config'];
|
||||
|
||||
$itemValue = $parameterArray['itemFormElValue'];
|
||||
$evalList = GeneralUtility::trimExplode(',', $config['eval'], true);
|
||||
$size = MathUtility::forceIntegerInRange($config['size'] ?? $this->defaultInputWidth, $this->minimumInputWidth, $this->maxInputWidth);
|
||||
$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">';
|
||||
$html[] = '<input class="form-control" value="' . htmlspecialchars($itemValue) . '" type="text" disabled>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$resultArray['html'] = implode(LF, $html);
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
// @todo: The whole eval handling is a mess and needs refactoring
|
||||
foreach ($evalList as $func) {
|
||||
// @todo: This is ugly: The code should find out on it's own whether a eval definition is a
|
||||
// @todo: keyword like "date", or a class reference. The global registration could be dropped then
|
||||
// Pair hook to the one in \TYPO3\CMS\Core\DataHandling\DataHandler::checkValue_input_Eval()
|
||||
if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tce']['formevals'][$func])) {
|
||||
if (class_exists($func)) {
|
||||
$evalObj = GeneralUtility::makeInstance($func);
|
||||
if (method_exists($evalObj, 'deevaluateFieldValue')) {
|
||||
$_params = [
|
||||
'value' => $itemValue
|
||||
];
|
||||
$itemValue = $evalObj->deevaluateFieldValue($_params);
|
||||
}
|
||||
if (method_exists($evalObj, 'returnFieldJS')) {
|
||||
$resultArray['additionalJavaScriptPost'][] = 'TBE_EDITOR.customEvalFunctions[' . GeneralUtility::quoteJSvalue($func) . ']'
|
||||
. ' = function(value) {' . $evalObj->returnFieldJS() . '};';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$attributes = [
|
||||
'value' => '',
|
||||
'id' => StringUtility::getUniqueId('formengine-input-'),
|
||||
'class' => implode(' ', [
|
||||
'form-control',
|
||||
't3js-clearable',
|
||||
't3js-form-field-inputlink-input',
|
||||
'hidden',
|
||||
'hasDefaultValue',
|
||||
]),
|
||||
'data-formengine-validation-rules' => $this->getValidationDataAsJsonString($config),
|
||||
'data-formengine-input-params' => json_encode([
|
||||
'field' => $parameterArray['itemFormElName'],
|
||||
'evalList' => implode(',', $evalList)
|
||||
]),
|
||||
'data-formengine-input-name' => $parameterArray['itemFormElName'],
|
||||
];
|
||||
|
||||
$maxLength = $config['max'] ?? 0;
|
||||
if ((int)$maxLength > 0) {
|
||||
$attributes['maxlength'] = (int)$maxLength;
|
||||
}
|
||||
if (!empty($config['placeholder'])) {
|
||||
$attributes['placeholder'] = trim($config['placeholder']);
|
||||
}
|
||||
if (isset($config['autocomplete'])) {
|
||||
$attributes['autocomplete'] = empty($config['autocomplete']) ? 'new-' . $fieldName : 'on';
|
||||
}
|
||||
|
||||
$valuePickerHtml = [];
|
||||
if (isset($config['valuePicker']['items']) && is_array($config['valuePicker']['items'])) {
|
||||
$mode = $config['valuePicker']['mode'] ?? '';
|
||||
$itemName = $parameterArray['itemFormElName'];
|
||||
$fieldChangeFunc = $parameterArray['fieldChangeFunc'];
|
||||
if ($mode === 'append') {
|
||||
$assignValue = 'document.querySelectorAll(' . GeneralUtility::quoteJSvalue('[data-formengine-input-name="' . $itemName . '"]') . ')[0]'
|
||||
. '.value=\'\'+this.options[this.selectedIndex].value+document.editform[' . GeneralUtility::quoteJSvalue($itemName) . '].value';
|
||||
} elseif ($mode === 'prepend') {
|
||||
$assignValue = 'document.querySelectorAll(' . GeneralUtility::quoteJSvalue('[data-formengine-input-name="' . $itemName . '"]') . ')[0]'
|
||||
. '.value+=\'\'+this.options[this.selectedIndex].value';
|
||||
} else {
|
||||
$assignValue = 'document.querySelectorAll(' . GeneralUtility::quoteJSvalue('[data-formengine-input-name="' . $itemName . '"]') . ')[0]'
|
||||
. '.value=this.options[this.selectedIndex].value';
|
||||
}
|
||||
$valuePickerHtml[] = '<select';
|
||||
$valuePickerHtml[] = ' class="form-control tceforms-select tceforms-wizardselect"';
|
||||
$valuePickerHtml[] = ' onchange="' . htmlspecialchars($assignValue . ';this.blur();this.selectedIndex=0;' . implode('', $fieldChangeFunc)) . '"';
|
||||
$valuePickerHtml[] = '>';
|
||||
$valuePickerHtml[] = '<option></option>';
|
||||
foreach ($config['valuePicker']['items'] as $item) {
|
||||
$valuePickerHtml[] = '<option value="' . htmlspecialchars($item[1]) . '">' . htmlspecialchars($languageService->sL($item[0])) . '</option>';
|
||||
}
|
||||
$valuePickerHtml[] = '</select>';
|
||||
}
|
||||
|
||||
$fieldWizardResult = $this->renderFieldWizard();
|
||||
$fieldWizardHtml = $fieldWizardResult['html'];
|
||||
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldWizardResult, false);
|
||||
|
||||
$fieldControlResult = $this->renderFieldControl();
|
||||
$fieldControlHtml = $fieldControlResult['html'];
|
||||
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldControlResult, false);
|
||||
|
||||
$linkExplanation = $this->getLinkExplanation($itemValue ?: '');
|
||||
$explanation = htmlspecialchars($linkExplanation['text']);
|
||||
$toggleButtonTitle = $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:buttons.toggleLinkExplanation');
|
||||
|
||||
$expansionHtml = [];
|
||||
$expansionHtml[] = '<div class="form-control-wrap" style="max-width: ' . $width . 'px">';
|
||||
$expansionHtml[] = '<div class="form-wizards-wrap">';
|
||||
$expansionHtml[] = '<div class="form-wizards-element">';
|
||||
$expansionHtml[] = '<div class="input-group t3js-form-field-inputlink">';
|
||||
$expansionHtml[] = '<span class="t3js-form-field-inputlink-icon input-group-addon">' . $linkExplanation['icon'] . '</span>';
|
||||
$expansionHtml[] = '<input class="form-control form-field-inputlink-explanation t3js-form-field-inputlink-explanation" data-toggle="tooltip" data-title="' . $explanation . '" value="' . $explanation . '" readonly>';
|
||||
$expansionHtml[] = '<input type="text" ' . GeneralUtility::implodeAttributes($attributes, true) . ' />';
|
||||
$expansionHtml[] = '<span class="input-group-btn">';
|
||||
$expansionHtml[] = '<button class="btn btn-default t3js-form-field-inputlink-explanation-toggle" type="button" title="' . htmlspecialchars($toggleButtonTitle) . '">';
|
||||
$expansionHtml[] = $this->iconFactory->getIcon('actions-version-workspaces-preview-link', Icon::SIZE_SMALL)->render();
|
||||
$expansionHtml[] = '</button>';
|
||||
$expansionHtml[] = '</span>';
|
||||
$expansionHtml[] = '<input type="hidden" name="' . $parameterArray['itemFormElName'] . '" value="' . htmlspecialchars($itemValue) . '" />';
|
||||
$expansionHtml[] = '</div>';
|
||||
$expansionHtml[] = '</div>';
|
||||
if (!empty($valuePickerHtml) || !empty($fieldControlHtml)) {
|
||||
$expansionHtml[] = '<div class="form-wizards-items-aside">';
|
||||
$expansionHtml[] = '<div class="btn-group">';
|
||||
$expansionHtml[] = implode(LF, $valuePickerHtml);
|
||||
$expansionHtml[] = $fieldControlHtml;
|
||||
$expansionHtml[] = '</div>';
|
||||
$expansionHtml[] = '</div>';
|
||||
}
|
||||
$expansionHtml[] = '<div class="form-wizards-items-bottom">';
|
||||
$expansionHtml[] = $linkExplanation['additionalAttributes'];
|
||||
$expansionHtml[] = $fieldWizardHtml;
|
||||
$expansionHtml[] = '</div>';
|
||||
$expansionHtml[] = '</div>';
|
||||
$expansionHtml[] = '</div>';
|
||||
$expansionHtml = implode(LF, $expansionHtml);
|
||||
|
||||
$fullElement = $expansionHtml;
|
||||
if ($this->hasNullCheckboxButNoPlaceholder()) {
|
||||
$checked = $itemValue !== null ? ' checked="checked"' : '';
|
||||
$fullElement = [];
|
||||
$fullElement[] = '<div class="t3-form-field-disable"></div>';
|
||||
$fullElement[] = '<div class="checkbox t3-form-field-eval-null-checkbox">';
|
||||
$fullElement[] = '<label for="' . $nullControlNameEscaped . '">';
|
||||
$fullElement[] = '<input type="hidden" name="' . $nullControlNameEscaped . '" value="0" />';
|
||||
$fullElement[] = '<input type="checkbox" name="' . $nullControlNameEscaped . '" id="' . $nullControlNameEscaped . '" value="1"' . $checked . ' />';
|
||||
$fullElement[] = $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.nullCheckbox');
|
||||
$fullElement[] = '</label>';
|
||||
$fullElement[] = '</div>';
|
||||
$fullElement[] = $expansionHtml;
|
||||
$fullElement = implode(LF, $fullElement);
|
||||
} elseif ($this->hasNullCheckboxWithPlaceholder()) {
|
||||
$checked = $itemValue !== null ? ' checked="checked"' : '';
|
||||
$placeholder = $shortenedPlaceholder = $config['placeholder'] ?? '';
|
||||
$disabled = '';
|
||||
$fallbackValue = 0;
|
||||
if (strlen($placeholder) > 0) {
|
||||
$shortenedPlaceholder = GeneralUtility::fixed_lgd_cs($placeholder, 20);
|
||||
if ($placeholder !== $shortenedPlaceholder) {
|
||||
$overrideLabel = sprintf(
|
||||
$languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.placeholder.override'),
|
||||
'<span title="' . htmlspecialchars($placeholder) . '">' . htmlspecialchars($shortenedPlaceholder) . '</span>'
|
||||
);
|
||||
} else {
|
||||
$overrideLabel = sprintf(
|
||||
$languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.placeholder.override'),
|
||||
htmlspecialchars($placeholder)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$overrideLabel = $languageService->sL(
|
||||
'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.placeholder.override_not_available'
|
||||
);
|
||||
}
|
||||
$fullElement = [];
|
||||
$fullElement[] = '<div class="checkbox t3js-form-field-eval-null-placeholder-checkbox">';
|
||||
$fullElement[] = '<label for="' . $nullControlNameEscaped . '">';
|
||||
$fullElement[] = '<input type="hidden" name="' . $nullControlNameEscaped . '" value="' . $fallbackValue . '" />';
|
||||
$fullElement[] = '<input type="checkbox" name="' . $nullControlNameEscaped . '" id="' . $nullControlNameEscaped . '" value="1"' . $checked . $disabled . ' />';
|
||||
$fullElement[] = $overrideLabel;
|
||||
$fullElement[] = '</label>';
|
||||
$fullElement[] = '</div>';
|
||||
$fullElement[] = '<div class="t3js-formengine-placeholder-placeholder">';
|
||||
$fullElement[] = '<div class="form-control-wrap" style="max-width:' . $width . 'px">';
|
||||
$fullElement[] = '<input type="text" class="form-control" disabled="disabled" value="' . htmlspecialchars($shortenedPlaceholder) . '" />';
|
||||
$fullElement[] = '</div>';
|
||||
$fullElement[] = '</div>';
|
||||
$fullElement[] = '<div class="t3js-formengine-placeholder-formfield">';
|
||||
$fullElement[] = $expansionHtml;
|
||||
$fullElement[] = '</div>';
|
||||
$fullElement = implode(LF, $fullElement);
|
||||
}
|
||||
|
||||
$resultArray['html'] = '<div class="formengine-field-item t3js-formengine-field-item">' . $fieldInformationHtml . $fullElement . '</div>';
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $itemValue
|
||||
* @return array
|
||||
*/
|
||||
protected function getLinkExplanation(string $itemValue): array
|
||||
{
|
||||
if (empty($itemValue)) {
|
||||
return [];
|
||||
}
|
||||
$data = ['text' => '', 'icon' => ''];
|
||||
$typolinkService = GeneralUtility::makeInstance(TypoLinkCodecService::class);
|
||||
$linkParts = $typolinkService->decode($itemValue);
|
||||
$linkService = GeneralUtility::makeInstance(LinkService::class);
|
||||
|
||||
try {
|
||||
$linkData = $linkService->resolve($linkParts['url']);
|
||||
} catch (FileDoesNotExistException $e) {
|
||||
return $data;
|
||||
} catch (FolderDoesNotExistException $e) {
|
||||
return $data;
|
||||
} catch (UnknownLinkHandlerException $e) {
|
||||
return $data;
|
||||
} catch (InvalidPathException $e) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
// Resolving the TypoLink parts (class, title, params)
|
||||
$additionalAttributes = [];
|
||||
foreach ($linkParts as $key => $value) {
|
||||
if ($key === 'url') {
|
||||
continue;
|
||||
}
|
||||
if ($value) {
|
||||
switch ($key) {
|
||||
case 'class':
|
||||
$label = $this->getLanguageService()->sL('LLL:EXT:recordlist/Resources/Private/Language/locallang_browse_links.xlf:class');
|
||||
break;
|
||||
case 'title':
|
||||
$label = $this->getLanguageService()->sL('LLL:EXT:recordlist/Resources/Private/Language/locallang_browse_links.xlf:title');
|
||||
break;
|
||||
case 'additionalParams':
|
||||
$label = $this->getLanguageService()->sL('LLL:EXT:recordlist/Resources/Private/Language/locallang_browse_links.xlf:params');
|
||||
break;
|
||||
default:
|
||||
$label = $key;
|
||||
}
|
||||
|
||||
$additionalAttributes[] = '<span><strong>' . htmlspecialchars($label) . ': </strong> ' . htmlspecialchars($value) . '</span>';
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve the actual link
|
||||
switch ($linkData['type']) {
|
||||
case LinkService::TYPE_PAGE:
|
||||
$pageRecord = BackendUtility::readPageAccess($linkData['pageuid'], '1=1');
|
||||
// Is this a real page
|
||||
if ($pageRecord['uid']) {
|
||||
$data = [
|
||||
'text' => $pageRecord['_thePathFull'] . '[' . $pageRecord['uid'] . ']',
|
||||
'icon' => $this->iconFactory->getIconForRecord('pages', $pageRecord, Icon::SIZE_SMALL)->render()
|
||||
];
|
||||
}
|
||||
break;
|
||||
case LinkService::TYPE_EMAIL:
|
||||
$data = [
|
||||
'text' => $linkData['email'],
|
||||
'icon' => $this->iconFactory->getIcon('content-elements-mailform', Icon::SIZE_SMALL)->render()
|
||||
];
|
||||
break;
|
||||
case LinkService::TYPE_URL:
|
||||
$data = [
|
||||
'text' => $this->getDomainByUrl($linkData['url']),
|
||||
'icon' => $this->iconFactory->getIcon('apps-pagetree-page-shortcut-external', Icon::SIZE_SMALL)->render()
|
||||
|
||||
];
|
||||
break;
|
||||
case LinkService::TYPE_FILE:
|
||||
/** @var File $file */
|
||||
$file = $linkData['file'];
|
||||
if ($file) {
|
||||
$data = [
|
||||
'text' => $file->getPublicUrl(),
|
||||
'icon' => $this->iconFactory->getIconForFileExtension($file->getExtension(), Icon::SIZE_SMALL)->render()
|
||||
];
|
||||
}
|
||||
break;
|
||||
case LinkService::TYPE_FOLDER:
|
||||
/** @var Folder $folder */
|
||||
$folder = $linkData['folder'];
|
||||
if ($folder) {
|
||||
$data = [
|
||||
'text' => $folder->getPublicUrl(),
|
||||
'icon' => $this->iconFactory->getIcon('apps-filetree-folder-default', Icon::SIZE_SMALL)->render()
|
||||
];
|
||||
}
|
||||
break;
|
||||
case LinkService::TYPE_RECORD:
|
||||
$table = $this->data['pageTsConfig']['TCEMAIN.']['linkHandler.'][$linkData['identifier'] . '.']['configuration.']['table'];
|
||||
$record = BackendUtility::getRecord($table, $linkData['uid']);
|
||||
if ($record) {
|
||||
$recordTitle = BackendUtility::getRecordTitle($table, $record);
|
||||
$tableTitle = $this->getLanguageService()->sL($GLOBALS['TCA'][$table]['ctrl']['title']);
|
||||
$data = [
|
||||
'text' => sprintf('%s [%s:%d]', $recordTitle, $tableTitle, $linkData['uid']),
|
||||
'icon' => $this->iconFactory->getIconForRecord($table, $record, Icon::SIZE_SMALL)->render(),
|
||||
];
|
||||
} else {
|
||||
$data = [
|
||||
'text' => sprintf('%s', $linkData['uid']),
|
||||
'icon' => $this->iconFactory->getIcon('tcarecords-' . $table . '-default', Icon::SIZE_SMALL, 'overlay-missing')->render(),
|
||||
];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Please note that this hook is preliminary and might change, as this element could become its own
|
||||
// TCA type in the future
|
||||
if (isset($GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['linkHandler'][$linkData['type']])) {
|
||||
$linkBuilder = GeneralUtility::makeInstance($GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['linkHandler'][$linkData['type']]);
|
||||
$data = $linkBuilder->getFormData($linkData, $linkParts, $this->data, $this);
|
||||
} else {
|
||||
$data = [
|
||||
'text' => 'not implemented type ' . $linkData['type'],
|
||||
'icon' => ''
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$data['additionalAttributes'] = '<div class="help-block">' . implode(' - ', $additionalAttributes) . '</div>';
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $uriString
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getDomainByUrl(string $uriString): string
|
||||
{
|
||||
$data = parse_url($uriString);
|
||||
return $data['host'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService()
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Form\Element;
|
||||
|
||||
/*
|
||||
* 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\Controller\FormSlugAjaxController;
|
||||
use TYPO3\CMS\Core\Imaging\Icon;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Site\Entity\SiteInterface;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
use TYPO3\CMS\Core\Utility\StringUtility;
|
||||
|
||||
/**
|
||||
* General type=input element with some additional value.
|
||||
*/
|
||||
class InputSlugElement extends AbstractFormElement
|
||||
{
|
||||
|
||||
/**
|
||||
* Default field information enabled for this element.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldInformation = [
|
||||
'tcaDescription' => [
|
||||
'renderType' => 'tcaDescription',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Default field wizards enabled for this element.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldWizard = [
|
||||
'localizationStateSelector' => [
|
||||
'renderType' => 'localizationStateSelector',
|
||||
],
|
||||
'otherLanguageContent' => [
|
||||
'renderType' => 'otherLanguageContent',
|
||||
'after' => [
|
||||
'localizationStateSelector'
|
||||
],
|
||||
],
|
||||
'defaultLanguageDifferences' => [
|
||||
'renderType' => 'defaultLanguageDifferences',
|
||||
'after' => [
|
||||
'otherLanguageContent',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* This will render a single-line input form field, possibly with various control/validation features
|
||||
*
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$table = $this->data['tableName'];
|
||||
$row = $this->data['databaseRow'];
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
$resultArray = $this->initializeResultArray();
|
||||
|
||||
$languageId = 0;
|
||||
if (isset($GLOBALS['TCA'][$table]['ctrl']['languageField']) && !empty($GLOBALS['TCA'][$table]['ctrl']['languageField'])) {
|
||||
$languageField = $GLOBALS['TCA'][$table]['ctrl']['languageField'];
|
||||
$languageId = (int)((is_array($row[$languageField]) ? $row[$languageField][0] : $row[$languageField]) ?? 0);
|
||||
}
|
||||
$baseUrl = $this->getPrefix($this->data['site'], $languageId);
|
||||
|
||||
$itemValue = $parameterArray['itemFormElValue'];
|
||||
$config = $parameterArray['fieldConf']['config'];
|
||||
$evalList = GeneralUtility::trimExplode(',', $config['eval'], true);
|
||||
$size = MathUtility::forceIntegerInRange($config['size'] ?? $this->defaultInputWidth, $this->minimumInputWidth, $this->maxInputWidth);
|
||||
$width = (int)$this->formMaxWidth($size);
|
||||
|
||||
// Convert UTF-8 characters back (that is important, see Slug class when sanitizing)
|
||||
$itemValue = rawurldecode($itemValue);
|
||||
|
||||
$fieldInformationResult = $this->renderFieldInformation();
|
||||
$fieldInformationHtml = $fieldInformationResult['html'];
|
||||
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false);
|
||||
|
||||
$fieldControlResult = $this->renderFieldControl();
|
||||
$fieldControlHtml = $fieldControlResult['html'];
|
||||
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldControlResult, false);
|
||||
|
||||
$fieldWizardResult = $this->renderFieldWizard();
|
||||
$fieldWizardHtml = $fieldWizardResult['html'];
|
||||
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldWizardResult, false);
|
||||
$toggleButtonTitle = $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:buttons.toggleSlugExplanation');
|
||||
$recreateButtonTitle = $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:buttons.recreateSlugExplanation');
|
||||
|
||||
$successMessage = sprintf($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:slugCreation.success.' . ($table === 'pages' ? 'page' : 'record')), $baseUrl);
|
||||
$errorMessage = sprintf($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:slugCreation.error'), $baseUrl);
|
||||
|
||||
$thisSlugId = 't3js-form-field-slug-id' . StringUtility::getUniqueId();
|
||||
$mainFieldHtml = [];
|
||||
$mainFieldHtml[] = '<div class="formengine-field-item t3js-formengine-field-item">';
|
||||
$mainFieldHtml[] = $fieldInformationHtml;
|
||||
$mainFieldHtml[] = '<div class="form-control-wrap" style="max-width: ' . $width . 'px" id="' . htmlspecialchars($thisSlugId) . '">';
|
||||
$mainFieldHtml[] = '<div class="form-wizards-wrap">';
|
||||
$mainFieldHtml[] = '<div class="form-wizards-element">';
|
||||
$mainFieldHtml[] = '<div class="input-group">';
|
||||
$mainFieldHtml[] = ($baseUrl ? '<span class="input-group-addon">' . htmlspecialchars($baseUrl) . '</span>' : '');
|
||||
// We deal with 3 fields here: a readonly field for current / default values, an input
|
||||
// field to manipulate the value, and the final hidden field used to send the value
|
||||
$mainFieldHtml[] = '<input';
|
||||
$mainFieldHtml[] = ' class="form-control t3js-form-field-slug-readonly"';
|
||||
$mainFieldHtml[] = ' data-toggle="tooltip"';
|
||||
$mainFieldHtml[] = ' data-title="' . htmlspecialchars($itemValue) . '"';
|
||||
$mainFieldHtml[] = ' value="' . htmlspecialchars($itemValue) . '"';
|
||||
$mainFieldHtml[] = ' readonly';
|
||||
$mainFieldHtml[] = ' />';
|
||||
$mainFieldHtml[] = '<input type="text"';
|
||||
$mainFieldHtml[] = ' id="' . htmlspecialchars(StringUtility::getUniqueId('formengine-input-')) . '"';
|
||||
$mainFieldHtml[] = ' class="form-control t3js-form-field-slug-input hidden"';
|
||||
$mainFieldHtml[] = ' placeholder="' . htmlspecialchars($row['slug'] ?? '/') . '"';
|
||||
$mainFieldHtml[] = ' data-formengine-validation-rules="' . htmlspecialchars($this->getValidationDataAsJsonString($config)) . '"';
|
||||
$mainFieldHtml[] = ' data-formengine-input-params="' . htmlspecialchars(json_encode(['field' => $parameterArray['itemFormElName'], 'evalList' => implode(',', $evalList)])) . '"';
|
||||
$mainFieldHtml[] = ' data-formengine-input-name="' . htmlspecialchars($parameterArray['itemFormElName']) . '"';
|
||||
$mainFieldHtml[] = ' />';
|
||||
$mainFieldHtml[] = '<span class="input-group-btn">';
|
||||
$mainFieldHtml[] = '<button class="btn btn-default t3js-form-field-slug-toggle" type="button" title="' . htmlspecialchars($toggleButtonTitle) . '">';
|
||||
$mainFieldHtml[] = $this->iconFactory->getIcon('actions-version-workspaces-preview-link', Icon::SIZE_SMALL)->render();
|
||||
$mainFieldHtml[] = '</button>';
|
||||
$mainFieldHtml[] = '<button class="btn btn-default t3js-form-field-slug-recreate" type="button" title="' . htmlspecialchars($recreateButtonTitle) . '">';
|
||||
$mainFieldHtml[] = $this->iconFactory->getIcon('actions-refresh', Icon::SIZE_SMALL)->render();
|
||||
$mainFieldHtml[] = '</button>';
|
||||
$mainFieldHtml[] = '</span>';
|
||||
$mainFieldHtml[] = '<input type="hidden"';
|
||||
$mainFieldHtml[] = ' class="t3js-form-field-slug-hidden"';
|
||||
$mainFieldHtml[] = ' name="' . htmlspecialchars($parameterArray['itemFormElName']) . '"';
|
||||
$mainFieldHtml[] = ' value="' . htmlspecialchars($itemValue) . '"';
|
||||
$mainFieldHtml[] = ' />';
|
||||
$mainFieldHtml[] = '</div>';
|
||||
$mainFieldHtml[] = '</div>';
|
||||
if (!empty($fieldControlHtml)) {
|
||||
$mainFieldHtml[] = '<div class="form-wizards-items-aside">';
|
||||
$mainFieldHtml[] = '<div class="btn-group">';
|
||||
$mainFieldHtml[] = $fieldControlHtml;
|
||||
$mainFieldHtml[] = '</div>';
|
||||
$mainFieldHtml[] = '</div>';
|
||||
}
|
||||
$mainFieldHtml[] = '<div class="form-wizards-items-bottom">';
|
||||
$mainFieldHtml[] = '<span class="t3js-form-proposal-accepted hidden label label-success">' . htmlspecialchars($successMessage) . '<span>/abc/</span></span>';
|
||||
$mainFieldHtml[] = '<span class="t3js-form-proposal-different hidden label label-warning">' . htmlspecialchars($errorMessage) . '<span>/abc/</span></span>';
|
||||
$mainFieldHtml[] = $fieldWizardHtml;
|
||||
$mainFieldHtml[] = '</div>';
|
||||
$mainFieldHtml[] = '</div>';
|
||||
$mainFieldHtml[] = '</div>';
|
||||
$mainFieldHtml[] = '</div>';
|
||||
|
||||
$resultArray['html'] = implode(LF, $mainFieldHtml);
|
||||
|
||||
[$commonElementPrefix] = GeneralUtility::revExplode('[', $parameterArray['itemFormElName'], 2);
|
||||
$validInputNamesToListenTo = [];
|
||||
$includeUidInValues = false;
|
||||
foreach ($config['generatorOptions']['fields'] ?? [] as $fieldNameParts) {
|
||||
if (is_string($fieldNameParts)) {
|
||||
$fieldNameParts = GeneralUtility::trimExplode(',', $fieldNameParts);
|
||||
}
|
||||
foreach ($fieldNameParts as $listenerFieldName) {
|
||||
if ($listenerFieldName === 'uid') {
|
||||
$includeUidInValues = true;
|
||||
continue;
|
||||
}
|
||||
$validInputNamesToListenTo[$listenerFieldName] = $commonElementPrefix . '[' . htmlspecialchars($listenerFieldName) . ']';
|
||||
}
|
||||
}
|
||||
$parentPageId = $this->data['parentPageRow']['uid'] ?? 0;
|
||||
$signature = GeneralUtility::hmac(
|
||||
implode(
|
||||
'',
|
||||
[
|
||||
$table,
|
||||
$this->data['effectivePid'],
|
||||
$row['uid'],
|
||||
$languageId,
|
||||
$this->data['fieldName'],
|
||||
$this->data['command'],
|
||||
$parentPageId
|
||||
]
|
||||
),
|
||||
FormSlugAjaxController::class
|
||||
);
|
||||
$optionsForModule = [
|
||||
'pageId' => $this->data['effectivePid'],
|
||||
'recordId' => $row['uid'],
|
||||
'tableName' => $table,
|
||||
'fieldName' => $this->data['fieldName'],
|
||||
'config' => $config,
|
||||
'listenerFieldNames' => $validInputNamesToListenTo,
|
||||
'language' => $languageId,
|
||||
'originalValue' => $itemValue,
|
||||
'signature' => $signature,
|
||||
'command' => $this->data['command'],
|
||||
'parentPageId' => $parentPageId,
|
||||
'includeUidInValues' => $includeUidInValues,
|
||||
];
|
||||
$resultArray['requireJsModules'][] = ['TYPO3/CMS/Backend/FormEngine/Element/SlugElement' => '
|
||||
function(SlugElement) {
|
||||
new SlugElement(' . GeneralUtility::quoteJSvalue('#' . $thisSlugId) . ', ' . json_encode($optionsForModule) . ');
|
||||
}'
|
||||
];
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the prefix for the input field.
|
||||
*
|
||||
* @param SiteInterface $site
|
||||
* @param int $requestLanguageId
|
||||
* @return string
|
||||
*/
|
||||
protected function getPrefix(SiteInterface $site, int $requestLanguageId = 0): string
|
||||
{
|
||||
try {
|
||||
$language = ($requestLanguageId < 0) ? $site->getDefaultLanguage() : $site->getLanguageById($requestLanguageId);
|
||||
$base = $language->getBase();
|
||||
$baseUrl = (string)$base;
|
||||
$baseUrl = rtrim($baseUrl, '/');
|
||||
if (!empty($baseUrl) && empty($base->getScheme()) && $base->getHost() !== '') {
|
||||
$baseUrl = 'http:' . $baseUrl;
|
||||
}
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
// No site / language found
|
||||
$baseUrl = '';
|
||||
}
|
||||
return $baseUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,344 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\Element;
|
||||
|
||||
/*
|
||||
* 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\Core\Utility\MathUtility;
|
||||
use TYPO3\CMS\Core\Utility\StringUtility;
|
||||
|
||||
/**
|
||||
* General type=input element.
|
||||
*
|
||||
* This one kicks in if no specific renderType like "inputDateTime"
|
||||
* or "inputColorPicker" is set.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldWizard = [
|
||||
'localizationStateSelector' => [
|
||||
'renderType' => 'localizationStateSelector',
|
||||
],
|
||||
'otherLanguageContent' => [
|
||||
'renderType' => 'otherLanguageContent',
|
||||
'after' => [
|
||||
'localizationStateSelector'
|
||||
],
|
||||
],
|
||||
'defaultLanguageDifferences' => [
|
||||
'renderType' => 'defaultLanguageDifferences',
|
||||
'after' => [
|
||||
'otherLanguageContent',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* This will render a single-line input form field, possibly with various control/validation features
|
||||
*
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
|
||||
$table = $this->data['tableName'];
|
||||
$fieldName = $this->data['fieldName'];
|
||||
$row = $this->data['databaseRow'];
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
$resultArray = $this->initializeResultArray();
|
||||
|
||||
$itemValue = $parameterArray['itemFormElValue'];
|
||||
$config = $parameterArray['fieldConf']['config'];
|
||||
$evalList = GeneralUtility::trimExplode(',', $config['eval'], true);
|
||||
$size = MathUtility::forceIntegerInRange($config['size'] ?? $this->defaultInputWidth, $this->minimumInputWidth, $this->maxInputWidth);
|
||||
$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)) {
|
||||
$itemValue = $itemValue ? '*********' : '';
|
||||
}
|
||||
|
||||
$disabledFieldAttributes = [
|
||||
'class' => 'form-control',
|
||||
'data-formengine-input-name' => $parameterArray['itemFormElName'],
|
||||
'type' => 'text',
|
||||
'value' => $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">';
|
||||
$html[] = '<input ' . GeneralUtility::implodeAttributes($disabledFieldAttributes, true) . ' disabled>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$resultArray['html'] = implode(LF, $html);
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
// @todo: The whole eval handling is a mess and needs refactoring
|
||||
foreach ($evalList as $func) {
|
||||
// @todo: This is ugly: The code should find out on it's own whether a eval definition is a
|
||||
// @todo: keyword like "date", or a class reference. The global registration could be dropped then
|
||||
// Pair hook to the one in \TYPO3\CMS\Core\DataHandling\DataHandler::checkValue_input_Eval()
|
||||
if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tce']['formevals'][$func])) {
|
||||
if (class_exists($func)) {
|
||||
$evalObj = GeneralUtility::makeInstance($func);
|
||||
if (method_exists($evalObj, 'deevaluateFieldValue')) {
|
||||
$_params = [
|
||||
'value' => $itemValue
|
||||
];
|
||||
$itemValue = $evalObj->deevaluateFieldValue($_params);
|
||||
}
|
||||
if (method_exists($evalObj, 'returnFieldJS')) {
|
||||
$resultArray['additionalJavaScriptPost'][] = 'TBE_EDITOR.customEvalFunctions[' . GeneralUtility::quoteJSvalue($func) . ']'
|
||||
. ' = function(value) {' . $evalObj->returnFieldJS() . '};';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$fieldId = StringUtility::getUniqueId('formengine-input-');
|
||||
|
||||
$attributes = [
|
||||
'value' => '',
|
||||
'id' => $fieldId,
|
||||
'class' => implode(' ', [
|
||||
'form-control',
|
||||
't3js-clearable',
|
||||
'hasDefaultValue',
|
||||
]),
|
||||
'data-formengine-validation-rules' => $this->getValidationDataAsJsonString($config),
|
||||
'data-formengine-input-params' => json_encode([
|
||||
'field' => $parameterArray['itemFormElName'],
|
||||
'evalList' => implode(',', $evalList),
|
||||
'is_in' => trim($config['is_in'])
|
||||
]),
|
||||
'data-formengine-input-name' => $parameterArray['itemFormElName'],
|
||||
];
|
||||
|
||||
$maxLength = $config['max'] ?? 0;
|
||||
if ((int)$maxLength > 0) {
|
||||
$attributes['maxlength'] = (int)$maxLength;
|
||||
}
|
||||
if (!empty($config['placeholder'])) {
|
||||
$attributes['placeholder'] = trim($config['placeholder']);
|
||||
}
|
||||
if (isset($config['autocomplete'])) {
|
||||
$attributes['autocomplete'] = empty($config['autocomplete']) ? 'new-' . $fieldName : 'on';
|
||||
}
|
||||
|
||||
$valuePickerHtml = [];
|
||||
if (isset($config['valuePicker']['items']) && is_array($config['valuePicker']['items'])) {
|
||||
$mode = $config['valuePicker']['mode'] ?? '';
|
||||
$itemName = $parameterArray['itemFormElName'];
|
||||
$fieldChangeFunc = $parameterArray['fieldChangeFunc'];
|
||||
if ($mode === 'append') {
|
||||
$assignValue = 'document.querySelectorAll(' . GeneralUtility::quoteJSvalue('[data-formengine-input-name="' . $itemName . '"]') . ')[0]'
|
||||
. '.value+=\'\'+this.options[this.selectedIndex].value';
|
||||
} elseif ($mode === 'prepend') {
|
||||
$assignValue = 'document.querySelectorAll(' . GeneralUtility::quoteJSvalue('[data-formengine-input-name="' . $itemName . '"]') . ')[0]'
|
||||
. '.value=\'\'+this.options[this.selectedIndex].value+document.editform[' . GeneralUtility::quoteJSvalue($itemName) . '].value';
|
||||
} else {
|
||||
$assignValue = 'document.querySelectorAll(' . GeneralUtility::quoteJSvalue('[data-formengine-input-name="' . $itemName . '"]') . ')[0]'
|
||||
. '.value=this.options[this.selectedIndex].value';
|
||||
}
|
||||
$valuePickerHtml[] = '<select';
|
||||
$valuePickerHtml[] = ' class="form-control tceforms-select tceforms-wizardselect"';
|
||||
$valuePickerHtml[] = ' onchange="' . htmlspecialchars($assignValue . ';this.blur();this.selectedIndex=0;' . implode('', $fieldChangeFunc)) . '"';
|
||||
$valuePickerHtml[] = '>';
|
||||
$valuePickerHtml[] = '<option></option>';
|
||||
foreach ($config['valuePicker']['items'] as $item) {
|
||||
$valuePickerHtml[] = '<option value="' . htmlspecialchars($item[1]) . '">' . htmlspecialchars($languageService->sL($item[0])) . '</option>';
|
||||
}
|
||||
$valuePickerHtml[] = '</select>';
|
||||
}
|
||||
|
||||
$valueSliderHtml = [];
|
||||
if (isset($config['slider']) && is_array($config['slider'])) {
|
||||
$id = 'slider-' . $fieldId;
|
||||
$resultArray['requireJsModules'][] = ['TYPO3/CMS/Backend/FormEngine/FieldWizard/ValueSlider' =>
|
||||
'function(ValueSlider) { new ValueSlider(' . GeneralUtility::quoteJSvalue($id) . '); }'
|
||||
];
|
||||
$min = $config['range']['lower'] ?? 0;
|
||||
$max = $config['range']['upper'] ?? 10000;
|
||||
$step = $config['slider']['step'] ?? 1;
|
||||
$width = $config['slider']['width'] ?? 400;
|
||||
$valueType = 'null';
|
||||
if (in_array('int', $evalList, true)) {
|
||||
$valueType = 'int';
|
||||
$itemValue = (int)$itemValue;
|
||||
} elseif (in_array('double2', $evalList, true)) {
|
||||
$valueType = 'double';
|
||||
$itemValue = (double)$itemValue;
|
||||
}
|
||||
$callbackParams = [ $table, $row['uid'], $fieldName, $parameterArray['itemFormElName'] ];
|
||||
$valueSliderHtml[] = '<div';
|
||||
$valueSliderHtml[] = ' id="' . $id . '"';
|
||||
$valueSliderHtml[] = ' data-slider-id="' . $id . '"';
|
||||
$valueSliderHtml[] = ' data-slider-min="' . (int)$min . '"';
|
||||
$valueSliderHtml[] = ' data-slider-max="' . (int)$max . '"';
|
||||
$valueSliderHtml[] = ' data-slider-step="' . htmlspecialchars($step) . '"';
|
||||
$valueSliderHtml[] = ' data-slider-value="' . htmlspecialchars($itemValue) . '"';
|
||||
$valueSliderHtml[] = ' data-slider-value-type="' . htmlspecialchars($valueType) . '"';
|
||||
$valueSliderHtml[] = ' data-slider-item-name="' . htmlspecialchars($parameterArray['itemFormElName']) . '"';
|
||||
$valueSliderHtml[] = ' data-slider-callback-params="' . htmlspecialchars(json_encode($callbackParams)) . '"';
|
||||
$valueSliderHtml[] = ' style="width: ' . $width . 'px;"';
|
||||
$valueSliderHtml[] = '>';
|
||||
$valueSliderHtml[] = '</div>';
|
||||
}
|
||||
|
||||
$fieldControlResult = $this->renderFieldControl();
|
||||
$fieldControlHtml = $fieldControlResult['html'];
|
||||
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldControlResult, false);
|
||||
|
||||
$fieldWizardResult = $this->renderFieldWizard();
|
||||
$fieldWizardHtml = $fieldWizardResult['html'];
|
||||
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldWizardResult, false);
|
||||
$inputType = 'text';
|
||||
|
||||
if (in_array('email', $evalList, true)) {
|
||||
$inputType = 'email';
|
||||
} elseif (!empty(array_intersect($evalList, ['int', 'num']))) {
|
||||
$inputType = 'number';
|
||||
|
||||
if (isset($config['range']['lower'])) {
|
||||
$attributes['min'] = (int)$config['range']['lower'];
|
||||
}
|
||||
if (isset($config['range']['upper'])) {
|
||||
$attributes['max'] = (int)$config['range']['upper'];
|
||||
}
|
||||
}
|
||||
|
||||
$mainFieldHtml = [];
|
||||
$mainFieldHtml[] = '<div class="form-control-wrap" style="max-width: ' . $width . 'px">';
|
||||
$mainFieldHtml[] = '<div class="form-wizards-wrap">';
|
||||
$mainFieldHtml[] = '<div class="form-wizards-element">';
|
||||
$mainFieldHtml[] = '<input type="' . $inputType . '" ' . GeneralUtility::implodeAttributes($attributes, true) . ' />';
|
||||
$mainFieldHtml[] = '<input type="hidden" name="' . $parameterArray['itemFormElName'] . '" value="' . htmlspecialchars($itemValue) . '" />';
|
||||
$mainFieldHtml[] = '</div>';
|
||||
if (!empty($valuePickerHtml) || !empty($valueSliderHtml) || !empty($fieldControlHtml)) {
|
||||
$mainFieldHtml[] = '<div class="form-wizards-items-aside">';
|
||||
$mainFieldHtml[] = '<div class="btn-group">';
|
||||
$mainFieldHtml[] = implode(LF, $valuePickerHtml);
|
||||
$mainFieldHtml[] = implode(LF, $valueSliderHtml);
|
||||
$mainFieldHtml[] = $fieldControlHtml;
|
||||
$mainFieldHtml[] = '</div>';
|
||||
$mainFieldHtml[] = '</div>';
|
||||
}
|
||||
if (!empty($fieldWizardHtml)) {
|
||||
$mainFieldHtml[] = '<div class="form-wizards-items-bottom">';
|
||||
$mainFieldHtml[] = $fieldWizardHtml;
|
||||
$mainFieldHtml[] = '</div>';
|
||||
}
|
||||
$mainFieldHtml[] = '</div>';
|
||||
$mainFieldHtml[] = '</div>';
|
||||
$mainFieldHtml = implode(LF, $mainFieldHtml);
|
||||
|
||||
$fullElement = $mainFieldHtml;
|
||||
if ($this->hasNullCheckboxButNoPlaceholder()) {
|
||||
$checked = $itemValue !== null ? ' checked="checked"' : '';
|
||||
$fullElement = [];
|
||||
$fullElement[] = '<div class="t3-form-field-disable"></div>';
|
||||
$fullElement[] = '<div class="checkbox t3-form-field-eval-null-checkbox">';
|
||||
$fullElement[] = '<label for="' . $nullControlNameEscaped . '">';
|
||||
$fullElement[] = '<input type="hidden" name="' . $nullControlNameEscaped . '" value="0" />';
|
||||
$fullElement[] = '<input type="checkbox" name="' . $nullControlNameEscaped . '" id="' . $nullControlNameEscaped . '" value="1"' . $checked . ' />';
|
||||
$fullElement[] = $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.nullCheckbox');
|
||||
$fullElement[] = '</label>';
|
||||
$fullElement[] = '</div>';
|
||||
$fullElement[] = $mainFieldHtml;
|
||||
$fullElement = implode(LF, $fullElement);
|
||||
} elseif ($this->hasNullCheckboxWithPlaceholder()) {
|
||||
$checked = $itemValue !== null ? ' checked="checked"' : '';
|
||||
$placeholder = $shortenedPlaceholder = $config['placeholder'] ?? '';
|
||||
$disabled = '';
|
||||
$fallbackValue = 0;
|
||||
if (strlen($placeholder) > 0) {
|
||||
$shortenedPlaceholder = GeneralUtility::fixed_lgd_cs($placeholder, 20);
|
||||
if ($placeholder !== $shortenedPlaceholder) {
|
||||
$overrideLabel = sprintf(
|
||||
$languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.placeholder.override'),
|
||||
'<span title="' . htmlspecialchars($placeholder) . '">' . htmlspecialchars($shortenedPlaceholder) . '</span>'
|
||||
);
|
||||
} else {
|
||||
$overrideLabel = sprintf(
|
||||
$languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.placeholder.override'),
|
||||
htmlspecialchars($placeholder)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$overrideLabel = $languageService->sL(
|
||||
'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.placeholder.override_not_available'
|
||||
);
|
||||
}
|
||||
$fullElement = [];
|
||||
$fullElement[] = '<div class="checkbox t3js-form-field-eval-null-placeholder-checkbox">';
|
||||
$fullElement[] = '<label for="' . $nullControlNameEscaped . '">';
|
||||
$fullElement[] = '<input type="hidden" name="' . $nullControlNameEscaped . '" value="' . $fallbackValue . '" />';
|
||||
$fullElement[] = '<input type="checkbox" name="' . $nullControlNameEscaped . '" id="' . $nullControlNameEscaped . '" value="1"' . $checked . $disabled . ' />';
|
||||
$fullElement[] = $overrideLabel;
|
||||
$fullElement[] = '</label>';
|
||||
$fullElement[] = '</div>';
|
||||
$fullElement[] = '<div class="t3js-formengine-placeholder-placeholder">';
|
||||
$fullElement[] = '<div class="form-control-wrap" style="max-width:' . $width . 'px">';
|
||||
$fullElement[] = '<input type="text" class="form-control" disabled="disabled" value="' . htmlspecialchars($shortenedPlaceholder) . '" />';
|
||||
$fullElement[] = '</div>';
|
||||
$fullElement[] = '</div>';
|
||||
$fullElement[] = '<div class="t3js-formengine-placeholder-formfield">';
|
||||
$fullElement[] = $mainFieldHtml;
|
||||
$fullElement[] = '</div>';
|
||||
$fullElement = implode(LF, $fullElement);
|
||||
}
|
||||
|
||||
$resultArray['html'] = '<div class="formengine-field-item t3js-formengine-field-item">' . $fieldInformationHtml . $fullElement . '</div>';
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService()
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\Element;
|
||||
|
||||
/*
|
||||
* 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\MathUtility;
|
||||
|
||||
/**
|
||||
* None element is a "disabled" input element with formatted values if needed.
|
||||
*/
|
||||
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 array The HTML code for the TCEform field
|
||||
*/
|
||||
public function render(): array
|
||||
{
|
||||
$resultArray = $this->initializeResultArray();
|
||||
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
$config = $parameterArray['fieldConf']['config'];
|
||||
$itemValue = $parameterArray['itemFormElValue'];
|
||||
|
||||
if (isset($config['format']) && $config['format']) {
|
||||
$formatOptions = $config['format.'] ?? [];
|
||||
$itemValue = $this->formatValue($config['format'], $itemValue, $formatOptions);
|
||||
}
|
||||
if (!$config['pass_content']) {
|
||||
$itemValue = htmlspecialchars($itemValue);
|
||||
}
|
||||
|
||||
$cols = $config['cols'] ?: ($config['size'] ?: $this->defaultInputWidth);
|
||||
$size = MathUtility::forceIntegerInRange($cols ?: $this->defaultInputWidth, 5, $this->maxInputWidth);
|
||||
$width = $this->formMaxWidth($size);
|
||||
|
||||
$fieldInformationResult = $this->renderFieldInformation();
|
||||
$fieldInformationHtml = $fieldInformationResult['html'];
|
||||
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, 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">';
|
||||
$html[] = '<div class="form-control-wrap" style="max-width: ' . $width . 'px">';
|
||||
$html[] = '<input class="form-control" value="' . htmlspecialchars($itemValue) . '" type="text" disabled>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
|
||||
$resultArray['html'] = implode(LF, $html);
|
||||
|
||||
return $resultArray;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Form\Element;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* PassThroughElement is the dummy element for type="passthrough".
|
||||
* It does not render anything.
|
||||
*/
|
||||
class PassThroughElement extends AbstractFormElement
|
||||
{
|
||||
/**
|
||||
* Return the empty initialized result array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function render(): array
|
||||
{
|
||||
return $this->initializeResultArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\Element;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Render elements of type radio
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldWizard = [
|
||||
'localizationStateSelector' => [
|
||||
'renderType' => 'localizationStateSelector',
|
||||
],
|
||||
'otherLanguageContent' => [
|
||||
'renderType' => 'otherLanguageContent',
|
||||
'after' => [
|
||||
'localizationStateSelector'
|
||||
],
|
||||
],
|
||||
'defaultLanguageDifferences' => [
|
||||
'renderType' => 'defaultLanguageDifferences',
|
||||
'after' => [
|
||||
'otherLanguageContent',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* This will render a series of radio buttons.
|
||||
*
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$resultArray = $this->initializeResultArray();
|
||||
|
||||
$disabled = '';
|
||||
if ($this->data['parameterArray']['fieldConf']['config']['readOnly']) {
|
||||
$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"' : '';
|
||||
|
||||
$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 && !empty($fieldWizardHtml)) {
|
||||
$html[] = '<div class="form-wizards-items-bottom">';
|
||||
$html[] = $fieldWizardHtml;
|
||||
$html[] = '</div>';
|
||||
}
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
|
||||
$resultArray['html'] = implode(LF, $html);
|
||||
return $resultArray;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\Element;
|
||||
|
||||
/*
|
||||
* 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\Utility\FormEngineUtility;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Imaging\Icon;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\StringUtility;
|
||||
|
||||
/**
|
||||
* Creates a widget with check box elements.
|
||||
*
|
||||
* This is rendered for config type=select, renderType=selectCheckBox
|
||||
*/
|
||||
class SelectCheckBoxElement extends AbstractFormElement
|
||||
{
|
||||
/**
|
||||
* Default field information enabled for this element.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldInformation = [
|
||||
'tcaDescription' => [
|
||||
'renderType' => 'tcaDescription',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Default field wizards enabled for this element.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldWizard = [
|
||||
'localizationStateSelector' => [
|
||||
'renderType' => 'localizationStateSelector',
|
||||
],
|
||||
'otherLanguageContent' => [
|
||||
'renderType' => 'otherLanguageContent',
|
||||
'after' => [
|
||||
'localizationStateSelector'
|
||||
],
|
||||
],
|
||||
'defaultLanguageDifferences' => [
|
||||
'renderType' => 'defaultLanguageDifferences',
|
||||
'after' => [
|
||||
'otherLanguageContent',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Render check boxes
|
||||
*
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$resultArray = $this->initializeResultArray();
|
||||
|
||||
$html = [];
|
||||
// Field configuration from TCA:
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
$config = $parameterArray['fieldConf']['config'];
|
||||
$disabled = !empty($config['readOnly']);
|
||||
|
||||
$selItems = $config['items'];
|
||||
if (!empty($selItems)) {
|
||||
// Get values in an array (and make unique, which is fine because there can be no duplicates anyway)
|
||||
// In case e.g. "l10n_display" is set to "defaultAsReadonly" only one value (as string) could be handed in
|
||||
if (is_array($parameterArray['itemFormElValue'])) {
|
||||
$itemArray = $parameterArray['itemFormElValue'];
|
||||
} else {
|
||||
$itemArray = [(string)$parameterArray['itemFormElValue']];
|
||||
}
|
||||
$itemArray = array_flip($itemArray);
|
||||
|
||||
// Traverse the Array of selector box items:
|
||||
$groups = [];
|
||||
$currentGroup = 0;
|
||||
$c = 0;
|
||||
$sOnChange = '';
|
||||
if (!$disabled) {
|
||||
$sOnChange = implode('', $parameterArray['fieldChangeFunc']);
|
||||
// Used to accumulate the JS needed to restore the original selection.
|
||||
foreach ($selItems as $p) {
|
||||
// Non-selectable element:
|
||||
if ($p[1] === '--div--') {
|
||||
$selIcon = '';
|
||||
if (isset($p[2]) && $p[2] !== 'empty-empty') {
|
||||
$selIcon = FormEngineUtility::getIconHtml($p[2]);
|
||||
}
|
||||
$currentGroup++;
|
||||
$groups[$currentGroup]['header'] = [
|
||||
'icon' => $selIcon,
|
||||
'title' => $p[0]
|
||||
];
|
||||
} else {
|
||||
// Check if some help text is available
|
||||
// Help text is expected to be an associative array
|
||||
// with two key, "title" and "description"
|
||||
// For the sake of backwards compatibility, we test if the help text
|
||||
// is a string and use it as a description (this could happen if items
|
||||
// are modified with an itemProcFunc)
|
||||
$hasHelp = false;
|
||||
$help = '';
|
||||
$helpArray = [];
|
||||
if (!empty($p[3])) {
|
||||
$hasHelp = true;
|
||||
if (is_array($p[3])) {
|
||||
$helpArray = $p[3];
|
||||
} else {
|
||||
$helpArray['description'] = $p[3];
|
||||
}
|
||||
}
|
||||
if ($hasHelp) {
|
||||
$help = BackendUtility::wrapInHelp('', '', '', $helpArray);
|
||||
}
|
||||
|
||||
// Selected or not by default:
|
||||
$checked = 0;
|
||||
if (isset($itemArray[$p[1]])) {
|
||||
$checked = 1;
|
||||
unset($itemArray[$p[1]]);
|
||||
}
|
||||
|
||||
// Build item array
|
||||
$groups[$currentGroup]['items'][] = [
|
||||
'id' => StringUtility::getUniqueId('select_checkbox_row_'),
|
||||
'name' => $parameterArray['itemFormElName'] . '[' . $c . ']',
|
||||
'value' => $p[1],
|
||||
'checked' => $checked,
|
||||
'disabled' => false,
|
||||
'class' => '',
|
||||
'icon' => FormEngineUtility::getIconHtml(!empty($p[2]) ? $p[2] : 'empty-empty'),
|
||||
'title' => $p[0],
|
||||
'help' => $help
|
||||
];
|
||||
$c++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$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">';
|
||||
$html[] = $fieldInformationHtml;
|
||||
$html[] = '<div class="form-wizards-wrap">';
|
||||
$html[] = '<div class="form-wizards-element">';
|
||||
|
||||
// Add an empty hidden field which will send a blank value if all items are unselected.
|
||||
$html[] = '<input type="hidden" class="select-checkbox" name="' . htmlspecialchars($parameterArray['itemFormElName']) . '" value="">';
|
||||
|
||||
// Building the checkboxes
|
||||
foreach ($groups as $groupKey => $group) {
|
||||
$groupId = htmlspecialchars($parameterArray['itemFormElID']) . '-group-' . $groupKey;
|
||||
$html[] = '<div class="panel panel-default">';
|
||||
if (is_array($group['header'])) {
|
||||
$html[] = '<div class="panel-heading">';
|
||||
$html[] = '<a data-toggle="collapse" href="#' . $groupId . '" aria-expanded="false" aria-controls="' . $groupId . '">';
|
||||
$html[] = $group['header']['icon'];
|
||||
$html[] = htmlspecialchars($group['header']['title']);
|
||||
$html[] = '</a>';
|
||||
$html[] = '</div>';
|
||||
}
|
||||
if (is_array($group['items']) && !empty($group['items'])) {
|
||||
$tableRows = [];
|
||||
$resetGroup = [];
|
||||
|
||||
// Render rows
|
||||
foreach ($group['items'] as $item) {
|
||||
$tableRows[] = '<tr class="' . $item['class'] . '">';
|
||||
$tableRows[] = '<td class="col-checkbox">';
|
||||
$tableRows[] = '<input type="checkbox" class="t3js-checkbox" '
|
||||
. 'id="' . $item['id'] . '" '
|
||||
. 'name="' . htmlspecialchars($item['name']) . '" '
|
||||
. 'value="' . htmlspecialchars($item['value']) . '" '
|
||||
. 'onclick="' . htmlspecialchars($sOnChange) . '" '
|
||||
. ($item['checked'] ? 'checked=checked ' : '')
|
||||
. ($item['disabled'] ? 'disabled=disabled ' : '') . '>';
|
||||
$tableRows[] = '</td>';
|
||||
$tableRows[] = '<td class="col-icon">';
|
||||
$tableRows[] = '<label class="label-block" for="' . $item['id'] . '">' . $item['icon'] . '</label>';
|
||||
$tableRows[] = '</td>';
|
||||
$tableRows[] = '<td class="col-title">';
|
||||
$tableRows[] = '<label class="label-block nowrap-disabled" for="' . $item['id'] . '">' . htmlspecialchars($this->appendValueToLabelInDebugMode($item['title'], $item['value']), ENT_COMPAT, 'UTF-8', false) . '</label>';
|
||||
$tableRows[] = '</td>';
|
||||
$tableRows[] = '<td class="text-right">' . $item['help'] . '</td>';
|
||||
$tableRows[] = '</tr>';
|
||||
$resetGroup[] = 'document.editform[' . GeneralUtility::quoteJSvalue($item['name']) . '].checked=' . $item['checked'] . ';';
|
||||
}
|
||||
|
||||
// Build reset group button
|
||||
$resetGroupBtn = '';
|
||||
if (!empty($resetGroup)) {
|
||||
$resetGroup[] = 'TYPO3.FormEngine.updateCheckboxState(this);';
|
||||
$title = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.revertSelection'));
|
||||
$resetGroupBtn = '<a href="#" '
|
||||
. 'class="btn btn-default btn-sm" '
|
||||
. 'onclick="' . implode('', $resetGroup) . ' return false;" '
|
||||
. 'title="' . $title . '">'
|
||||
. $this->iconFactory->getIcon('actions-edit-undo', Icon::SIZE_SMALL)->render() . ' '
|
||||
. $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.revertSelection') . '</a>';
|
||||
}
|
||||
|
||||
if (is_array($group['header'])) {
|
||||
$html[] = '<div id="' . $groupId . '" class="panel-collapse collapse" role="tabpanel">';
|
||||
}
|
||||
$checkboxId = uniqid($groupId);
|
||||
$title = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.toggleall'));
|
||||
$html[] = '<div class="table-responsive">';
|
||||
$html[] = '<table class="table table-transparent table-hover">';
|
||||
$html[] = '<thead>';
|
||||
$html[] = '<tr>';
|
||||
$html[] = '<th class="col-checkbox">';
|
||||
$html[] = '<input type="checkbox" id="' . $checkboxId . '" class="t3js-toggle-checkboxes" data-trigger="hover" data-placement="right" data-title="' . $title . '" data-toggle="tooltip" />';
|
||||
$html[] = '</th>';
|
||||
$html[] = '<th class="col-title" colspan="2"><label for="' . $checkboxId . '">' . $title . '</label></th>';
|
||||
$html[] = '<th class="text-right">' . $resetGroupBtn . '</th>';
|
||||
$html[] = '</tr>';
|
||||
$html[] = '</thead>';
|
||||
$html[] = '<tbody>' . implode(LF, $tableRows) . '</tbody>';
|
||||
$html[] = '</table>';
|
||||
$html[] = '</div>';
|
||||
if (is_array($group['header'])) {
|
||||
$html[] = '</div>';
|
||||
}
|
||||
}
|
||||
$html[] = '</div>';
|
||||
}
|
||||
|
||||
$html[] = '</div>';
|
||||
if (!$disabled && !empty($fieldWizardHtml)) {
|
||||
$html[] = '<div class="form-wizards-items-bottom">';
|
||||
$html[] = $fieldWizardHtml;
|
||||
$html[] = '</div>';
|
||||
}
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
}
|
||||
|
||||
$resultArray['html'] = implode(LF, $html);
|
||||
$resultArray['requireJsModules'][] = 'TYPO3/CMS/Backend/Tooltip';
|
||||
return $resultArray;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,432 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\Element;
|
||||
|
||||
/*
|
||||
* 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\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Imaging\Icon;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
use TYPO3\CMS\Core\Utility\StringUtility;
|
||||
|
||||
/**
|
||||
* Render a widget with two boxes side by side.
|
||||
*
|
||||
* This is rendered for config type=select, renderType=selectMultipleSideBySide set
|
||||
*/
|
||||
class SelectMultipleSideBySideElement extends AbstractFormElement
|
||||
{
|
||||
/**
|
||||
* Default field information enabled for this element.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldInformation = [
|
||||
'tcaDescription' => [
|
||||
'renderType' => 'tcaDescription',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Default field controls for this element.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldControl = [
|
||||
'editPopup' => [
|
||||
'renderType' => 'editPopup',
|
||||
'disabled' => true,
|
||||
],
|
||||
'addRecord' => [
|
||||
'renderType' => 'addRecord',
|
||||
'disabled' => true,
|
||||
'after' => [ 'editPopup' ],
|
||||
],
|
||||
'listModule' => [
|
||||
'renderType' => 'listModule',
|
||||
'disabled' => true,
|
||||
'after' => [ 'addRecord' ],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Default field wizards enabled for this element.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldWizard = [
|
||||
'localizationStateSelector' => [
|
||||
'renderType' => 'localizationStateSelector',
|
||||
],
|
||||
'otherLanguageContent' => [
|
||||
'renderType' => 'otherLanguageContent',
|
||||
'after' => [
|
||||
'localizationStateSelector'
|
||||
],
|
||||
],
|
||||
'defaultLanguageDifferences' => [
|
||||
'renderType' => 'defaultLanguageDifferences',
|
||||
'after' => [
|
||||
'otherLanguageContent',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Render side by side element.
|
||||
*
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
$resultArray = $this->initializeResultArray();
|
||||
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
$config = $parameterArray['fieldConf']['config'];
|
||||
$elementName = $parameterArray['itemFormElName'];
|
||||
|
||||
if ($config['readOnly']) {
|
||||
// Early return for the relatively simple read only case
|
||||
return $this->renderReadOnly();
|
||||
}
|
||||
|
||||
$possibleItems = $config['items'];
|
||||
$selectedItems = $parameterArray['itemFormElValue'] ?: [];
|
||||
$selectedItemsCount = count($selectedItems);
|
||||
|
||||
$maxItems = $config['maxitems'];
|
||||
$autoSizeMax = MathUtility::forceIntegerInRange($config['autoSizeMax'], 0);
|
||||
$size = 2;
|
||||
if (isset($config['size'])) {
|
||||
$size = (int)$config['size'];
|
||||
}
|
||||
if ($autoSizeMax >= 1) {
|
||||
$size = MathUtility::forceIntegerInRange($selectedItemsCount + 1, MathUtility::forceIntegerInRange($size, 1), $autoSizeMax);
|
||||
}
|
||||
$itemCanBeSelectedMoreThanOnce = !empty($config['multiple']);
|
||||
|
||||
$listOfSelectedValues = [];
|
||||
$selectedItemsHtml = [];
|
||||
foreach ($selectedItems as $itemValue) {
|
||||
foreach ($possibleItems as $possibleItem) {
|
||||
if ($possibleItem[1] == $itemValue) {
|
||||
$title = $possibleItem[0];
|
||||
$listOfSelectedValues[] = $itemValue;
|
||||
$selectedItemsHtml[] = '<option value="' . htmlspecialchars($itemValue) . '" title="' . htmlspecialchars($title) . '">' . htmlspecialchars($this->appendValueToLabelInDebugMode($title, $itemValue)) . '</option>';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$selectableItemsHtml = [];
|
||||
foreach ($possibleItems as $possibleItem) {
|
||||
$disabledAttr = '';
|
||||
$classAttr = '';
|
||||
if (!$itemCanBeSelectedMoreThanOnce && in_array((string)$possibleItem[1], $selectedItems, true)) {
|
||||
$disabledAttr = ' disabled="disabled"';
|
||||
$classAttr = ' class="hidden"';
|
||||
}
|
||||
$selectableItemsHtml[] =
|
||||
'<option value="'
|
||||
. htmlspecialchars($possibleItem[1])
|
||||
. '" title="' . htmlspecialchars($possibleItem[0]) . '"'
|
||||
. $classAttr . $disabledAttr
|
||||
. '>'
|
||||
. htmlspecialchars($this->appendValueToLabelInDebugMode($possibleItem[0], $possibleItem[1])) .
|
||||
'</option>';
|
||||
}
|
||||
|
||||
// Html stuff for filter and select filter on top of right side of multi select boxes
|
||||
$filterTextfield = [];
|
||||
if ($config['enableMultiSelectFilterTextfield']) {
|
||||
$filterTextfield[] = '<span class="input-group input-group-sm">';
|
||||
$filterTextfield[] = '<span class="input-group-addon">';
|
||||
$filterTextfield[] = '<span class="fa fa-filter"></span>';
|
||||
$filterTextfield[] = '</span>';
|
||||
$filterTextfield[] = '<input class="t3js-formengine-multiselect-filter-textfield form-control" value="">';
|
||||
$filterTextfield[] = '</span>';
|
||||
}
|
||||
$filterDropDownOptions = [];
|
||||
if (isset($config['multiSelectFilterItems']) && is_array($config['multiSelectFilterItems']) && count($config['multiSelectFilterItems']) > 1) {
|
||||
foreach ($config['multiSelectFilterItems'] as $optionElement) {
|
||||
$value = $languageService->sL($optionElement[0]);
|
||||
$label = $value;
|
||||
if (isset($optionElement[1]) && trim($optionElement[1]) !== '') {
|
||||
$label = $languageService->sL($optionElement[1]);
|
||||
}
|
||||
$filterDropDownOptions[] = '<option value="' . htmlspecialchars($value) . '">' . htmlspecialchars($label) . '</option>';
|
||||
}
|
||||
}
|
||||
$filterHtml = [];
|
||||
if (!empty($filterTextfield) || !empty($filterDropDownOptions)) {
|
||||
$filterHtml[] = '<div class="form-multigroup-item-wizard">';
|
||||
if (!empty($filterTextfield) && !empty($filterDropDownOptions)) {
|
||||
$filterHtml[] = '<div class="t3js-formengine-multiselect-filter-container form-multigroup-wrap">';
|
||||
$filterHtml[] = '<div class="form-multigroup-item form-multigroup-element">';
|
||||
$filterHtml[] = '<select class="form-control input-sm t3js-formengine-multiselect-filter-dropdown">';
|
||||
$filterHtml[] = implode(LF, $filterDropDownOptions);
|
||||
$filterHtml[] = '</select>';
|
||||
$filterHtml[] = '</div>';
|
||||
$filterHtml[] = '<div class="form-multigroup-item form-multigroup-element">';
|
||||
$filterHtml[] = implode(LF, $filterTextfield);
|
||||
$filterHtml[] = '</div>';
|
||||
$filterHtml[] = '</div>';
|
||||
} elseif (!empty($filterTextfield)) {
|
||||
$filterHtml[] = implode(LF, $filterTextfield);
|
||||
} else {
|
||||
$filterHtml[] = '<select class="form-control input-sm t3js-formengine-multiselect-filter-dropdown">';
|
||||
$filterHtml[] = implode(LF, $filterDropDownOptions);
|
||||
$filterHtml[] = '</select>';
|
||||
}
|
||||
$filterHtml[] = '</div>';
|
||||
}
|
||||
|
||||
$classes = [];
|
||||
$classes[] = 'form-control';
|
||||
$classes[] = 'tceforms-multiselect';
|
||||
if ($maxItems === 1) {
|
||||
$classes[] = 'form-select-no-siblings';
|
||||
}
|
||||
$multipleAttribute = '';
|
||||
if ($maxItems !== 1 && $size !== 1) {
|
||||
$multipleAttribute = ' 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);
|
||||
|
||||
$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">';
|
||||
$html[] = '<input type="hidden" data-formengine-input-name="' . htmlspecialchars($elementName) . '" value="' . (int)$itemCanBeSelectedMoreThanOnce . '" />';
|
||||
$html[] = '<div class="form-multigroup-wrap t3js-formengine-field-group">';
|
||||
$html[] = '<div class="form-multigroup-item form-multigroup-element">';
|
||||
$html[] = '<label>';
|
||||
$html[] = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.selected'));
|
||||
$html[] = '</label>';
|
||||
$html[] = '<div class="form-wizards-wrap form-wizards-aside">';
|
||||
$html[] = '<div class="form-wizards-element">';
|
||||
$html[] = '<select';
|
||||
$html[] = ' id="' . StringUtility::getUniqueId('tceforms-multiselect-') . '"';
|
||||
$html[] = ' size="' . $size . '"';
|
||||
$html[] = ' class="' . implode(' ', $classes) . '"';
|
||||
$html[] = $multipleAttribute;
|
||||
$html[] = ' data-formengine-input-name="' . htmlspecialchars($elementName) . '"';
|
||||
$html[] = '>';
|
||||
$html[] = implode(LF, $selectedItemsHtml);
|
||||
$html[] = '</select>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '<div class="form-wizards-items-aside">';
|
||||
$html[] = '<div class="btn-group-vertical">';
|
||||
if ($maxItems > 1 && $size >= 5) {
|
||||
$html[] = '<a href="#"';
|
||||
$html[] = ' class="btn btn-default t3js-btn-moveoption-top"';
|
||||
$html[] = ' data-fieldname="' . htmlspecialchars($elementName) . '"';
|
||||
$html[] = ' title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.move_to_top')) . '"';
|
||||
$html[] = '>';
|
||||
$html[] = $this->iconFactory->getIcon('actions-move-to-top', Icon::SIZE_SMALL)->render();
|
||||
$html[] = '</a>';
|
||||
}
|
||||
if ($maxItems > 1) {
|
||||
$html[] = '<a href="#"';
|
||||
$html[] = ' class="btn btn-default t3js-btn-moveoption-up"';
|
||||
$html[] = ' data-fieldname="' . htmlspecialchars($elementName) . '"';
|
||||
$html[] = ' title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.move_up')) . '"';
|
||||
$html[] = '>';
|
||||
$html[] = $this->iconFactory->getIcon('actions-move-up', Icon::SIZE_SMALL)->render();
|
||||
$html[] = '</a>';
|
||||
$html[] = '<a href="#"';
|
||||
$html[] = ' class="btn btn-default t3js-btn-moveoption-down"';
|
||||
$html[] = ' data-fieldname="' . htmlspecialchars($elementName) . '"';
|
||||
$html[] = ' title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.move_down')) . '"';
|
||||
$html[] = '>';
|
||||
$html[] = $this->iconFactory->getIcon('actions-move-down', Icon::SIZE_SMALL)->render();
|
||||
$html[] = '</a>';
|
||||
}
|
||||
if ($maxItems > 1 && $size >= 5) {
|
||||
$html[] = '<a href="#"';
|
||||
$html[] = ' class="btn btn-default t3js-btn-moveoption-bottom"';
|
||||
$html[] = ' data-fieldname="' . htmlspecialchars($elementName) . '"';
|
||||
$html[] = ' title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.move_to_bottom')) . '"';
|
||||
$html[] = '>';
|
||||
$html[] = $this->iconFactory->getIcon('actions-move-to-bottom', Icon::SIZE_SMALL)->render();
|
||||
$html[] = '</a>';
|
||||
}
|
||||
$html[] = '<a href="#"';
|
||||
$html[] = ' class="btn btn-default t3js-btn-removeoption"';
|
||||
$html[] = ' data-fieldname="' . htmlspecialchars($elementName) . '"';
|
||||
$html[] = ' title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.remove_selected')) . '"';
|
||||
$html[] = '>';
|
||||
$html[] = $this->iconFactory->getIcon('actions-selection-delete', Icon::SIZE_SMALL)->render();
|
||||
$html[] = '</a>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '<div class="form-multigroup-item form-multigroup-element">';
|
||||
$html[] = '<label>';
|
||||
$html[] = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.items'));
|
||||
$html[] = '</label>';
|
||||
$html[] = '<div class="form-wizards-wrap form-wizards-aside">';
|
||||
$html[] = '<div class="form-wizards-element">';
|
||||
$html[] = implode(LF, $filterHtml);
|
||||
$html[] = '<select';
|
||||
$html[] = ' data-relatedfieldname="' . htmlspecialchars($elementName) . '"';
|
||||
$html[] = ' data-exclusivevalues="' . htmlspecialchars($config['exclusiveKeys']) . '"';
|
||||
$html[] = ' id="' . StringUtility::getUniqueId('tceforms-multiselect-') . '"';
|
||||
$html[] = ' data-formengine-input-name="' . htmlspecialchars($elementName) . '"';
|
||||
$html[] = ' class="form-control t3js-formengine-select-itemstoselect"';
|
||||
$html[] = ' size="' . $size . '"';
|
||||
$html[] = ' onchange="' . htmlspecialchars(implode('', $parameterArray['fieldChangeFunc'])) . '"';
|
||||
$html[] = ' data-formengine-validation-rules="' . htmlspecialchars($this->getValidationDataAsJsonString($config)) . '"';
|
||||
$html[] = '>';
|
||||
$html[] = implode(LF, $selectableItemsHtml);
|
||||
$html[] = '</select>';
|
||||
$html[] = '</div>';
|
||||
if (!empty($fieldControlHtml)) {
|
||||
$html[] = '<div class="form-wizards-items-aside">';
|
||||
$html[] = '<div class="btn-group-vertical">';
|
||||
$html[] = $fieldControlHtml;
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
}
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '<input type="hidden" name="' . htmlspecialchars($elementName) . '" value="' . htmlspecialchars(implode(',', $listOfSelectedValues)) . '" />';
|
||||
$html[] = '</div>';
|
||||
if (!empty($fieldWizardHtml)) {
|
||||
$html[] = '<div class="form-wizards-items-bottom">';
|
||||
$html[] = $fieldWizardHtml;
|
||||
$html[] = '</div>';
|
||||
}
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
|
||||
$resultArray['html'] = implode(LF, $html);
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create HTML of a read only multi select. Right side is not
|
||||
* rendered, but just the left side with the selected items.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function renderReadOnly()
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
$resultArray = $this->initializeResultArray();
|
||||
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
$config = $parameterArray['fieldConf']['config'];
|
||||
$fieldName = $parameterArray['itemFormElName'];
|
||||
|
||||
$possibleItems = $config['items'];
|
||||
$selectedItems = $parameterArray['itemFormElValue'] ?: [];
|
||||
if (!is_array($selectedItems)) {
|
||||
$selectedItems = GeneralUtility::trimExplode(',', $selectedItems, true);
|
||||
}
|
||||
$selectedItemsCount = count($selectedItems);
|
||||
|
||||
$autoSizeMax = MathUtility::forceIntegerInRange($config['autoSizeMax'], 0);
|
||||
$size = 2;
|
||||
if (isset($config['size'])) {
|
||||
$size = (int)$config['size'];
|
||||
}
|
||||
if ($autoSizeMax >= 1) {
|
||||
$size = MathUtility::forceIntegerInRange($selectedItemsCount + 1, MathUtility::forceIntegerInRange($size, 1), $autoSizeMax);
|
||||
}
|
||||
$multiple = '';
|
||||
if ($size !== 1) {
|
||||
$multiple = ' multiple="multiple"';
|
||||
}
|
||||
|
||||
$listOfSelectedValues = [];
|
||||
$optionsHtml = [];
|
||||
foreach ($selectedItems as $itemValue) {
|
||||
foreach ($possibleItems as $possibleItem) {
|
||||
if ($possibleItem[1] == $itemValue) {
|
||||
$title = $possibleItem[0];
|
||||
$listOfSelectedValues[] = $itemValue;
|
||||
$optionsHtml[] = '<option value="' . htmlspecialchars($itemValue) . '" title="' . htmlspecialchars($title) . '">' . htmlspecialchars($title) . '</option>';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$fieldInformationResult = $this->renderFieldInformation();
|
||||
$fieldInformationHtml = $fieldInformationResult['html'];
|
||||
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, 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">';
|
||||
$html[] = '<label>';
|
||||
$html[] = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.selected'));
|
||||
$html[] = '</label>';
|
||||
$html[] = '<div class="form-wizards-wrap form-wizards-aside">';
|
||||
$html[] = '<div class="form-wizards-element">';
|
||||
$html[] = '<select';
|
||||
$html[] = ' id="' . StringUtility::getUniqueId('tceforms-multiselect-') . '"';
|
||||
$html[] = ' size="' . $size . '"';
|
||||
$html[] = ' class="form-control tceforms-multiselect"';
|
||||
$html[] = $multiple;
|
||||
$html[] = ' data-formengine-input-name="' . htmlspecialchars($fieldName) . '"';
|
||||
$html[] = ' disabled="disabled">';
|
||||
$html[] = '/>';
|
||||
$html[] = implode(LF, $optionsHtml);
|
||||
$html[] = '</select>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '<input type="hidden" name="' . htmlspecialchars($fieldName) . '" value="' . htmlspecialchars(implode(',', $listOfSelectedValues)) . '" />';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
|
||||
$resultArray['html'] = implode(LF, $html);
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService()
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUserAuthentication()
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\Element;
|
||||
|
||||
/*
|
||||
* 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\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
use TYPO3\CMS\Core\Utility\StringUtility;
|
||||
|
||||
/**
|
||||
* Create a widget with a select box where multiple items can be selected
|
||||
*
|
||||
* This is rendered for config type=select, renderType=selectSingleBox
|
||||
*/
|
||||
class SelectSingleBoxElement extends AbstractFormElement
|
||||
{
|
||||
/**
|
||||
* Default field information enabled for this element.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldInformation = [
|
||||
'tcaDescription' => [
|
||||
'renderType' => 'tcaDescription',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Default field controls for this element.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldControl = [
|
||||
'resetSelection' => [
|
||||
'renderType' => 'resetSelection',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Default field wizards enabled for this element.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldWizard = [
|
||||
'localizationStateSelector' => [
|
||||
'renderType' => 'localizationStateSelector',
|
||||
],
|
||||
'otherLanguageContent' => [
|
||||
'renderType' => 'otherLanguageContent',
|
||||
'after' => [
|
||||
'localizationStateSelector'
|
||||
],
|
||||
],
|
||||
'defaultLanguageDifferences' => [
|
||||
'renderType' => 'defaultLanguageDifferences',
|
||||
'after' => [
|
||||
'otherLanguageContent',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* This will render a selector box element, or possibly a special construction with two selector boxes.
|
||||
*
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
$resultArray = $this->initializeResultArray();
|
||||
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
// Field configuration from TCA:
|
||||
$config = $parameterArray['fieldConf']['config'];
|
||||
$selectItems = $parameterArray['fieldConf']['config']['items'];
|
||||
$disabled = !empty($config['readOnly']);
|
||||
|
||||
// Get values in an array (and make unique, which is fine because there can be no duplicates anyway):
|
||||
$itemArray = array_flip($parameterArray['itemFormElValue']);
|
||||
$width = $this->formMaxWidth($this->defaultInputWidth);
|
||||
|
||||
$optionElements = [];
|
||||
foreach ($selectItems as $i => $item) {
|
||||
$value = $item[1];
|
||||
$attributes = [];
|
||||
// Selected or not by default
|
||||
if (isset($itemArray[$value])) {
|
||||
$attributes['selected'] = 'selected';
|
||||
unset($itemArray[$value]);
|
||||
}
|
||||
// Non-selectable element
|
||||
if ((string)$value === '--div--') {
|
||||
$attributes['disabled'] = 'disabled';
|
||||
$attributes['class'] = 'formcontrol-select-divider';
|
||||
}
|
||||
$optionElements[] = $this->renderOptionElement($value, $item[0], $attributes);
|
||||
}
|
||||
|
||||
$selectElement = $this->renderSelectElement($optionElements, $parameterArray, $config);
|
||||
|
||||
$fieldInformationResult = $this->renderFieldInformation();
|
||||
$fieldInformationHtml = $fieldInformationResult['html'];
|
||||
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false);
|
||||
|
||||
$fieldControlResult = $this->renderFieldControl();
|
||||
$fieldControlHtml = $fieldControlResult['html'];
|
||||
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldControlResult, 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-control-wrap" style="max-width: ' . $width . 'px">';
|
||||
$html[] = '<div class="form-wizards-wrap form-wizards-aside">';
|
||||
$html[] = '<div class="form-wizards-element">';
|
||||
if (!$disabled) {
|
||||
// Add an empty hidden field which will send a blank value if all items are unselected.
|
||||
$html[] = '<input type="hidden" name="' . htmlspecialchars($parameterArray['itemFormElName']) . '" value="">';
|
||||
}
|
||||
$html[] = $selectElement;
|
||||
$html[] = '</div>';
|
||||
if (!$disabled) {
|
||||
if (!empty($fieldControlHtml)) {
|
||||
$html[] = '<div class="form-wizards-items-aside">';
|
||||
$html[] = $fieldControlHtml;
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
}
|
||||
$html[] = '<p>';
|
||||
$html[] = '<em>' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.holdDownCTRL')) . '</em>';
|
||||
$html[] = '</p>';
|
||||
if (!empty($fieldWizardHtml)) {
|
||||
$html[] = '<div class="form-wizards-items-bottom">';
|
||||
$html[] = $fieldWizardHtml;
|
||||
$html[] = '</div>';
|
||||
}
|
||||
}
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
|
||||
$resultArray['html'] = implode(LF, $html);
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a <select> element
|
||||
*
|
||||
* @param array $optionElements List of rendered <option> elements
|
||||
* @param array $parameterArray
|
||||
* @param array $config Field configuration
|
||||
* @return string
|
||||
*/
|
||||
protected function renderSelectElement(array $optionElements, array $parameterArray, array $config)
|
||||
{
|
||||
$selectItems = $parameterArray['fieldConf']['config']['items'];
|
||||
$size = (int)$config['size'];
|
||||
$cssPrefix = $size === 1 ? 'tceforms-select' : 'tceforms-multiselect';
|
||||
|
||||
if ($config['autoSizeMax']) {
|
||||
$size = MathUtility::forceIntegerInRange(
|
||||
count($selectItems) + 1,
|
||||
MathUtility::forceIntegerInRange($size, 1),
|
||||
$config['autoSizeMax']
|
||||
);
|
||||
}
|
||||
|
||||
$attributes = [
|
||||
'name' => $parameterArray['itemFormElName'] . '[]',
|
||||
'multiple' => 'multiple',
|
||||
'onchange' => implode('', $parameterArray['fieldChangeFunc']),
|
||||
'id' => StringUtility::getUniqueId($cssPrefix),
|
||||
'class' => 'form-control ' . $cssPrefix,
|
||||
'data-formengine-validation-rules' => $this->getValidationDataAsJsonString($config),
|
||||
];
|
||||
if ($size) {
|
||||
$attributes['size'] = $size;
|
||||
}
|
||||
if ($config['readOnly']) {
|
||||
$attributes['disabled'] = 'disabled';
|
||||
}
|
||||
|
||||
$html = [];
|
||||
$html[] = '<select ' . GeneralUtility::implodeAttributes($attributes, true) . '>';
|
||||
$html[] = implode(LF, $optionElements);
|
||||
$html[] = '</select>';
|
||||
|
||||
return implode(LF, $html);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a single <option> element
|
||||
*
|
||||
* @param string $value The option value
|
||||
* @param string $label The option label
|
||||
* @param array $attributes Map of attribute names and values
|
||||
* @return string
|
||||
*/
|
||||
protected function renderOptionElement($value, $label, array $attributes = [])
|
||||
{
|
||||
$attributes['value'] = $value;
|
||||
$html = [
|
||||
'<option ' . GeneralUtility::implodeAttributes($attributes, true) . '>',
|
||||
htmlspecialchars($this->appendValueToLabelInDebugMode($label, $value), ENT_COMPAT, 'UTF-8', false),
|
||||
'</option>'
|
||||
|
||||
];
|
||||
|
||||
return implode('', $html);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\Element;
|
||||
|
||||
/*
|
||||
* 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\InlineStackProcessor;
|
||||
use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\StringUtility;
|
||||
|
||||
/**
|
||||
* Creates a widget where only one item can be selected.
|
||||
* This is either a select drop-down if no size config is given or set to 1, or a select box.
|
||||
*
|
||||
* This is rendered for type=select, renderType=selectSingle
|
||||
*/
|
||||
class SelectSingleElement extends AbstractFormElement
|
||||
{
|
||||
/**
|
||||
* Default field information enabled for this element.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldInformation = [
|
||||
'tcaDescription' => [
|
||||
'renderType' => 'tcaDescription',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Default field wizards enabled for this element.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldWizard = [
|
||||
'selectIcons' => [
|
||||
'renderType' => 'selectIcons',
|
||||
'disabled' => true,
|
||||
],
|
||||
'localizationStateSelector' => [
|
||||
'renderType' => 'localizationStateSelector',
|
||||
'after' => [
|
||||
'selectIcons',
|
||||
],
|
||||
],
|
||||
'otherLanguageContent' => [
|
||||
'renderType' => 'otherLanguageContent',
|
||||
'after' => [ 'localizationStateSelector' ],
|
||||
],
|
||||
'defaultLanguageDifferences' => [
|
||||
'renderType' => 'defaultLanguageDifferences',
|
||||
'after' => [ 'otherLanguageContent' ],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Render single element
|
||||
*
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$resultArray = $this->initializeResultArray();
|
||||
|
||||
$table = $this->data['tableName'];
|
||||
$field = $this->data['fieldName'];
|
||||
$row = $this->data['databaseRow'];
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
$config = $parameterArray['fieldConf']['config'];
|
||||
|
||||
$selectItems = $parameterArray['fieldConf']['config']['items'];
|
||||
|
||||
// Check against inline uniqueness
|
||||
/** @var InlineStackProcessor $inlineStackProcessor */
|
||||
$inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
|
||||
$inlineStackProcessor->initializeByGivenStructure($this->data['inlineStructure']);
|
||||
$uniqueIds = null;
|
||||
if ($this->data['isInlineChild'] && $this->data['inlineParentUid']) {
|
||||
// @todo: At least parts of this if is dead and/or broken: $uniqueIds is filled but never used.
|
||||
// See InlineControlContainer where 'inlineData' 'unique' 'used' is set. What exactly is
|
||||
// this if supposed to do and when should it kick in and what for?
|
||||
$inlineObjectName = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']);
|
||||
$inlineFormName = $inlineStackProcessor->getCurrentStructureFormPrefix();
|
||||
if ($this->data['inlineParentConfig']['foreign_table'] === $table
|
||||
&& $this->data['inlineParentConfig']['foreign_unique'] === $field
|
||||
) {
|
||||
$uniqueIds = $this->data['inlineData']['unique'][$inlineObjectName . '-' . $table]['used'];
|
||||
$parameterArray['fieldChangeFunc']['inlineUnique'] = 'inline.updateUnique(this,'
|
||||
. GeneralUtility::quoteJSvalue($inlineObjectName . '-' . $table) . ','
|
||||
. GeneralUtility::quoteJSvalue($inlineFormName) . ','
|
||||
. GeneralUtility::quoteJSvalue($row['uid']) . ');';
|
||||
}
|
||||
// hide uid of parent record for symmetric relations
|
||||
if ($this->data['inlineParentConfig']['foreign_table'] === $table
|
||||
&& (
|
||||
$this->data['inlineParentConfig']['foreign_field'] === $field
|
||||
|| $this->data['inlineParentConfig']['symmetric_field'] === $field
|
||||
)
|
||||
) {
|
||||
$uniqueIds[] = $this->data['inlineParentUid'];
|
||||
}
|
||||
}
|
||||
|
||||
// Initialization:
|
||||
$selectId = StringUtility::getUniqueId('tceforms-select-');
|
||||
$selectedIcon = '';
|
||||
$size = (int)$config['size'];
|
||||
|
||||
// Style set on <select/>
|
||||
$options = '';
|
||||
$disabled = false;
|
||||
if (!empty($config['readOnly'])) {
|
||||
$disabled = true;
|
||||
}
|
||||
|
||||
// Prepare groups
|
||||
$selectItemCounter = 0;
|
||||
$selectItemGroupCount = 0;
|
||||
$selectItemGroups = [];
|
||||
$selectedValue = '';
|
||||
$hasIcons = false;
|
||||
|
||||
// In case e.g. "l10n_display" is set to "defaultAsReadonly" only one value (as string) could be handed in
|
||||
if (!empty($parameterArray['itemFormElValue'])) {
|
||||
if (is_array($parameterArray['itemFormElValue'])) {
|
||||
$selectedValue = (string)$parameterArray['itemFormElValue'][0];
|
||||
} else {
|
||||
$selectedValue = (string)$parameterArray['itemFormElValue'];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($selectItems as $item) {
|
||||
if ($item[1] === '--div--') {
|
||||
// IS OPTGROUP
|
||||
if ($selectItemCounter !== 0) {
|
||||
$selectItemGroupCount++;
|
||||
}
|
||||
$selectItemGroups[$selectItemGroupCount]['header'] = [
|
||||
'title' => $item[0],
|
||||
];
|
||||
} else {
|
||||
// IS ITEM
|
||||
$icon = !empty($item[2]) ? FormEngineUtility::getIconHtml($item[2], $item[0], $item[0]) : '';
|
||||
$selected = $selectedValue === (string)$item[1];
|
||||
|
||||
if ($selected) {
|
||||
$selectedIcon = $icon;
|
||||
}
|
||||
|
||||
$selectItemGroups[$selectItemGroupCount]['items'][] = [
|
||||
'title' => $this->appendValueToLabelInDebugMode($item[0], $item[1]),
|
||||
'value' => $item[1],
|
||||
'icon' => $icon,
|
||||
'selected' => $selected,
|
||||
];
|
||||
$selectItemCounter++;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback icon
|
||||
// @todo: assign a special icon for non matching values?
|
||||
if (!$selectedIcon && $selectItemGroups[0]['items'][0]['icon']) {
|
||||
$selectedIcon = $selectItemGroups[0]['items'][0]['icon'];
|
||||
}
|
||||
|
||||
// Process groups
|
||||
foreach ($selectItemGroups as $selectItemGroup) {
|
||||
// suppress groups without items
|
||||
if (empty($selectItemGroup['items'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$optionGroup = is_array($selectItemGroup['header']);
|
||||
$options .= ($optionGroup ? '<optgroup label="' . htmlspecialchars($selectItemGroup['header']['title'], ENT_COMPAT, 'UTF-8', false) . '">' : '');
|
||||
|
||||
if (is_array($selectItemGroup['items'])) {
|
||||
foreach ($selectItemGroup['items'] as $item) {
|
||||
$options .= '<option value="' . htmlspecialchars($item['value']) . '" data-icon="' .
|
||||
htmlspecialchars($item['icon']) . '"'
|
||||
. ($item['selected'] ? ' selected="selected"' : '') . '>' . htmlspecialchars($item['title'], ENT_COMPAT, 'UTF-8', false) . '</option>';
|
||||
}
|
||||
$hasIcons = !empty($item['icon']);
|
||||
}
|
||||
|
||||
$options .= ($optionGroup ? '</optgroup>' : '');
|
||||
}
|
||||
|
||||
$selectAttributes = [
|
||||
'id' => $selectId,
|
||||
'name' => $parameterArray['itemFormElName'],
|
||||
'data-formengine-validation-rules' => $this->getValidationDataAsJsonString($config),
|
||||
'class' => 'form-control form-control-adapt',
|
||||
];
|
||||
if ($size) {
|
||||
$selectAttributes['size'] = $size;
|
||||
}
|
||||
if ($disabled) {
|
||||
$selectAttributes['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-control-wrap">';
|
||||
$html[] = '<div class="form-wizards-wrap">';
|
||||
$html[] = '<div class="form-wizards-element">';
|
||||
if ($hasIcons) {
|
||||
$html[] = '<div class="input-group">';
|
||||
$html[] = '<span class="input-group-addon input-group-icon">';
|
||||
$html[] = $selectedIcon;
|
||||
$html[] = '</span>';
|
||||
}
|
||||
$html[] = '<select ' . GeneralUtility::implodeAttributes($selectAttributes, true) . '>';
|
||||
$html[] = $options;
|
||||
$html[] = '</select>';
|
||||
if ($hasIcons) {
|
||||
$html[] = '</div>';
|
||||
}
|
||||
$html[] = '</div>';
|
||||
if (!$disabled && !empty($fieldWizardHtml)) {
|
||||
$html[] = '<div class="form-wizards-items-bottom">';
|
||||
$html[] = $fieldWizardHtml;
|
||||
$html[] = '</div>';
|
||||
}
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
|
||||
$resultArray['requireJsModules'][] = ['TYPO3/CMS/Backend/FormEngine/Element/SelectSingleElement' => implode(LF, [
|
||||
'function(SelectSingleElement) {',
|
||||
'require([\'jquery\'], function($) {',
|
||||
'$(function() {',
|
||||
'SelectSingleElement.initialize(',
|
||||
GeneralUtility::quoteJSvalue('#' . $selectId) . ',',
|
||||
'{',
|
||||
'onChange: function() {',
|
||||
implode('', $parameterArray['fieldChangeFunc']),
|
||||
'}',
|
||||
'}',
|
||||
');',
|
||||
'});',
|
||||
'});',
|
||||
'}',
|
||||
])];
|
||||
|
||||
$resultArray['html'] = implode(LF, $html);
|
||||
return $resultArray;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\Element;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Render data as a tree.
|
||||
*
|
||||
* Typically rendered for config type=select, renderType=selectTree
|
||||
*/
|
||||
class SelectTreeElement extends AbstractFormElement
|
||||
{
|
||||
/**
|
||||
* Default field information enabled for this element.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldInformation = [
|
||||
'tcaDescription' => [
|
||||
'renderType' => 'tcaDescription',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array Default wizards
|
||||
*/
|
||||
protected $defaultFieldWizard = [
|
||||
'localizationStateSelector' => [
|
||||
'renderType' => 'localizationStateSelector',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Default number of tree nodes to show (determines tree height)
|
||||
* when no ['config']['size'] is set
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $itemsToShow = 15;
|
||||
|
||||
/**
|
||||
* Number of items to show at last
|
||||
* e.g. when you have only 2 items in a tree
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $minItemsToShow = 5;
|
||||
|
||||
/**
|
||||
* Pixel height of a single tree node
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $itemHeight = 20;
|
||||
|
||||
/**
|
||||
* Render tree widget
|
||||
*
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
* @see AbstractNode::initializeResultArray()
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$resultArray = $this->initializeResultArray();
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
$formElementId = md5($parameterArray['itemFormElName']);
|
||||
|
||||
// Field configuration from TCA:
|
||||
$config = $parameterArray['fieldConf']['config'];
|
||||
$readOnly = !empty($config['readOnly']) ? 'true' : 'false';
|
||||
$exclusiveKeys = !empty($config['exclusiveKeys']) ? $config['exclusiveKeys'] : '';
|
||||
$exclusiveKeys = $exclusiveKeys . ',';
|
||||
$appearance = !empty($config['treeConfig']['appearance']) ? $config['treeConfig']['appearance'] : [];
|
||||
$expanded = !empty($appearance['expandAll']);
|
||||
$showHeader = !empty($appearance['showHeader']);
|
||||
if (isset($config['size']) && (int)$config['size'] > 0) {
|
||||
$height = max($this->minItemsToShow, (int)$config['size']);
|
||||
} else {
|
||||
$height = $this->itemsToShow;
|
||||
}
|
||||
$heightInPx = $height * $this->itemHeight;
|
||||
$treeWrapperId = 'tree_' . $formElementId;
|
||||
|
||||
$fieldName = $this->data['fieldName'];
|
||||
|
||||
$dataStructureIdentifier = '';
|
||||
$flexFormSheetName = '';
|
||||
$flexFormFieldName = '';
|
||||
$flexFormContainerName = '';
|
||||
$flexFormContainerIdentifier = '';
|
||||
$flexFormContainerFieldName = '';
|
||||
$flexFormSectionContainerIsNew = false;
|
||||
if ($this->data['processedTca']['columns'][$fieldName]['config']['type'] === 'flex') {
|
||||
$dataStructureIdentifier = $this->data['processedTca']['columns'][$fieldName]['config']['dataStructureIdentifier'];
|
||||
if (isset($this->data['flexFormSheetName'])) {
|
||||
$flexFormSheetName = $this->data['flexFormSheetName'];
|
||||
}
|
||||
if (isset($this->data['flexFormFieldName'])) {
|
||||
$flexFormFieldName = $this->data['flexFormFieldName'];
|
||||
}
|
||||
if (isset($this->data['flexFormContainerName'])) {
|
||||
$flexFormContainerName = $this->data['flexFormContainerName'];
|
||||
}
|
||||
if (isset($this->data['flexFormContainerFieldName'])) {
|
||||
$flexFormContainerFieldName = $this->data['flexFormContainerFieldName'];
|
||||
}
|
||||
if (isset($this->data['flexFormContainerIdentifier'])) {
|
||||
$flexFormContainerIdentifier = $this->data['flexFormContainerIdentifier'];
|
||||
}
|
||||
// Add a flag this is a tree in a new flex section container element. This is needed to initialize
|
||||
// the databaseRow with this container again so the tree data provider is able to calculate tree items.
|
||||
if (!empty($this->data['flexSectionContainerPreparation'])) {
|
||||
$flexFormSectionContainerIsNew = true;
|
||||
}
|
||||
}
|
||||
|
||||
$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-control-wrap">';
|
||||
$html[] = '<div class="form-wizards-wrap">';
|
||||
$html[] = '<div class="form-wizards-element">';
|
||||
$html[] = '<div class="typo3-tceforms-tree">';
|
||||
$html[] = '<input class="treeRecord" type="hidden"';
|
||||
$html[] = ' data-formengine-validation-rules="' . htmlspecialchars($this->getValidationDataAsJsonString($config)) . '"';
|
||||
$html[] = ' data-relatedfieldname="' . htmlspecialchars($parameterArray['itemFormElName']) . '"';
|
||||
$html[] = ' data-tablename="' . htmlspecialchars($this->data['tableName']) . '"';
|
||||
$html[] = ' data-fieldname="' . htmlspecialchars($this->data['fieldName']) . '"';
|
||||
$html[] = ' data-uid="' . (int)$this->data['vanillaUid'] . '"';
|
||||
$html[] = ' data-recordtypevalue="' . htmlspecialchars($this->data['recordTypeValue']) . '"';
|
||||
$html[] = ' data-datastructureidentifier="' . htmlspecialchars($dataStructureIdentifier) . '"';
|
||||
$html[] = ' data-flexformsheetname="' . htmlspecialchars($flexFormSheetName) . '"';
|
||||
$html[] = ' data-flexformfieldname="' . htmlspecialchars($flexFormFieldName) . '"';
|
||||
$html[] = ' data-flexformcontainername="' . htmlspecialchars($flexFormContainerName) . '"';
|
||||
$html[] = ' data-flexformcontaineridentifier="' . htmlspecialchars($flexFormContainerIdentifier) . '"';
|
||||
$html[] = ' data-flexformcontainerfieldname="' . htmlspecialchars($flexFormContainerFieldName) . '"';
|
||||
$html[] = ' data-flexformsectioncontainerisnew="' . htmlspecialchars($flexFormSectionContainerIsNew) . '"';
|
||||
$html[] = ' data-command="' . htmlspecialchars($this->data['command']) . '"';
|
||||
$html[] = ' data-read-only="' . $readOnly . '"';
|
||||
$html[] = ' data-tree-exclusive-keys="' . htmlspecialchars($exclusiveKeys) . '"';
|
||||
$html[] = ' data-tree-expand-up-to-level="' . ($expanded ? '999' : '1') . '"';
|
||||
$html[] = ' data-tree-show-toolbar="' . $showHeader . '"';
|
||||
$html[] = ' name="' . htmlspecialchars($parameterArray['itemFormElName']) . '"';
|
||||
$html[] = ' id="treeinput' . $formElementId . '"';
|
||||
$html[] = ' value="' . htmlspecialchars(implode(',', $parameterArray['itemFormElValue'])) . '"';
|
||||
$html[] = '/>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '<div id="' . $treeWrapperId . '" class="svg-tree-wrapper" style="height: ' . $heightInPx . 'px;"></div>';
|
||||
$html[] = '<script type="text/javascript">var ' . $treeWrapperId . ' = ' . $this->getTreeOnChangeJs() . '</script>';
|
||||
$html[] = '</div>';
|
||||
if ($readOnly === 'false' && !empty($fieldWizardHtml)) {
|
||||
$html[] = '<div class="form-wizards-items-bottom">';
|
||||
$html[] = $fieldWizardHtml;
|
||||
$html[] = '</div>';
|
||||
}
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
|
||||
$resultArray['html'] = implode(LF, $html);
|
||||
|
||||
// add necessary labels for tree header
|
||||
if ($showHeader) {
|
||||
$resultArray['additionalInlineLanguageLabelFiles'][] = 'EXT:core/Resources/Private/Language/locallang_csh_corebe.xlf';
|
||||
}
|
||||
$resultArray['requireJsModules']['selectTreeElement'] = [
|
||||
'TYPO3/CMS/Backend/FormEngine/Element/SelectTreeElement' => 'function (SelectTreeElement) { SelectTreeElement.initialize(); }'
|
||||
];
|
||||
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates JS code triggered on change of the tree
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getTreeOnChangeJs()
|
||||
{
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
$onChange = !empty($parameterArray['fieldChangeFunc']['TBE_EDITOR_fieldChanged']) ? $parameterArray['fieldChangeFunc']['TBE_EDITOR_fieldChanged'] : '';
|
||||
$onChange .= !empty($parameterArray['fieldChangeFunc']['alert']) ? $parameterArray['fieldChangeFunc']['alert'] : '';
|
||||
return 'function () {' . $onChange . '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\Element;
|
||||
|
||||
/*
|
||||
* 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\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
use TYPO3\CMS\Core\Utility\StringUtility;
|
||||
|
||||
/**
|
||||
* Generation of TCEform elements of the type "text"
|
||||
*/
|
||||
class TextElement extends AbstractFormElement
|
||||
{
|
||||
/**
|
||||
* Default field information enabled for this element.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldInformation = [
|
||||
'tcaDescription' => [
|
||||
'renderType' => 'tcaDescription',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Default field wizards enabled for this element.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldWizard = [
|
||||
'localizationStateSelector' => [
|
||||
'renderType' => 'localizationStateSelector',
|
||||
],
|
||||
'otherLanguageContent' => [
|
||||
'renderType' => 'otherLanguageContent',
|
||||
'after' => [
|
||||
'localizationStateSelector'
|
||||
],
|
||||
],
|
||||
'defaultLanguageDifferences' => [
|
||||
'renderType' => 'defaultLanguageDifferences',
|
||||
'after' => [
|
||||
'otherLanguageContent',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* The number of chars expected per row when the height of a text area field is
|
||||
* automatically calculated based on the number of characters found in the field content.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $charactersPerRow = 40;
|
||||
|
||||
/**
|
||||
* This will render a <textarea>
|
||||
*
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
$backendUser = $this->getBackendUserAuthentication();
|
||||
|
||||
$table = $this->data['tableName'];
|
||||
$fieldName = $this->data['fieldName'];
|
||||
$row = $this->data['databaseRow'];
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
$resultArray = $this->initializeResultArray();
|
||||
|
||||
$itemValue = $parameterArray['itemFormElValue'];
|
||||
$config = $parameterArray['fieldConf']['config'];
|
||||
$evalList = GeneralUtility::trimExplode(',', $config['eval'], true);
|
||||
$cols = MathUtility::forceIntegerInRange($config['cols'] ?: $this->defaultInputWidth, $this->minimumInputWidth, $this->maxInputWidth);
|
||||
$width = $this->formMaxWidth($cols);
|
||||
$nullControlNameEscaped = htmlspecialchars('control[active][' . $table . '][' . $row['uid'] . '][' . $fieldName . ']');
|
||||
|
||||
// Setting number of rows
|
||||
$rows = MathUtility::forceIntegerInRange($config['rows'] ?: 5, 1, 20);
|
||||
$originalRows = $rows;
|
||||
$itemFormElementValueLength = strlen($itemValue);
|
||||
if ($itemFormElementValueLength > $this->charactersPerRow * 2) {
|
||||
$rows = MathUtility::forceIntegerInRange(
|
||||
round($itemFormElementValueLength / $this->charactersPerRow),
|
||||
count(explode(LF, $itemValue)),
|
||||
20
|
||||
);
|
||||
if ($rows < $originalRows) {
|
||||
$rows = $originalRows;
|
||||
}
|
||||
}
|
||||
|
||||
$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">';
|
||||
$html[] = '<textarea class="form-control" rows="' . $rows . '" disabled>';
|
||||
$html[] = htmlspecialchars($itemValue);
|
||||
$html[] = '</textarea>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$resultArray['html'] = implode(LF, $html);
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
// @todo: The whole eval handling is a mess and needs refactoring
|
||||
foreach ($evalList as $func) {
|
||||
// @todo: This is ugly: The code should find out on it's own whether a eval definition is a
|
||||
// @todo: keyword like "date", or a class reference. The global registration could be dropped then
|
||||
// Pair hook to the one in \TYPO3\CMS\Core\DataHandling\DataHandler::checkValue_input_Eval()
|
||||
// There is a similar hook for "evaluateFieldValue" in DataHandler and InputTextElement
|
||||
if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tce']['formevals'][$func])) {
|
||||
if (class_exists($func)) {
|
||||
$evalObj = GeneralUtility::makeInstance($func);
|
||||
if (method_exists($evalObj, 'deevaluateFieldValue')) {
|
||||
$_params = [
|
||||
'value' => $itemValue
|
||||
];
|
||||
$itemValue = $evalObj->deevaluateFieldValue($_params);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$attributes = [
|
||||
'id' => StringUtility::getUniqueId('formengine-textarea-'),
|
||||
'name' => htmlspecialchars($parameterArray['itemFormElName']),
|
||||
'data-formengine-validation-rules' => $this->getValidationDataAsJsonString($config),
|
||||
'data-formengine-input-name' => htmlspecialchars($parameterArray['itemFormElName']),
|
||||
'rows' => $rows,
|
||||
'wrap' => $config['wrap'] ?: 'virtual',
|
||||
'onChange' => implode('', $parameterArray['fieldChangeFunc']),
|
||||
];
|
||||
$classes = [
|
||||
'form-control',
|
||||
't3js-formengine-textarea',
|
||||
'formengine-textarea',
|
||||
];
|
||||
if ($config['fixedFont']) {
|
||||
$classes[] = 'text-monospace';
|
||||
}
|
||||
if ($config['enableTabulator']) {
|
||||
$classes[] = 't3js-enable-tab';
|
||||
}
|
||||
$attributes['class'] = implode(' ', $classes);
|
||||
$maximumHeight = (int)$backendUser->uc['resizeTextareas_MaxHeight'];
|
||||
if ($maximumHeight > 0) {
|
||||
// add the max-height from the users' preference to it
|
||||
$attributes['style'] = 'max-height: ' . $maximumHeight . 'px';
|
||||
}
|
||||
if (isset($config['max']) && (int)$config['max'] > 0) {
|
||||
$attributes['maxlength'] = (int)$config['max'];
|
||||
}
|
||||
if (!empty($config['placeholder'])) {
|
||||
$attributes['placeholder'] = htmlspecialchars(trim($config['placeholder']));
|
||||
}
|
||||
|
||||
$valuePickerHtml = [];
|
||||
if (isset($config['valuePicker']['items']) && is_array($config['valuePicker']['items'])) {
|
||||
$mode = $config['valuePicker']['mode'] ?? '';
|
||||
$itemName = $parameterArray['itemFormElName'];
|
||||
$fieldChangeFunc = $parameterArray['fieldChangeFunc'];
|
||||
if ($mode === 'append') {
|
||||
$assignValue = 'document.querySelectorAll(' . GeneralUtility::quoteJSvalue('[data-formengine-input-name="' . $itemName . '"]') . ')[0]'
|
||||
. '.value=\'\'+this.options[this.selectedIndex].value+document.editform[' . GeneralUtility::quoteJSvalue($itemName) . '].value';
|
||||
} elseif ($mode === 'prepend') {
|
||||
$assignValue = 'document.querySelectorAll(' . GeneralUtility::quoteJSvalue('[data-formengine-input-name="' . $itemName . '"]') . ')[0]'
|
||||
. '.value+=\'\'+this.options[this.selectedIndex].value';
|
||||
} else {
|
||||
$assignValue = 'document.querySelectorAll(' . GeneralUtility::quoteJSvalue('[data-formengine-input-name="' . $itemName . '"]') . ')[0]'
|
||||
. '.value=this.options[this.selectedIndex].value';
|
||||
}
|
||||
$valuePickerHtml[] = '<select';
|
||||
$valuePickerHtml[] = ' class="form-control tceforms-select tceforms-wizardselect"';
|
||||
$valuePickerHtml[] = ' onchange="' . htmlspecialchars($assignValue . ';this.blur();this.selectedIndex=0;' . implode('', $fieldChangeFunc)) . '"';
|
||||
$valuePickerHtml[] = '>';
|
||||
$valuePickerHtml[] = '<option></option>';
|
||||
foreach ($config['valuePicker']['items'] as $item) {
|
||||
$valuePickerHtml[] = '<option value="' . htmlspecialchars($item[1]) . '">' . htmlspecialchars($languageService->sL($item[0])) . '</option>';
|
||||
}
|
||||
$valuePickerHtml[] = '</select>';
|
||||
}
|
||||
|
||||
$fieldControlResult = $this->renderFieldControl();
|
||||
$fieldControlHtml = $fieldControlResult['html'];
|
||||
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldControlResult, false);
|
||||
|
||||
$fieldWizardResult = $this->renderFieldWizard();
|
||||
$fieldWizardHtml = $fieldWizardResult['html'];
|
||||
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldWizardResult, false);
|
||||
|
||||
$mainFieldHtml = [];
|
||||
$mainFieldHtml[] = '<div class="form-control-wrap" style="max-width: ' . $width . 'px">';
|
||||
$mainFieldHtml[] = '<div class="form-wizards-wrap">';
|
||||
$mainFieldHtml[] = '<div class="form-wizards-element">';
|
||||
$mainFieldHtml[] = '<textarea ' . GeneralUtility::implodeAttributes($attributes, true) . '>' . htmlspecialchars($itemValue) . '</textarea>';
|
||||
$mainFieldHtml[] = '</div>';
|
||||
if (!empty($valuePickerHtml) || !empty($fieldControlHtml)) {
|
||||
$mainFieldHtml[] = '<div class="form-wizards-items-aside">';
|
||||
$mainFieldHtml[] = '<div class="btn-group">';
|
||||
$mainFieldHtml[] = implode(LF, $valuePickerHtml);
|
||||
$mainFieldHtml[] = $fieldControlHtml;
|
||||
$mainFieldHtml[] = '</div>';
|
||||
$mainFieldHtml[] = '</div>';
|
||||
}
|
||||
if (!empty($fieldWizardHtml)) {
|
||||
$mainFieldHtml[] = '<div class="form-wizards-items-bottom">';
|
||||
$mainFieldHtml[] = $fieldWizardHtml;
|
||||
$mainFieldHtml[] = '</div>';
|
||||
}
|
||||
$mainFieldHtml[] = '</div>';
|
||||
$mainFieldHtml[] = '</div>';
|
||||
$mainFieldHtml = implode(LF, $mainFieldHtml);
|
||||
|
||||
$fullElement = $mainFieldHtml;
|
||||
if ($this->hasNullCheckboxButNoPlaceholder()) {
|
||||
$checked = $itemValue !== null ? ' checked="checked"' : '';
|
||||
$fullElement = [];
|
||||
$fullElement[] = '<div class="t3-form-field-disable"></div>';
|
||||
$fullElement[] = '<div class="checkbox t3-form-field-eval-null-checkbox">';
|
||||
$fullElement[] = '<label for="' . $nullControlNameEscaped . '">';
|
||||
$fullElement[] = '<input type="hidden" name="' . $nullControlNameEscaped . '" value="0" />';
|
||||
$fullElement[] = '<input type="checkbox" name="' . $nullControlNameEscaped . '" id="' . $nullControlNameEscaped . '" value="1"' . $checked . ' />';
|
||||
$fullElement[] = $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.nullCheckbox');
|
||||
$fullElement[] = '</label>';
|
||||
$fullElement[] = '</div>';
|
||||
$fullElement[] = $mainFieldHtml;
|
||||
$fullElement = implode(LF, $fullElement);
|
||||
} elseif ($this->hasNullCheckboxWithPlaceholder()) {
|
||||
$checked = $itemValue !== null ? ' checked="checked"' : '';
|
||||
$placeholder = $shortenedPlaceholder = $config['placeholder'] ?? '';
|
||||
$disabled = '';
|
||||
$fallbackValue = 0;
|
||||
if (strlen($placeholder) > 0) {
|
||||
$shortenedPlaceholder = GeneralUtility::fixed_lgd_cs($placeholder, 20);
|
||||
if ($placeholder !== $shortenedPlaceholder) {
|
||||
$overrideLabel = sprintf(
|
||||
$languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.placeholder.override'),
|
||||
'<span title="' . htmlspecialchars($placeholder) . '">' . htmlspecialchars($shortenedPlaceholder) . '</span>'
|
||||
);
|
||||
} else {
|
||||
$overrideLabel = sprintf(
|
||||
$languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.placeholder.override'),
|
||||
htmlspecialchars($placeholder)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$overrideLabel = $languageService->sL(
|
||||
'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.placeholder.override_not_available'
|
||||
);
|
||||
}
|
||||
$fullElement = [];
|
||||
$fullElement[] = '<div class="checkbox t3js-form-field-eval-null-placeholder-checkbox">';
|
||||
$fullElement[] = '<label for="' . $nullControlNameEscaped . '">';
|
||||
$fullElement[] = '<input type="hidden" name="' . $nullControlNameEscaped . '" value="' . $fallbackValue . '" />';
|
||||
$fullElement[] = '<input type="checkbox" name="' . $nullControlNameEscaped . '" id="' . $nullControlNameEscaped . '" value="1"' . $checked . $disabled . ' />';
|
||||
$fullElement[] = $overrideLabel;
|
||||
$fullElement[] = '</label>';
|
||||
$fullElement[] = '</div>';
|
||||
$fullElement[] = '<div class="t3js-formengine-placeholder-placeholder">';
|
||||
$fullElement[] = '<div class="form-control-wrap" style="max-width:' . $width . 'px">';
|
||||
$fullElement[] = '<textarea';
|
||||
$fullElement[] = ' class="form-control formengine-textarea' . (isset($config['fixedFont']) ? ' text-monospace' : '') . '"';
|
||||
$fullElement[] = ' disabled="disabled"';
|
||||
$fullElement[] = ' rows="' . htmlspecialchars($attributes['rows']) . '"';
|
||||
$fullElement[] = ' wrap="' . htmlspecialchars($attributes['wrap']) . '"';
|
||||
$fullElement[] = isset($attributes['style']) ? ' style="' . htmlspecialchars($attributes['style']) . '"' : '';
|
||||
$fullElement[] = isset($attributes['maxlength']) ? ' maxlength="' . htmlspecialchars($attributes['maxlength']) . '"' : '';
|
||||
$fullElement[] = '>';
|
||||
$fullElement[] = htmlspecialchars($shortenedPlaceholder);
|
||||
$fullElement[] = '</textarea>';
|
||||
$fullElement[] = '</div>';
|
||||
$fullElement[] = '</div>';
|
||||
$fullElement[] = '<div class="t3js-formengine-placeholder-formfield">';
|
||||
$fullElement[] = $mainFieldHtml;
|
||||
$fullElement[] = '</div>';
|
||||
$fullElement = implode(LF, $fullElement);
|
||||
}
|
||||
|
||||
$resultArray['html'] = '<div class="formengine-field-item t3js-formengine-field-item">' . $fieldInformationHtml . $fullElement . '</div>';
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUserAuthentication()
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService()
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\Element;
|
||||
|
||||
/*
|
||||
* 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\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
use TYPO3\CMS\Core\Utility\StringUtility;
|
||||
|
||||
/**
|
||||
* Generation of TCEform elements of the type "text"
|
||||
*/
|
||||
class TextTableElement extends AbstractFormElement
|
||||
{
|
||||
/**
|
||||
* Default field information enabled for this element.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldInformation = [
|
||||
'tcaDescription' => [
|
||||
'renderType' => 'tcaDescription',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Default field wizards enabled for this element.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldWizard = [
|
||||
'localizationStateSelector' => [
|
||||
'renderType' => 'localizationStateSelector',
|
||||
],
|
||||
'otherLanguageContent' => [
|
||||
'renderType' => 'otherLanguageContent',
|
||||
'after' => [
|
||||
'localizationStateSelector'
|
||||
],
|
||||
],
|
||||
'defaultLanguageDifferences' => [
|
||||
'renderType' => 'defaultLanguageDifferences',
|
||||
'after' => [
|
||||
'otherLanguageContent',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* This renderType adds the "table wizard icon" by default (on not new records)
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldControl = [
|
||||
'tableWizard' => [
|
||||
'renderType' => 'tableWizard',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* The number of chars expected per row when the height of a text area field is
|
||||
* automatically calculated based on the number of characters found in the field content.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $charactersPerRow = 40;
|
||||
|
||||
/**
|
||||
* This will render a <textarea> with table wizard
|
||||
*
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$backendUser = $this->getBackendUserAuthentication();
|
||||
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
$resultArray = $this->initializeResultArray();
|
||||
|
||||
$itemValue = $parameterArray['itemFormElValue'];
|
||||
$config = $parameterArray['fieldConf']['config'];
|
||||
$evalList = GeneralUtility::trimExplode(',', $config['eval'], true);
|
||||
$cols = MathUtility::forceIntegerInRange($config['cols'] ?: $this->defaultInputWidth, $this->minimumInputWidth, $this->maxInputWidth);
|
||||
$width = $this->formMaxWidth($cols);
|
||||
|
||||
// Setting number of rows
|
||||
$rows = MathUtility::forceIntegerInRange($config['rows'] ?: 5, 1, 20);
|
||||
$originalRows = $rows;
|
||||
$itemFormElementValueLength = strlen($itemValue);
|
||||
if ($itemFormElementValueLength > $this->charactersPerRow * 2) {
|
||||
$rows = MathUtility::forceIntegerInRange(
|
||||
round($itemFormElementValueLength / $this->charactersPerRow),
|
||||
count(explode(LF, $itemValue)),
|
||||
20
|
||||
);
|
||||
if ($rows < $originalRows) {
|
||||
$rows = $originalRows;
|
||||
}
|
||||
}
|
||||
|
||||
$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">';
|
||||
$html[] = '<textarea class="form-control" rows="' . $rows . '" disabled>';
|
||||
$html[] = htmlspecialchars($itemValue);
|
||||
$html[] = '</textarea>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$resultArray['html'] = implode(LF, $html);
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
// @todo: The whole eval handling is a mess and needs refactoring
|
||||
foreach ($evalList as $func) {
|
||||
// @todo: This is ugly: The code should find out on it's own whether a eval definition is a
|
||||
// @todo: keyword like "date", or a class reference. The global registration could be dropped then
|
||||
// Pair hook to the one in \TYPO3\CMS\Core\DataHandling\DataHandler::checkValue_input_Eval()
|
||||
// There is a similar hook for "evaluateFieldValue" in DataHandler and InputTextElement
|
||||
if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tce']['formevals'][$func])) {
|
||||
if (class_exists($func)) {
|
||||
$evalObj = GeneralUtility::makeInstance($func);
|
||||
if (method_exists($evalObj, 'deevaluateFieldValue')) {
|
||||
$_params = [
|
||||
'value' => $itemValue
|
||||
];
|
||||
$itemValue = $evalObj->deevaluateFieldValue($_params);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$attributes = [
|
||||
'id' => StringUtility::getUniqueId('formengine-textarea-'),
|
||||
'name' => htmlspecialchars($parameterArray['itemFormElName']),
|
||||
'data-formengine-validation-rules' => $this->getValidationDataAsJsonString($config),
|
||||
'data-formengine-input-name' => htmlspecialchars($parameterArray['itemFormElName']),
|
||||
'rows' => $rows,
|
||||
'wrap' => $config['wrap'] ?: 'virtual',
|
||||
'onChange' => implode('', $parameterArray['fieldChangeFunc']),
|
||||
];
|
||||
$classes = [
|
||||
'form-control',
|
||||
't3js-formengine-textarea',
|
||||
'formengine-textarea',
|
||||
];
|
||||
if ($config['fixedFont']) {
|
||||
$classes[] = 'text-monospace';
|
||||
}
|
||||
if ($config['enableTabulator']) {
|
||||
$classes[] = 't3js-enable-tab';
|
||||
}
|
||||
$attributes['class'] = implode(' ', $classes);
|
||||
$maximumHeight = (int)$backendUser->uc['resizeTextareas_MaxHeight'];
|
||||
if ($maximumHeight > 0) {
|
||||
// add the max-height from the users' preference to it
|
||||
$attributes['style'] = 'max-height: ' . $maximumHeight . 'px';
|
||||
}
|
||||
if (isset($config['max']) && (int)$config['max'] > 0) {
|
||||
$attributes['maxlength'] = (int)$config['max'];
|
||||
}
|
||||
if (!empty($config['placeholder'])) {
|
||||
$attributes['placeholder'] = htmlspecialchars(trim($config['placeholder']));
|
||||
}
|
||||
|
||||
$fieldControlResult = $this->renderFieldControl();
|
||||
$fieldControlHtml = $fieldControlResult['html'];
|
||||
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldControlResult, 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-control-wrap" style="max-width: ' . $width . 'px">';
|
||||
$html[] = '<div class="form-wizards-wrap">';
|
||||
$html[] = '<div class="form-wizards-element">';
|
||||
$html[] = '<textarea ' . GeneralUtility::implodeAttributes($attributes, true) . '>' . htmlspecialchars($itemValue) . '</textarea>';
|
||||
$html[] = '</div>';
|
||||
if (!empty($fieldControlHtml)) {
|
||||
$html[] = '<div class="form-wizards-items-aside">';
|
||||
$html[] = '<div class="btn-group">';
|
||||
$html[] = $fieldControlHtml;
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
}
|
||||
if (!empty($fieldWizardHtml)) {
|
||||
$html[] = '<div class="form-wizards-items-bottom">';
|
||||
$html[] = $fieldWizardHtml;
|
||||
$html[] = '</div>';
|
||||
}
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
|
||||
$resultArray['html'] = implode(LF, $html);
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUserAuthentication()
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\Element;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Generation of TCEform elements of where the type is unknown
|
||||
*/
|
||||
class UnknownElement extends AbstractFormElement
|
||||
{
|
||||
/**
|
||||
* Handler for unknown types.
|
||||
*
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$type = $this->data['parameterArray']['fieldConf']['config']['type'];
|
||||
$renderType = $this->data['renderType'];
|
||||
$resultArray = $this->initializeResultArray();
|
||||
$resultArray['html'] = '<div class="alert alert-warning">Unknown type: <code>' . $type . '</code>' . ($renderType ? ', render type: <code>' . $renderType . '</code>' : '') . '</div>';
|
||||
return $resultArray;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Form\Element;
|
||||
|
||||
/*
|
||||
* 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\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Generation of elements of the type "user". This is a dummy implementation.
|
||||
*
|
||||
* type="user" elements should be combined with a custom renderType to create custom output.
|
||||
* This implementation registered for type="user" kicks in if no renderType is given and is just
|
||||
* a fallback implementation to hint developers that the TCA registration is incomplete.
|
||||
*/
|
||||
class UserElement extends AbstractFormElement
|
||||
{
|
||||
/**
|
||||
* User defined field type
|
||||
*
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
$resultArray = $this->initializeResultArray();
|
||||
|
||||
if (empty($parameterArray['fieldConf']['config']['userFunc'])) {
|
||||
// If there is no (deprecated) userFunc, render some dummy output to explain this element
|
||||
// should usually not be called at all.
|
||||
// @deprecated The if can be removed in TYPO3 v10.0, keeping the body only.
|
||||
$resultArray['html'] = '<div class="alert alert-warning">';
|
||||
$resultArray['html'] .= 'This is dummy output: Field <code>' . htmlspecialchars($this->data['fieldName']) . '</code>';
|
||||
$resultArray['html'] .= 'of table <code>' . htmlspecialchars($this->data['tableName']) . '</code>';
|
||||
$resultArray['html'] .= ' is registered as type="user" element without a specific renderType.';
|
||||
$resultArray['html'] .= ' Please look up details in TCA reference documentation for type="user".';
|
||||
$resultArray['html'] .= '</div>';
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
// @deprecated since TYPO3 v9, everything below will be removed in TYPO3 v10.0.
|
||||
trigger_error(
|
||||
'Properties "userFunc", "noTableWrapping" and "parameters" will be removed in TYPO3 v10.0. Use a renderType instead.',
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
$parameterArray['table'] = $this->data['tableName'];
|
||||
$parameterArray['field'] = $this->data['fieldName'];
|
||||
$parameterArray['row'] = $this->data['databaseRow'];
|
||||
$parameterArray['parameters'] = $parameterArray['fieldConf']['config']['parameters'] ?? [];
|
||||
$html = GeneralUtility::callUserFunction(
|
||||
$parameterArray['fieldConf']['config']['userFunc'],
|
||||
$parameterArray,
|
||||
$this
|
||||
);
|
||||
if (!isset($parameterArray['fieldConf']['config']['noTableWrapping'])
|
||||
|| (bool)$parameterArray['fieldConf']['config']['noTableWrapping'] === false
|
||||
) {
|
||||
$html = '<div class="formengine-field-item t3js-formengine-field-item">' . $html . '</div>';
|
||||
}
|
||||
$resultArray['html'] = $html;
|
||||
return $resultArray;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Form\Element;
|
||||
|
||||
/*
|
||||
* 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\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessageService;
|
||||
use TYPO3\CMS\Core\Resource\Exception\InvalidPathException;
|
||||
use TYPO3\CMS\Core\Resource\ResourceFactory;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Special type="user" element used in sys_file_storage is_public field
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class UserSysFileStorageIsPublicElement extends AbstractFormElement
|
||||
{
|
||||
/**
|
||||
* Default field information enabled for this element.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldInformation = [
|
||||
'tcaDescription' => [
|
||||
'renderType' => 'tcaDescription',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* There are some edge cases where "is_public" can never be marked as true in the BE,
|
||||
* for instance for storage located outside the document root or
|
||||
* for storages driven by special driver such as Flickr, ...
|
||||
*
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
*/
|
||||
public function render(): array
|
||||
{
|
||||
$row = $this->data['databaseRow'];
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
$isPublic = (bool)$GLOBALS['TCA']['sys_file_storage']['columns']['is_public']['config']['default'];
|
||||
|
||||
if ($this->data['command'] === 'edit') {
|
||||
// Make sure the storage object can be retrieved which is not the case when new storage.
|
||||
$lang = $this->getLanguageService();
|
||||
$flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
|
||||
$defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
|
||||
try {
|
||||
$storage = ResourceFactory::getInstance()->getStorageObject((int)$row['uid']);
|
||||
$storageRecord = $storage->getStorageRecord();
|
||||
$isPublic = $storage->isPublic() && $storageRecord['is_public'];
|
||||
|
||||
// Display a warning to the BE User in case settings is not inline with storage capability.
|
||||
if ($storageRecord['is_public'] && !$storage->isPublic()) {
|
||||
$message = GeneralUtility::makeInstance(
|
||||
FlashMessage::class,
|
||||
$lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.message.storage_is_no_public'),
|
||||
$lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.header.storage_is_no_public'),
|
||||
FlashMessage::WARNING
|
||||
);
|
||||
$defaultFlashMessageQueue->enqueue($message);
|
||||
}
|
||||
} catch (InvalidPathException $e) {
|
||||
$message = GeneralUtility::makeInstance(
|
||||
FlashMessage::class,
|
||||
$lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:filestorage.invalidpathexception.message'),
|
||||
$lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:filestorage.invalidpathexception.title'),
|
||||
FlashMessage::ERROR
|
||||
);
|
||||
$defaultFlashMessageQueue->enqueue($message);
|
||||
}
|
||||
}
|
||||
|
||||
$isPublicAsString = $isPublic ? '1' : '0';
|
||||
$fieldInformationResult = $this->renderFieldInformation();
|
||||
$fieldInformationHtml = $fieldInformationResult['html'];
|
||||
$resultArray = $this->mergeChildReturnIntoExistingResult($this->initializeResultArray(), $fieldInformationResult, false);
|
||||
|
||||
$checkboxParameters = $this->checkBoxParams($parameterArray['itemFormElName'], $isPublic, 0, 1, implode('', $parameterArray['fieldChangeFunc']));
|
||||
$checkboxId = $parameterArray['itemFormElID'] . '_1';
|
||||
$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="checkbox checkbox-type-toggle">';
|
||||
$html[] = '<input type="checkbox"';
|
||||
$html[] = ' class="checkbox-input"';
|
||||
$html[] = ' value="1"';
|
||||
$html[] = ' data-formengine-input-name="' . htmlspecialchars($parameterArray['itemFormElName'], ENT_QUOTES) . '"';
|
||||
$html[] = ' id="' . htmlspecialchars($checkboxId, ENT_QUOTES) . '"';
|
||||
$html[] = $checkboxParameters;
|
||||
$html[] = $isPublic ? ' checked="checked"' : '';
|
||||
$html[] = '/>';
|
||||
$html[] = '<label class="checkbox-label" for="' . htmlspecialchars($checkboxId, ENT_QUOTES) . '">';
|
||||
$html[] = '<span class="checkbox-label-text">' . $this->appendValueToLabelInDebugMode(' ', $isPublicAsString) . '</span>';
|
||||
$html[] = '</label>';
|
||||
$html[] = '<input type="hidden"';
|
||||
$html[] = ' name="' . htmlspecialchars($parameterArray['itemFormElName'], ENT_QUOTES) . '"';
|
||||
$html[] = ' value="' . $isPublicAsString . '"';
|
||||
$html[] = ' />';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$resultArray['html'] = implode(LF, $html);
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Generic backend form exception
|
||||
*/
|
||||
class Exception extends \TYPO3\CMS\Backend\Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* User is not allowed to edit this content elements
|
||||
*/
|
||||
class AccessDeniedContentEditException extends AccessDeniedException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* User check did not survive "recordEditAccessInternals" to edit record
|
||||
*/
|
||||
class AccessDeniedEditInternalsException extends AccessDeniedException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\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\Backend\Form\Exception;
|
||||
|
||||
/**
|
||||
* Access denied exception.
|
||||
* This indicated a recoverable error that should be changed to a user message.
|
||||
* This abstract exception is extended by more fine grained exceptions.
|
||||
*/
|
||||
abstract class AccessDeniedException extends Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* User access to record was denied by a makeEditForm_accessCheck hook
|
||||
*/
|
||||
class AccessDeniedHookException extends AccessDeniedException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* User is not allowed to edit this page
|
||||
*/
|
||||
class AccessDeniedPageEditException extends AccessDeniedException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* User is not allowed to add a new page
|
||||
*/
|
||||
class AccessDeniedPageNewException extends AccessDeniedException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* User has no sufficient rights to modify a row that is located at root node
|
||||
*/
|
||||
class AccessDeniedRootNodeException extends AccessDeniedException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* User has no modify table access to a table record
|
||||
*/
|
||||
class AccessDeniedTableModifyException extends AccessDeniedException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\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\Backend\Form\Exception;
|
||||
|
||||
/**
|
||||
* Thrown if a language overlay record is handled and no default language record is found.
|
||||
*/
|
||||
class DatabaseDefaultLanguageException extends Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\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\Backend\Form\Exception;
|
||||
|
||||
/**
|
||||
* A record could not be fetched from database, maybe it vanished meanwhile.
|
||||
*/
|
||||
class DatabaseRecordException extends Exception
|
||||
{
|
||||
/**
|
||||
* @var string Table name
|
||||
*/
|
||||
protected $tableName;
|
||||
|
||||
/**
|
||||
* @var int Table row uid
|
||||
*/
|
||||
protected $uid;
|
||||
|
||||
/**
|
||||
* Constructor overwrites default constructor.
|
||||
*
|
||||
* @param string $message Human readable error message
|
||||
* @param int $code Exception code timestamp
|
||||
* @param \Exception $previousException Possible exception from database layer
|
||||
* @param string $tableName Table name query was working on
|
||||
* @param int $uid Table row uid
|
||||
*/
|
||||
public function __construct($message, $code, \Exception $previousException = null, $tableName, $uid)
|
||||
{
|
||||
parent::__construct($message, $code, $previousException);
|
||||
$this->tableName = $tableName;
|
||||
$this->uid = $uid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return table name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTableName()
|
||||
{
|
||||
return $this->tableName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return row uid
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getUid()
|
||||
{
|
||||
return $this->uid;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Form\FieldControl;
|
||||
|
||||
/*
|
||||
* 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\AbstractNode;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\StringUtility;
|
||||
|
||||
/**
|
||||
* Renders the icon with link parameters to add a new record,
|
||||
* typically used for single elements of type=group or type=select.
|
||||
*/
|
||||
class AddRecord extends AbstractNode
|
||||
{
|
||||
/**
|
||||
* Add button control
|
||||
*
|
||||
* @return array As defined by FieldControl class
|
||||
*/
|
||||
public function render(): array
|
||||
{
|
||||
$options = $this->data['renderData']['fieldControlOptions'];
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
$itemName = $parameterArray['itemFormElName'];
|
||||
|
||||
// Handle options and fallback
|
||||
$title = $options['title'] ?? 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.createNew';
|
||||
$setValue = $options['setValue'] ?? 'append';
|
||||
|
||||
$table = '';
|
||||
if (isset($options['table'])) {
|
||||
// Table given in options - use it
|
||||
$table = $options['table'];
|
||||
} elseif ($parameterArray['fieldConf']['config']['type'] === 'group'
|
||||
&& isset($parameterArray['fieldConf']['config']['internal_type'])
|
||||
&& $parameterArray['fieldConf']['config']['internal_type'] === 'db'
|
||||
&& !empty($parameterArray['fieldConf']['config']['allowed'])
|
||||
) {
|
||||
// Use first table from allowed list if specific table is not set in options
|
||||
$allowedTables = GeneralUtility::trimExplode(',', $parameterArray['fieldConf']['config']['allowed'], true);
|
||||
$table = array_pop($allowedTables);
|
||||
} elseif ($parameterArray['fieldConf']['config']['type'] === 'select'
|
||||
&& !empty($parameterArray['fieldConf']['config']['foreign_table'])
|
||||
) {
|
||||
// Use foreign_table if given for type=select
|
||||
$table = $parameterArray['fieldConf']['config']['foreign_table'];
|
||||
}
|
||||
if (empty($table)) {
|
||||
// Still no table - this element can not handle the add control.
|
||||
return [];
|
||||
}
|
||||
|
||||
// Target pid of new records is current pid by default
|
||||
$pid = $this->data['effectivePid'];
|
||||
if (isset($options['pid'])) {
|
||||
// pid configured in options - use it
|
||||
$pid = $options['pid'];
|
||||
} elseif (
|
||||
isset($GLOBALS['TCA'][$table]['ctrl']['rootLevel'])
|
||||
&& (int)$GLOBALS['TCA'][$table]['ctrl']['rootLevel'] === 1
|
||||
) {
|
||||
// Target table can only exist on root level - set 0 as pid
|
||||
$pid = 0;
|
||||
}
|
||||
|
||||
$prefixOfFormElName = 'data[' . $this->data['tableName'] . '][' . $this->data['databaseRow']['uid'] . '][' . $this->data['fieldName'] . ']';
|
||||
$flexFormPath = '';
|
||||
if (GeneralUtility::isFirstPartOfStr($itemName, $prefixOfFormElName)) {
|
||||
$flexFormPath = str_replace('][', '/', substr($itemName, strlen($prefixOfFormElName) + 1, -1));
|
||||
}
|
||||
|
||||
$urlParameters = [
|
||||
'P' => [
|
||||
'params' => [
|
||||
'table' => $table,
|
||||
'pid' => $pid,
|
||||
'setValue' => $setValue,
|
||||
],
|
||||
'table' => $this->data['tableName'],
|
||||
'field' => $this->data['fieldName'],
|
||||
'uid' => $this->data['databaseRow']['uid'],
|
||||
'flexFormPath' => $flexFormPath,
|
||||
'returnUrl' => $this->data['returnUrl'],
|
||||
],
|
||||
];
|
||||
|
||||
$id = StringUtility::getUniqueId('t3js-formengine-fieldcontrol-');
|
||||
|
||||
/** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
|
||||
$uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
|
||||
return [
|
||||
'iconIdentifier' => 'actions-add',
|
||||
'title' => $title,
|
||||
'linkAttributes' => [
|
||||
'id' => htmlspecialchars($id),
|
||||
'href' => (string)$uriBuilder->buildUriFromRoute('wizard_add', $urlParameters),
|
||||
],
|
||||
'requireJsModules' => [
|
||||
['TYPO3/CMS/Backend/FormEngine/FieldControl/AddRecord' => 'function(FieldControl) {new FieldControl(' . GeneralUtility::quoteJSvalue('#' . $id) . ');}'],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Form\FieldControl;
|
||||
|
||||
/*
|
||||
* 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\AbstractNode;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Renders the icon with link parameters to edit a selected element,
|
||||
* typically used for single elements of type=group or type=select.
|
||||
*/
|
||||
class EditPopup extends AbstractNode
|
||||
{
|
||||
/**
|
||||
* Edit popup control
|
||||
*
|
||||
* @return array As defined by FieldControl class
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
$options = $this->data['renderData']['fieldControlOptions'];
|
||||
|
||||
$title = $options['title'] ?? 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.edit';
|
||||
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
$itemName = $parameterArray['itemFormElName'];
|
||||
$windowOpenParameters = $options['windowOpenParameters'] ?? 'height=800,width=600,status=0,menubar=0,scrollbars=1';
|
||||
|
||||
$flexFormDataStructureIdentifier = $this->data['flexFormDataStructureIdentifier'] ?? '';
|
||||
$flexFormDataStructurePath = '';
|
||||
if (!empty($flexFormDataStructureIdentifier)) {
|
||||
if (empty($this->data['flexFormContainerName'])) {
|
||||
// simple flex form element
|
||||
$flexFormDataStructurePath = 'sheets/'
|
||||
. $this->data['flexFormSheetName']
|
||||
. '/ROOT/el/'
|
||||
. $this->data['flexFormFieldName']
|
||||
. '/TCEforms/config';
|
||||
} else {
|
||||
// flex form section container element
|
||||
$flexFormDataStructurePath = 'sheets/'
|
||||
. $this->data['flexFormSheetName']
|
||||
. '/ROOT/el/'
|
||||
. $this->data['flexFormFieldName']
|
||||
. '/el/'
|
||||
. $this->data['flexFormContainerName']
|
||||
. '/el/'
|
||||
. $this->data['flexFormContainerFieldName']
|
||||
. '/TCEforms/config';
|
||||
}
|
||||
}
|
||||
|
||||
$urlParameters = [
|
||||
'P' => [
|
||||
'table' => $this->data['tableName'],
|
||||
'field' => $this->data['fieldName'],
|
||||
'formName' => 'editform',
|
||||
'flexFormDataStructureIdentifier' => $flexFormDataStructureIdentifier,
|
||||
'flexFormDataStructurePath' => $flexFormDataStructurePath,
|
||||
'hmac' => GeneralUtility::hmac('editform' . $itemName, 'wizard_js'),
|
||||
'fieldChangeFunc' => $parameterArray['fieldChangeFunc'],
|
||||
'fieldChangeFuncHash' => GeneralUtility::hmac(serialize($parameterArray['fieldChangeFunc']), 'backend-link-browser'),
|
||||
],
|
||||
];
|
||||
$uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
|
||||
$url = (string)$uriBuilder->buildUriFromRoute('wizard_edit', $urlParameters);
|
||||
|
||||
$onClick = [];
|
||||
$onClick[] = 'this.blur();';
|
||||
$onClick[] = 'if (!TBE_EDITOR.curSelected(' . GeneralUtility::quoteJSvalue($itemName) . ')) {';
|
||||
$onClick[] = 'top.TYPO3.Modal.confirm(';
|
||||
$onClick[] = '"' . $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.header') . '",';
|
||||
$onClick[] = '"' . $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:mess.noSelItemForEdit') . '",';
|
||||
$onClick[] = 'top.TYPO3.Severity.notice, [{text: TYPO3.lang[\'button.ok\'] || \'OK\', btnClass: \'btn-notice\', name: \'ok\'}]';
|
||||
$onClick[] = ')';
|
||||
$onClick[] = '.on("button.clicked", function(e) {';
|
||||
$onClick[] = 'if (e.target.name == "ok") { top.TYPO3.Modal.dismiss(); }}';
|
||||
$onClick[] = ');';
|
||||
$onClick[] = 'return false;';
|
||||
$onClick[] = '}';
|
||||
$onClick[] = 'vHWin=window.open(';
|
||||
$onClick[] = GeneralUtility::quoteJSvalue($url);
|
||||
$onClick[] = '+\'&P[currentValue]=\'+TBE_EDITOR.rawurlencode(';
|
||||
$onClick[] = 'document.editform[' . GeneralUtility::quoteJSvalue($itemName) . '].value';
|
||||
$onClick[] = ')';
|
||||
$onClick[] = '+\'&P[currentSelectedValues]=\'+TBE_EDITOR.curSelected(';
|
||||
$onClick[] = GeneralUtility::quoteJSvalue($itemName);
|
||||
$onClick[] = '),';
|
||||
$onClick[] = '\'\',';
|
||||
$onClick[] = GeneralUtility::quoteJSvalue($windowOpenParameters);
|
||||
$onClick[] = ');';
|
||||
$onClick[] = 'vHWin.focus();';
|
||||
$onClick[] = 'return false;';
|
||||
|
||||
return [
|
||||
'iconIdentifier' => 'actions-open',
|
||||
'title' => $title,
|
||||
'linkAttributes' => [
|
||||
'onClick' => implode('', $onClick),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of LanguageService
|
||||
*
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService()
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Form\FieldControl;
|
||||
|
||||
/*
|
||||
* 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\AbstractNode;
|
||||
use TYPO3\CMS\Backend\Form\InlineStackProcessor;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Renders the icon "select element via element browser",
|
||||
* typically used for type=group.
|
||||
*/
|
||||
class ElementBrowser extends AbstractNode
|
||||
{
|
||||
/**
|
||||
* Add button control
|
||||
*
|
||||
* @return array As defined by FieldControl class
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$table = $this->data['tableName'];
|
||||
$fieldName = $this->data['fieldName'];
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
$elementName = $parameterArray['itemFormElName'];
|
||||
$config = $parameterArray['fieldConf']['config'];
|
||||
$internalType = (string)$config['internal_type'];
|
||||
$allowed = $config['allowed'];
|
||||
|
||||
if (isset($config['readOnly']) && $config['readOnly']) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ($internalType === 'db') {
|
||||
$title = 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.browse_db';
|
||||
} else {
|
||||
$title = 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.browse_file';
|
||||
}
|
||||
|
||||
// Check against inline uniqueness - Create some onclick js for delete control and element browser
|
||||
// to override record selection in some FAL scenarios - See 'appearance' docs of group element
|
||||
$inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
|
||||
$inlineStackProcessor->initializeByGivenStructure($this->data['inlineStructure']);
|
||||
$elementBrowserOnClickInline = '';
|
||||
if ($this->data['isInlineChild']
|
||||
&& $this->data['inlineParentUid']
|
||||
&& $this->data['inlineParentConfig']['foreign_table'] === $table
|
||||
&& $this->data['inlineParentConfig']['foreign_unique'] === $fieldName
|
||||
) {
|
||||
$objectPrefix = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']) . '-' . $table;
|
||||
$elementBrowserOnClickInline = $objectPrefix . '|inline.checkUniqueElement|inline.setUniqueElement';
|
||||
}
|
||||
$elementBrowserType = $internalType;
|
||||
if (is_array($config['appearance']) && isset($config['appearance']['elementBrowserType'])) {
|
||||
$elementBrowserType = $config['appearance']['elementBrowserType'];
|
||||
}
|
||||
if (is_array($config['appearance']) && isset($config['appearance']['elementBrowserAllowed'])) {
|
||||
$allowed = $config['appearance']['elementBrowserAllowed'];
|
||||
}
|
||||
// Remove any white-spaces from the allowed extension lists
|
||||
$elementBrowserAllowed = implode(',', GeneralUtility::trimExplode(',', $allowed, true));
|
||||
|
||||
$elementBrowserOnClick = 'setFormValueOpenBrowser('
|
||||
. GeneralUtility::quoteJSvalue($elementBrowserType) . ','
|
||||
. GeneralUtility::quoteJSvalue($elementName . '|||' . $elementBrowserAllowed . '|' . $elementBrowserOnClickInline)
|
||||
. ');'
|
||||
. ' return false;';
|
||||
|
||||
return [
|
||||
'iconIdentifier' => 'actions-insert-record',
|
||||
'title' => $title,
|
||||
'linkAttributes' => [
|
||||
'onClick' => $elementBrowserOnClick,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Form\FieldControl;
|
||||
|
||||
/*
|
||||
* 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\AbstractNode;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\PathUtility;
|
||||
use TYPO3\CMS\Core\Utility\StringUtility;
|
||||
|
||||
/**
|
||||
* Renders the icon "insert record from clipboard",
|
||||
* typically used for type=group.
|
||||
*/
|
||||
class InsertClipboard extends AbstractNode
|
||||
{
|
||||
/**
|
||||
* Add button control
|
||||
*
|
||||
* @return array As defined by FieldControl class
|
||||
*/
|
||||
public function render(): array
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
$elementName = $parameterArray['itemFormElName'];
|
||||
$config = $parameterArray['fieldConf']['config'];
|
||||
$internalType = (string)$config['internal_type'];
|
||||
$clipboardElements = $config['clipboardElements'];
|
||||
|
||||
if ((isset($config['readOnly']) && $config['readOnly'])
|
||||
|| empty($clipboardElements)
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$title = '';
|
||||
$dataAttributes = [
|
||||
'element' => $elementName,
|
||||
'clipboardItems' => [],
|
||||
];
|
||||
if ($internalType === 'file_reference' || $internalType === 'file') {
|
||||
// @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0. Deprecation logged by TcaMigration class.
|
||||
$title = sprintf($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.clipInsert_file'), count($clipboardElements));
|
||||
foreach ($clipboardElements as $clipboardElement) {
|
||||
$dataAttributes['clipboardItems'][] = [
|
||||
'title' => rawurlencode(PathUtility::basename($clipboardElement['title'])),
|
||||
'value' => $clipboardElement['value'],
|
||||
];
|
||||
}
|
||||
} elseif ($internalType === 'db') {
|
||||
$title = sprintf($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.clipInsert_db'), count($clipboardElements));
|
||||
foreach ($clipboardElements as $clipboardElement) {
|
||||
$dataAttributes['clipboardItems'][] = [
|
||||
'title' => $clipboardElement['title'],
|
||||
'value' => $clipboardElement['value'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$id = StringUtility::getUniqueId('t3js-formengine-fieldcontrol-');
|
||||
|
||||
return [
|
||||
'iconIdentifier' => 'actions-document-paste-into',
|
||||
'title' => $title,
|
||||
'linkAttributes' => [
|
||||
'id' => htmlspecialchars($id),
|
||||
'data-element' => $dataAttributes['element'],
|
||||
'data-clipboard-items' => json_encode($dataAttributes['clipboardItems']),
|
||||
],
|
||||
'requireJsModules' => [
|
||||
['TYPO3/CMS/Backend/FormEngine/FieldControl/InsertClipboard' => 'function(FieldControl) {new FieldControl(' . GeneralUtility::quoteJSvalue('#' . $id) . ');}'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Form\FieldControl;
|
||||
|
||||
/*
|
||||
* 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\AbstractNode;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Renders the icon with link parameters to open the element browser.
|
||||
* Used in InputLinkElement.
|
||||
*/
|
||||
class LinkPopup extends AbstractNode
|
||||
{
|
||||
/**
|
||||
* Link popup control
|
||||
*
|
||||
* @return array As defined by FieldControl class
|
||||
*/
|
||||
public function render(): array
|
||||
{
|
||||
$options = $this->data['renderData']['fieldControlOptions'];
|
||||
|
||||
$title = $options['title'] ?? 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.link';
|
||||
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
$itemName = $parameterArray['itemFormElName'];
|
||||
$windowOpenParameters = $options['windowOpenParameters'] ?? 'height=800,width=1000,status=0,menubar=0,scrollbars=1';
|
||||
|
||||
$linkBrowserArguments = [];
|
||||
if (isset($options['blindLinkOptions'])) {
|
||||
$linkBrowserArguments['blindLinkOptions'] = $options['blindLinkOptions'];
|
||||
}
|
||||
if (isset($options['blindLinkFields'])) {
|
||||
$linkBrowserArguments['blindLinkFields'] = $options['blindLinkFields'];
|
||||
}
|
||||
if (isset($options['allowedExtensions'])) {
|
||||
$linkBrowserArguments['allowedExtensions'] = $options['allowedExtensions'];
|
||||
}
|
||||
$urlParameters = [
|
||||
'P' => [
|
||||
'params' => $linkBrowserArguments,
|
||||
'table' => $this->data['tableName'],
|
||||
'uid' => $this->data['databaseRow']['uid'],
|
||||
'pid' => $this->data['databaseRow']['pid'],
|
||||
'field' => $this->data['fieldName'],
|
||||
'formName' => 'editform',
|
||||
'itemName' => $itemName,
|
||||
'hmac' => GeneralUtility::hmac('editform' . $itemName, 'wizard_js'),
|
||||
'fieldChangeFunc' => $parameterArray['fieldChangeFunc'],
|
||||
'fieldChangeFuncHash' => GeneralUtility::hmac(serialize($parameterArray['fieldChangeFunc']), 'backend-link-browser'),
|
||||
],
|
||||
];
|
||||
/** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
|
||||
$uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
|
||||
$url = (string)$uriBuilder->buildUriFromRoute('wizard_link', $urlParameters);
|
||||
$onClick = [];
|
||||
$onClick[] = 'this.blur();';
|
||||
$onClick[] = 'vHWin=window.open(';
|
||||
$onClick[] = GeneralUtility::quoteJSvalue($url);
|
||||
$onClick[] = '+\'&P[currentValue]=\'+TBE_EDITOR.rawurlencode(';
|
||||
$onClick[] = 'document.editform[' . GeneralUtility::quoteJSvalue($itemName) . '].value';
|
||||
$onClick[] = ')';
|
||||
$onClick[] = '+\'&P[currentSelectedValues]=\'+TBE_EDITOR.curSelected(';
|
||||
$onClick[] = GeneralUtility::quoteJSvalue($itemName);
|
||||
$onClick[] = '),';
|
||||
$onClick[] = '\'\',';
|
||||
$onClick[] = GeneralUtility::quoteJSvalue($windowOpenParameters);
|
||||
$onClick[] = ');';
|
||||
$onClick[] = 'vHWin.focus();';
|
||||
$onClick[] = 'return false;';
|
||||
|
||||
return [
|
||||
'iconIdentifier' => 'actions-wizard-link',
|
||||
'title' => $title,
|
||||
'linkAttributes' => [
|
||||
'onClick' => implode('', $onClick),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Form\FieldControl;
|
||||
|
||||
/*
|
||||
* 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\AbstractNode;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\StringUtility;
|
||||
|
||||
/**
|
||||
* Renders the icon with link parameters to jump to the list module
|
||||
* "single table" view, showing only one configurable table.
|
||||
*/
|
||||
class ListModule extends AbstractNode
|
||||
{
|
||||
/**
|
||||
* Add button control
|
||||
*
|
||||
* @return array As defined by FieldControl class
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$options = $this->data['renderData']['fieldControlOptions'];
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
|
||||
// Handle options and fallback
|
||||
$title = $options['title'] ?? 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.list';
|
||||
|
||||
$table = '';
|
||||
if (isset($options['table'])) {
|
||||
// Table given in options - use it
|
||||
$table = $options['table'];
|
||||
} elseif ($parameterArray['fieldConf']['config']['type'] === 'group'
|
||||
&& isset($parameterArray['fieldConf']['config']['internal_type'])
|
||||
&& $parameterArray['fieldConf']['config']['internal_type'] === 'db'
|
||||
&& !empty($parameterArray['fieldConf']['config']['allowed'])
|
||||
) {
|
||||
// Use first table from allowed list if specific table is not set in options
|
||||
$allowedTables = GeneralUtility::trimExplode(',', $parameterArray['fieldConf']['config']['allowed'], true);
|
||||
$table = array_pop($allowedTables);
|
||||
} elseif ($parameterArray['fieldConf']['config']['type'] === 'select'
|
||||
&& !empty($parameterArray['fieldConf']['config']['foreign_table'])
|
||||
) {
|
||||
// Use foreign_table if given for type=select
|
||||
$table = $parameterArray['fieldConf']['config']['foreign_table'];
|
||||
}
|
||||
if (empty($table)) {
|
||||
// Still no table - this element can not handle the list control.
|
||||
return [];
|
||||
}
|
||||
|
||||
// Target pid of new records is current pid by default
|
||||
$pid = $this->data['effectivePid'];
|
||||
if (isset($options['pid'])) {
|
||||
// pid configured in options - use it
|
||||
$pid = $options['pid'];
|
||||
} elseif (isset($GLOBALS['TCA'][$table]['ctrl']['rootLevel']) && (int)$GLOBALS['TCA'][$table]['ctrl'] === 1) {
|
||||
// Target table can only exist on root level - set 0 as pid
|
||||
$pid = 0;
|
||||
}
|
||||
|
||||
$urlParameters = [
|
||||
'P' => [
|
||||
'params' => [
|
||||
'table' => $table,
|
||||
'pid' => $pid,
|
||||
],
|
||||
'table' => $this->data['tableName'],
|
||||
'uid' => $this->data['databaseRow']['uid'],
|
||||
'returnUrl' => $this->data['returnUrl'],
|
||||
],
|
||||
];
|
||||
|
||||
$id = StringUtility::getUniqueId('t3js-formengine-fieldcontrol-');
|
||||
|
||||
/** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
|
||||
$uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
|
||||
|
||||
return [
|
||||
'iconIdentifier' => 'actions-system-list-open',
|
||||
'title' => $title,
|
||||
'linkAttributes' => [
|
||||
'id' => htmlspecialchars($id),
|
||||
'href' => (string)$uriBuilder->buildUriFromRoute('wizard_list', $urlParameters),
|
||||
],
|
||||
'requireJsModules' => [
|
||||
['TYPO3/CMS/Backend/FormEngine/FieldControl/ListModule' => 'function(FieldControl) {new FieldControl(' . GeneralUtility::quoteJSvalue('#' . $id) . ');}'],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Form\FieldControl;
|
||||
|
||||
/*
|
||||
* 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\AbstractNode;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\StringUtility;
|
||||
|
||||
/**
|
||||
* "Reset selection to previous selected items" icon,
|
||||
* typically used by type=select with renderType=selectSingleBox
|
||||
*/
|
||||
class ResetSelection extends AbstractNode
|
||||
{
|
||||
/**
|
||||
* Add button control
|
||||
*
|
||||
* @return array As defined by FieldControl class
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
$itemName = $parameterArray['itemFormElName'];
|
||||
|
||||
$selectItems = $parameterArray['fieldConf']['config']['items'];
|
||||
$itemArray = array_flip($parameterArray['itemFormElValue']);
|
||||
$initiallySelectedIndices = [];
|
||||
foreach ($selectItems as $i => $item) {
|
||||
$value = $item[1];
|
||||
// Selected or not by default
|
||||
if (isset($itemArray[$value])) {
|
||||
$initiallySelectedIndices[] = $i;
|
||||
}
|
||||
}
|
||||
|
||||
$id = StringUtility::getUniqueId('t3js-formengine-fieldcontrol-');
|
||||
|
||||
return [
|
||||
'iconIdentifier' => 'actions-edit-undo',
|
||||
'title' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.revertSelection',
|
||||
'linkAttributes' => [
|
||||
'id' => htmlspecialchars($id),
|
||||
'data-item-name' => htmlspecialchars($itemName),
|
||||
'data-selected-indices' => json_encode($initiallySelectedIndices),
|
||||
],
|
||||
'requireJsModules' => [
|
||||
['TYPO3/CMS/Backend/FormEngine/FieldControl/ResetSelection' => 'function(FieldControl) {new FieldControl(' . GeneralUtility::quoteJSvalue('#' . $id) . ');}'],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Form\FieldControl;
|
||||
|
||||
/*
|
||||
* 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\AbstractNode;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
use TYPO3\CMS\Core\Utility\StringUtility;
|
||||
|
||||
/**
|
||||
* Renders the icon with link parameters to the table wizard,
|
||||
* typically used for type=text with renderType=textTable.
|
||||
*/
|
||||
class TableWizard extends AbstractNode
|
||||
{
|
||||
/**
|
||||
* Add button control
|
||||
*
|
||||
* @return array As defined by FieldControl class
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$options = $this->data['renderData']['fieldControlOptions'];
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
$itemName = $parameterArray['itemFormElName'];
|
||||
$row = $this->data['databaseRow'];
|
||||
|
||||
if (!MathUtility::canBeInterpretedAsInteger($row['uid'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Handle options and fallback
|
||||
$title = $options['title'] ?? 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.tableWizard';
|
||||
$xmlOutput = isset($options['xmlOutput']) ? (int)$options['xmlOutput'] : 0;
|
||||
$numNewRows = isset($options['numNewRows']) ? (int)$options['numNewRows'] : 5;
|
||||
|
||||
$prefixOfFormElName = 'data[' . $this->data['tableName'] . '][' . $this->data['databaseRow']['uid'] . '][' . $this->data['fieldName'] . ']';
|
||||
$flexFormPath = '';
|
||||
if (GeneralUtility::isFirstPartOfStr($itemName, $prefixOfFormElName)) {
|
||||
$flexFormPath = str_replace('][', '/', substr($itemName, strlen($prefixOfFormElName) + 1, -1));
|
||||
}
|
||||
|
||||
$urlParameters = [
|
||||
'P' => [
|
||||
'params' => [
|
||||
'xmlOutput' => $xmlOutput,
|
||||
'numNewRows' => $numNewRows,
|
||||
],
|
||||
'table' => $this->data['tableName'],
|
||||
'field' => $this->data['fieldName'],
|
||||
'uid' => $this->data['databaseRow']['uid'],
|
||||
'flexFormPath' => $flexFormPath,
|
||||
'returnUrl' => $this->data['returnUrl'],
|
||||
],
|
||||
];
|
||||
|
||||
$id = StringUtility::getUniqueId('t3js-formengine-fieldcontrol-');
|
||||
|
||||
/** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
|
||||
$uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
|
||||
return [
|
||||
'iconIdentifier' => 'content-table',
|
||||
'title' => $title,
|
||||
'linkAttributes' => [
|
||||
'id' => htmlspecialchars($id),
|
||||
'href' => (string)$uriBuilder->buildUriFromRoute('wizard_table', $urlParameters),
|
||||
],
|
||||
'requireJsModules' => [
|
||||
['TYPO3/CMS/Backend/FormEngine/FieldControl/TableWizard' => 'function(FieldControl) {new FieldControl(' . GeneralUtility::quoteJSvalue('#' . $id) . ');}'],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Form\FieldInformation;
|
||||
|
||||
/*
|
||||
* 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\AbstractNode;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
|
||||
/**
|
||||
* This field information node is used in be_user admin field
|
||||
* to show some additional information if the edited user
|
||||
* is a system maintainer or becomes one if togging the admin flag.
|
||||
*/
|
||||
class AdminIsSystemMaintainer extends AbstractNode
|
||||
{
|
||||
/**
|
||||
* Handler for single nodes
|
||||
*
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function render(): array
|
||||
{
|
||||
if ($this->data['tableName'] !== 'be_users' || $this->data['fieldName'] !== 'admin') {
|
||||
throw new \RuntimeException(
|
||||
'The adminIsSystemMaintainer field information can be used for admin field in be_users table only',
|
||||
1537273550
|
||||
);
|
||||
}
|
||||
|
||||
$resultArray = $this->initializeResultArray();
|
||||
if ($this->data['command'] === 'new') {
|
||||
// Early return on 'new' records - nothing we can do here
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
// False if current user is not in system maintainer list or if switch to user mode is active
|
||||
$isCurrentUserSystemMaintainer = $this->getBackendUser()->isSystemMaintainer();
|
||||
$systemMaintainers = array_map('intval', $GLOBALS['TYPO3_CONF_VARS']['SYS']['systemMaintainers'] ?? []);
|
||||
$isTargetUserInSystemMaintainerList = in_array((int)$this->data['vanillaUid'], $systemMaintainers, true);
|
||||
|
||||
if ($isTargetUserInSystemMaintainerList) {
|
||||
$languageService = $this->getLanguageService();
|
||||
$isTargetUserAdmin = (int)$this->data['databaseRow']['admin'] === 1;
|
||||
if ($isCurrentUserSystemMaintainer) {
|
||||
if ($isTargetUserAdmin) {
|
||||
// User is a system maintainer
|
||||
$fieldInformationText = '<strong>' . htmlspecialchars($languageService->sL(
|
||||
'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:formEngine.beUser.admin.information.userIsSystemMaintainer'
|
||||
)) . '</strong>';
|
||||
} else {
|
||||
// User is currently not an admin, but set as system maintainer (in-effective).
|
||||
// If admin field is set to 1, the user is therefore system maintainer again.
|
||||
$fieldInformationText = '<strong>' . htmlspecialchars($languageService->sL(
|
||||
'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:formEngine.beUser.admin.information.userWillBecomeSystemMaintainer'
|
||||
)) . '</strong>';
|
||||
}
|
||||
} else {
|
||||
// User is in system maintainer list, user can not change admin and password
|
||||
$fieldInformationText = '<strong>' . htmlspecialchars($languageService->sL(
|
||||
'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:formEngine.beUser.admin.information.userAdminAndPasswordChangeNotAllowed'
|
||||
)) . '</strong>';
|
||||
}
|
||||
$resultArray['html'] = $fieldInformationText;
|
||||
}
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUser(): BackendUserAuthentication
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace TYPO3\CMS\Backend\Form\FieldInformation;
|
||||
|
||||
/*
|
||||
* 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\AbstractNode;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
|
||||
/**
|
||||
* Render localized ['columns']['theField']['description'] text as default
|
||||
* field information node. This is typically displayed in elements below the
|
||||
* element label and the field content.
|
||||
*/
|
||||
class TcaDescription extends AbstractNode
|
||||
{
|
||||
/**
|
||||
* Handler for single nodes
|
||||
*
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
*/
|
||||
public function render(): array
|
||||
{
|
||||
$resultArray = $this->initializeResultArray();
|
||||
if (!empty($this->data['parameterArray']['fieldConf']['description'])) {
|
||||
$fieldInformationText = $this->getLanguageService()->sL($this->data['parameterArray']['fieldConf']['description']);
|
||||
if (trim($fieldInformationText) !== '') {
|
||||
$resultArray['html'] = '<span class="formengine-field-item-description text-muted">' . htmlspecialchars($fieldInformationText) . '</span>';
|
||||
}
|
||||
}
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the LanguageService
|
||||
*
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Form\FieldWizard;
|
||||
|
||||
/*
|
||||
* 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\AbstractNode;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Utility\DiffUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Renders the diff-view of default language record content compared with what the record was originally
|
||||
* translated from. Will render content if any is found in the internal array.
|
||||
*
|
||||
* This is typically used of renderTypes that are based on text input
|
||||
*/
|
||||
class DefaultLanguageDifferences extends AbstractNode
|
||||
{
|
||||
/**
|
||||
* Render the diff view if enabled
|
||||
*
|
||||
* @return array Result array
|
||||
*/
|
||||
public function render(): array
|
||||
{
|
||||
$result = $this->initializeResultArray();
|
||||
|
||||
$row = $this->data['databaseRow'];
|
||||
$table = $this->data['tableName'];
|
||||
$fieldName = $this->data['fieldName'];
|
||||
$fieldConfig = $this->data['processedTca']['columns'][$fieldName];
|
||||
$l10nDisplay = $this->data['parameterArray']['fieldConf']['l10n_display'] ?? '';
|
||||
$defaultLanguageRow = $this->data['defaultLanguageRow'] ?? null;
|
||||
$defaultLanguageDiffRow = $this->data['defaultLanguageDiffRow'][$table . ':' . $row['uid']] ?? null;
|
||||
|
||||
if (!is_array($defaultLanguageDiffRow)
|
||||
|| GeneralUtility::inList($l10nDisplay, 'hideDiff')
|
||||
|| GeneralUtility::inList($l10nDisplay, 'defaultAsReadonly')
|
||||
|| !isset($defaultLanguageDiffRow[$fieldName])
|
||||
|| $fieldConfig['config']['type'] === 'inline'
|
||||
|| $fieldConfig['config']['type'] === 'flex'
|
||||
) {
|
||||
// Early return if there is no diff row or if display is disabled
|
||||
return $result;
|
||||
}
|
||||
|
||||
$languageService = $this->getLanguageService();
|
||||
$html = [];
|
||||
if ((string)$defaultLanguageDiffRow[$fieldName] !== (string)$defaultLanguageRow[$fieldName]) {
|
||||
// Create diff-result:
|
||||
$diffUtility = GeneralUtility::makeInstance(DiffUtility::class);
|
||||
$diffUtility->stripTags = false;
|
||||
$diffResult = $diffUtility->makeDiffDisplay(
|
||||
BackendUtility::getProcessedValue($table, $fieldName, $defaultLanguageDiffRow[$fieldName], 0, 1),
|
||||
BackendUtility::getProcessedValue($table, $fieldName, $defaultLanguageRow[$fieldName], 0, 1)
|
||||
);
|
||||
$html[] = '<div class="t3-form-original-language-diff">';
|
||||
$html[] = '<div class="t3-form-original-language-diffheader">';
|
||||
$html[] = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.changeInOrig'));
|
||||
$html[] = '</div>';
|
||||
$html[] = '<div class="t3-form-original-language-diffcontent">';
|
||||
$html[] = '<div class="diff">';
|
||||
$html[] = '<div class="diff-item">';
|
||||
$html[] = '<div class="diff-item-result diff-item-result-inline">' . $diffResult . '</div>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
}
|
||||
$result['html'] = implode(LF, $html);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService()
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Form\FieldWizard;
|
||||
|
||||
/*
|
||||
* 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\AbstractNode;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessageService;
|
||||
use TYPO3\CMS\Core\Resource\ProcessedFile;
|
||||
use TYPO3\CMS\Core\Resource\ResourceFactory;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
|
||||
/**
|
||||
* Render thumbnails of selected files,
|
||||
* typically used with type=group and internal_type=file and file_reference.
|
||||
*/
|
||||
class FileThumbnails extends AbstractNode
|
||||
{
|
||||
/**
|
||||
* Render thumbnails of selected files
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function render(): array
|
||||
{
|
||||
$result = $this->initializeResultArray();
|
||||
|
||||
$table = $this->data['tableName'];
|
||||
$fieldName = $this->data['fieldName'];
|
||||
$row = $this->data['databaseRow'];
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
$config = $parameterArray['fieldConf']['config'];
|
||||
$selectedItems = $parameterArray['itemFormElValue'];
|
||||
|
||||
if (!isset($config['internal_type'])
|
||||
|| ($config['internal_type'] !== 'file' && $config['internal_type'] !== 'file_reference')
|
||||
) {
|
||||
// @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0. Deprecation logged by TcaMigration class.
|
||||
// Thumbnails only make sense on file and file_reference
|
||||
return $result;
|
||||
}
|
||||
|
||||
$fileFactory = ResourceFactory::getInstance();
|
||||
$thumbnailsHtml = [];
|
||||
foreach ($selectedItems as $selectedItem) {
|
||||
$uidOrPath = $selectedItem['uidOrPath'];
|
||||
if (MathUtility::canBeInterpretedAsInteger($uidOrPath)) {
|
||||
$fileObject = $fileFactory->getFileObject($uidOrPath);
|
||||
if (!$fileObject->isMissing()) {
|
||||
$extension = $fileObject->getExtension();
|
||||
if (GeneralUtility::inList(
|
||||
$GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'],
|
||||
$extension
|
||||
)
|
||||
) {
|
||||
$thumbnailsHtml[] = '<li>';
|
||||
$thumbnailsHtml[] = '<span class="thumbnail">';
|
||||
$thumbnailsHtml[] = $fileObject->process(ProcessedFile::CONTEXT_IMAGEPREVIEW, [])->getPublicUrl(true);
|
||||
$thumbnailsHtml[] = '</span>';
|
||||
$thumbnailsHtml[] = '</li>';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$rowCopy = [];
|
||||
$rowCopy[$fieldName] = $uidOrPath;
|
||||
try {
|
||||
$icon = BackendUtility::thumbCode(
|
||||
$rowCopy,
|
||||
$table,
|
||||
$fieldName,
|
||||
'',
|
||||
'',
|
||||
$config['uploadfolder'],
|
||||
0,
|
||||
' align="middle"'
|
||||
);
|
||||
$thumbnailsHtml[] =
|
||||
'<li>'
|
||||
. '<span class="thumbnail">'
|
||||
. $icon
|
||||
. '</span>'
|
||||
. '</li>';
|
||||
} catch (\Exception $exception) {
|
||||
$message = $exception->getMessage();
|
||||
$flashMessage = GeneralUtility::makeInstance(
|
||||
FlashMessage::class,
|
||||
$message,
|
||||
'',
|
||||
FlashMessage::ERROR,
|
||||
true
|
||||
);
|
||||
$flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
|
||||
$defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
|
||||
$defaultFlashMessageQueue->enqueue($flashMessage);
|
||||
$this->logger->warning($message, ['table' => $table, 'row' => $row]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$html = [];
|
||||
if (!empty($thumbnailsHtml)) {
|
||||
$html[] = '<ul class="list-inline">';
|
||||
$html[] = implode(LF, $thumbnailsHtml);
|
||||
$html[] = '</ul>';
|
||||
}
|
||||
|
||||
$result['html'] = implode(LF, $html);
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Form\FieldWizard;
|
||||
|
||||
/*
|
||||
* 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\AbstractNode;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Render list of allowed / disalowwed file types,
|
||||
* typically used with type=group and internal_type=file.
|
||||
*/
|
||||
class FileTypeList extends AbstractNode
|
||||
{
|
||||
/**
|
||||
* Render list of allowed and disallowed file types
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function render(): array
|
||||
{
|
||||
$result = $this->initializeResultArray();
|
||||
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
$config = $parameterArray['fieldConf']['config'];
|
||||
|
||||
if (!isset($config['allowed']) || !is_string($config['allowed']) || empty($config['allowed'])
|
||||
|| !isset($config['internal_type']) || $config['internal_type'] !== 'file'
|
||||
) {
|
||||
// @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0. Deprecation logged by TcaMigration class.
|
||||
// No handling if the field has no, or funny "allowed" setting, and if internal_type is not "file"
|
||||
return $result;
|
||||
}
|
||||
|
||||
$allowed = GeneralUtility::trimExplode(',', $config['allowed'], true);
|
||||
$disallowed = isset($config['disallowed']) ? GeneralUtility::trimExplode(',', $config['disallowed'], true) : [];
|
||||
|
||||
$allowedHtml = [];
|
||||
foreach ($allowed as $item) {
|
||||
$allowedHtml[] = '<span class="label label-success">' . htmlspecialchars(strtoupper($item)) . '</span> ';
|
||||
}
|
||||
$disallowedHtml = [];
|
||||
foreach ($disallowed as $item) {
|
||||
$disallowedHtml[] = '<span class="label label-danger">' . htmlspecialchars(strtoupper($item)) . '</span> ';
|
||||
}
|
||||
$html = [];
|
||||
if (!empty($allowedHtml) || !empty($disallowedHtml)) {
|
||||
$html[] = '<div class="help-block">';
|
||||
$html[] = implode(LF, $allowedHtml);
|
||||
$html[] = implode(LF, $disallowedHtml);
|
||||
$html[] = '</div>';
|
||||
}
|
||||
|
||||
$result['html'] = implode(LF, $html);
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Form\FieldWizard;
|
||||
|
||||
/*
|
||||
* 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\AbstractNode;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Render file upload,
|
||||
* typically used with type=group and internal_type=file and file_reference.
|
||||
*/
|
||||
class FileUpload extends AbstractNode
|
||||
{
|
||||
/**
|
||||
* Render file upload
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function render(): array
|
||||
{
|
||||
$backendUser = $this->getBackendUserAuthentication();
|
||||
$result = $this->initializeResultArray();
|
||||
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
$itemName = $parameterArray['itemFormElName'];
|
||||
$uploadFieldId = $parameterArray['itemFormElID'] . '_files';
|
||||
$config = $parameterArray['fieldConf']['config'];
|
||||
$maxItems = $config['maxitems'];
|
||||
$isDirectFileUploadEnabled = (bool)$backendUser->uc['edit_docModuleUpload'];
|
||||
|
||||
if (!isset($config['internal_type'])
|
||||
|| $config['internal_type'] !== 'file'
|
||||
|| !$isDirectFileUploadEnabled
|
||||
|| empty($config['uploadfolder'])
|
||||
) {
|
||||
// @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0. Deprecation logged by TcaMigration class.
|
||||
// No upload if disabled for user or upload folder missing
|
||||
return $result;
|
||||
}
|
||||
|
||||
if ($maxItems === 1) {
|
||||
// If maxItems = 1 then automatically replace the current item file selector
|
||||
$resultArray['additionalJavaScriptPost'][] =
|
||||
'TBE_EDITOR.clearBeforeSettingFormValueFromBrowseWin[' . GeneralUtility::quoteJSvalue($itemName) . '] = {'
|
||||
. 'itemFormElID_file: ' . GeneralUtility::quoteJSvalue($uploadFieldId)
|
||||
. '}';
|
||||
}
|
||||
|
||||
// Insert the multiple attribute to enable HTML5 multiple file upload
|
||||
$selectorMultipleAttribute = '';
|
||||
$multipleFilenameSuffix = '';
|
||||
if ($maxItems > 1) {
|
||||
$selectorMultipleAttribute = ' multiple="multiple"';
|
||||
$multipleFilenameSuffix = '[]';
|
||||
}
|
||||
|
||||
$html= [];
|
||||
$html[] = '<div id="' . $uploadFieldId . '">';
|
||||
$html[] = '<input';
|
||||
$html[] = ' type="file"';
|
||||
$html[] = $selectorMultipleAttribute;
|
||||
$html[] = ' name="data_files' . $this->data['elementBaseName'] . $multipleFilenameSuffix . '"';
|
||||
$html[] = ' size="35"';
|
||||
$html[] = ' onchange="' . implode('', $parameterArray['fieldChangeFunc']) . '"';
|
||||
$html[] = '/>';
|
||||
$html[] = '</div>';
|
||||
|
||||
$result['html'] = implode(LF, $html);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUserAuthentication()
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Form\FieldWizard;
|
||||
|
||||
/*
|
||||
* 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\AbstractNode;
|
||||
use TYPO3\CMS\Core\DataHandling\Localization\State;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
|
||||
/**
|
||||
* Allows to define the localization state per field.
|
||||
*/
|
||||
class LocalizationStateSelector extends AbstractNode
|
||||
{
|
||||
/**
|
||||
* Render the radio buttons if enabled
|
||||
*
|
||||
* @return array Result array
|
||||
*/
|
||||
public function render(): array
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
$result = $this->initializeResultArray();
|
||||
|
||||
$fieldName = $this->data['fieldName'];
|
||||
$l10nStateFieldName = '';
|
||||
if (isset($l10nStateFieldName)) {
|
||||
$l10nStateFieldName = 'l10n_state';
|
||||
}
|
||||
if (
|
||||
!$l10nStateFieldName
|
||||
|| !isset($this->data['defaultLanguageRow'])
|
||||
|| !isset($this->data['processedTca']['columns'][$fieldName]['config']['behaviour']['allowLanguageSynchronization'])
|
||||
|| !$this->data['processedTca']['columns'][$fieldName]['config']['behaviour']['allowLanguageSynchronization']
|
||||
) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$l10nParentFieldName = $this->data['processedTca']['ctrl']['transOrigPointerField'] ?? null;
|
||||
$l10nSourceFieldName = $this->data['processedTca']['ctrl']['translationSource'] ?? null;
|
||||
|
||||
$sourceLanguageTitle = '';
|
||||
$fieldValueInParentRow = '';
|
||||
$fieldValueInSourceRow = null;
|
||||
if ($l10nParentFieldName && $this->data['databaseRow'][$l10nParentFieldName] > 0) {
|
||||
if ($l10nSourceFieldName && $this->data['databaseRow'][$l10nSourceFieldName] > 0) {
|
||||
$languageField = $this->data['processedTca']['ctrl']['languageField'] ?? null;
|
||||
if ($languageField
|
||||
&& isset($this->data['sourceLanguageRow'][$languageField])
|
||||
&& $this->data['sourceLanguageRow'][$languageField] > 0
|
||||
) {
|
||||
$languageUidOfSourceRow = $this->data['sourceLanguageRow'][$languageField];
|
||||
$sourceLanguageTitle = $this->data['systemLanguageRows'][$languageUidOfSourceRow]['title'] ?? '';
|
||||
$fieldValueInSourceRow = $this->data['sourceLanguageRow'][$fieldName] ?? null;
|
||||
}
|
||||
}
|
||||
$fieldValueInParentRow = (string)$this->data['defaultLanguageRow'][$fieldName];
|
||||
}
|
||||
|
||||
$localizationState = State::fromJSON(
|
||||
$this->data['tableName'],
|
||||
$this->data['databaseRow'][$l10nStateFieldName] ?? null
|
||||
);
|
||||
|
||||
$fieldElementName = 'data[' . htmlspecialchars($this->data['tableName']) . ']'
|
||||
. '[' . htmlspecialchars((string)$this->data['databaseRow']['uid']) . ']'
|
||||
. '[' . htmlspecialchars($l10nStateFieldName) . ']'
|
||||
. '[' . htmlspecialchars($this->data['fieldName']) . ']';
|
||||
|
||||
$html = [];
|
||||
$html[] = '<div class="t3js-l10n-state-container">';
|
||||
$html[] = '<div>';
|
||||
$html[] = '<strong>';
|
||||
$html[] = $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_wizards.xlf:localizationStateSelector.header');
|
||||
$html[] = '</strong>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '<div class="radio radio-inline">';
|
||||
$html[] = '<label>';
|
||||
$html[] = '<input';
|
||||
$html[] = ' type="radio"';
|
||||
$html[] = ' name="' . htmlspecialchars($fieldElementName) . '"';
|
||||
$html[] = ' class="t3js-l10n-state-custom"';
|
||||
$html[] = ' value="custom"';
|
||||
$html[] = $localizationState->isCustomState($fieldName) ? ' checked="checked"' : '';
|
||||
$html[] = ' data-original-language-value=""';
|
||||
$html[] = '>';
|
||||
$html[] = $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_wizards.xlf:localizationStateSelector.customValue');
|
||||
$html[] = '</label>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '<div class="radio radio-inline">';
|
||||
$html[] = '<label>';
|
||||
$html[] = '<input';
|
||||
$html[] = ' type="radio"';
|
||||
$html[] = ' name="' . htmlspecialchars($fieldElementName) . '"';
|
||||
$html[] = ' value="parent"';
|
||||
$html[] = $localizationState->isParentState($fieldName) ? ' checked="checked"' : '';
|
||||
$html[] = ' data-original-language-value="' . htmlspecialchars((string)$fieldValueInParentRow) . '"';
|
||||
$html[] = '>';
|
||||
$html[] = $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_wizards.xlf:localizationStateSelector.defaultLanguageValue');
|
||||
$html[] = '</label>';
|
||||
$html[] = '</div>';
|
||||
if ($fieldValueInSourceRow !== null) {
|
||||
$html[] = '<div class="radio radio-inline">';
|
||||
$html[] = '<label>';
|
||||
$html[] = '<input';
|
||||
$html[] = ' type="radio"';
|
||||
$html[] = ' name="' . htmlspecialchars($fieldElementName) . '"';
|
||||
$html[] = ' value="source"';
|
||||
$html[] = $localizationState->isSourceState($fieldName) ? ' checked="checked"' : '';
|
||||
$html[] = ' data-original-language-value="' . htmlspecialchars((string)$fieldValueInSourceRow) . '"';
|
||||
$html[] = '>';
|
||||
$html[] = sprintf($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_wizards.xlf:localizationStateSelector.sourceLanguageValue'), htmlspecialchars($sourceLanguageTitle));
|
||||
$html[] = '</label>';
|
||||
$html[] = '</div>';
|
||||
}
|
||||
$html[] = '</div>';
|
||||
|
||||
$result['html'] = implode(LF, $html);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService()
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Form\FieldWizard;
|
||||
|
||||
/*
|
||||
* 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\AbstractNode;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Imaging\Icon;
|
||||
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Render values of "other" languages. If editing a localized row, this is typically
|
||||
* the content value of the according default record, but it may render field values
|
||||
* of other languages too, depending on configuration.
|
||||
*/
|
||||
class OtherLanguageContent extends AbstractNode
|
||||
{
|
||||
/**
|
||||
* Render other language content if enabled.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function render(): array
|
||||
{
|
||||
$result = $this->initializeResultArray();
|
||||
|
||||
$fieldName = $this->data['fieldName'];
|
||||
$fieldConfig = $this->data['processedTca']['columns'][$fieldName];
|
||||
$l10nDisplay = $this->data['parameterArray']['fieldConf']['l10n_display'] ?? '';
|
||||
$defaultLanguageRow = $this->data['defaultLanguageRow'] ?? null;
|
||||
if (!is_array($defaultLanguageRow)
|
||||
|| GeneralUtility::inList($l10nDisplay, 'hideDiff')
|
||||
|| GeneralUtility::inList($l10nDisplay, 'defaultAsReadonly')
|
||||
|| $fieldConfig['config']['type'] === 'inline'
|
||||
|| $fieldConfig['config']['type'] === 'flex'
|
||||
|| ($fieldConfig['config']['type'] === 'group' && isset($fieldConfig['config']['MM']))
|
||||
|| ($fieldConfig['config']['type'] === 'select' && isset($fieldConfig['config']['MM']))
|
||||
) {
|
||||
// Early return if there is no default language row or the display is disabled
|
||||
return $result;
|
||||
}
|
||||
|
||||
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
|
||||
$table = $this->data['tableName'];
|
||||
$html = [];
|
||||
$defaultLanguageValue = BackendUtility::getProcessedValue(
|
||||
$table,
|
||||
$fieldName,
|
||||
$defaultLanguageRow[$fieldName],
|
||||
0,
|
||||
true,
|
||||
false,
|
||||
$defaultLanguageRow['uid'],
|
||||
true,
|
||||
$defaultLanguageRow['pid']
|
||||
);
|
||||
if ($defaultLanguageValue !== '') {
|
||||
$iconIdentifier = $this->data['systemLanguageRows'][0]['flagIconIdentifier'] ?: 'flags-multiple';
|
||||
$html[] = '<div class="t3-form-original-language">';
|
||||
$html[] = $iconFactory->getIcon($iconIdentifier, Icon::SIZE_SMALL)->render();
|
||||
$html[] = $this->previewFieldValue($defaultLanguageValue, $fieldConfig, $fieldName);
|
||||
$html[] = '</div>';
|
||||
}
|
||||
$additionalPreviewLanguages = $this->data['additionalLanguageRows'];
|
||||
foreach ($additionalPreviewLanguages as $previewLanguage) {
|
||||
$defaultLanguageValue = BackendUtility::getProcessedValue(
|
||||
$table,
|
||||
$fieldName,
|
||||
$previewLanguage[$fieldName],
|
||||
0,
|
||||
true
|
||||
);
|
||||
if ($defaultLanguageValue !== '') {
|
||||
$html[] = '<div class="t3-form-original-language">';
|
||||
$html[] = $iconFactory->getIcon($this->data['systemLanguageRows'][$previewLanguage['sys_language_uid']]['flagIconIdentifier'], Icon::SIZE_SMALL)->render();
|
||||
$html[] = $this->previewFieldValue($defaultLanguageValue, $fieldConfig, $fieldName);
|
||||
$html[] = '</div>';
|
||||
}
|
||||
}
|
||||
$result['html'] = implode(LF, $html);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendering preview output of a field value which is not shown as a form field but just outputted.
|
||||
*
|
||||
* @param string $value The value to output
|
||||
* @param array $config Configuration for field.
|
||||
* @param string $field Name of field.
|
||||
* @return string HTML formatted output
|
||||
*/
|
||||
protected function previewFieldValue($value, $config, $field = '')
|
||||
{
|
||||
// @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0. Deprecation logged by TcaMigration class.
|
||||
$value = (string)$value;
|
||||
if ($config['config']['type'] === 'group'
|
||||
&& ($config['config']['internal_type'] === 'file' || $config['config']['internal_type'] === 'file_reference')
|
||||
) {
|
||||
// Ignore upload folder if internal_type is file_reference
|
||||
if ($config['config']['internal_type'] === 'file_reference') {
|
||||
$config['config']['uploadfolder'] = '';
|
||||
}
|
||||
$table = 'tt_content';
|
||||
// Making the array of file items:
|
||||
$itemArray = GeneralUtility::trimExplode(',', $value, true);
|
||||
// Showing thumbnails:
|
||||
$imgs = [];
|
||||
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
|
||||
foreach ($itemArray as $imgRead) {
|
||||
$imgParts = explode('|', $imgRead);
|
||||
$imgPath = rawurldecode($imgParts[0]);
|
||||
$rowCopy = [];
|
||||
$rowCopy[$field] = $imgPath;
|
||||
// Icon + click menu:
|
||||
$absFilePath = GeneralUtility::getFileAbsFileName($config['config']['uploadfolder'] ? $config['config']['uploadfolder'] . '/' . $imgPath : $imgPath);
|
||||
$fileInformation = pathinfo($imgPath);
|
||||
$title = $fileInformation['basename'] . ($absFilePath && @is_file($absFilePath))
|
||||
? ' (' . GeneralUtility::formatSize(filesize($absFilePath)) . ')'
|
||||
: ' - FILE NOT FOUND!';
|
||||
$fileIcon = '<span title="' . htmlspecialchars($title) . '">' . $iconFactory->getIconForFileExtension($fileInformation['extension'], Icon::SIZE_SMALL)->render() . '</span>';
|
||||
$imgs[] =
|
||||
'<span class="text-nowrap">' .
|
||||
BackendUtility::thumbCode(
|
||||
$rowCopy,
|
||||
$table,
|
||||
$field,
|
||||
'',
|
||||
'',
|
||||
$config['config']['uploadfolder'],
|
||||
0,
|
||||
' align="middle"'
|
||||
) .
|
||||
($absFilePath ? BackendUtility::wrapClickMenuOnIcon($fileIcon, $absFilePath) : $fileIcon) .
|
||||
$imgPath .
|
||||
'</span>';
|
||||
}
|
||||
return implode('<br />', $imgs);
|
||||
}
|
||||
return nl2br(htmlspecialchars($value));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Form\FieldWizard;
|
||||
|
||||
/*
|
||||
* 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\AbstractNode;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Imaging\Icon;
|
||||
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Render details of selected records,
|
||||
* typically used with type=group and internal_type=db.
|
||||
*/
|
||||
class RecordsOverview extends AbstractNode
|
||||
{
|
||||
/**
|
||||
* Render table with record details
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function render(): array
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
$backendUser = $this->getBackendUserAuthentication();
|
||||
$result = $this->initializeResultArray();
|
||||
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
$config = $parameterArray['fieldConf']['config'];
|
||||
$selectedItems = $parameterArray['itemFormElValue'];
|
||||
$maxTitleLength = $backendUser->uc['titleLen'];
|
||||
|
||||
if (!isset($config['internal_type']) || $config['internal_type'] !== 'db') {
|
||||
// Table list makes sense on db only
|
||||
return $result;
|
||||
}
|
||||
|
||||
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
|
||||
$recordsOverviewHtml = [];
|
||||
foreach ($selectedItems as $selectedItem) {
|
||||
$title = (string)$selectedItem['title'];
|
||||
if (empty($title)) {
|
||||
$title = '[' . $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.no_title') . ']';
|
||||
}
|
||||
$shortenedTitle = GeneralUtility::fixed_lgd_cs($title, $maxTitleLength);
|
||||
$linkedIcon = BackendUtility::wrapClickMenuOnIcon(
|
||||
$iconFactory->getIconForRecord($selectedItem['table'], $selectedItem['row'], Icon::SIZE_SMALL)->render(),
|
||||
$selectedItem['table'],
|
||||
$selectedItem['uid']
|
||||
);
|
||||
$linkedTitle = BackendUtility::wrapClickMenuOnIcon(
|
||||
htmlspecialchars($shortenedTitle),
|
||||
$selectedItem['table'],
|
||||
$selectedItem['uid']
|
||||
);
|
||||
$recordsOverviewHtml[] = '<tr>';
|
||||
$recordsOverviewHtml[] = '<td class="col-icon">';
|
||||
$recordsOverviewHtml[] = $linkedIcon;
|
||||
$recordsOverviewHtml[] = '</td>';
|
||||
$recordsOverviewHtml[] = '<td class="col-title">';
|
||||
$recordsOverviewHtml[] = $linkedTitle;
|
||||
$recordsOverviewHtml[] = '<span class="text-muted">';
|
||||
$recordsOverviewHtml[] = ' [' . $selectedItem['uid'] . ']';
|
||||
$recordsOverviewHtml[] = '</span>';
|
||||
$recordsOverviewHtml[] = '</td>';
|
||||
$recordsOverviewHtml[] = '</tr>';
|
||||
}
|
||||
|
||||
$html = [];
|
||||
if (!empty($recordsOverviewHtml)) {
|
||||
$html[] = '<div class="table-fit">';
|
||||
$html[] = '<table class="table table-white">';
|
||||
$html[] = '<tbody>';
|
||||
$html[] = implode(LF, $recordsOverviewHtml);
|
||||
$html[] = '</tbody>';
|
||||
$html[] = '</table>';
|
||||
$html[] = '</div>';
|
||||
}
|
||||
|
||||
$result['html'] = implode(LF, $html);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUserAuthentication()
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService()
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Form\FieldWizard;
|
||||
|
||||
/*
|
||||
* 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\AbstractNode;
|
||||
use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
|
||||
|
||||
/**
|
||||
* Render thumbnails of icons,
|
||||
* typically used with type=select.
|
||||
*/
|
||||
class SelectIcons extends AbstractNode
|
||||
{
|
||||
/**
|
||||
* Render thumbnails of selected files
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function render(): array
|
||||
{
|
||||
$result = $this->initializeResultArray();
|
||||
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
$selectItems = $parameterArray['fieldConf']['config']['items'];
|
||||
|
||||
$selectItemCounter = 0;
|
||||
foreach ($selectItems as $item) {
|
||||
if ($item[1] === '--div--') {
|
||||
continue;
|
||||
}
|
||||
$icon = !empty($item[2]) ? FormEngineUtility::getIconHtml($item[2], $item[0], $item[0]) : '';
|
||||
if ($icon) {
|
||||
$fieldValue = $this->data['databaseRow'][$this->data['fieldName']];
|
||||
$selectIcons[] = [
|
||||
'title' => $item[0],
|
||||
'active' => ($fieldValue[0] === (string)$item[1]) ? true : false,
|
||||
'icon' => $icon,
|
||||
'index' => $selectItemCounter,
|
||||
];
|
||||
}
|
||||
$selectItemCounter++;
|
||||
}
|
||||
|
||||
$html = [];
|
||||
if (!empty($selectIcons)) {
|
||||
$html[] = '<div class="t3js-forms-select-single-icons icon-list">';
|
||||
$html[] = '<div class="row">';
|
||||
foreach ($selectIcons as $i => $selectIcon) {
|
||||
$active = $selectIcon['active'] ? ' active' : '';
|
||||
$html[] = '<div class="item' . $active . '">';
|
||||
if (is_array($selectIcon)) {
|
||||
$html[] = '<a href="#" title="' . htmlspecialchars($selectIcon['title'], ENT_COMPAT, 'UTF-8', false) . '" data-select-index="' . htmlspecialchars((string)$selectIcon['index']) . '">';
|
||||
$html[] = $selectIcon['icon'];
|
||||
$html[] = '</a>';
|
||||
}
|
||||
$html[] = '</div>';
|
||||
}
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
}
|
||||
|
||||
$result['html'] = implode(LF, $html);
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Form\FieldWizard;
|
||||
|
||||
/*
|
||||
* 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\AbstractNode;
|
||||
use TYPO3\CMS\Core\Imaging\Icon;
|
||||
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Render list of tables and link to element browser,
|
||||
* typically used with type=group and internal_type=db.
|
||||
*/
|
||||
class TableList extends AbstractNode
|
||||
{
|
||||
/**
|
||||
* Render table buttons
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function render(): array
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
$result = $this->initializeResultArray();
|
||||
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
$config = $parameterArray['fieldConf']['config'];
|
||||
$itemName = $parameterArray['itemFormElName'];
|
||||
|
||||
if (!isset($config['allowed']) || !is_string($config['allowed']) || empty($config['allowed'])
|
||||
|| !isset($config['internal_type']) || $config['internal_type'] !== 'db'
|
||||
) {
|
||||
// No handling if the field has no, or funny "allowed" setting, and if internal_type is not "db"
|
||||
return $result;
|
||||
}
|
||||
|
||||
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
|
||||
$allowed = GeneralUtility::trimExplode(',', $config['allowed'], true);
|
||||
$allowedTablesHtml = [];
|
||||
foreach ($allowed as $tableName) {
|
||||
if ($tableName === '*') {
|
||||
$label = $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.allTables');
|
||||
$allowedTablesHtml[] = '<span>';
|
||||
$allowedTablesHtml[] = htmlspecialchars($label);
|
||||
$allowedTablesHtml[] = '</span>';
|
||||
} else {
|
||||
$label = $languageService->sL($GLOBALS['TCA'][$tableName]['ctrl']['title']);
|
||||
$icon = $iconFactory->getIconForRecord($tableName, [], Icon::SIZE_SMALL)->render();
|
||||
if ((bool)($config['fieldControl']['elementBrowser']['disabled'] ?? false)) {
|
||||
$allowedTablesHtml[] = '<span class="tablelist-item-nolink">';
|
||||
$allowedTablesHtml[] = $icon;
|
||||
$allowedTablesHtml[] = htmlspecialchars($label);
|
||||
$allowedTablesHtml[] = '</span>';
|
||||
} else {
|
||||
$onClick = [];
|
||||
$onClick[] = 'setFormValueOpenBrowser(';
|
||||
$onClick[] = '\'db\',';
|
||||
$onClick[] = GeneralUtility::quoteJSvalue($itemName . '|||' . $tableName);
|
||||
$onClick[] = ');';
|
||||
$onClick[] = 'return false;';
|
||||
$allowedTablesHtml[] = '<a href="#" onClick="' . htmlspecialchars(implode('', $onClick)) . '" class="btn btn-default">';
|
||||
$allowedTablesHtml[] = $icon;
|
||||
$allowedTablesHtml[] = htmlspecialchars($label);
|
||||
$allowedTablesHtml[] = '</a>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$html = [];
|
||||
$html[] = '<div class="help-block">';
|
||||
$html[] = implode(LF, $allowedTablesHtml);
|
||||
$html[] = '</div>';
|
||||
|
||||
$result['html'] = implode(LF, $html);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService()
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,322 @@
|
||||
<?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!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
|
||||
/**
|
||||
* Create and return a defined array of data ready to be used by the
|
||||
* container / element render part of FormEngine
|
||||
*/
|
||||
class FormDataCompiler
|
||||
{
|
||||
/**
|
||||
* Data group that provides data
|
||||
*
|
||||
* @var FormDataGroupInterface
|
||||
*/
|
||||
protected $formDataGroup;
|
||||
|
||||
/**
|
||||
* List of top level array elements to be unset from
|
||||
* result array before final result is returned.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $removeKeysFromFinalResultArray = [
|
||||
];
|
||||
|
||||
/**
|
||||
* Get form data group injected
|
||||
*
|
||||
* @param FormDataGroupInterface $formDataGroup
|
||||
*/
|
||||
public function __construct(FormDataGroupInterface $formDataGroup)
|
||||
{
|
||||
$this->formDataGroup = $formDataGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main entry method maps given data input array and sanitizes some
|
||||
* crucial input parameters and calls compile on FormDataGroupInterface.
|
||||
*
|
||||
* @param array $initialData Initial set of data to map into result array
|
||||
* @return array Result with data
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \UnexpectedValueException
|
||||
*/
|
||||
public function compile(array $initialData)
|
||||
{
|
||||
$result = $this->initializeResultArray();
|
||||
|
||||
// There must be only keys that actually exist in result data.
|
||||
$keysNotInResult = array_diff(array_keys($initialData), array_keys($result));
|
||||
if (!empty($keysNotInResult)) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Array keys ' . implode(',', $keysNotInResult) . ' do not exist in result array and can not be set',
|
||||
1440601540
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($initialData as $dataKey => $dataValue) {
|
||||
if ($dataKey === 'command') {
|
||||
// Sanitize $command
|
||||
if ($dataValue !== 'edit' && $dataValue !== 'new') {
|
||||
throw new \InvalidArgumentException('Command must be either "edit" or "new"', 1437653136);
|
||||
}
|
||||
}
|
||||
if ($dataKey === 'tableName') {
|
||||
// Sanitize $tableName
|
||||
if (empty($dataValue)) {
|
||||
throw new \InvalidArgumentException('No $tableName given', 1437654409);
|
||||
}
|
||||
}
|
||||
if ($dataKey === 'vanillaUid') {
|
||||
if (!MathUtility::canBeInterpretedAsInteger($dataValue) && strpos($dataValue, 'NEW') !== 0) {
|
||||
throw new \InvalidArgumentException('$vanillaUid is not an integer or "NEW..." string ID', 1437654247);
|
||||
}
|
||||
if (isset($initialData['command']) && $initialData['command'] === 'edit' && $dataValue < 0) {
|
||||
throw new \InvalidArgumentException('Negative $vanillaUid is not supported with $command="edit', 1437654332);
|
||||
}
|
||||
}
|
||||
$result[$dataKey] = $dataValue;
|
||||
}
|
||||
|
||||
// Call the data group provider but take care it does not add or remove result keys
|
||||
// This is basically a safety measure against data providers colliding with our array "contract"
|
||||
$resultKeysBeforeFormDataGroup = array_keys($result);
|
||||
|
||||
$result = $this->formDataGroup->compile($result);
|
||||
|
||||
if (!is_array($result)) {
|
||||
throw new \UnexpectedValueException(
|
||||
'Data group provider must return array',
|
||||
1446664764
|
||||
);
|
||||
}
|
||||
|
||||
if (!empty($result['renderData'])) {
|
||||
throw new \RuntimeException(
|
||||
'Array \'renderData\' not empty. Data providers must not add data here',
|
||||
1485201279
|
||||
);
|
||||
}
|
||||
|
||||
$resultKeysAfterFormDataGroup = array_keys($result);
|
||||
|
||||
if ($resultKeysAfterFormDataGroup !== $resultKeysBeforeFormDataGroup) {
|
||||
throw new \UnexpectedValueException(
|
||||
'Data group provider must not change result key list',
|
||||
1438079402
|
||||
);
|
||||
}
|
||||
|
||||
// Remove some data elements form result that are data provider internal and should
|
||||
// not be exposed to calling object.
|
||||
foreach ($this->removeKeysFromFinalResultArray as $key) {
|
||||
unset($result[$key]);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function initializeResultArray()
|
||||
{
|
||||
return [
|
||||
// Either "edit" or "new"
|
||||
'command' => '',
|
||||
// Table name of the handled row
|
||||
'tableName' => '',
|
||||
// Not changed uid of the record, meaning of value depends on context (new / edit)
|
||||
// * If $command is "edit"
|
||||
// ** $vanillaUid is a positive integer > 0 pointing to the record in the table
|
||||
// * If $command is "new":
|
||||
// ** If $vanillaUid > 0, it is the uid of a page the record should be added at
|
||||
// ** If $vanillaUid < 0, it is the uid of a record in the same table after which the new record should be added
|
||||
// ** If $vanillaUid = 0, a new record is added on page 0
|
||||
// ** If $vanillaUid = "NEW..." Id of a parent page record if an inline child is added to a not yet persisted page
|
||||
'vanillaUid' => 0,
|
||||
// Url to return to
|
||||
'returnUrl' => null,
|
||||
// Title of the handled record.
|
||||
'recordTitle' => '',
|
||||
// Parent page record is either the full row of the parent page the record is located at or should
|
||||
// be added to, or it is NULL, if a record is added or edited below the root page node.
|
||||
'parentPageRow' => null,
|
||||
// If a translated page is handled, the page row of the default language (the page against all page checks
|
||||
// are made) is set here
|
||||
'defaultLanguagePageRow' => null,
|
||||
// Holds the "neighbor" row if incoming vanillaUid is negative and record creation is relative to a row of the same table.
|
||||
'neighborRow' => null,
|
||||
// For "new" this is the fully initialized row with defaults
|
||||
// The database row. For "edit" fixVersioningPid() was applied already.
|
||||
// @todo: rename to valueStructure or handledData or similar
|
||||
'databaseRow' => [],
|
||||
// The "effective" page uid we're working on. This is the uid of a page if a page is edited, or the uid
|
||||
// of the parent page if a page or other record is added, or 0 if a record is added or edited below root node.
|
||||
'effectivePid' => 0,
|
||||
// Rootline of page the record that is handled is located at as created by BackendUtility::BEgetRootline()
|
||||
'rootline' => [],
|
||||
// The resolved SiteInterface object of the page or page given record is located on.
|
||||
'site' => null,
|
||||
// For "edit", this is the permission bitmask of the page that is edited, or of the page a record is located at
|
||||
// For "new", this is the permission bitmask of the page the record is added to
|
||||
// @todo: Remove if not needed on a lower level
|
||||
'userPermissionOnPage' => 0,
|
||||
// Full user TsConfig
|
||||
'userTsConfig' => [],
|
||||
// Full page TSConfig of the page that is edited or of the parent page if a record is added.
|
||||
// This includes any defaultPageTSconfig and is merged with user TsConfig page. section. After type
|
||||
// of handled record was determined, record type specific settings [TCEFORM.][tableName.][field.][types.][type.]
|
||||
// are merged into [TCEFORM.][tableName.][field.]. Array keys still contain the concatenation dots.
|
||||
'pageTsConfig' => [],
|
||||
// Not changed TCA of parent page row if record is edited or added below a page and not root node
|
||||
'vanillaParentPageTca' => null,
|
||||
// List of available system languages. Array key is the system language uid, value array
|
||||
// contains details of the record, with iso code resolved. Key is the sys_language_uid uid.
|
||||
'systemLanguageRows' => [],
|
||||
// If the page that is handled has "page_language_overlay" records (page has localizations in
|
||||
// different languages), then this array holds those rows.
|
||||
'pageLanguageOverlayRows' => [],
|
||||
// If the handled row is a localized row, this entry hold the default language row array
|
||||
'defaultLanguageRow' => null,
|
||||
// If the handled row is a localived row and $TCA[<tableName>]['ctrl']['translationSource'] is configured,
|
||||
// This entry holds the row of the language source record.
|
||||
'sourceLanguageRow' => null,
|
||||
// If the handled row is a localized row and a transOrigDiffSourceField is defined, this
|
||||
// is the unserialized version of it. The diff source field is basically a shadow version
|
||||
// of the default language record at the time when the language overlay record was created.
|
||||
// This is used later to compare the default record with this content to show a "diff" if
|
||||
// the default language record changed meanwhile.
|
||||
'defaultLanguageDiffRow' => null,
|
||||
// With userTS options.additionalPreviewLanguages set, field values of additional languages
|
||||
// can be shown. This array holds those additional language records, Array key is sys_language_uid.
|
||||
'additionalLanguageRows' => [],
|
||||
// The tca record type value of the record. Forced to string, there can be "named" type values.
|
||||
'recordTypeValue' => '',
|
||||
// TCA of table with processed fields. After processing, this array contains merged and resolved
|
||||
// array data, items were resolved, only used types are set, renderTypes are set.
|
||||
'processedTca' => [],
|
||||
// List of columns to be processed by data provider. Array value is the column name.
|
||||
'columnsToProcess' => [],
|
||||
// If set to TRUE, no wizards are calculated and rendered later
|
||||
'disabledWizards' => false,
|
||||
|
||||
// Flex form field data handling is done in a separated FormDataCompiler instance. The full databaseRow
|
||||
// of the record this flex form is embedded in is transferred in case features like single fields
|
||||
// itemsProcFunc need to have this data at hand to do their job.
|
||||
'flexParentDatabaseRow' => [],
|
||||
// If not empty, it tells the TcaFlexProcess data provider to not calculate existing flex fields and
|
||||
// existing flex container sections, but to instead prepare field values and the data structure TCA
|
||||
// for a new container section. This is used by FormFlexAjaxController, the array contains details
|
||||
// which container of which flex field should be created.
|
||||
'flexSectionContainerPreparation' => [],
|
||||
|
||||
// If true, TcaSelectTreeItems data provider will compile tree items. This is false by default since
|
||||
// on opening a record items are not calculated but are fetch in an ajax request, see FormSelectTreeAjaxController.
|
||||
'selectTreeCompileItems' => false,
|
||||
|
||||
// BackendUser->uc['inlineView'] - This array holds status of expand / collapsed inline items
|
||||
// This array is "flat", an inline structure with parent uid 1 having firstChild uid 2 having secondChild uid 3
|
||||
// firstChild and secondChild are not nested. If an uid is set it means "record is expanded", example:
|
||||
// 'parent' => [
|
||||
// 1 => [
|
||||
// 'firstChild' => [ 2 ], // record 2 of firstChild table is open in inline context to parent 1
|
||||
// 'secondChild' => [ 3 ], // record 3 of secondChild table is open in inline context to parent 1
|
||||
// ],
|
||||
// ]
|
||||
'inlineExpandCollapseStateArray' => [],
|
||||
// The "entry" pid for inline records. Nested inline records can potentially hang around on different
|
||||
// pid's, but the entry pid is needed for AJAX calls, so that they would know where the action takes
|
||||
// place on the page structure. This is usually an int, but can be a "NEW..." string if an inline relation
|
||||
// is added to a currently being created page.
|
||||
'inlineFirstPid' => null,
|
||||
// The "config" section of an inline parent, prepared and sanitized by TcaInlineConfiguration provider
|
||||
'inlineParentConfig' => [],
|
||||
// Flag that is enabled if a records is child of an inline parent
|
||||
'isInlineChild' => false,
|
||||
// Flag if an inline child is expanded so that additional fields need to be rendered
|
||||
'isInlineChildExpanded' => false,
|
||||
// Flag if the inline is in an ajax context that wants to expand the element
|
||||
'isInlineAjaxOpeningContext' => false,
|
||||
// Uid of the direct parent of the inline element. Handled as string since it may be a "NEW123" string
|
||||
'inlineParentUid' => '',
|
||||
// Table name of the direct parent of the inline element
|
||||
'inlineParentTableName' => '',
|
||||
// Field name of the direct parent of the inline element
|
||||
'inlineParentFieldName' => '',
|
||||
// Uid of the top most parent element. Handled as string since it may be a "NEW123" string
|
||||
'inlineTopMostParentUid' => '',
|
||||
// Table name of the top most parent element
|
||||
'inlineTopMostParentTableName' => '',
|
||||
// Field name of the top most parent element
|
||||
'inlineTopMostParentFieldName' => '',
|
||||
|
||||
// If is on symetric side of an inline child parent reference.
|
||||
// symmetric side can be achieved in case of an mm relation to the same table. If record A has a relation
|
||||
// to record B, the symmetric side is set in case that record B gets edited.
|
||||
// Record A (table1) <=> mm <=> Record B (table1)
|
||||
'isOnSymmetricSide' => false,
|
||||
|
||||
// Uid of a "child-child" if a new record of an intermediate table is compiled to an existing child. This
|
||||
// happens if foreign_selector in parent inline config is set. It will be used by default database row
|
||||
// data providers to set this as value for the foreign_selector field on the intermediate table. One use
|
||||
// case is FAL, where for instance a tt_content parent adds relation to an existing sys_file record and
|
||||
// should set the uid of the existing sys_file record as uid_local - the foreign_selector of this inline
|
||||
// configuration - of the new intermediate sys_file_reference record. Data provider that are called later
|
||||
// will then use this relation to resolve for instance input placeholder relation values.
|
||||
'inlineChildChildUid' => null,
|
||||
// Inline scenario: A localized parent record is handled, so inline parents can have localized children.
|
||||
// This value is set to TRUE if this array represents a default language
|
||||
// child record that was not yet localized.
|
||||
'isInlineDefaultLanguageRecordInLocalizedParentContext' => false,
|
||||
// If set, inline children will be resolved. This is set to FALSE in inline ajax context where new children
|
||||
// are created and existing children don't matter much.
|
||||
'inlineResolveExistingChildren' => true,
|
||||
// @todo - for input placeholder inline to suppress an infinite loop, this *may* become obsolete if
|
||||
// @todo compilation of certain fields is possible
|
||||
'inlineCompileExistingChildren' => true,
|
||||
|
||||
// @todo: keys below must be handled / further defined
|
||||
'elementBaseName' => '',
|
||||
'tabAndInlineStack' => [],
|
||||
'inlineData' => [],
|
||||
'inlineStructure' => [],
|
||||
|
||||
// This array of fields will be set as hidden-fields instead of rendered normally!
|
||||
// This is used by EditDocumentController to force some field values if set as "overrideVals" in _GP
|
||||
'overrideValues' => [],
|
||||
// Default values for fields. This is for example used in DatabaseRowInitializeNew data provider to set
|
||||
// fields to specific values if new records are created. Values are often carried around as "defVals" GET
|
||||
// parameters and hand over by controllers to FormEngine using this array. Array structure is
|
||||
// ['aTableName']['aFieldName'] = 'aValue'.
|
||||
'defaultValues' => [],
|
||||
|
||||
// This array must NOT be set / manipulated by data providers but is instead used by the render part
|
||||
// of FormEngine to add runtime data. Containers and elements add data here which is given to
|
||||
// sub-containers, elements, controls and wizards.
|
||||
'renderData' => [],
|
||||
|
||||
// A place for non-core, additional, custom data providers to add data. If a data provider needs to add
|
||||
// additional data to the data array that doesn't fit elsewhere, it can place it here to use it in the
|
||||
// render part again. Data in here should be namespaced in a way that it does not collide with other
|
||||
// data providers adding further data here. Using the extension key as array key could be a good idea.
|
||||
'customData' => [],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\FormDataGroup;
|
||||
|
||||
/*
|
||||
* 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\FormDataGroupInterface;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* A data provider group for flex form elements
|
||||
*/
|
||||
class FlexFormSegment implements FormDataGroupInterface
|
||||
{
|
||||
/**
|
||||
* Compile form data
|
||||
*
|
||||
* @param array $result Initialized result array
|
||||
* @return array Result filled with data
|
||||
* @throws \UnexpectedValueException
|
||||
*/
|
||||
public function compile(array $result)
|
||||
{
|
||||
$orderedProviderList = GeneralUtility::makeInstance(OrderedProviderList::class);
|
||||
$orderedProviderList->setProviderList(
|
||||
$GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['formDataGroup']['flexFormSegment']
|
||||
);
|
||||
|
||||
return $orderedProviderList->compile($result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\FormDataGroup;
|
||||
|
||||
/*
|
||||
* 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\FormDataGroupInterface;
|
||||
use TYPO3\CMS\Backend\Form\FormDataProviderInterface;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Calls provider given by setter. This group is used to call a hard coded list of providers
|
||||
* in some situations where extendability is not wanted. Use with care if at all ...
|
||||
*/
|
||||
class OnTheFly implements FormDataGroupInterface
|
||||
{
|
||||
/**
|
||||
* @var array<FormDataProviderInterface>
|
||||
*/
|
||||
protected $providerList = [];
|
||||
|
||||
/**
|
||||
* Compile form data
|
||||
*
|
||||
* @param array $result Initialized result array
|
||||
* @return array Result filled with data
|
||||
* @throws \UnexpectedValueException
|
||||
*/
|
||||
public function compile(array $result)
|
||||
{
|
||||
if (empty($this->providerList)) {
|
||||
throw new \UnexpectedValueException(
|
||||
'Data provider list is empty, call setProviderList first',
|
||||
1441108674
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($this->providerList as $providerClassName) {
|
||||
/** @var FormDataProviderInterface $provider */
|
||||
$provider = GeneralUtility::makeInstance($providerClassName);
|
||||
|
||||
if (!$provider instanceof FormDataProviderInterface) {
|
||||
throw new \UnexpectedValueException(
|
||||
'Data provider ' . $providerClassName . ' must implement FormDataProviderInterface',
|
||||
1441108719
|
||||
);
|
||||
}
|
||||
|
||||
$result = $provider->addData($result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set list of providers to be called
|
||||
*
|
||||
* @param array $list Given list of Provider class names
|
||||
*/
|
||||
public function setProviderList(array $list)
|
||||
{
|
||||
$this->providerList = $list;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Form\FormDataGroup;
|
||||
|
||||
/*
|
||||
* 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\FormDataGroupInterface;
|
||||
use TYPO3\CMS\Backend\Form\FormDataProviderInterface;
|
||||
use TYPO3\CMS\Core\Service\DependencyOrderingService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Calls provider with dependencies specified given by setter
|
||||
*
|
||||
* This group is used to call a list of providers in order by specified
|
||||
* dependencies before/depends.
|
||||
*/
|
||||
class OrderedProviderList implements FormDataGroupInterface
|
||||
{
|
||||
/**
|
||||
* @var FormDataProviderInterface[]
|
||||
*/
|
||||
protected $providerList = [];
|
||||
|
||||
/**
|
||||
* Compile form data
|
||||
*
|
||||
* @param array $result Initialized result array
|
||||
* @return array Result filled with data
|
||||
* @throws \UnexpectedValueException
|
||||
*/
|
||||
public function compile(array $result): array
|
||||
{
|
||||
$orderingService = GeneralUtility::makeInstance(DependencyOrderingService::class);
|
||||
$orderedDataProvider = $orderingService->orderByDependencies($this->providerList, 'before', 'depends');
|
||||
|
||||
foreach ($orderedDataProvider as $providerClassName => $providerConfig) {
|
||||
if (isset($providerConfig['disabled']) && $providerConfig['disabled'] === true) {
|
||||
// Skip this data provider if disabled by configuration
|
||||
continue;
|
||||
}
|
||||
|
||||
/** @var FormDataProviderInterface $provider */
|
||||
$provider = GeneralUtility::makeInstance($providerClassName);
|
||||
|
||||
if (!$provider instanceof FormDataProviderInterface) {
|
||||
throw new \UnexpectedValueException(
|
||||
'Data provider ' . $providerClassName . ' must implement FormDataProviderInterface',
|
||||
1485299408
|
||||
);
|
||||
}
|
||||
|
||||
$result = $provider->addData($result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set list of providers to be called
|
||||
*
|
||||
* The dependencies of a provider are specified as:
|
||||
*
|
||||
* FormDataProvider::class => [
|
||||
* 'before' => [AnotherFormDataProvider::class]
|
||||
* 'depends' => [YetAnotherFormDataProvider::class]
|
||||
* ]
|
||||
*
|
||||
* @param array $list Given list of Provider class names
|
||||
* @see \TYPO3\CMS\Core\Service\DependencyOrderingService
|
||||
*/
|
||||
public function setProviderList(array $list)
|
||||
{
|
||||
$this->providerList = $list;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Form\FormDataGroup;
|
||||
|
||||
/*
|
||||
* 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\FormDataGroupInterface;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* A data provider group for site and language configuration.
|
||||
*
|
||||
* This data group is for data fetched from sites yml files,
|
||||
* it is fed by "fake TCA" since there are no real db records.
|
||||
*
|
||||
* It's similar to "fullDatabaseRecord", with some unused TCA types
|
||||
* kicked out and some own data providers for record data and inline handling.
|
||||
*/
|
||||
class SiteConfigurationDataGroup implements FormDataGroupInterface
|
||||
{
|
||||
/**
|
||||
* Compile form data
|
||||
*
|
||||
* @param array $result Initialized result array
|
||||
* @return array Result filled with data
|
||||
* @throws \UnexpectedValueException
|
||||
*/
|
||||
public function compile(array $result)
|
||||
{
|
||||
$orderedProviderList = GeneralUtility::makeInstance(OrderedProviderList::class);
|
||||
$orderedProviderList->setProviderList(
|
||||
$GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['formDataGroup']['siteConfiguration']
|
||||
);
|
||||
return $orderedProviderList->compile($result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\FormDataGroup;
|
||||
|
||||
/*
|
||||
* 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\FormDataGroupInterface;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* A data provider group for casual database records
|
||||
*/
|
||||
class TcaDatabaseRecord implements FormDataGroupInterface
|
||||
{
|
||||
/**
|
||||
* Compile form data
|
||||
*
|
||||
* @param array $result Initialized result array
|
||||
* @return array Result filled with data
|
||||
* @throws \UnexpectedValueException
|
||||
*/
|
||||
public function compile(array $result)
|
||||
{
|
||||
$orderedProviderList = GeneralUtility::makeInstance(OrderedProviderList::class);
|
||||
$orderedProviderList->setProviderList(
|
||||
$GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['formDataGroup']['tcaDatabaseRecord']
|
||||
);
|
||||
|
||||
return $orderedProviderList->compile($result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\FormDataGroup;
|
||||
|
||||
/*
|
||||
* 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\FormDataGroupInterface;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* A data provider group for database records used for input placeholder values
|
||||
*/
|
||||
class TcaInputPlaceholderRecord implements FormDataGroupInterface
|
||||
{
|
||||
/**
|
||||
* Compile form data
|
||||
*
|
||||
* @param array $result Initialized result array
|
||||
* @return array Result filled with data
|
||||
* @throws \UnexpectedValueException
|
||||
*/
|
||||
public function compile(array $result)
|
||||
{
|
||||
$orderedProviderList = GeneralUtility::makeInstance(OrderedProviderList::class);
|
||||
$orderedProviderList->setProviderList(
|
||||
$GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['formDataGroup']['tcaInputPlaceholderRecord']
|
||||
);
|
||||
|
||||
return $orderedProviderList->compile($result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Form\FormDataGroup;
|
||||
|
||||
/*
|
||||
* 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\FormDataGroupInterface;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* A data provider group dedicated for the type='select', renderType='selectTree'
|
||||
* calculate tree items FormSelectTreeAjaxController.
|
||||
*/
|
||||
class TcaSelectTreeAjaxFieldData implements FormDataGroupInterface
|
||||
{
|
||||
/**
|
||||
* Compile TCA tree items
|
||||
*
|
||||
* @param array $result Initialized result array
|
||||
* @return array Result filled with data
|
||||
* @throws \UnexpectedValueException
|
||||
*/
|
||||
public function compile(array $result)
|
||||
{
|
||||
$orderedProviderList = GeneralUtility::makeInstance(OrderedProviderList::class);
|
||||
$orderedProviderList->setProviderList(
|
||||
$GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['formDataGroup']['tcaSelectTreeAjaxFieldData']
|
||||
);
|
||||
|
||||
return $orderedProviderList->compile($result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?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 form data group classes.
|
||||
* This interface is type hinted in FormDataCompiler and is a wrapper to group single data provider.
|
||||
*/
|
||||
interface FormDataGroupInterface
|
||||
{
|
||||
/**
|
||||
* Compile form data
|
||||
*
|
||||
* @param array $result Initialized result array
|
||||
* @return array Result filled with data
|
||||
*/
|
||||
public function compile(array $result);
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
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 Psr\Log\LoggerAwareInterface;
|
||||
use Psr\Log\LoggerAwareTrait;
|
||||
use TYPO3\CMS\Backend\Form\Exception\DatabaseRecordException;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Extended by other provider that fetch records from database
|
||||
*/
|
||||
abstract class AbstractDatabaseRecordProvider implements LoggerAwareInterface
|
||||
{
|
||||
use LoggerAwareTrait;
|
||||
|
||||
/**
|
||||
* Fetch a record from database. Deleted records will NOT be fetched.
|
||||
* Method is similar to BackendUtility::getRecord, but is more picky
|
||||
* about input and result.
|
||||
*
|
||||
* @param string $tableName The table name to fetch record from
|
||||
* @param int $uid Uid of record to fetch
|
||||
* @return array Fetched record row
|
||||
* @throws DatabaseRecordException|\InvalidArgumentException|\UnexpectedValueException|\RuntimeException
|
||||
*/
|
||||
protected function getRecordFromDatabase($tableName, $uid)
|
||||
{
|
||||
if ($uid <= 0) {
|
||||
throw new \InvalidArgumentException(
|
||||
'$uid must be positive integer, ' . $uid . ' given',
|
||||
1437656456
|
||||
);
|
||||
}
|
||||
$row = $this->getDatabaseRow($tableName, $uid);
|
||||
if (empty($row)) {
|
||||
// Indicates a runtime error (eg. record was killed by other editor meanwhile) can be caught elsewhere
|
||||
// and transformed to a message to the user or something
|
||||
throw new DatabaseRecordException(
|
||||
'Record with uid ' . $uid . ' from table ' . $tableName . ' not found',
|
||||
1437656081,
|
||||
null,
|
||||
$tableName,
|
||||
(int)$uid
|
||||
);
|
||||
}
|
||||
return $row;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the requested row from the database
|
||||
*
|
||||
* @param string $tableName
|
||||
* @param int $uid
|
||||
* @return array
|
||||
*/
|
||||
protected function getDatabaseRow(string $tableName, int $uid): array
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($tableName);
|
||||
$queryBuilder->getRestrictions()
|
||||
->removeAll()
|
||||
->add(GeneralUtility::makeInstance(DeletedRestriction::class));
|
||||
|
||||
$row = $queryBuilder->select('*')
|
||||
->from($tableName)
|
||||
->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)))
|
||||
->execute()
|
||||
->fetch();
|
||||
|
||||
return $row ?: [];
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,41 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* Fetch page in default language from database if it's a translated pages record
|
||||
*/
|
||||
class DatabaseDefaultLanguagePageRow extends AbstractDatabaseRecordProvider implements FormDataProviderInterface
|
||||
{
|
||||
/**
|
||||
* Add default language page row of existing row to result
|
||||
* defaultLanguagePageRow will stay NULL in result if a record is added or edited below root node
|
||||
*
|
||||
* @param array $result
|
||||
* @return array
|
||||
*/
|
||||
public function addData(array $result)
|
||||
{
|
||||
// $defaultLanguagePageRow end up NULL if a record added or edited on root node
|
||||
$tableName = $result['tableName'];
|
||||
if ($tableName === 'pages' && $result['databaseRow'][$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']] > 0) {
|
||||
$result['defaultLanguagePageRow'] = $this->getRecordFromDatabase('pages', $result['databaseRow'][$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']]);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
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\Backend\Utility\BackendUtility;
|
||||
|
||||
/**
|
||||
* Fetch existing database row on edit
|
||||
*/
|
||||
class DatabaseEditRow extends AbstractDatabaseRecordProvider implements FormDataProviderInterface
|
||||
{
|
||||
/**
|
||||
* Fetch existing record from database
|
||||
*
|
||||
* @param array $result
|
||||
* @return array
|
||||
* @throws \UnexpectedValueException
|
||||
*/
|
||||
public function addData(array $result)
|
||||
{
|
||||
if ($result['command'] !== 'edit' || !empty($result['databaseRow'])) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$databaseRow = $this->getRecordFromDatabase($result['tableName'], $result['vanillaUid']);
|
||||
if (!array_key_exists('pid', $databaseRow)) {
|
||||
throw new \UnexpectedValueException(
|
||||
'Parent record does not have a pid field',
|
||||
1437663061
|
||||
);
|
||||
}
|
||||
// Warning: By reference! In case the record is in a workspace, the -1 in pid will be
|
||||
// changed to the real pid of the life record here.
|
||||
BackendUtility::fixVersioningPid($result['tableName'], $databaseRow);
|
||||
$result['databaseRow'] = $databaseRow;
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
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;
|
||||
|
||||
/**
|
||||
* Set effective pid we're working on
|
||||
*/
|
||||
class DatabaseEffectivePid implements FormDataProviderInterface
|
||||
{
|
||||
/**
|
||||
* Effective pid is used to determine entry point for page ts and is also
|
||||
* the pid where new records are stored later.
|
||||
*
|
||||
* @param array $result
|
||||
* @return array
|
||||
*/
|
||||
public function addData(array $result)
|
||||
{
|
||||
$effectivePid = 0;
|
||||
if ($result['command'] === 'edit' && $result['tableName'] === 'pages') {
|
||||
$effectivePid = $result['databaseRow']['uid'];
|
||||
} elseif ($result['command'] === 'edit') {
|
||||
$effectivePid = $result['databaseRow']['pid'];
|
||||
} elseif ($result['command'] === 'new' && is_array($result['parentPageRow'])) {
|
||||
$effectivePid = $result['parentPageRow']['uid'];
|
||||
}
|
||||
$result['effectivePid'] = (int)$effectivePid;
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
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\Configuration\TranslationConfigurationProvider;
|
||||
use TYPO3\CMS\Backend\Form\Exception\DatabaseDefaultLanguageException;
|
||||
use TYPO3\CMS\Backend\Form\FormDataProviderInterface;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Add language related data to result array
|
||||
*/
|
||||
class DatabaseLanguageRows implements FormDataProviderInterface
|
||||
{
|
||||
/**
|
||||
* Fetch default language if handled record is a localized one,
|
||||
* unserialize transOrigDiffSourceField if it is defined,
|
||||
* fetch additional languages if requested.
|
||||
*
|
||||
* @param array $result
|
||||
* @return array
|
||||
* @throws DatabaseDefaultLanguageException
|
||||
*/
|
||||
public function addData(array $result)
|
||||
{
|
||||
if (!empty($result['processedTca']['ctrl']['languageField'])
|
||||
&& !empty($result['processedTca']['ctrl']['transOrigPointerField'])
|
||||
) {
|
||||
$languageField = $result['processedTca']['ctrl']['languageField'];
|
||||
$fieldWithUidOfDefaultRecord = $result['processedTca']['ctrl']['transOrigPointerField'];
|
||||
|
||||
if (isset($result['databaseRow'][$languageField]) && $result['databaseRow'][$languageField] > 0
|
||||
&& isset($result['databaseRow'][$fieldWithUidOfDefaultRecord]) && $result['databaseRow'][$fieldWithUidOfDefaultRecord] > 0
|
||||
) {
|
||||
// Default language record of localized record
|
||||
$defaultLanguageRow = $this->getRecordWorkspaceOverlay(
|
||||
$result['tableName'],
|
||||
(int)$result['databaseRow'][$fieldWithUidOfDefaultRecord]
|
||||
);
|
||||
if (empty($defaultLanguageRow)) {
|
||||
throw new DatabaseDefaultLanguageException(
|
||||
'Default language record with id ' . (int)$result['databaseRow'][$fieldWithUidOfDefaultRecord]
|
||||
. ' not found in table ' . $result['tableName'] . ' while editing record ' . $result['databaseRow']['uid'],
|
||||
1438249426
|
||||
);
|
||||
}
|
||||
$result['defaultLanguageRow'] = $defaultLanguageRow;
|
||||
|
||||
// Unserialize the "original diff source" if given
|
||||
if (!empty($result['processedTca']['ctrl']['transOrigDiffSourceField'])
|
||||
&& !empty($result['databaseRow'][$result['processedTca']['ctrl']['transOrigDiffSourceField']])
|
||||
) {
|
||||
$defaultLanguageKey = $result['tableName'] . ':' . (int)$result['databaseRow']['uid'];
|
||||
$result['defaultLanguageDiffRow'][$defaultLanguageKey] = unserialize(
|
||||
$result['databaseRow'][$result['processedTca']['ctrl']['transOrigDiffSourceField']],
|
||||
['allowed_classes' => false]
|
||||
);
|
||||
}
|
||||
|
||||
// Add language overlays from further localizations if requested
|
||||
// @todo: Permission check if user is in "restrict ot language" is missing here.
|
||||
// @todo: The TranslationConfigurationProvider is more stupid than good for us ... invent a better translation overlay api!
|
||||
if (!empty($result['userTsConfig']['options.']['additionalPreviewLanguages'])) {
|
||||
$additionalLanguageUids = GeneralUtility::intExplode(',', $result['userTsConfig']['options.']['additionalPreviewLanguages'], true);
|
||||
/** @var TranslationConfigurationProvider $translationProvider */
|
||||
$translationProvider = GeneralUtility::makeInstance(TranslationConfigurationProvider::class);
|
||||
foreach ($additionalLanguageUids as $additionalLanguageUid) {
|
||||
// Continue if this system language record does not exist or if 0 or -1 is requested
|
||||
// or if row is the same as the to-be-displayed row
|
||||
if ($additionalLanguageUid <= 0
|
||||
|| !isset($result['systemLanguageRows'][$additionalLanguageUid])
|
||||
|| $additionalLanguageUid === (int)$result['databaseRow'][$languageField]
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
$translationInfo = $translationProvider->translationInfo(
|
||||
$result['tableName'],
|
||||
(int)$result['databaseRow'][$fieldWithUidOfDefaultRecord],
|
||||
$additionalLanguageUid
|
||||
);
|
||||
if (!empty($translationInfo['translations'][$additionalLanguageUid]['uid'])) {
|
||||
$record = $this->getRecordWorkspaceOverlay(
|
||||
$result['tableName'],
|
||||
(int)$translationInfo['translations'][$additionalLanguageUid]['uid']
|
||||
);
|
||||
$result['additionalLanguageRows'][$additionalLanguageUid] = $record;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @todo do that only if l10n_parent > 0 (not in "free mode")?
|
||||
if (!empty($result['processedTca']['ctrl']['translationSource'])
|
||||
&& is_string($result['processedTca']['ctrl']['translationSource'])
|
||||
) {
|
||||
$translationSourceFieldName = $result['processedTca']['ctrl']['translationSource'];
|
||||
if (isset($result['databaseRow'][$translationSourceFieldName])
|
||||
&& $result['databaseRow'][$translationSourceFieldName] > 0
|
||||
) {
|
||||
$uidOfTranslationSource = $result['databaseRow'][$translationSourceFieldName];
|
||||
$result['sourceLanguageRow'] = $this->getRecordWorkspaceOverlay(
|
||||
$result['tableName'],
|
||||
$uidOfTranslationSource
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the requested row from the database
|
||||
*
|
||||
* @param string $tableName
|
||||
* @param int $uid
|
||||
* @return array
|
||||
*/
|
||||
protected function getRecordWorkspaceOverlay(string $tableName, int $uid): array
|
||||
{
|
||||
$row = BackendUtility::getRecordWSOL($tableName, $uid);
|
||||
|
||||
return $row ?: [];
|
||||
}
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
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\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Fill the "pageLanguageOverlayRows" part of the result array
|
||||
*/
|
||||
class DatabasePageLanguageOverlayRows implements FormDataProviderInterface
|
||||
{
|
||||
/**
|
||||
* Fetch available page overlay records of page
|
||||
*
|
||||
* @param array $result
|
||||
* @return array
|
||||
*/
|
||||
public function addData(array $result)
|
||||
{
|
||||
if ($result['effectivePid'] === 0) {
|
||||
// No overlays for records on pid 0 and not for new pages below root
|
||||
return $result;
|
||||
}
|
||||
|
||||
$result['pageLanguageOverlayRows'] = $this->getDatabaseRows((int)$result['effectivePid']);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the requested overlay row from the database
|
||||
*
|
||||
* @param int $pid
|
||||
* @return array
|
||||
*/
|
||||
protected function getDatabaseRows(int $pid): array
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('pages');
|
||||
$queryBuilder->getRestrictions()
|
||||
->removeAll()
|
||||
->add(GeneralUtility::makeInstance(DeletedRestriction::class))
|
||||
->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
|
||||
|
||||
$rows = $queryBuilder->select('*')
|
||||
->from('pages')
|
||||
->where($queryBuilder->expr()->eq(
|
||||
$GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'],
|
||||
$queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT)
|
||||
))
|
||||
->execute()
|
||||
->fetchAll();
|
||||
|
||||
return $rows;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
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\Backend\Utility\BackendUtility;
|
||||
|
||||
/**
|
||||
* Set rootline
|
||||
*/
|
||||
class DatabasePageRootline implements FormDataProviderInterface
|
||||
{
|
||||
/**
|
||||
* Fetch rootline
|
||||
*
|
||||
* @param array $result
|
||||
* @return array
|
||||
*/
|
||||
public function addData(array $result)
|
||||
{
|
||||
$result['rootline'] = BackendUtility::BEgetRootLine($result['effectivePid'], '', true);
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
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;
|
||||
|
||||
/**
|
||||
* Fetch parent page row from database if possible
|
||||
*/
|
||||
class DatabaseParentPageRow extends AbstractDatabaseRecordProvider implements FormDataProviderInterface
|
||||
{
|
||||
/**
|
||||
* Add parent page row of existing row to result
|
||||
* parentPageRow will stay NULL in result if a record is added or edited below root node
|
||||
*
|
||||
* @param array $result
|
||||
* @return array
|
||||
*/
|
||||
public function addData(array $result)
|
||||
{
|
||||
// $parentPageRow end up NULL if a record added or edited on root node
|
||||
$parentPageRow = null;
|
||||
if ($result['command'] === 'new') {
|
||||
if ($result['vanillaUid'] < 0) {
|
||||
// vanillaUid points to a neighbor record in same table - get its record and its pid from there to find parent record
|
||||
$neighborRow = $this->getRecordFromDatabase($result['tableName'], abs($result['vanillaUid']));
|
||||
if (!empty($neighborRow['t3ver_oid'])) {
|
||||
$neighborRow = $this->getRecordFromDatabase($result['tableName'], (int)$neighborRow['t3ver_oid']);
|
||||
}
|
||||
$result['neighborRow'] = $neighborRow;
|
||||
// uid of page the record is located in
|
||||
$neighborRowPid = (int)$neighborRow['pid'];
|
||||
if ($neighborRowPid !== 0) {
|
||||
// Fetch the parent page record only if it is not the '0' root
|
||||
$parentPageRow = $this->getRecordFromDatabase('pages', $neighborRowPid);
|
||||
}
|
||||
} elseif ($result['vanillaUid'] > 0) {
|
||||
// vanillaUid points to a page uid directly
|
||||
$parentPageRow = $this->getRecordFromDatabase('pages', $result['vanillaUid']);
|
||||
}
|
||||
} else {
|
||||
// On "edit", the row itself has been fetched already
|
||||
if ($result['databaseRow']['pid'] > 0) {
|
||||
$parentPageRow = $this->getRecordFromDatabase('pages', $result['databaseRow']['pid']);
|
||||
}
|
||||
}
|
||||
$result['parentPageRow'] = $parentPageRow;
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
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;
|
||||
|
||||
/**
|
||||
* Determine the final TCA type value
|
||||
*/
|
||||
class DatabaseRecordOverrideValues implements FormDataProviderInterface
|
||||
{
|
||||
/**
|
||||
* Add override values to the databaseRow fields. As those values are not meant to
|
||||
* be overwritten by the user, the TCA of the field is set to type hidden.
|
||||
*
|
||||
* @param array $result
|
||||
* @return array
|
||||
*/
|
||||
public function addData(array $result)
|
||||
{
|
||||
foreach ($result['overrideValues'] as $fieldName => $fieldValue) {
|
||||
if (isset($result['processedTca']['columns'][$fieldName])) {
|
||||
$result['databaseRow'][$fieldName] = $fieldValue;
|
||||
$result['processedTca']['columns'][$fieldName]['config'] = [
|
||||
'type' => 'hidden',
|
||||
'renderType' => 'hidden',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
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\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
|
||||
/**
|
||||
* Determine the final TCA type value
|
||||
*/
|
||||
class DatabaseRecordTypeValue implements FormDataProviderInterface
|
||||
{
|
||||
/**
|
||||
* TCA type value depends on several parameters. The simple case is
|
||||
* a direct lookup in the database row, which then just needs handling
|
||||
* in case the row is a localization overlay.
|
||||
* More complex is the field:field syntax that can look up the actual
|
||||
* value in a different table.
|
||||
*
|
||||
* @param array $result
|
||||
* @return array
|
||||
* @throws \UnexpectedValueException
|
||||
*/
|
||||
public function addData(array $result)
|
||||
{
|
||||
if (!isset($result['processedTca']['types'])
|
||||
|| !is_array($result['processedTca']['types'])
|
||||
|| empty($result['processedTca']['types'])
|
||||
) {
|
||||
throw new \UnexpectedValueException(
|
||||
'At least one "types" array must be defined for table ' . $result['tableName'] . ', preferred "0"',
|
||||
1438185331
|
||||
);
|
||||
}
|
||||
|
||||
// Guard clause to suppress any calculation if record type value has been set from outside already
|
||||
if ($result['recordTypeValue'] !== '') {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$recordTypeValue = '0';
|
||||
if (!empty($result['processedTca']['ctrl']['type'])) {
|
||||
$tcaTypeField = $result['processedTca']['ctrl']['type'];
|
||||
|
||||
if (strpos($tcaTypeField, ':') === false) {
|
||||
// $tcaTypeField is the name of a field in database row
|
||||
if (!array_key_exists($tcaTypeField, $result['databaseRow'])) {
|
||||
throw new \UnexpectedValueException(
|
||||
'TCA table ' . $result['tableName'] . ' ctrl[\'type\'] is set to ' . $tcaTypeField . ', but'
|
||||
. ' this field does not exist in the database of this table',
|
||||
1438183881
|
||||
);
|
||||
}
|
||||
$recordTypeValue = $result['databaseRow'][$tcaTypeField];
|
||||
} else {
|
||||
// If type is configured as localField:foreignField, fetch the type value from
|
||||
// a foreign table. localField then point to a group or select field in the own table,
|
||||
// this points to a record in a foreign table and the value of foreignField is then
|
||||
// used as type field. This was introduced for some FAL scenarios.
|
||||
list($pointerField, $foreignTableTypeField) = explode(':', $tcaTypeField);
|
||||
|
||||
$relationType = $result['processedTca']['columns'][$pointerField]['config']['type'];
|
||||
if ($relationType !== 'select' && $relationType !== 'group') {
|
||||
throw new \UnexpectedValueException(
|
||||
'TCA foreign field pointer fields are only allowed to be used with group or select field types.'
|
||||
. ' Handling field ' . $pointerField . ' with type configured as ' . $tcaTypeField,
|
||||
1325862241
|
||||
);
|
||||
}
|
||||
|
||||
$foreignUid = $result['databaseRow'][$pointerField];
|
||||
// Resolve the foreign record only if there is a uid, otherwise fall back 0
|
||||
if (!empty($foreignUid)) {
|
||||
// Determine table name to fetch record from
|
||||
if ($relationType === 'select') {
|
||||
$foreignTable = $result['processedTca']['columns'][$pointerField]['config']['foreign_table'] ?? '';
|
||||
} else {
|
||||
$allowedTables = explode(',', $result['processedTca']['columns'][$pointerField]['config']['allowed']);
|
||||
// Always take the first configured table.
|
||||
$foreignTable = $allowedTables[0];
|
||||
}
|
||||
if (empty($foreignTable)) {
|
||||
throw new \UnexpectedValueException(
|
||||
'No target table defined for type config field ' . $pointerField . ' of table ' . $result['tableName'],
|
||||
1438253614
|
||||
);
|
||||
}
|
||||
if (!MathUtility::canBeInterpretedAsInteger($foreignUid) && is_array($foreignUid[0])) {
|
||||
// A group relation - has been resolved to array by TcaGroup data provider already
|
||||
$foreignUid = $foreignUid[0]['uid'];
|
||||
}
|
||||
// Fetch field of this foreign row from db
|
||||
if (MathUtility::canBeInterpretedAsInteger($foreignUid)) {
|
||||
$foreignRow = $this->getDatabaseRow($foreignTable, (int)$foreignUid, $foreignTableTypeField);
|
||||
if ($foreignRow[$foreignTableTypeField]) {
|
||||
// @todo: It might be necessary to fetch the value from default language record as well here,
|
||||
// @todo: this was buggy in the "old" implementation and never worked. It was therefor left out here for now.
|
||||
// @todo: To implement that, see if the foreign row is a localized overlay, fetch default and merge exclude
|
||||
$recordTypeValue = $foreignRow[$foreignTableTypeField];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Throw another exception if determined value and '0' and '1' do not exist
|
||||
if (empty($result['processedTca']['types'][$recordTypeValue])
|
||||
&& empty($result['processedTca']['types']['0'])
|
||||
&& empty($result['processedTca']['types']['1'])
|
||||
) {
|
||||
throw new \UnexpectedValueException(
|
||||
'Type value ' . $recordTypeValue . ' from database record not defined in TCA of table '
|
||||
. $result['tableName'] . ' and neither 0 nor 1 are defined as fallback.',
|
||||
1438185437
|
||||
);
|
||||
}
|
||||
|
||||
// Check the determined value actually exists as types key, otherwise fall back to 0 or 1, 1 for "historical reasons"
|
||||
if (empty($result['processedTca']['types'][$recordTypeValue])) {
|
||||
$recordTypeValue = !empty($result['processedTca']['types']['0']) ? '0' : '1';
|
||||
}
|
||||
|
||||
$result['recordTypeValue'] = (string)$recordTypeValue;
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the requested row from the database
|
||||
*
|
||||
* @param string $tableName
|
||||
* @param int $uid
|
||||
* @param string $fieldName
|
||||
* @return array
|
||||
*/
|
||||
protected function getDatabaseRow(string $tableName, int $uid, string $fieldName): array
|
||||
{
|
||||
$row = BackendUtility::getRecord($tableName, $uid, $fieldName);
|
||||
|
||||
return $row ?: [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
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\Database\Query\QueryHelper;
|
||||
|
||||
/**
|
||||
* Migrate date and datetime db field values to timestamp
|
||||
*/
|
||||
class DatabaseRowDateTimeFields implements FormDataProviderInterface
|
||||
{
|
||||
/**
|
||||
* Migrate date and datetime db field values to timestamp
|
||||
*
|
||||
* @param array $result
|
||||
* @return array
|
||||
*/
|
||||
public function addData(array $result)
|
||||
{
|
||||
$dateTimeTypes = QueryHelper::getDateTimeTypes();
|
||||
$dateTimeFormats = QueryHelper::getDateTimeFormats();
|
||||
|
||||
foreach ($result['processedTca']['columns'] as $column => $columnConfig) {
|
||||
if (isset($columnConfig['config']['dbType'])
|
||||
&& in_array($columnConfig['config']['dbType'], $dateTimeTypes, true)
|
||||
) {
|
||||
if (!empty($result['databaseRow'][$column])
|
||||
&& $result['databaseRow'][$column] !== $dateTimeFormats[$columnConfig['config']['dbType']]['empty']
|
||||
) {
|
||||
// Create an ISO-8601 date from current field data; the database always contains UTC
|
||||
// The field value is something like "2016-01-01" or "2016-01-01 10:11:12", so appending "UTC"
|
||||
// makes date() treat it as a UTC date (which is what we store in the database).
|
||||
$result['databaseRow'][$column] = date('c', strtotime($result['databaseRow'][$column] . ' UTC'));
|
||||
} else {
|
||||
$result['databaseRow'][$column] = null;
|
||||
}
|
||||
}
|
||||
// its a UNIX timestamp! We do not modify this here, as it will only be treated as a datetime because
|
||||
// of eval being set to "date" or "datetime". This is handled in InputTextElement then.
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
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\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Handle TCA default values on row. This affects existing rows as well as new rows.
|
||||
*
|
||||
* Hint: Even after this class it is NOT safe no rely that *all* fields from
|
||||
* columns are set in databaseRow.
|
||||
*/
|
||||
class DatabaseRowDefaultValues implements FormDataProviderInterface
|
||||
{
|
||||
/**
|
||||
* Initialize new row with default values from various sources
|
||||
*
|
||||
* @param array $result
|
||||
* @return array
|
||||
*/
|
||||
public function addData(array $result)
|
||||
{
|
||||
$databaseRow = $result['databaseRow'];
|
||||
|
||||
$newRow = $databaseRow;
|
||||
foreach ($result['processedTca']['columns'] as $fieldName => $fieldConfig) {
|
||||
// Keep current value if it can be resolved to "there is something" directly
|
||||
if (isset($databaseRow[$fieldName])) {
|
||||
$newRow[$fieldName] = $databaseRow[$fieldName];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Special handling for eval null
|
||||
if (!empty($fieldConfig['config']['eval']) && GeneralUtility::inList($fieldConfig['config']['eval'], 'null')) {
|
||||
if (// Field exists and is set to NULL
|
||||
array_key_exists($fieldName, $databaseRow)
|
||||
// Default NULL is set, and this is a new record!
|
||||
|| (array_key_exists('default', $fieldConfig['config']) && $fieldConfig['config']['default'] === null)
|
||||
) {
|
||||
$newRow[$fieldName] = null;
|
||||
} else {
|
||||
$newRow[$fieldName] = (string)$fieldConfig['config']['default'];
|
||||
}
|
||||
} else {
|
||||
// Fun part: This forces empty string for any field even if no default is set. This is
|
||||
// a useful side effect in flex form section containers where a new field is added to an existing
|
||||
// value array because it was added to a data structure.
|
||||
$newRow[$fieldName] = (string)$fieldConfig['config']['default'];
|
||||
}
|
||||
}
|
||||
|
||||
$result['databaseRow'] = $newRow;
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
<?php
|
||||
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\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
|
||||
/**
|
||||
* On "new" command, initialize new database row with default data
|
||||
*/
|
||||
class DatabaseRowInitializeNew implements FormDataProviderInterface
|
||||
{
|
||||
/**
|
||||
* Initialize new row with default values from various sources
|
||||
* There are 4 sources of default values. Mind the order, the last takes precedence.
|
||||
*
|
||||
* @param array $result
|
||||
* @return array
|
||||
* @throws \UnexpectedValueException
|
||||
*/
|
||||
public function addData(array $result)
|
||||
{
|
||||
if ($result['command'] !== 'new') {
|
||||
return $result;
|
||||
}
|
||||
if (!is_array($result['databaseRow'])) {
|
||||
throw new \UnexpectedValueException(
|
||||
'databaseRow of table ' . $result['tableName'] . ' is not an array',
|
||||
1444431128
|
||||
);
|
||||
}
|
||||
|
||||
$result = $this->setDefaultsFromUserTsConfig($result);
|
||||
$result = $this->setDefaultsFromPageTsConfig($result);
|
||||
$result = $this->setDefaultsFromNeighborRow($result);
|
||||
$result = $this->setDefaultsFromDefaultValues($result);
|
||||
$result = $this->setDefaultsFromInlineRelations($result);
|
||||
$result = $this->setDefaultsFromInlineParentLanguage($result);
|
||||
$result = $this->setDefaultsFromInlineParentUid($result);
|
||||
$result = $this->setPid($result);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set defaults defined by user ts "TCAdefaults"
|
||||
*
|
||||
* @param array $result Result array
|
||||
* @return array Modified result array
|
||||
*/
|
||||
protected function setDefaultsFromUserTsConfig(array $result)
|
||||
{
|
||||
$tableNameWithDot = $result['tableName'] . '.';
|
||||
// Apply default values from user typo script "TCAdefaults" if any
|
||||
if (isset($result['userTsConfig']['TCAdefaults.'][$tableNameWithDot])
|
||||
&& is_array($result['userTsConfig']['TCAdefaults.'][$tableNameWithDot])
|
||||
) {
|
||||
foreach ($result['userTsConfig']['TCAdefaults.'][$tableNameWithDot] as $fieldName => $fieldValue) {
|
||||
if (isset($result['processedTca']['columns'][$fieldName])) {
|
||||
$result['databaseRow'][$fieldName] = $fieldValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set defaults defined by page ts "TCAdefaults"
|
||||
*
|
||||
* @param array $result Result array
|
||||
* @return array Modified result array
|
||||
*/
|
||||
protected function setDefaultsFromPageTsConfig(array $result)
|
||||
{
|
||||
$tableNameWithDot = $result['tableName'] . '.';
|
||||
if (isset($result['pageTsConfig']['TCAdefaults.'][$tableNameWithDot])
|
||||
&& is_array($result['pageTsConfig']['TCAdefaults.'][$tableNameWithDot])
|
||||
) {
|
||||
foreach ($result['pageTsConfig']['TCAdefaults.'][$tableNameWithDot] as $fieldName => $fieldValue) {
|
||||
if (isset($result['processedTca']['columns'][$fieldName])) {
|
||||
$result['databaseRow'][$fieldName] = $fieldValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* If a neighbor row is given (if vanillaUid was negative), field can be initialized with values
|
||||
* from neighbor for fields registered in TCA['ctrl']['useColumnsForDefaultValues'].
|
||||
*
|
||||
* @param array $result Result array
|
||||
* @return array Modified result array
|
||||
*/
|
||||
protected function setDefaultsFromNeighborRow(array $result)
|
||||
{
|
||||
if (is_array($result['neighborRow'])
|
||||
&& !empty($result['processedTca']['ctrl']['useColumnsForDefaultValues'])
|
||||
) {
|
||||
$defaultColumns = GeneralUtility::trimExplode(',', $result['processedTca']['ctrl']['useColumnsForDefaultValues'], true);
|
||||
foreach ($defaultColumns as $fieldName) {
|
||||
if (isset($result['processedTca']['columns'][$fieldName])
|
||||
&& isset($result['neighborRow'][$fieldName])
|
||||
) {
|
||||
$result['databaseRow'][$fieldName] = $result['neighborRow'][$fieldName];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply default values.
|
||||
* These are typically carried around as "defVals" GET vars and set by controllers
|
||||
* in $result['defaultValues'] array as init values.
|
||||
*
|
||||
* @param array $result Result array
|
||||
* @return array Modified result array
|
||||
*/
|
||||
protected function setDefaultsFromDefaultValues(array $result)
|
||||
{
|
||||
$result = $this->setDefaultValuesFromGetPost($result);
|
||||
$tableName = $result['tableName'];
|
||||
$defaultValues = $result['defaultValues'] ?? [];
|
||||
if (isset($defaultValues[$tableName]) && is_array($defaultValues[$tableName])) {
|
||||
foreach ($defaultValues[$tableName] as $fieldName => $fieldValue) {
|
||||
if (isset($result['processedTca']['columns'][$fieldName])) {
|
||||
$result['databaseRow'][$fieldName] = $fieldValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $result
|
||||
* @return array
|
||||
* @deprecated since TYPO3 v9 will be removed in TYPO3 v10.0 - see $result['defaultValues']
|
||||
*/
|
||||
protected function setDefaultValuesFromGetPost(array $result)
|
||||
{
|
||||
if (!empty($result['defaultValues'])) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$defaultValues = GeneralUtility::_GP('defVals');
|
||||
if (!empty($defaultValues)) {
|
||||
trigger_error(
|
||||
'Default values for new database rows should be set from controller context. Applying default values'
|
||||
. ' via GET/POST parameters is deprecated since 9.2 and will be removed in version 10',
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
$result['defaultValues'] = $defaultValues;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inline scenario if a new intermediate record to an existing child-child is
|
||||
* compiled. Set "foreign_selector" field of this intermediate row to given
|
||||
* "childChildUid". See TcaDataCompiler array comment of inlineChildChildUid
|
||||
* for more details.
|
||||
*
|
||||
* @param array $result Result array
|
||||
* @return array Modified result array
|
||||
* @throws \UnexpectedValueException
|
||||
*/
|
||||
protected function setDefaultsFromInlineRelations(array $result)
|
||||
{
|
||||
if ($result['inlineChildChildUid'] === null) {
|
||||
return $result;
|
||||
}
|
||||
if (!is_int($result['inlineChildChildUid'])) {
|
||||
throw new \UnexpectedValueException(
|
||||
'An inlineChildChildUid is given for table ' . $result['tableName'] . ', but is not an integer',
|
||||
1444434103
|
||||
);
|
||||
}
|
||||
if (!isset($result['inlineParentConfig']['foreign_selector'])) {
|
||||
throw new \UnexpectedValueException(
|
||||
'An inlineChildChildUid is given for table ' . $result['tableName'] . ', but no foreign_selector in inlineParentConfig',
|
||||
1444434102
|
||||
);
|
||||
}
|
||||
$selectorFieldName = $result['inlineParentConfig']['foreign_selector'];
|
||||
if (!isset($result['processedTca']['columns'][$selectorFieldName]['config']['type'])
|
||||
|| (
|
||||
$result['processedTca']['columns'][$selectorFieldName]['config']['type'] !== 'select'
|
||||
&& $result['processedTca']['columns'][$selectorFieldName]['config']['type'] !== 'group'
|
||||
)
|
||||
) {
|
||||
throw new \UnexpectedValueException(
|
||||
$selectorFieldName . ' is target type of a foreign_selector field to table ' . $result['tableName'] . ' and must be either a select or group type field',
|
||||
1444434104
|
||||
);
|
||||
}
|
||||
|
||||
if ($result['inlineChildChildUid']) {
|
||||
$result['databaseRow'][$selectorFieldName] = $result['inlineChildChildUid'];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* If a new child is created in an inline relation via ajax, and if the parent is a localized record,
|
||||
* the child should have the same sys_language_uid set in the field declared in ['ctrl']['languageField']
|
||||
* if the child is localizable itself.
|
||||
* A localized parent transfers its sys_language_uid via inlineParentConfig['inline']['parentSysLanguageUid'],
|
||||
* use that value as default for the child record languageField.
|
||||
*
|
||||
* @param array $result Result array
|
||||
* @return array Modified result array
|
||||
* @throws \UnexpectedValueException
|
||||
*/
|
||||
protected function setDefaultsFromInlineParentLanguage(array $result): array
|
||||
{
|
||||
if (!isset($result['inlineParentConfig']['inline']['parentSysLanguageUid'])
|
||||
|| empty($result['processedTca']['ctrl']['languageField'])
|
||||
|| empty($result['processedTca']['ctrl']['transOrigPointerField'])
|
||||
) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
if (!MathUtility::canBeInterpretedAsInteger($result['inlineParentConfig']['inline']['parentSysLanguageUid'])) {
|
||||
throw new \UnexpectedValueException(
|
||||
'A sys_language_uid is set from inline parent config but the value is no integer',
|
||||
1490360772
|
||||
);
|
||||
}
|
||||
$parentSysLanguageUid = (int)$result['inlineParentConfig']['inline']['parentSysLanguageUid'];
|
||||
$languageFieldName = $result['processedTca']['ctrl']['languageField'];
|
||||
$result['databaseRow'][$languageFieldName] = $parentSysLanguageUid;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the parent uid of inline relations created via ajax to the corresponding foreign field
|
||||
*
|
||||
* @param array $result Result array
|
||||
* @return array
|
||||
*/
|
||||
protected function setDefaultsFromInlineParentUid(array $result): array
|
||||
{
|
||||
$isInlineChild = $result['isInlineChild'] ?? false;
|
||||
$parentField = $result['inlineParentConfig']['foreign_field'] ?? false;
|
||||
|
||||
if ($isInlineChild && $parentField && !empty($result['inlineParentUid'])) {
|
||||
$result['databaseRow'][$parentField] = $result['inlineParentUid'];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the pid. This is either the vanillaUid (see description in FormDataCompiler),
|
||||
* or a pid given by pageTsConfig for inline children.
|
||||
*
|
||||
* @param array $result Result array
|
||||
* @return array Modified result array
|
||||
* @throws \UnexpectedValueException
|
||||
*/
|
||||
protected function setPid(array $result)
|
||||
{
|
||||
// Set pid to vanillaUid. This can be a negative value
|
||||
// if the record is added relative to another record.
|
||||
$result['databaseRow']['pid'] = $result['vanillaUid'];
|
||||
|
||||
// In case a new inline record is created, the pid can be set to a different value
|
||||
// by pageTsConfig, but not by userTsConfig. This overrides the above pid selection
|
||||
// and forces the pid of new inline children.
|
||||
$tableNameWithDot = $result['tableName'] . '.';
|
||||
if ($result['isInlineChild'] && isset($result['pageTsConfig']['TCAdefaults.'][$tableNameWithDot]['pid'])) {
|
||||
if (!MathUtility::canBeInterpretedAsInteger($result['pageTsConfig']['TCAdefaults.'][$tableNameWithDot]['pid'])) {
|
||||
throw new \UnexpectedValueException(
|
||||
'page TSConfig setting TCAdefaults.' . $tableNameWithDot . 'pid must be a number, but given string '
|
||||
. $result['pageTsConfig']['TCAdefaults.'][$tableNameWithDot]['pid'] . ' can not be interpreted as integer',
|
||||
1461598332
|
||||
);
|
||||
}
|
||||
$result['databaseRow']['pid'] = (int)$result['pageTsConfig']['TCAdefaults.'][$tableNameWithDot]['pid'];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
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\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessageService;
|
||||
use TYPO3\CMS\Core\Site\Entity\SiteInterface;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Fill the "systemLanguageRows" part of the result array
|
||||
*/
|
||||
class DatabaseSystemLanguageRows implements FormDataProviderInterface
|
||||
{
|
||||
/**
|
||||
* Fetch available system languages and resolve iso code if necessary.
|
||||
*
|
||||
* @param array $result
|
||||
* @return array
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function addData(array $result)
|
||||
{
|
||||
$site = $result['site'] ?? null;
|
||||
if (!$site instanceof SiteInterface) {
|
||||
throw new \LogicException(
|
||||
'No valid site object found in $result[\'site\']',
|
||||
1534952559
|
||||
);
|
||||
}
|
||||
$pageIdDefaultLanguage = $result['defaultLanguagePageRow']['uid'] ?? $result['effectivePid'];
|
||||
$languages = $site->getAvailableLanguages($this->getBackendUser(), true, $pageIdDefaultLanguage);
|
||||
|
||||
$languageRows = [];
|
||||
foreach ($languages as $language) {
|
||||
$languageId = $language->getLanguageId();
|
||||
if ($languageId > 0) {
|
||||
$iso = $language->getTwoLetterIsoCode();
|
||||
} else {
|
||||
$iso = 'DEF';
|
||||
}
|
||||
$languageRows[$languageId] = [
|
||||
'uid' => $languageId,
|
||||
'title' => $language->getTitle(),
|
||||
'iso' => $iso,
|
||||
'flagIconIdentifier' => $language->getFlagIdentifier()
|
||||
];
|
||||
|
||||
if (empty($iso)) {
|
||||
// No iso code could be found. This is currently possible in the system but discouraged.
|
||||
// So, code within FormEngine has to be suited to work with an empty iso code. However,
|
||||
// it may impact certain multi language scenarios, so we add a flash message hinting for
|
||||
// incomplete configuration here.
|
||||
// It might be possible to convert this to a non-catchable exception later if
|
||||
// it iso code is enforced on a different layer of the system (tca required + migration wizard).
|
||||
// @todo: This could be relaxed again if flex form language handling is extracted,
|
||||
// @todo: since the rest of the FormEngine code does not rely on iso code?
|
||||
$message = sprintf(
|
||||
$this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:error.missingLanguageIsocode'),
|
||||
$language->getTwoLetterIsoCode(),
|
||||
$languageId
|
||||
);
|
||||
/** @var FlashMessage $flashMessage */
|
||||
$flashMessage = GeneralUtility::makeInstance(
|
||||
FlashMessage::class,
|
||||
$message,
|
||||
'',
|
||||
FlashMessage::ERROR
|
||||
);
|
||||
/** @var FlashMessageService $flashMessageService */
|
||||
$flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
|
||||
$defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
|
||||
$defaultFlashMessageQueue->enqueue($flashMessage);
|
||||
}
|
||||
}
|
||||
$result['systemLanguageRows'] = $languageRows;
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUser(): BackendUserAuthentication
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
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\Utility\StringUtility;
|
||||
|
||||
/**
|
||||
* On "new" command, initialize uid with an unqique uid
|
||||
*/
|
||||
class DatabaseUniqueUidNewRow implements FormDataProviderInterface
|
||||
{
|
||||
/**
|
||||
* Initialize new row with unique uid
|
||||
*
|
||||
* @param array $result
|
||||
* @return array
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function addData(array $result)
|
||||
{
|
||||
if ($result['command'] !== 'new') {
|
||||
return $result;
|
||||
}
|
||||
// Throw exception if uid is already set and does not start with NEW.
|
||||
// In some situations a new record needs to be created again so the initialization of default
|
||||
// values is triggered, but the "ID" of the new record is already known: This is the case if a
|
||||
// new section container element is added by FormFlexAjaxController to a not yet persisted record.
|
||||
// In this case, command "new" is given to the data compiler, but the "NEW1234" id has been calculated
|
||||
// by the former compiler when opening the record already. The ajax controller then hands in the
|
||||
// "new" command together with the id calculated by the first call.
|
||||
if (isset($result['databaseRow']['uid']) && strpos($result['databaseRow']['uid'], 'NEW') !== 0) {
|
||||
throw new \InvalidArgumentException(
|
||||
'uid is already set to ' . $result['databaseRow']['uid'] . ' and does not start with NEW for a "new" command',
|
||||
1437991120
|
||||
);
|
||||
}
|
||||
if (!isset($result['databaseRow']['uid'])) {
|
||||
$result['databaseRow']['uid'] = StringUtility::getUniqueId('NEW');
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
<?php
|
||||
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\Exception\AccessDeniedContentEditException;
|
||||
use TYPO3\CMS\Backend\Form\Exception\AccessDeniedEditInternalsException;
|
||||
use TYPO3\CMS\Backend\Form\Exception\AccessDeniedException;
|
||||
use TYPO3\CMS\Backend\Form\Exception\AccessDeniedHookException;
|
||||
use TYPO3\CMS\Backend\Form\Exception\AccessDeniedPageEditException;
|
||||
use TYPO3\CMS\Backend\Form\Exception\AccessDeniedPageNewException;
|
||||
use TYPO3\CMS\Backend\Form\Exception\AccessDeniedRootNodeException;
|
||||
use TYPO3\CMS\Backend\Form\Exception\AccessDeniedTableModifyException;
|
||||
use TYPO3\CMS\Backend\Form\FormDataProviderInterface;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Type\Bitmask\Permission;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Determine user permission for action and check them
|
||||
*/
|
||||
class DatabaseUserPermissionCheck implements FormDataProviderInterface
|
||||
{
|
||||
/**
|
||||
* Set userPermissionOnPage to result array and check access rights.
|
||||
*
|
||||
* A couple of different exceptions are thrown here:
|
||||
* * If something weird happens a top level SPL exception is thrown.
|
||||
* This indicates a non recoverable error.
|
||||
* * If user has no access to whatever should be done, an exception that
|
||||
* extends from Form\Exception\AccessDeniedException is thrown. This
|
||||
* can be caught by upper level controller code and can be translated
|
||||
* to a specific error message that is shown to the user depending on
|
||||
* specific exception that is thrown.
|
||||
*
|
||||
* @param array $result
|
||||
* @return array
|
||||
* @throws AccessDeniedException|\LogicException|\RuntimeException
|
||||
*/
|
||||
public function addData(array $result)
|
||||
{
|
||||
$backendUser = $this->getBackendUser();
|
||||
|
||||
// Early return for admins
|
||||
if ($backendUser->isAdmin()) {
|
||||
$result['userPermissionOnPage'] = Permission::ALL;
|
||||
return $result;
|
||||
}
|
||||
|
||||
if (!$backendUser->check('tables_modify', $result['tableName'])) {
|
||||
// If user has no modify rights on table, processing is stopped by throwing an
|
||||
// exception immediately. This case can not be circumvented by hooks.
|
||||
throw new AccessDeniedTableModifyException(
|
||||
'No table modify permission for user ' . $backendUser->user['uid'] . ' on table ' . $result['tableName'],
|
||||
1437683248
|
||||
);
|
||||
}
|
||||
|
||||
$exception = null;
|
||||
$userHasAccess = false;
|
||||
$userPermissionOnPage = Permission::NOTHING;
|
||||
if ($result['command'] === 'new') {
|
||||
// A new record is created. Access rights of parent record are important here
|
||||
// @todo: In case of new inline child, parentPageRow should probably be the
|
||||
// @todo: "inlineFirstPid" page - Maybe effectivePid and parentPageRow should be calculated differently then?
|
||||
if (is_array($result['parentPageRow'])) {
|
||||
// Record is added below an existing page
|
||||
$userPermissionOnPage = $backendUser->calcPerms($result['parentPageRow']);
|
||||
if ($result['tableName'] === 'pages') {
|
||||
// New page is created, user needs PAGE_NEW for this
|
||||
if ((bool)($userPermissionOnPage & Permission::PAGE_NEW)) {
|
||||
$userHasAccess = true;
|
||||
} else {
|
||||
$exception = new AccessDeniedPageNewException(
|
||||
'No page new permission for user ' . $backendUser->user['uid'] . ' on page ' . $result['databaseRow']['uid'],
|
||||
1437745640
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// A regular record is added, not a page. User needs CONTENT_EDIT permission
|
||||
if ((bool)($userPermissionOnPage & Permission::CONTENT_EDIT)) {
|
||||
$userHasAccess = true;
|
||||
} else {
|
||||
$exception = new AccessDeniedContentEditException(
|
||||
'No content new permission for user ' . $backendUser->user['uid'] . ' on page ' . $result['parentPageRow']['uid'],
|
||||
1437745759
|
||||
);
|
||||
}
|
||||
}
|
||||
} elseif (BackendUtility::isRootLevelRestrictionIgnored($result['tableName'])) {
|
||||
// Non admin is creating a record on root node for a table that is actively allowed
|
||||
$userHasAccess = true;
|
||||
$userPermissionOnPage = Permission::ALL;
|
||||
} else {
|
||||
// Non admin has no create permission on root node records
|
||||
$exception = new AccessDeniedRootNodeException(
|
||||
'No record creation permission for user ' . $backendUser->user['uid'] . ' on page root node',
|
||||
1437745221
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// A page or a record on a page is edited
|
||||
if ($result['tableName'] === 'pages') {
|
||||
// A page record is edited, check edit rights of this record directly
|
||||
$userPermissionOnPage = $backendUser->calcPerms($result['defaultLanguagePageRow'] ?? $result['databaseRow']);
|
||||
if ((bool)($userPermissionOnPage & Permission::PAGE_EDIT) && $backendUser->check('pagetypes_select', $result['databaseRow'][$result['processedTca']['ctrl']['type']])) {
|
||||
$userHasAccess = true;
|
||||
} else {
|
||||
$exception = new AccessDeniedPageEditException(
|
||||
'No page edit permission for user ' . $backendUser->user['uid'] . ' on page ' . $result['databaseRow']['uid'],
|
||||
1437679336
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// A non page record is edited.
|
||||
if (isset($result['parentPageRow']) && is_array($result['parentPageRow'])) {
|
||||
// If there is a parent page row, check content edit right of user
|
||||
$userPermissionOnPage = $backendUser->calcPerms($result['parentPageRow']);
|
||||
if ((bool)($userPermissionOnPage & Permission::CONTENT_EDIT)) {
|
||||
$userHasAccess = true;
|
||||
} else {
|
||||
$exception = new AccessDeniedContentEditException(
|
||||
'No content edit permission for user ' . $backendUser->user['uid'] . ' on page ' . $result['parentPageRow']['uid'],
|
||||
1437679657
|
||||
);
|
||||
}
|
||||
} elseif (BackendUtility::isRootLevelRestrictionIgnored($result['tableName'])) {
|
||||
// Non admin is editing a record on root node for a table that is actively allowed
|
||||
$userHasAccess = true;
|
||||
$userPermissionOnPage = Permission::ALL;
|
||||
} else {
|
||||
// Non admin has no edit permission on root node records
|
||||
// @todo: This probably needs further handling, see http://review.typo3.org/40835
|
||||
$exception = new AccessDeniedRootNodeException(
|
||||
'No content edit permission for user ' . $backendUser->user['uid'] . ' on page root node',
|
||||
1437679856
|
||||
);
|
||||
}
|
||||
}
|
||||
if ($userHasAccess) {
|
||||
// If general access is allowed, check "recordEditAccessInternals"
|
||||
$userHasAccess = $backendUser->recordEditAccessInternals($result['tableName'], $result['databaseRow']);
|
||||
if (!$userHasAccess) {
|
||||
$exception = new AccessDeniedEditInternalsException(
|
||||
$backendUser->errorMsg,
|
||||
1437687404
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($userHasAccess && $exception) {
|
||||
// Having user access TRUE here and an exception defined must not happen,
|
||||
// indicates an internal error and throws a logic exception
|
||||
throw new \LogicException(
|
||||
'Access was TRUE but an exception was raised as well for table ' . $result['tableName'] . ' and user ' . $backendUser->user['uid'],
|
||||
1437688402
|
||||
);
|
||||
}
|
||||
|
||||
if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/alt_doc.php']['makeEditForm_accessCheck'] ?? null)) {
|
||||
// A hook may modify the $userHasAccess decision. Previous state is saved to see if a hook changed
|
||||
// a previous decision from TRUE to FALSE to throw a specific exception in this case
|
||||
$userHasAccessBeforeHook = $userHasAccess;
|
||||
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/alt_doc.php']['makeEditForm_accessCheck'] as $methodReference) {
|
||||
$parameters = [
|
||||
'table' => $result['tableName'],
|
||||
'uid' => $result['databaseRow']['uid'],
|
||||
'cmd' => $result['command'],
|
||||
'hasAccess' => $userHasAccess,
|
||||
];
|
||||
$userHasAccess = (bool)GeneralUtility::callUserFunction($methodReference, $parameters, $this);
|
||||
}
|
||||
if ($userHasAccessBeforeHook && !$userHasAccess) {
|
||||
$exception = new AccessDeniedHookException(
|
||||
'Access to table ' . $result['tableName'] . ' for user ' . $backendUser->user['uid'] . ' was denied by a makeEditForm_accessCheck hook',
|
||||
1437689705
|
||||
);
|
||||
}
|
||||
if (!$userHasAccessBeforeHook && $userHasAccess) {
|
||||
// Unset a previous exception if hook allowed access where previous checks didn't
|
||||
$exception = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$userHasAccess && !$exception) {
|
||||
// User has no access, but no according exception was defined. This is an
|
||||
// internal error and throws a logic exception.
|
||||
throw new \LogicException(
|
||||
'Access to table ' . $result['tableName'] . ' denied, but no reason given',
|
||||
1437690507
|
||||
);
|
||||
}
|
||||
|
||||
if ($exception) {
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
$result['userPermissionOnPage'] = $userPermissionOnPage;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUser()
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
}
|
||||
Loaded 100 of 12716 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user