‪TYPO3CMS  9.5
InputLinkElement.php
Go to the documentation of this file.
1 <?php
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
31 
38 {
44  protected ‪$defaultFieldInformation = [
45  'tcaDescription' => [
46  'renderType' => 'tcaDescription',
47  ],
48  ];
49 
55  protected ‪$defaultFieldControl = [
56  'linkPopup' => [
57  'renderType' => 'linkPopup',
58  'options' => []
59  ],
60  ];
61 
67  protected ‪$defaultFieldWizard = [
68  'localizationStateSelector' => [
69  'renderType' => 'localizationStateSelector',
70  ],
71  'otherLanguageContent' => [
72  'renderType' => 'otherLanguageContent',
73  'after' => [
74  'localizationStateSelector'
75  ],
76  ],
77  'defaultLanguageDifferences' => [
78  'renderType' => 'defaultLanguageDifferences',
79  'after' => [
80  'otherLanguageContent',
81  ],
82  ],
83  ];
84 
90  public function ‪render()
91  {
92  $languageService = $this->‪getLanguageService();
93 
94  $table = $this->data['tableName'];
95  $fieldName = $this->data['fieldName'];
96  $row = $this->data['databaseRow'];
97  $parameterArray = $this->data['parameterArray'];
98  $resultArray = $this->‪initializeResultArray();
99  $config = $parameterArray['fieldConf']['config'];
100 
101  $itemValue = $parameterArray['itemFormElValue'];
102  $evalList = GeneralUtility::trimExplode(',', $config['eval'], true);
103  $size = ‪MathUtility::forceIntegerInRange($config['size'] ?? $this->defaultInputWidth, $this->minimumInputWidth, $this->maxInputWidth);
104  $width = (int)$this->‪formMaxWidth($size);
105  $nullControlNameEscaped = htmlspecialchars('control[active][' . $table . '][' . $row['uid'] . '][' . $fieldName . ']');
106 
107  $fieldInformationResult = $this->‪renderFieldInformation();
108  $fieldInformationHtml = $fieldInformationResult['html'];
109  $resultArray = $this->‪mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false);
110 
111  if ($config['readOnly']) {
112  // Early return for read only fields
113  $html = [];
114  $html[] = '<div class="formengine-field-item t3js-formengine-field-item">';
115  $html[] = $fieldInformationHtml;
116  $html[] = '<div class="form-wizards-wrap">';
117  $html[] = '<div class="form-wizards-element">';
118  $html[] = '<div class="form-control-wrap" style="max-width: ' . $width . 'px">';
119  $html[] = '<input class="form-control" value="' . htmlspecialchars($itemValue) . '" type="text" disabled>';
120  $html[] = '</div>';
121  $html[] = '</div>';
122  $html[] = '</div>';
123  $html[] = '</div>';
124  $resultArray['html'] = implode(LF, $html);
125  return $resultArray;
126  }
127 
128  // @todo: The whole eval handling is a mess and needs refactoring
129  foreach ($evalList as $func) {
130  // @todo: This is ugly: The code should find out on it's own whether a eval definition is a
131  // @todo: keyword like "date", or a class reference. The global registration could be dropped then
132  // Pair hook to the one in \TYPO3\CMS\Core\DataHandling\DataHandler::checkValue_input_Eval()
133  if (isset(‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tce']['formevals'][$func])) {
134  if (class_exists($func)) {
135  $evalObj = GeneralUtility::makeInstance($func);
136  if (method_exists($evalObj, 'deevaluateFieldValue')) {
137  $_params = [
138  'value' => $itemValue
139  ];
140  $itemValue = $evalObj->deevaluateFieldValue($_params);
141  }
142  if (method_exists($evalObj, 'returnFieldJS')) {
143  $resultArray['additionalJavaScriptPost'][] = 'TBE_EDITOR.customEvalFunctions[' . GeneralUtility::quoteJSvalue($func) . ']'
144  . ' = function(value) {' . $evalObj->returnFieldJS() . '};';
145  }
146  }
147  }
148  }
149 
150  $attributes = [
151  'value' => '',
152  'id' => ‪StringUtility::getUniqueId('formengine-input-'),
153  'class' => implode(' ', [
154  'form-control',
155  't3js-clearable',
156  't3js-form-field-inputlink-input',
157  'hidden',
158  'hasDefaultValue',
159  ]),
160  'data-formengine-validation-rules' => $this->‪getValidationDataAsJsonString($config),
161  'data-formengine-input-params' => json_encode([
162  'field' => $parameterArray['itemFormElName'],
163  'evalList' => implode(',', $evalList)
164  ]),
165  'data-formengine-input-name' => $parameterArray['itemFormElName'],
166  ];
167 
168  $maxLength = $config['max'] ?? 0;
169  if ((int)$maxLength > 0) {
170  $attributes['maxlength'] = (int)$maxLength;
171  }
172  if (!empty($config['placeholder'])) {
173  $attributes['placeholder'] = trim($config['placeholder']);
174  }
175  if (isset($config['autocomplete'])) {
176  $attributes['autocomplete'] = empty($config['autocomplete']) ? 'new-' . $fieldName : 'on';
177  }
178 
179  $valuePickerHtml = [];
180  if (isset($config['valuePicker']['items']) && is_array($config['valuePicker']['items'])) {
181  $mode = $config['valuePicker']['mode'] ?? '';
182  $itemName = $parameterArray['itemFormElName'];
183  $fieldChangeFunc = $parameterArray['fieldChangeFunc'];
184  if ($mode === 'append') {
185  $assignValue = 'document.querySelectorAll(' . GeneralUtility::quoteJSvalue('[data-formengine-input-name="' . $itemName . '"]') . ')[0]'
186  . '.value=\'\'+this.options[this.selectedIndex].value+document.editform[' . GeneralUtility::quoteJSvalue($itemName) . '].value';
187  } elseif ($mode === 'prepend') {
188  $assignValue = 'document.querySelectorAll(' . GeneralUtility::quoteJSvalue('[data-formengine-input-name="' . $itemName . '"]') . ')[0]'
189  . '.value+=\'\'+this.options[this.selectedIndex].value';
190  } else {
191  $assignValue = 'document.querySelectorAll(' . GeneralUtility::quoteJSvalue('[data-formengine-input-name="' . $itemName . '"]') . ')[0]'
192  . '.value=this.options[this.selectedIndex].value';
193  }
194  $valuePickerHtml[] = '<select';
195  $valuePickerHtml[] = ' class="form-control tceforms-select tceforms-wizardselect"';
196  $valuePickerHtml[] = ' onchange="' . htmlspecialchars($assignValue . ';this.blur();this.selectedIndex=0;' . implode('', $fieldChangeFunc)) . '"';
197  $valuePickerHtml[] = '>';
198  $valuePickerHtml[] = '<option></option>';
199  foreach ($config['valuePicker']['items'] as $item) {
200  $valuePickerHtml[] = '<option value="' . htmlspecialchars($item[1]) . '">' . htmlspecialchars($languageService->sL($item[0])) . '</option>';
201  }
202  $valuePickerHtml[] = '</select>';
203  }
204 
205  $fieldWizardResult = $this->‪renderFieldWizard();
206  $fieldWizardHtml = $fieldWizardResult['html'];
207  $resultArray = $this->‪mergeChildReturnIntoExistingResult($resultArray, $fieldWizardResult, false);
208 
209  $fieldControlResult = $this->‪renderFieldControl();
210  $fieldControlHtml = $fieldControlResult['html'];
211  $resultArray = $this->‪mergeChildReturnIntoExistingResult($resultArray, $fieldControlResult, false);
212 
213  $linkExplanation = $this->‪getLinkExplanation($itemValue ?: '');
214  $explanation = htmlspecialchars($linkExplanation['text']);
215  $toggleButtonTitle = $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:buttons.toggleLinkExplanation');
216 
217  $expansionHtml = [];
218  $expansionHtml[] = '<div class="form-control-wrap" style="max-width: ' . $width . 'px">';
219  $expansionHtml[] = '<div class="form-wizards-wrap">';
220  $expansionHtml[] = '<div class="form-wizards-element">';
221  $expansionHtml[] = '<div class="input-group t3js-form-field-inputlink">';
222  $expansionHtml[] = '<span class="t3js-form-field-inputlink-icon input-group-addon">' . $linkExplanation['icon'] . '</span>';
223  $expansionHtml[] = '<input class="form-control form-field-inputlink-explanation t3js-form-field-inputlink-explanation" data-toggle="tooltip" data-title="' . $explanation . '" value="' . $explanation . '" readonly>';
224  $expansionHtml[] = '<input type="text" ' . GeneralUtility::implodeAttributes($attributes, true) . ' />';
225  $expansionHtml[] = '<span class="input-group-btn">';
226  $expansionHtml[] = '<button class="btn btn-default t3js-form-field-inputlink-explanation-toggle" type="button" title="' . htmlspecialchars($toggleButtonTitle) . '">';
227  $expansionHtml[] = $this->iconFactory->getIcon('actions-version-workspaces-preview-link', ‪Icon::SIZE_SMALL)->render();
228  $expansionHtml[] = '</button>';
229  $expansionHtml[] = '</span>';
230  $expansionHtml[] = '<input type="hidden" name="' . $parameterArray['itemFormElName'] . '" value="' . htmlspecialchars($itemValue) . '" />';
231  $expansionHtml[] = '</div>';
232  $expansionHtml[] = '</div>';
233  if (!empty($valuePickerHtml) || !empty($fieldControlHtml)) {
234  $expansionHtml[] = '<div class="form-wizards-items-aside">';
235  $expansionHtml[] = '<div class="btn-group">';
236  $expansionHtml[] = implode(LF, $valuePickerHtml);
237  $expansionHtml[] = $fieldControlHtml;
238  $expansionHtml[] = '</div>';
239  $expansionHtml[] = '</div>';
240  }
241  $expansionHtml[] = '<div class="form-wizards-items-bottom">';
242  $expansionHtml[] = $linkExplanation['additionalAttributes'];
243  $expansionHtml[] = $fieldWizardHtml;
244  $expansionHtml[] = '</div>';
245  $expansionHtml[] = '</div>';
246  $expansionHtml[] = '</div>';
247  $expansionHtml = implode(LF, $expansionHtml);
248 
249  $fullElement = $expansionHtml;
250  if ($this->‪hasNullCheckboxButNoPlaceholder()) {
251  $checked = $itemValue !== null ? ' checked="checked"' : '';
252  $fullElement = [];
253  $fullElement[] = '<div class="t3-form-field-disable"></div>';
254  $fullElement[] = '<div class="checkbox t3-form-field-eval-null-checkbox">';
255  $fullElement[] = '<label for="' . $nullControlNameEscaped . '">';
256  $fullElement[] = '<input type="hidden" name="' . $nullControlNameEscaped . '" value="0" />';
257  $fullElement[] = '<input type="checkbox" name="' . $nullControlNameEscaped . '" id="' . $nullControlNameEscaped . '" value="1"' . $checked . ' />';
258  $fullElement[] = $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.nullCheckbox');
259  $fullElement[] = '</label>';
260  $fullElement[] = '</div>';
261  $fullElement[] = $expansionHtml;
262  $fullElement = implode(LF, $fullElement);
263  } elseif ($this->‪hasNullCheckboxWithPlaceholder()) {
264  $checked = $itemValue !== null ? ' checked="checked"' : '';
265  $placeholder = $shortenedPlaceholder = $config['placeholder'] ?? '';
266  $disabled = '';
267  $fallbackValue = 0;
268  if (strlen($placeholder) > 0) {
269  $shortenedPlaceholder = GeneralUtility::fixed_lgd_cs($placeholder, 20);
270  if ($placeholder !== $shortenedPlaceholder) {
271  $overrideLabel = sprintf(
272  $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.placeholder.override'),
273  '<span title="' . htmlspecialchars($placeholder) . '">' . htmlspecialchars($shortenedPlaceholder) . '</span>'
274  );
275  } else {
276  $overrideLabel = sprintf(
277  $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.placeholder.override'),
278  htmlspecialchars($placeholder)
279  );
280  }
281  } else {
282  $overrideLabel = $languageService->sL(
283  'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.placeholder.override_not_available'
284  );
285  }
286  $fullElement = [];
287  $fullElement[] = '<div class="checkbox t3js-form-field-eval-null-placeholder-checkbox">';
288  $fullElement[] = '<label for="' . $nullControlNameEscaped . '">';
289  $fullElement[] = '<input type="hidden" name="' . $nullControlNameEscaped . '" value="' . $fallbackValue . '" />';
290  $fullElement[] = '<input type="checkbox" name="' . $nullControlNameEscaped . '" id="' . $nullControlNameEscaped . '" value="1"' . $checked . $disabled . ' />';
291  $fullElement[] = $overrideLabel;
292  $fullElement[] = '</label>';
293  $fullElement[] = '</div>';
294  $fullElement[] = '<div class="t3js-formengine-placeholder-placeholder">';
295  $fullElement[] = '<div class="form-control-wrap" style="max-width:' . $width . 'px">';
296  $fullElement[] = '<input type="text" class="form-control" disabled="disabled" value="' . htmlspecialchars($shortenedPlaceholder) . '" />';
297  $fullElement[] = '</div>';
298  $fullElement[] = '</div>';
299  $fullElement[] = '<div class="t3js-formengine-placeholder-formfield">';
300  $fullElement[] = $expansionHtml;
301  $fullElement[] = '</div>';
302  $fullElement = implode(LF, $fullElement);
303  }
304 
305  $resultArray['html'] = '<div class="formengine-field-item t3js-formengine-field-item">' . $fieldInformationHtml . $fullElement . '</div>';
306  return $resultArray;
307  }
308 
313  protected function ‪getLinkExplanation(string $itemValue): array
314  {
315  if (empty($itemValue)) {
316  return [];
317  }
318  ‪$data = ['text' => '', 'icon' => ''];
319  $typolinkService = GeneralUtility::makeInstance(TypoLinkCodecService::class);
320  $linkParts = $typolinkService->decode($itemValue);
321  $linkService = GeneralUtility::makeInstance(LinkService::class);
322 
323  try {
324  $linkData = $linkService->resolve($linkParts['url']);
325  } catch (‪FileDoesNotExistException $e) {
326  return ‪$data;
327  } catch (‪FolderDoesNotExistException $e) {
328  return ‪$data;
329  } catch (‪UnknownLinkHandlerException $e) {
330  return ‪$data;
331  } catch (‪InvalidPathException $e) {
332  return ‪$data;
333  }
334 
335  // Resolving the TypoLink parts (class, title, params)
336  $additionalAttributes = [];
337  foreach ($linkParts as $key => $value) {
338  if ($key === 'url') {
339  continue;
340  }
341  if ($value) {
342  switch ($key) {
343  case 'class':
344  $label = $this->‪getLanguageService()->‪sL('LLL:EXT:recordlist/Resources/Private/Language/locallang_browse_links.xlf:class');
345  break;
346  case 'title':
347  $label = $this->‪getLanguageService()->‪sL('LLL:EXT:recordlist/Resources/Private/Language/locallang_browse_links.xlf:title');
348  break;
349  case 'additionalParams':
350  $label = $this->‪getLanguageService()->‪sL('LLL:EXT:recordlist/Resources/Private/Language/locallang_browse_links.xlf:params');
351  break;
352  default:
353  $label = $key;
354  }
355 
356  $additionalAttributes[] = '<span><strong>' . htmlspecialchars($label) . ': </strong> ' . htmlspecialchars($value) . '</span>';
357  }
358  }
359 
360  // Resolve the actual link
361  switch ($linkData['type']) {
363  $pageRecord = ‪BackendUtility::readPageAccess($linkData['pageuid'], '1=1');
364  // Is this a real page
365  if ($pageRecord['uid']) {
366  ‪$data = [
367  'text' => $pageRecord['_thePathFull'] . '[' . $pageRecord['uid'] . ']',
368  'icon' => $this->iconFactory->getIconForRecord('pages', $pageRecord, ‪Icon::SIZE_SMALL)->render()
369  ];
370  }
371  break;
373  ‪$data = [
374  'text' => $linkData['email'],
375  'icon' => $this->iconFactory->getIcon('content-elements-mailform', ‪Icon::SIZE_SMALL)->render()
376  ];
377  break;
379  ‪$data = [
380  'text' => $this->‪getDomainByUrl($linkData['url']),
381  'icon' => $this->iconFactory->getIcon('apps-pagetree-page-shortcut-external', ‪Icon::SIZE_SMALL)->render()
382 
383  ];
384  break;
387  $file = $linkData['file'];
388  if ($file) {
389  ‪$data = [
390  'text' => $file->getPublicUrl(),
391  'icon' => $this->iconFactory->getIconForFileExtension($file->getExtension(), ‪Icon::SIZE_SMALL)->render()
392  ];
393  }
394  break;
397  $folder = $linkData['folder'];
398  if ($folder) {
399  ‪$data = [
400  'text' => $folder->getPublicUrl(),
401  'icon' => $this->iconFactory->getIcon('apps-filetree-folder-default', ‪Icon::SIZE_SMALL)->render()
402  ];
403  }
404  break;
406  $table = $this->data['pageTsConfig']['TCEMAIN.']['linkHandler.'][$linkData['identifier'] . '.']['configuration.']['table'];
407  $record = ‪BackendUtility::getRecord($table, $linkData['uid']);
408  if ($record) {
409  $recordTitle = ‪BackendUtility::getRecordTitle($table, $record);
410  $tableTitle = $this->‪getLanguageService()->‪sL(‪$GLOBALS['TCA'][$table]['ctrl']['title']);
411  ‪$data = [
412  'text' => sprintf('%s [%s:%d]', $recordTitle, $tableTitle, $linkData['uid']),
413  'icon' => $this->iconFactory->getIconForRecord($table, $record, ‪Icon::SIZE_SMALL)->render(),
414  ];
415  } else {
416  ‪$data = [
417  'text' => sprintf('%s', $linkData['uid']),
418  'icon' => $this->iconFactory->getIcon('tcarecords-' . $table . '-default', ‪Icon::SIZE_SMALL, 'overlay-missing')->render(),
419  ];
420  }
421  break;
422  default:
423  // Please note that this hook is preliminary and might change, as this element could become its own
424  // TCA type in the future
425  if (isset(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['linkHandler'][$linkData['type']])) {
426  $linkBuilder = GeneralUtility::makeInstance(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['linkHandler'][$linkData['type']]);
427  ‪$data = $linkBuilder->getFormData($linkData, $linkParts, $this->data, $this);
428  } else {
429  ‪$data = [
430  'text' => 'not implemented type ' . $linkData['type'],
431  'icon' => ''
432  ];
433  }
434  }
435 
436  ‪$data['additionalAttributes'] = '<div class="help-block">' . implode(' - ', $additionalAttributes) . '</div>';
437  return ‪$data;
438  }
439 
445  protected function ‪getDomainByUrl(string $uriString): string
446  {
447  ‪$data = parse_url($uriString);
448  return ‪$data['host'] ?? '';
449  }
450 
454  protected function ‪getLanguageService()
455  {
456  return ‪$GLOBALS['LANG'];
457  }
458 }
‪TYPO3\CMS\Core\Imaging\Icon\SIZE_SMALL
‪const SIZE_SMALL
Definition: Icon.php:29
‪TYPO3\CMS\Backend\Form\Element\AbstractFormElement\renderFieldInformation
‪array renderFieldInformation()
Definition: AbstractFormElement.php:71
‪TYPO3\CMS\Backend\Form\AbstractNode\mergeChildReturnIntoExistingResult
‪array mergeChildReturnIntoExistingResult(array $existing, array $childReturn, bool $mergeHtml=true)
Definition: AbstractNode.php:115
‪TYPO3\CMS\Backend\Form\AbstractNode\initializeResultArray
‪array initializeResultArray()
Definition: AbstractNode.php:88
‪TYPO3\CMS\Core\Imaging\Icon
Definition: Icon.php:25
‪TYPO3\CMS\Core\Utility\MathUtility\forceIntegerInRange
‪static int forceIntegerInRange($theInt, $min, $max=2000000000, $defaultValue=0)
Definition: MathUtility.php:31
‪TYPO3\CMS\Backend\Form\Element\AbstractFormElement
Definition: AbstractFormElement.php:31
‪TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException
Definition: FileDoesNotExistException.php:21
‪TYPO3\CMS\Core\Localization\LanguageService\sL
‪string sL($input)
Definition: LanguageService.php:158
‪TYPO3\CMS\Backend\Form\Element
Definition: AbstractFormElement.php:2
‪TYPO3\CMS\Backend\Form\AbstractNode\getValidationDataAsJsonString
‪string getValidationDataAsJsonString(array $config)
Definition: AbstractNode.php:153
‪TYPO3\CMS\Backend\Form\Element\AbstractFormElement\renderFieldControl
‪array renderFieldControl()
Definition: AbstractFormElement.php:87
‪TYPO3\CMS\Backend\Form\Element\AbstractFormElement\hasNullCheckboxWithPlaceholder
‪bool hasNullCheckboxWithPlaceholder()
Definition: AbstractFormElement.php:166
‪TYPO3\CMS\Backend\Form\Element\AbstractFormElement\hasNullCheckboxButNoPlaceholder
‪bool hasNullCheckboxButNoPlaceholder()
Definition: AbstractFormElement.php:131
‪TYPO3\CMS\Backend\Form\AbstractNode\$data
‪array $data
Definition: AbstractNode.php:40
‪TYPO3\CMS\Core\Resource\Folder
Definition: Folder.php:34
‪TYPO3\CMS\Core\Resource\Exception\FolderDoesNotExistException
Definition: FolderDoesNotExistException.php:21
‪TYPO3\CMS\Core\Resource\File
Definition: File.php:23
‪TYPO3\CMS\Backend\Utility\BackendUtility\getRecordTitle
‪static string getRecordTitle($table, $row, $prep=false, $forceResult=true)
Definition: BackendUtility.php:1811
‪TYPO3\CMS\Backend\Form\Element\AbstractFormElement\formMaxWidth
‪int formMaxWidth($size=48)
Definition: AbstractFormElement.php:297
‪TYPO3\CMS\Backend\Utility\BackendUtility
Definition: BackendUtility.php:72
‪TYPO3\CMS\Backend\Utility\BackendUtility\getRecord
‪static array null getRecord($table, $uid, $fields=' *', $where='', $useDeleteClause=true)
Definition: BackendUtility.php:130
‪TYPO3\CMS\Core\Utility\StringUtility\getUniqueId
‪static string getUniqueId($prefix='')
Definition: StringUtility.php:91
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:5
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:21
‪TYPO3\CMS\Core\Localization\LanguageService
Definition: LanguageService.php:29
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:45
‪TYPO3\CMS\Core\Utility\StringUtility
Definition: StringUtility.php:21
‪TYPO3\CMS\Backend\Form\Element\AbstractFormElement\renderFieldWizard
‪array renderFieldWizard()
Definition: AbstractFormElement.php:103
‪TYPO3\CMS\Core\Resource\Exception\InvalidPathException
Definition: InvalidPathException.php:21
‪TYPO3\CMS\Backend\Utility\BackendUtility\readPageAccess
‪static array bool readPageAccess($id, $perms_clause)
Definition: BackendUtility.php:635