‪TYPO3CMS  ‪main
InputSlugElement.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
22 use TYPO3\CMS\Core\Imaging\IconSize;
27 
32 {
38  protected ‪$defaultFieldInformation = [
39  'tcaDescription' => [
40  'renderType' => 'tcaDescription',
41  ],
42  ];
43 
49  protected ‪$defaultFieldWizard = [
50  'localizationStateSelector' => [
51  'renderType' => 'localizationStateSelector',
52  ],
53  'otherLanguageContent' => [
54  'renderType' => 'otherLanguageContent',
55  'after' => [
56  'localizationStateSelector',
57  ],
58  ],
59  'defaultLanguageDifferences' => [
60  'renderType' => 'defaultLanguageDifferences',
61  'after' => [
62  'otherLanguageContent',
63  ],
64  ],
65  ];
66 
67  public function ‪__construct(
68  private readonly ‪IconFactory $iconFactory,
69  ) {}
70 
76  public function ‪render(): array
77  {
78  $table = $this->data['tableName'];
79  $row = $this->data['databaseRow'];
80  $parameterArray = $this->data['parameterArray'];
81  $resultArray = $this->‪initializeResultArray();
82 
83  $languageId = 0;
84  if (isset(‪$GLOBALS['TCA'][$table]['ctrl']['languageField']) && !empty(‪$GLOBALS['TCA'][$table]['ctrl']['languageField'])) {
85  $languageField = ‪$GLOBALS['TCA'][$table]['ctrl']['languageField'];
86  $languageId = (int)((is_array($row[$languageField] ?? null) ? ($row[$languageField][0] ?? 0) : $row[$languageField]) ?? 0);
87  }
88 
89  $itemValue = $parameterArray['itemFormElValue'];
90  $config = $parameterArray['fieldConf']['config'];
91  $evalList = ‪GeneralUtility::trimExplode(',', $config['eval'] ?? '', true);
92  $size = ‪MathUtility::forceIntegerInRange($config['size'] ?? $this->defaultInputWidth, $this->minimumInputWidth, $this->maxInputWidth);
93  $width = $this->‪formMaxWidth($size);
94  $baseUrl = $this->data['customData'][$this->data['fieldName']]['slugPrefix'] ?? '';
95  $fieldId = ‪StringUtility::getUniqueId('formengine-input-');
96 
97  // Convert UTF-8 characters back (that is important, see Slug class when sanitizing)
98  $itemValue = rawurldecode($itemValue);
99 
100  $fieldInformationResult = $this->‪renderFieldInformation();
101  $fieldInformationHtml = $fieldInformationResult['html'];
102  $resultArray = $this->‪mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false);
103 
104  // readOnly is not supported as columns config but might be set by SingleFieldContainer in case
105  // "l10n_display" is set to "defaultAsReadonly". To prevent misbehaviour for fields, which falsely
106  // set this, we also check for "defaultAsReadonly" being set and whether the record is an overlay.
107  if (($config['readOnly'] ?? false)
108  && ($this->data['processedTca']['ctrl']['transOrigPointerField'] ?? false)
109  && ($row[$this->data['processedTca']['ctrl']['transOrigPointerField']][0] ?? $row[$this->data['processedTca']['ctrl']['transOrigPointerField']] ?? false)
110  && ‪GeneralUtility::inList($parameterArray['fieldConf']['l10n_display'] ?? '', 'defaultAsReadonly')
111  ) {
112  $disabledFieldAttributes = [
113  'class' => 'form-control',
114  'data-formengine-input-name' => $parameterArray['itemFormElName'],
115  'type' => 'text',
116  'value' => $itemValue,
117  'title' => $itemValue,
118  'id' => $fieldId,
119  ];
120  $html = [];
121  $html[] = $this->‪renderLabel($fieldId);
122  $html[] = '<div class="formengine-field-item t3js-formengine-field-item">';
123  $html[] = $fieldInformationHtml;
124  $html[] = '<div class="form-control-wrap" style="max-width: ' . $width . 'px">';
125  $html[] = '<div class="form-wizards-wrap">';
126  $html[] = '<div class="form-wizards-element">';
127  $html[] = '<div class="input-group">';
128  $html[] = ($baseUrl ? '<span class="input-group-text">' . htmlspecialchars($baseUrl) . '</span>' : '');
129  $html[] = '<input ' . GeneralUtility::implodeAttributes($disabledFieldAttributes, true) . ' disabled>';
130  $html[] = '</div>';
131  $html[] = '</div>';
132  $html[] = '</div>';
133  $html[] = '</div>';
134  $html[] = '</div>';
135  $resultArray['html'] = implode(LF, $html);
136  return $resultArray;
137  }
138 
139  $fieldControlResult = $this->‪renderFieldControl();
140  $fieldControlHtml = $fieldControlResult['html'];
141  $resultArray = $this->‪mergeChildReturnIntoExistingResult($resultArray, $fieldControlResult, false);
142 
143  $fieldWizardResult = $this->‪renderFieldWizard();
144  $fieldWizardHtml = $fieldWizardResult['html'];
145  $resultArray = $this->‪mergeChildReturnIntoExistingResult($resultArray, $fieldWizardResult, false);
146  $toggleButtonTitle = $this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:buttons.toggleSlugExplanation');
147  $recreateButtonTitle = $this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:buttons.recreateSlugExplanation');
148 
149  $successMessage = $this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:slugCreation.success.' . ($table === 'pages' ? 'page' : 'record'));
150  $errorMessage = $this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:slugCreation.error');
151 
152  $thisSlugId = 't3js-form-field-slug-id' . ‪StringUtility::getUniqueId();
153  $mainFieldHtml = [];
154  $mainFieldHtml[] = '<div class="formengine-field-item t3js-formengine-field-item">';
155  $mainFieldHtml[] = $fieldInformationHtml;
156  $mainFieldHtml[] = '<div class="form-control-wrap" style="max-width: ' . $width . 'px" id="' . htmlspecialchars($thisSlugId) . '">';
157  $mainFieldHtml[] = '<div class="form-wizards-wrap">';
158  $mainFieldHtml[] = '<div class="form-wizards-element">';
159  $mainFieldHtml[] = '<div class="input-group">';
160  $mainFieldHtml[] = ($baseUrl ? '<span class="input-group-text">' . htmlspecialchars($baseUrl) . '</span>' : '');
161  // We deal with 3 fields here: a readonly field for current / default values, an input
162  // field to manipulate the value, and the final hidden field used to send the value
163  $mainFieldHtml[] = '<input';
164  $mainFieldHtml[] = ' class="form-control t3js-form-field-slug-readonly"';
165  $mainFieldHtml[] = ' title="' . htmlspecialchars($itemValue) . '"';
166  $mainFieldHtml[] = ' value="' . htmlspecialchars($itemValue) . '"';
167  $mainFieldHtml[] = ' readonly';
168  $mainFieldHtml[] = ' />';
169  $mainFieldHtml[] = '<input type="text"';
170  $mainFieldHtml[] = ' id="' . htmlspecialchars($fieldId) . '"';
171  $mainFieldHtml[] = ' class="form-control t3js-form-field-slug-input hidden"';
172  $mainFieldHtml[] = ' placeholder="' . htmlspecialchars($row['slug'] ?? '/') . '"';
173  $mainFieldHtml[] = ' data-formengine-validation-rules="' . htmlspecialchars($this->‪getValidationDataAsJsonString($config)) . '"';
174  $mainFieldHtml[] = ' data-formengine-input-params="' . htmlspecialchars((string)json_encode(['field' => $parameterArray['itemFormElName'], 'evalList' => implode(',', $evalList)])) . '"';
175  $mainFieldHtml[] = ' data-formengine-input-name="' . htmlspecialchars($parameterArray['itemFormElName']) . '"';
176  $mainFieldHtml[] = ' />';
177  $mainFieldHtml[] = '<input type="hidden"';
178  $mainFieldHtml[] = ' class="t3js-form-field-slug-hidden"';
179  $mainFieldHtml[] = ' name="' . htmlspecialchars($parameterArray['itemFormElName']) . '"';
180  $mainFieldHtml[] = ' value="' . htmlspecialchars($itemValue) . '"';
181  $mainFieldHtml[] = ' />';
182  $mainFieldHtml[] = '<button class="btn btn-default t3js-form-field-slug-toggle" type="button" title="' . htmlspecialchars($toggleButtonTitle) . '">';
183  $mainFieldHtml[] = $this->iconFactory->getIcon('actions-version-workspaces-preview-link', IconSize::SMALL)->render();
184  $mainFieldHtml[] = '</button>';
185  $mainFieldHtml[] = '<button class="btn btn-default t3js-form-field-slug-recreate" type="button" title="' . htmlspecialchars($recreateButtonTitle) . '">';
186  $mainFieldHtml[] = $this->iconFactory->getIcon('actions-refresh', IconSize::SMALL)->render();
187  $mainFieldHtml[] = '</button>';
188  $mainFieldHtml[] = '</div>';
189  $mainFieldHtml[] = '</div>';
190  if (!empty($fieldControlHtml)) {
191  $mainFieldHtml[] = '<div class="form-wizards-items-aside form-wizards-items-aside--field-control">';
192  $mainFieldHtml[] = '<div class="btn-group">';
193  $mainFieldHtml[] = $fieldControlHtml;
194  $mainFieldHtml[] = '</div>';
195  $mainFieldHtml[] = '</div>';
196  }
197  $mainFieldHtml[] = '<div class="form-wizards-items-bottom">';
198  $mainFieldHtml[] = '<div class="t3js-form-proposal-accepted callout callout-success hidden mt-3 mb-0">';
199  $mainFieldHtml[] = '<div class="callout-body">';
200  $mainFieldHtml[] = sprintf(htmlspecialchars($successMessage), '<samp class="text-nowrap">' . htmlspecialchars($baseUrl) . '<span class="fw-bold">/abc/</span></samp>');
201  $mainFieldHtml[] = '</div>';
202  $mainFieldHtml[] = '</div>';
203  $mainFieldHtml[] = '<div class="t3js-form-proposal-different callout callout-warning hidden mt-3 mb-0">';
204  $mainFieldHtml[] = '<div class="callout-body">';
205  $mainFieldHtml[] = sprintf(htmlspecialchars($errorMessage), '<samp class="text-nowrap">' . htmlspecialchars($baseUrl) . '<span class="fw-bold">/abc/</span></samp>');
206  $mainFieldHtml[] = '</div>';
207  $mainFieldHtml[] = '</div>';
208  $mainFieldHtml[] = $fieldWizardHtml;
209  $mainFieldHtml[] = '</div>';
210  $mainFieldHtml[] = '</div>';
211  $mainFieldHtml[] = '</div>';
212  $mainFieldHtml[] = '</div>';
213 
214  $resultArray['html'] = $this->‪wrapWithFieldsetAndLegend(implode(LF, $mainFieldHtml));
215 
216  [$commonElementPrefix] = ‪GeneralUtility::revExplode('[', $parameterArray['itemFormElName'], 2);
217  $validInputNamesToListenTo = [];
218  $includeUidInValues = false;
219  foreach ($config['generatorOptions']['fields'] ?? [] as $fieldNameParts) {
220  if (is_string($fieldNameParts)) {
221  $fieldNameParts = ‪GeneralUtility::trimExplode(',', $fieldNameParts);
222  }
223  foreach ($fieldNameParts as $listenerFieldName) {
224  if ($listenerFieldName === 'uid') {
225  $includeUidInValues = true;
226  continue;
227  }
228  $validInputNamesToListenTo[$listenerFieldName] = $commonElementPrefix . '[' . htmlspecialchars($listenerFieldName) . ']';
229  }
230  }
231  $parentPageId = $this->data['parentPageRow']['uid'] ?? 0;
232  $signature = ‪GeneralUtility::hmac(
233  implode(
234  '',
235  [
236  $table,
237  $this->data['effectivePid'],
238  $row['uid'],
239  $languageId,
240  $this->data['fieldName'],
241  $this->data['command'],
242  $parentPageId,
243  ]
244  ),
245  FormSlugAjaxController::class
246  );
247  $optionsForModule = [
248  'pageId' => $this->data['effectivePid'],
249  'recordId' => $row['uid'],
250  'tableName' => $table,
251  'fieldName' => $this->data['fieldName'],
252  'config' => $config,
253  'listenerFieldNames' => $validInputNamesToListenTo,
254  'language' => $languageId,
255  'originalValue' => $itemValue,
256  'signature' => $signature,
257  'command' => $this->data['command'],
258  'parentPageId' => $parentPageId,
259  'includeUidInValues' => $includeUidInValues,
260  ];
261  $resultArray['javaScriptModules'][] = ‪JavaScriptModuleInstruction::create(
262  '@typo3/backend/form-engine/element/slug-element.js'
263  )->instance('#' . $thisSlugId, $optionsForModule);
264  return $resultArray;
265  }
266 }
‪TYPO3\CMS\Backend\Form\Element\AbstractFormElement\renderFieldInformation
‪array renderFieldInformation()
Definition: AbstractFormElement.php:73
‪TYPO3\CMS\Backend\Form\AbstractNode\mergeChildReturnIntoExistingResult
‪array mergeChildReturnIntoExistingResult(array $existing, array $childReturn, bool $mergeHtml=true)
Definition: AbstractNode.php:104
‪TYPO3\CMS\Backend\Form\Element\InputSlugElement\$defaultFieldInformation
‪array $defaultFieldInformation
Definition: InputSlugElement.php:37
‪TYPO3\CMS\Core\Page\JavaScriptModuleInstruction\create
‪static create(string $name, string $exportName=null)
Definition: JavaScriptModuleInstruction.php:47
‪TYPO3\CMS\Backend\Form\Element\AbstractFormElement
Definition: AbstractFormElement.php:37
‪TYPO3\CMS\Core\Imaging\IconFactory
Definition: IconFactory.php:34
‪TYPO3\CMS\Core\Page\JavaScriptModuleInstruction
Definition: JavaScriptModuleInstruction.php:23
‪TYPO3\CMS\Backend\Form\Element
Definition: AbstractFormElement.php:16
‪TYPO3\CMS\Backend\Form\Element\AbstractFormElement\wrapWithFieldsetAndLegend
‪wrapWithFieldsetAndLegend(string $innerHTML)
Definition: AbstractFormElement.php:133
‪TYPO3\CMS\Backend\Form\Element\AbstractFormElement\renderFieldControl
‪array renderFieldControl()
Definition: AbstractFormElement.php:89
‪TYPO3\CMS\Backend\Form\Element\InputSlugElement
Definition: InputSlugElement.php:32
‪TYPO3\CMS\Backend\Form\Element\AbstractFormElement\getLanguageService
‪getLanguageService()
Definition: AbstractFormElement.php:456
‪TYPO3\CMS\Backend\Form\Element\InputSlugElement\render
‪array render()
Definition: InputSlugElement.php:74
‪TYPO3\CMS\Core\Utility\GeneralUtility\hmac
‪static string hmac($input, $additionalSecret='')
Definition: GeneralUtility.php:474
‪TYPO3\CMS\Backend\Form\Element\AbstractFormElement\formMaxWidth
‪int formMaxWidth($size=48)
Definition: AbstractFormElement.php:332
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Backend\Form\Element\InputSlugElement\$defaultFieldWizard
‪array $defaultFieldWizard
Definition: InputSlugElement.php:47
‪TYPO3\CMS\Backend\Form\AbstractNode\getValidationDataAsJsonString
‪getValidationDataAsJsonString(array $config)
Definition: AbstractNode.php:133
‪TYPO3\CMS\Backend\Form\Element\AbstractFormElement\renderLabel
‪renderLabel(string $for)
Definition: AbstractFormElement.php:119
‪TYPO3\CMS\Core\Utility\GeneralUtility\revExplode
‪static list< string > revExplode(string $delimiter, string $string, int $limit=0)
Definition: GeneralUtility.php:782
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:24
‪TYPO3\CMS\Core\Utility\GeneralUtility\inList
‪static bool inList($list, $item)
Definition: GeneralUtility.php:422
‪TYPO3\CMS\Backend\Form\Element\InputSlugElement\__construct
‪__construct(private readonly IconFactory $iconFactory,)
Definition: InputSlugElement.php:65
‪TYPO3\CMS\Core\Utility\MathUtility\forceIntegerInRange
‪static int forceIntegerInRange(mixed $theInt, int $min, int $max=2000000000, int $defaultValue=0)
Definition: MathUtility.php:34
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Core\Utility\StringUtility
Definition: StringUtility.php:24
‪TYPO3\CMS\Backend\Form\Element\AbstractFormElement\renderFieldWizard
‪array renderFieldWizard()
Definition: AbstractFormElement.php:105
‪TYPO3\CMS\Backend\Controller\FormSlugAjaxController
Definition: FormSlugAjaxController.php:38
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static list< string > trimExplode(string $delim, string $string, bool $removeEmptyValues=false, int $limit=0)
Definition: GeneralUtility.php:817
‪TYPO3\CMS\Core\Utility\StringUtility\getUniqueId
‪static getUniqueId(string $prefix='')
Definition: StringUtility.php:57
‪TYPO3\CMS\Backend\Form\AbstractNode\initializeResultArray
‪initializeResultArray()
Definition: AbstractNode.php:77