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