[TASK] Make "$hookObject must implement interface FooInterface" more verbose
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Form / Element / AbstractFormElement.php
1 <?php
2 namespace TYPO3\CMS\Backend\Form\Element;
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
17 use TYPO3\CMS\Backend\Clipboard\Clipboard;
18 use TYPO3\CMS\Backend\Form\AbstractNode;
19 use TYPO3\CMS\Backend\Form\DatabaseFileIconsHookInterface;
20 use TYPO3\CMS\Backend\Form\FormDataCompiler;
21 use TYPO3\CMS\Backend\Form\FormDataGroup\OnTheFly;
22 use TYPO3\CMS\Backend\Form\FormDataProvider\TcaSelectItems;
23 use TYPO3\CMS\Backend\Form\InlineStackProcessor;
24 use TYPO3\CMS\Backend\Form\NodeFactory;
25 use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
26 use TYPO3\CMS\Backend\Form\Wizard\SuggestWizard;
27 use TYPO3\CMS\Backend\Form\Wizard\ValueSliderWizard;
28 use TYPO3\CMS\Backend\Utility\BackendUtility;
29 use TYPO3\CMS\Core\Imaging\Icon;
30 use TYPO3\CMS\Core\Imaging\IconFactory;
31 use TYPO3\CMS\Core\Utility\ArrayUtility;
32 use TYPO3\CMS\Core\Utility\GeneralUtility;
33 use TYPO3\CMS\Core\Utility\MathUtility;
34 use TYPO3\CMS\Core\Utility\StringUtility;
35 use TYPO3\CMS\Lang\LanguageService;
36
37 /**
38 * Base class for form elements of FormEngine. Contains several helper methods used by single elements.
39 */
40 abstract class AbstractFormElement extends AbstractNode
41 {
42 /**
43 * Default width value for a couple of elements like text
44 *
45 * @var int
46 */
47 protected $defaultInputWidth = 30;
48
49 /**
50 * Minimum width value for a couple of elements like text
51 *
52 * @var int
53 */
54 protected $minimumInputWidth = 10;
55
56 /**
57 * Maximum width value for a couple of elements like text
58 *
59 * @var int
60 */
61 protected $maxInputWidth = 50;
62
63 /**
64 * @var \TYPO3\CMS\Backend\Clipboard\Clipboard|NULL
65 */
66 protected $clipboard = null;
67
68 /**
69 * Container objects give $nodeFactory down to other containers.
70 *
71 * @param NodeFactory $nodeFactory
72 * @param array $data
73 */
74 public function __construct(NodeFactory $nodeFactory, array $data)
75 {
76 parent::__construct($nodeFactory, $data);
77 $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
78 // @todo: this must vanish as soon as elements are clean
79 $this->nodeFactory = $nodeFactory;
80 }
81
82 /**
83 * @return bool TRUE if wizards are disabled on a global level
84 */
85 protected function isWizardsDisabled()
86 {
87 return !empty($this->data['disabledWizards']);
88 }
89
90 /**
91 * Returns the max width in pixels for an elements like input and text
92 *
93 * @param int $size The abstract size value (1-48)
94 * @return int Maximum width in pixels
95 */
96 protected function formMaxWidth($size = 48)
97 {
98 $compensationForLargeDocuments = 1.33;
99 $compensationForFormFields = 12;
100
101 $size = round($size * $compensationForLargeDocuments);
102 return ceil($size * $compensationForFormFields);
103 }
104
105 /**
106 * @var IconFactory
107 */
108 protected $iconFactory;
109
110 /**
111 * Rendering wizards for form fields.
112 *
113 * @param array $itemKinds Array with the real item in the first value
114 * @param array $wizConf The "wizards" key from the config array for the field (from TCA)
115 * @param string $table Table name
116 * @param array $row The record array
117 * @param string $field The field name
118 * @param array $PA Additional configuration array.
119 * @param string $itemName The field name
120 * @param array $specConf Special configuration if available.
121 * @param bool $RTE Whether the RTE could have been loaded.
122 *
123 * @return string The new item value.
124 * @throws \InvalidArgumentException
125 */
126 protected function renderWizards($itemKinds, $wizConf, $table, $row, $field, $PA, $itemName, $specConf, $RTE = false)
127 {
128 // Return not changed main item directly if wizards are disabled
129 if (!is_array($wizConf) || $this->isWizardsDisabled()) {
130 return $itemKinds[0];
131 }
132
133 $languageService = $this->getLanguageService();
134
135 $fieldChangeFunc = $PA['fieldChangeFunc'];
136 $item = $itemKinds[0];
137 $md5ID = 'ID' . GeneralUtility::shortmd5($itemName);
138 $prefixOfFormElName = 'data[' . $table . '][' . $row['uid'] . '][' . $field . ']';
139 $flexFormPath = '';
140 if (GeneralUtility::isFirstPartOfStr($PA['itemFormElName'], $prefixOfFormElName)) {
141 $flexFormPath = str_replace('][', '/', substr($PA['itemFormElName'], strlen($prefixOfFormElName) + 1, -1));
142 }
143
144 // Add a suffix-value if the item is a selector box with renderType "selectSingleBox":
145 if ($PA['fieldConf']['config']['type'] === 'select' && (int)$PA['fieldConf']['config']['maxitems'] > 1 && $PA['fieldConf']['config']['renderType'] === 'selectSingleBox') {
146 $itemName .= '[]';
147 }
148
149 // Contains wizard identifiers enabled for this record type, see "special configuration" docs
150 $wizardsEnabledByType = $specConf['wizards']['parameters'];
151
152 $buttonWizards = array();
153 $otherWizards = array();
154 foreach ($wizConf as $wizardIdentifier => $wizardConfiguration) {
155 if (!isset($wizardConfiguration['module']['name']) && isset($wizardConfiguration['script'])) {
156 throw new \InvalidArgumentException('The way registering a wizard in TCA has changed in 6.2 and was removed in CMS 7. '
157 . 'Please set module[name]=module_name instead of using script=path/to/script.php in your TCA. ', 1437750231);
158 }
159
160 // If an identifier starts with "_", this is a configuration option like _POSITION and not a wizard
161 if ($wizardIdentifier[0] === '_') {
162 continue;
163 }
164
165 // Sanitize wizard type
166 $wizardConfiguration['type'] = (string)$wizardConfiguration['type'];
167
168 // Wizards can be shown based on selected "type" of record. If this is the case, the wizard configuration
169 // is set to enableByTypeConfig = 1, and the wizardIdentifier is found in $wizardsEnabledByType
170 $wizardIsEnabled = true;
171 if (
172 isset($wizardConfiguration['enableByTypeConfig'])
173 && (bool)$wizardConfiguration['enableByTypeConfig']
174 && (!is_array($wizardsEnabledByType) || !in_array($wizardIdentifier, $wizardsEnabledByType))
175 ) {
176 $wizardIsEnabled = false;
177 }
178 // Disable if wizard is for RTE fields only and the handled field is no RTE field or RTE can not be loaded
179 if (isset($wizardConfiguration['RTEonly']) && (bool)$wizardConfiguration['RTEonly'] && !$RTE) {
180 $wizardIsEnabled = false;
181 }
182 // Disable if wizard is for not-new records only and we're handling a new record
183 if (isset($wizardConfiguration['notNewRecords']) && $wizardConfiguration['notNewRecords'] && !MathUtility::canBeInterpretedAsInteger($row['uid'])) {
184 $wizardIsEnabled = false;
185 }
186 // Wizard types script, colorbox and popup must contain a module name configuration
187 if (!isset($wizardConfiguration['module']['name']) && in_array($wizardConfiguration['type'], array('script', 'colorbox', 'popup'), true)) {
188 $wizardIsEnabled = false;
189 }
190
191 if (!$wizardIsEnabled) {
192 continue;
193 }
194
195 // Title / icon:
196 $iTitle = htmlspecialchars($languageService->sL($wizardConfiguration['title']));
197 if (isset($wizardConfiguration['icon'])) {
198 $icon = FormEngineUtility::getIconHtml($wizardConfiguration['icon'], $iTitle, $iTitle);
199 } else {
200 $icon = $iTitle;
201 }
202
203 switch ($wizardConfiguration['type']) {
204 case 'userFunc':
205 $params = array();
206 $params['params'] = $wizardConfiguration['params'];
207 $params['exampleImg'] = $wizardConfiguration['exampleImg'];
208 $params['table'] = $table;
209 $params['uid'] = $row['uid'];
210 $params['pid'] = $row['pid'];
211 $params['field'] = $field;
212 $params['flexFormPath'] = $flexFormPath;
213 $params['md5ID'] = $md5ID;
214 $params['returnUrl'] = $this->data['returnUrl'];
215
216 $params['formName'] = 'editform';
217 $params['itemName'] = $itemName;
218 $params['hmac'] = GeneralUtility::hmac($params['formName'] . $params['itemName'], 'wizard_js');
219 $params['fieldChangeFunc'] = $fieldChangeFunc;
220 $params['fieldChangeFuncHash'] = GeneralUtility::hmac(serialize($fieldChangeFunc));
221
222 $params['item'] = &$item;
223 $params['icon'] = $icon;
224 $params['iTitle'] = $iTitle;
225 $params['wConf'] = $wizardConfiguration;
226 $params['row'] = $row;
227 $otherWizards[] = GeneralUtility::callUserFunction($wizardConfiguration['userFunc'], $params, $this);
228 break;
229
230 case 'script':
231 $params = array();
232 $params['params'] = $wizardConfiguration['params'];
233 $params['exampleImg'] = $wizardConfiguration['exampleImg'];
234 $params['table'] = $table;
235 $params['uid'] = $row['uid'];
236 $params['pid'] = $row['pid'];
237 $params['field'] = $field;
238 $params['flexFormPath'] = $flexFormPath;
239 $params['md5ID'] = $md5ID;
240 $params['returnUrl'] = $this->data['returnUrl'];
241
242 // Resolving script filename and setting URL.
243 $urlParameters = array();
244 if (isset($wizardConfiguration['module']['urlParameters']) && is_array($wizardConfiguration['module']['urlParameters'])) {
245 $urlParameters = $wizardConfiguration['module']['urlParameters'];
246 }
247 $wScript = BackendUtility::getModuleUrl($wizardConfiguration['module']['name'], $urlParameters, '');
248 $url = $wScript . (strstr($wScript, '?') ? '' : '?') . GeneralUtility::implodeArrayForUrl('', array('P' => $params));
249 $buttonWizards[] =
250 '<a class="btn btn-default" href="' . htmlspecialchars($url) . '" onclick="this.blur(); return !TBE_EDITOR.isFormChanged();">'
251 . $icon .
252 '</a>';
253 break;
254
255 case 'popup':
256 $params = array();
257 $params['params'] = $wizardConfiguration['params'];
258 $params['exampleImg'] = $wizardConfiguration['exampleImg'];
259 $params['table'] = $table;
260 $params['uid'] = $row['uid'];
261 $params['pid'] = $row['pid'];
262 $params['field'] = $field;
263 $params['flexFormPath'] = $flexFormPath;
264 $params['md5ID'] = $md5ID;
265 $params['returnUrl'] = $this->data['returnUrl'];
266
267 $params['formName'] = 'editform';
268 $params['itemName'] = $itemName;
269 $params['hmac'] = GeneralUtility::hmac($params['formName'] . $params['itemName'], 'wizard_js');
270 $params['fieldChangeFunc'] = $fieldChangeFunc;
271 $params['fieldChangeFuncHash'] = GeneralUtility::hmac(serialize($fieldChangeFunc));
272
273 // Resolving script filename and setting URL.
274 $urlParameters = array();
275 if (isset($wizardConfiguration['module']['urlParameters']) && is_array($wizardConfiguration['module']['urlParameters'])) {
276 $urlParameters = $wizardConfiguration['module']['urlParameters'];
277 }
278 $wScript = BackendUtility::getModuleUrl($wizardConfiguration['module']['name'], $urlParameters, '');
279 $url = $wScript . (strstr($wScript, '?') ? '' : '?') . GeneralUtility::implodeArrayForUrl('', array('P' => $params));
280
281 $onlyIfSelectedJS = '';
282 if (isset($wizardConfiguration['popup_onlyOpenIfSelected']) && $wizardConfiguration['popup_onlyOpenIfSelected']) {
283 $notSelectedText = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:mess.noSelItemForEdit');
284 $onlyIfSelectedJS =
285 'if (!TBE_EDITOR.curSelected(' . GeneralUtility::quoteJSvalue($itemName) . ')){' .
286 'alert(' . GeneralUtility::quoteJSvalue($notSelectedText) . ');' .
287 'return false;' .
288 '}';
289 }
290 $aOnClick =
291 'this.blur();' .
292 $onlyIfSelectedJS .
293 'vHWin=window.open(' . GeneralUtility::quoteJSvalue($url) . '+\'&P[currentValue]=\'+TBE_EDITOR.rawurlencode(' .
294 'document.editform[' . GeneralUtility::quoteJSvalue($itemName) . '].value,300' .
295 ')' .
296 '+\'&P[currentSelectedValues]=\'+TBE_EDITOR.curSelected(' . GeneralUtility::quoteJSvalue($itemName) . '),' .
297 GeneralUtility::quoteJSvalue('popUp' . $md5ID) . ',' .
298 GeneralUtility::quoteJSvalue($wizardConfiguration['JSopenParams']) .
299 ');' .
300 'vHWin.focus();' .
301 'return false;';
302
303 $buttonWizards[] =
304 '<a class="btn btn-default" href="#" onclick="' . htmlspecialchars($aOnClick) . '">' .
305 $icon .
306 '</a>';
307 break;
308
309 case 'colorbox':
310 $params = array();
311 $params['params'] = $wizardConfiguration['params'];
312 $params['exampleImg'] = $wizardConfiguration['exampleImg'];
313 $params['table'] = $table;
314 $params['uid'] = $row['uid'];
315 $params['pid'] = $row['pid'];
316 $params['field'] = $field;
317 $params['flexFormPath'] = $flexFormPath;
318 $params['md5ID'] = $md5ID;
319 $params['returnUrl'] = $this->data['returnUrl'];
320
321 $params['formName'] = 'editform';
322 $params['itemName'] = $itemName;
323 $params['hmac'] = GeneralUtility::hmac($params['formName'] . $params['itemName'], 'wizard_js');
324 $params['fieldChangeFunc'] = $fieldChangeFunc;
325 $params['fieldChangeFuncHash'] = GeneralUtility::hmac(serialize($fieldChangeFunc));
326
327 // Resolving script filename and setting URL.
328 $urlParameters = array();
329 if (isset($wizardConfiguration['module']['urlParameters']) && is_array($wizardConfiguration['module']['urlParameters'])) {
330 $urlParameters = $wizardConfiguration['module']['urlParameters'];
331 }
332 $wScript = BackendUtility::getModuleUrl($wizardConfiguration['module']['name'], $urlParameters, '');
333 $url = $wScript . (strstr($wScript, '?') ? '' : '?') . GeneralUtility::implodeArrayForUrl('', array('P' => $params));
334
335 $aOnClick =
336 'this.blur();' .
337 'vHWin=window.open(' . GeneralUtility::quoteJSvalue($url) . '+\'&P[currentValue]=\'+TBE_EDITOR.rawurlencode(' .
338 'document.editform[' . GeneralUtility::quoteJSvalue($itemName) . '].value,300' .
339 ')' .
340 '+\'&P[currentSelectedValues]=\'+TBE_EDITOR.curSelected(' . GeneralUtility::quoteJSvalue($itemName) . '),' .
341 GeneralUtility::quoteJSvalue('popUp' . $md5ID) . ',' .
342 GeneralUtility::quoteJSvalue($wizardConfiguration['JSopenParams']) .
343 ');' .
344 'vHWin.focus();' .
345 'return false;';
346
347 $otherWizards[] = '<a id="' . $md5ID . '" class="btn btn-default" href="#" onclick="' . htmlspecialchars($aOnClick) . '"><span class="t3-icon fa fa-eyedropper"></span></a>';
348 break;
349 case 'slider':
350 $params = array();
351 $params['fieldConfig'] = $PA['fieldConf']['config'];
352 $params['field'] = $field;
353 $params['table'] = $table;
354 $params['flexFormPath'] = $flexFormPath;
355 $params['md5ID'] = $md5ID;
356 $params['itemName'] = $itemName;
357 $params['wConf'] = $wizardConfiguration;
358 $params['row'] = $row;
359
360 /** @var ValueSliderWizard $wizard */
361 $wizard = GeneralUtility::makeInstance(ValueSliderWizard::class);
362 $otherWizards[] = $wizard->renderWizard($params);
363 break;
364
365 case 'select':
366 // The select wizard is a select drop down added to the main element. It provides all the functionality
367 // that select items can do for us, so we process this element via data processing.
368 // @todo: This should be embedded in an own provider called in the main data group to not handle this on the fly here
369
370 // Select wizard page TS can be set in TCEFORM."table"."field".wizards."wizardName"
371 $pageTsConfig = [];
372 if (isset($this->data['pageTsConfig']['TCEFORM.'][$table . '.'][$field . '.']['wizards.'][$wizardIdentifier . '.'])
373 && is_array($this->data['pageTsConfig']['TCEFORM.'][$table . '.'][$field . '.']['wizards.'][$wizardIdentifier . '.'])
374 ) {
375 $pageTsConfig['TCEFORM.']['dummySelectWizard.'][$wizardIdentifier . '.'] = $this->data['pageTsConfig']['TCEFORM.'][$table . '.'][$field . '.']['wizards.'][$wizardIdentifier . '.'];
376 }
377 $selectWizardDataInput = [
378 'tableName' => 'dummySelectWizard',
379 'command' => 'edit',
380 'pageTsConfig' => $pageTsConfig,
381 'processedTca' => [
382 'ctrl' => [],
383 'columns' => [
384 $wizardIdentifier => [
385 'type' => 'select',
386 'renderType' => 'selectSingle',
387 'config' => $wizardConfiguration,
388 ],
389 ],
390 ],
391 ];
392 /** @var OnTheFly $formDataGroup */
393 $formDataGroup = GeneralUtility::makeInstance(OnTheFly::class);
394 $formDataGroup->setProviderList([ TcaSelectItems::class ]);
395 /** @var FormDataCompiler $formDataCompiler */
396 $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
397 $compilerResult = $formDataCompiler->compile($selectWizardDataInput);
398 $selectWizardItems = $compilerResult['processedTca']['columns'][$wizardIdentifier]['config']['items'];
399
400 $options = [];
401 $options[] = '<option>' . $iTitle . '</option>';
402 foreach ($selectWizardItems as $selectWizardItem) {
403 $options[] = '<option value="' . htmlspecialchars($selectWizardItem[1]) . '">' . htmlspecialchars($selectWizardItem[0]) . '</option>';
404 }
405 if ($wizardConfiguration['mode'] == 'append') {
406 $assignValue = 'document.querySelectorAll(' . GeneralUtility::quoteJSvalue('[data-formengine-input-name="' . $itemName . '"]') . ')[0].value=\'\'+this.options[this.selectedIndex].value+document.editform[' . GeneralUtility::quoteJSvalue($itemName) . '].value';
407 } elseif ($wizardConfiguration['mode'] == 'prepend') {
408 $assignValue = 'document.querySelectorAll(' . GeneralUtility::quoteJSvalue('[data-formengine-input-name="' . $itemName . '"]') . ')[0].value+=\'\'+this.options[this.selectedIndex].value';
409 } else {
410 $assignValue = 'document.querySelectorAll(' . GeneralUtility::quoteJSvalue('[data-formengine-input-name="' . $itemName . '"]') . ')[0].value=this.options[this.selectedIndex].value';
411 }
412 $otherWizards[] =
413 '<select' .
414 ' id="' . StringUtility::getUniqueId('tceforms-select-') . '"' .
415 ' class="form-control tceforms-select tceforms-wizardselect"' .
416 ' onchange="' . htmlspecialchars($assignValue . ';this.blur();this.selectedIndex=0;' . implode('', $fieldChangeFunc)) . '"' .
417 '>' .
418 implode('', $options) .
419 '</select>';
420 break;
421 case 'suggest':
422 if (!empty($PA['fieldTSConfig']['suggest.']['default.']['hide'])) {
423 break;
424 }
425 /** @var SuggestWizard $suggestWizard */
426 $suggestWizard = GeneralUtility::makeInstance(SuggestWizard::class);
427 $otherWizards[] = $suggestWizard->renderSuggestSelector($PA['itemFormElName'], $table, $field, $row, $PA);
428 break;
429 }
430 }
431
432 // For each rendered wizard, put them together around the item.
433 if (!empty($buttonWizards) || !empty($otherWizards)) {
434 $innerContent = '';
435 if (!empty($buttonWizards)) {
436 $innerContent .= '<div class="btn-group' . ($wizConf['_VERTICAL'] ? ' btn-group-vertical' : '') . '">' . implode('', $buttonWizards) . '</div>';
437 }
438 $innerContent .= implode(' ', $otherWizards);
439
440 // Position
441 $classes = array('form-wizards-wrap');
442 if ($wizConf['_POSITION'] === 'left') {
443 $classes[] = 'form-wizards-aside';
444 $innerContent = '<div class="form-wizards-items">' . $innerContent . '</div><div class="form-wizards-element">' . $item . '</div>';
445 } elseif ($wizConf['_POSITION'] === 'top') {
446 $classes[] = 'form-wizards-top';
447 $innerContent = '<div class="form-wizards-items">' . $innerContent . '</div><div class="form-wizards-element">' . $item . '</div>';
448 } elseif ($wizConf['_POSITION'] === 'bottom') {
449 $classes[] = 'form-wizards-bottom';
450 $innerContent = '<div class="form-wizards-element">' . $item . '</div><div class="form-wizards-items">' . $innerContent . '</div>';
451 } else {
452 $classes[] = 'form-wizards-aside';
453 $innerContent = '<div class="form-wizards-element">' . $item . '</div><div class="form-wizards-items">' . $innerContent . '</div>';
454 }
455 $item = '
456 <div class="' . implode(' ', $classes) . '">
457 ' . $innerContent . '
458 </div>';
459 }
460
461 return $item;
462 }
463
464 /**
465 * Prints the selector box form-field for the db/file/select elements (multiple)
466 *
467 * @param string $fName Form element name
468 * @param string $mode Mode "db", "file" (internal_type for the "group" type) OR blank (then for the "select" type)
469 * @param string $allowed Commalist of "allowed
470 * @param array $itemArray The array of items. For "select" and "group"/"file" this is just a set of value. For "db" its an array of arrays with table/uid pairs.
471 * @param string $selector Alternative selector box.
472 * @param array $params An array of additional parameters, eg: "size", "info", "headers" (array with "selector" and "items"), "noBrowser", "thumbnails
473 * @param string $onFocus On focus attribute string
474 * @param string $table (optional) Table name processing for
475 * @param string $field (optional) Field of table name processing for
476 * @param string $uid (optional) uid of table record processing for
477 * @param array $config (optional) The TCA field config
478 * @return string The form fields for the selection.
479 * @throws \UnexpectedValueException
480 * @todo: Hack this mess into pieces and inline to group / select element depending on what they need
481 */
482 protected function dbFileIcons($fName, $mode, $allowed, $itemArray, $selector = '', $params = array(), $onFocus = '', $table = '', $field = '', $uid = '', $config = array())
483 {
484 $languageService = $this->getLanguageService();
485 $disabled = '';
486 if ($params['readOnly']) {
487 $disabled = ' disabled="disabled"';
488 }
489 // INIT
490 $uidList = array();
491 $opt = array();
492 $itemArrayC = 0;
493 // Creating <option> elements:
494 if (is_array($itemArray)) {
495 $itemArrayC = count($itemArray);
496 switch ($mode) {
497 case 'db':
498 foreach ($itemArray as $pp) {
499 $pRec = BackendUtility::getRecordWSOL($pp['table'], $pp['id']);
500 if (is_array($pRec)) {
501 $pTitle = BackendUtility::getRecordTitle($pp['table'], $pRec, false, true);
502 $pUid = $pp['table'] . '_' . $pp['id'];
503 $uidList[] = $pUid;
504 $title = htmlspecialchars($pTitle);
505 $opt[] = '<option value="' . htmlspecialchars($pUid) . '" title="' . $title . '">' . $title . '</option>';
506 }
507 }
508 break;
509 case 'file_reference':
510
511 case 'file':
512 foreach ($itemArray as $item) {
513 $itemParts = explode('|', $item);
514 $uidList[] = ($pUid = ($pTitle = $itemParts[0]));
515 $title = htmlspecialchars(rawurldecode($itemParts[1]));
516 $opt[] = '<option value="' . htmlspecialchars(rawurldecode($itemParts[0])) . '" title="' . $title . '">' . $title . '</option>';
517 }
518 break;
519 case 'folder':
520 foreach ($itemArray as $pp) {
521 $pParts = explode('|', $pp);
522 $uidList[] = ($pUid = ($pTitle = $pParts[0]));
523 $title = htmlspecialchars(rawurldecode($pParts[0]));
524 $opt[] = '<option value="' . htmlspecialchars(rawurldecode($pParts[0])) . '" title="' . $title . '">' . $title . '</option>';
525 }
526 break;
527 default:
528 foreach ($itemArray as $pp) {
529 $pParts = explode('|', $pp, 2);
530 $uidList[] = ($pUid = $pParts[0]);
531 $pTitle = $pParts[1];
532 $title = htmlspecialchars(rawurldecode($pTitle));
533 $opt[] = '<option value="' . htmlspecialchars(rawurldecode($pUid)) . '" title="' . $title . '">' . $title . '</option>';
534 }
535 }
536 }
537 // Create selector box of the options
538 $sSize = $params['autoSizeMax']
539 ? MathUtility::forceIntegerInRange($itemArrayC + 1, MathUtility::forceIntegerInRange($params['size'], 1), $params['autoSizeMax'])
540 : $params['size'];
541 if (!$selector) {
542 $isMultiple = $params['maxitems'] != 1 && $params['size'] != 1;
543 $selector = '<select id="' . StringUtility::getUniqueId('tceforms-multiselect-') . '" '
544 . ($params['noList'] ? 'style="display: none"' : 'size="' . $sSize . '" class="form-control tceforms-multiselect"')
545 . ($isMultiple ? ' multiple="multiple"' : '')
546 . ' data-formengine-input-name="' . htmlspecialchars($fName) . '" ' . $this->getValidationDataAsDataAttribute($config) . $onFocus . $params['style'] . $disabled . '>' . implode('', $opt)
547 . '</select>';
548 }
549 $icons = array(
550 'L' => array(),
551 'R' => array()
552 );
553 $rOnClickInline = '';
554 if (!$params['readOnly'] && !$params['noList']) {
555 if (!$params['noBrowser']) {
556 // Check against inline uniqueness
557 /** @var InlineStackProcessor $inlineStackProcessor */
558 $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
559 $inlineStackProcessor->initializeByGivenStructure($this->data['inlineStructure']);
560 $aOnClickInline = '';
561 if ($this->data['isInlineChild'] && $this->data['inlineParentUid']) {
562 if ($this->data['inlineParentConfig']['foreign_table'] === $table
563 && $this->data['inlineParentConfig']['foreign_unique'] === $field
564 ) {
565 $objectPrefix = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']) . '-' . $table;
566 $aOnClickInline = $objectPrefix . '|inline.checkUniqueElement|inline.setUniqueElement';
567 $rOnClickInline = 'inline.revertUnique(' . GeneralUtility::quoteJSvalue($objectPrefix) . ',null,' . GeneralUtility::quoteJSvalue($uid) . ');';
568 }
569 }
570 if (is_array($config['appearance']) && isset($config['appearance']['elementBrowserType'])) {
571 $elementBrowserType = $config['appearance']['elementBrowserType'];
572 } else {
573 $elementBrowserType = $mode;
574 }
575 if (is_array($config['appearance']) && isset($config['appearance']['elementBrowserAllowed'])) {
576 $elementBrowserAllowed = $config['appearance']['elementBrowserAllowed'];
577 } else {
578 $elementBrowserAllowed = $allowed;
579 }
580 $aOnClick = 'setFormValueOpenBrowser(' . GeneralUtility::quoteJSvalue($elementBrowserType) . ','
581 . GeneralUtility::quoteJSvalue(($fName . '|||' . $elementBrowserAllowed . '|' . $aOnClickInline)) . '); return false;';
582 $icons['R'][] = '
583 <a href="#"
584 onclick="' . htmlspecialchars($aOnClick) . '"
585 class="btn btn-default"
586 title="' . htmlspecialchars($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.browse_' . ($mode == 'db' ? 'db' : 'file'))) . '">
587 ' . $this->iconFactory->getIcon('actions-insert-record', Icon::SIZE_SMALL)->render() . '
588 </a>';
589 }
590 if (!$params['dontShowMoveIcons']) {
591 if ($sSize >= 5) {
592 $icons['L'][] = '
593 <a href="#"
594 class="btn btn-default t3js-btn-moveoption-top"
595 data-fieldname="' . $fName . '"
596 title="' . htmlspecialchars($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.move_to_top')) . '">
597 ' . $this->iconFactory->getIcon('actions-move-to-top', Icon::SIZE_SMALL)->render() . '
598 </a>';
599 }
600 $icons['L'][] = '
601 <a href="#"
602 class="btn btn-default t3js-btn-moveoption-up"
603 data-fieldname="' . $fName . '"
604 title="' . htmlspecialchars($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.move_up')) . '">
605 ' . $this->iconFactory->getIcon('actions-move-up', Icon::SIZE_SMALL)->render() . '
606 </a>';
607 $icons['L'][] = '
608 <a href="#"
609 class="btn btn-default t3js-btn-moveoption-down"
610 data-fieldname="' . $fName . '"
611 title="' . htmlspecialchars($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.move_down')) . '">
612 ' . $this->iconFactory->getIcon('actions-move-down', Icon::SIZE_SMALL)->render() . '
613 </a>';
614 if ($sSize >= 5) {
615 $icons['L'][] = '
616 <a href="#"
617 class="btn btn-default t3js-btn-moveoption-bottom"
618 data-fieldname="' . $fName . '"
619 title="' . htmlspecialchars($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.move_to_bottom')) . '">
620 ' . $this->iconFactory->getIcon('actions-move-to-bottom', Icon::SIZE_SMALL)->render() . '
621 </a>';
622 }
623 }
624 $clipElements = $this->getClipboardElements($allowed, $mode);
625 if (!empty($clipElements)) {
626 $aOnClick = '';
627 foreach ($clipElements as $elValue) {
628 if ($mode == 'db') {
629 list($itemTable, $itemUid) = explode('|', $elValue);
630 $recordTitle = BackendUtility::getRecordTitle($itemTable, BackendUtility::getRecordWSOL($itemTable, $itemUid));
631 $itemTitle = GeneralUtility::quoteJSvalue($recordTitle);
632 $elValue = $itemTable . '_' . $itemUid;
633 } else {
634 // 'file', 'file_reference' and 'folder' mode
635 $itemTitle = 'unescape(' . GeneralUtility::quoteJSvalue(rawurlencode(basename($elValue))) . ')';
636 }
637 $aOnClick .= 'setFormValueFromBrowseWin(' . GeneralUtility::quoteJSvalue($fName) . ',unescape('
638 . GeneralUtility::quoteJSvalue(rawurlencode(str_replace('%20', ' ', $elValue))) . '),' . $itemTitle . ',' . $itemTitle . ');';
639 }
640 $aOnClick .= 'return false;';
641 $icons['R'][] = '
642 <a href="#"
643 class="btn btn-default"
644 onclick="' . htmlspecialchars($aOnClick) . '"
645 title="' . htmlspecialchars(sprintf($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.clipInsert_' . ($mode == 'db' ? 'db' : 'file')), count($clipElements))) . '">
646 ' . $this->iconFactory->getIcon('actions-document-paste-into', Icon::SIZE_SMALL)->render() . '
647 </a>';
648 }
649 }
650 if (!$params['readOnly'] && !$params['noDelete']) {
651 $icons['L'][] = '
652 <a href="#"
653 class="btn btn-default t3js-btn-removeoption"
654 onClick="' . $rOnClickInline . '"
655 data-fieldname="' . $fName . '"
656 title="' . htmlspecialchars($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.remove_selected')) . '">
657 ' . $this->iconFactory->getIcon('actions-selection-delete', Icon::SIZE_SMALL)->render() . '
658 </a>';
659 }
660
661 // Thumbnails
662 $imagesOnly = false;
663 if ($params['thumbnails'] && $params['allowed']) {
664 // In case we have thumbnails, check if only images are allowed.
665 // In this case, render them below the field, instead of to the right
666 $allowedExtensionList = $params['allowed'];
667 $imageExtensionList = GeneralUtility::trimExplode(',', strtolower($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext']), true);
668 $imagesOnly = true;
669 foreach ($allowedExtensionList as $allowedExtension) {
670 if (!ArrayUtility::inArray($imageExtensionList, $allowedExtension)) {
671 $imagesOnly = false;
672 break;
673 }
674 }
675 }
676 $thumbnails = '';
677 if (is_array($params['thumbnails']) && !empty($params['thumbnails'])) {
678 if ($imagesOnly) {
679 $thumbnails .= '<ul class="list-inline">';
680 foreach ($params['thumbnails'] as $thumbnail) {
681 $thumbnails .= '<li><span class="thumbnail">' . $thumbnail['image'] . '</span></li>';
682 }
683 $thumbnails .= '</ul>';
684 } else {
685 $thumbnails .= '<div class="table-fit"><table class="table table-white"><tbody>';
686 foreach ($params['thumbnails'] as $thumbnail) {
687 $thumbnails .= '
688 <tr>
689 <td class="col-icon">
690 ' . ($config['internal_type'] === 'db'
691 ? BackendUtility::wrapClickMenuOnIcon($thumbnail['image'], $thumbnail['table'], $thumbnail['uid'], 1, '', '+copy,info,edit,view')
692 : $thumbnail['image']) . '
693 </td>
694 <td class="col-title">
695 ' . ($config['internal_type'] === 'db'
696 ? BackendUtility::wrapClickMenuOnIcon($thumbnail['name'], $thumbnail['table'], $thumbnail['uid'], 1, '', '+copy,info,edit,view')
697 : $thumbnail['name']) . '
698 ' . ($config['internal_type'] === 'db' ? ' <span class="text-muted">[' . $thumbnail['uid'] . ']</span>' : '') . '
699 </td>
700 </tr>
701 ';
702 }
703 $thumbnails .= '</tbody></table></div>';
704 }
705 }
706
707 // Allowed Tables
708 $allowedTables = '';
709 if (is_array($params['allowedTables']) && !empty($params['allowedTables'])) {
710 $allowedTables .= '<div class="help-block">';
711 foreach ($params['allowedTables'] as $key => $item) {
712 if (is_array($item)) {
713 if (empty($params['readOnly'])) {
714 $allowedTables .= '<a href="#" onClick="' . htmlspecialchars($item['onClick']) . '" class="btn btn-default">' . $item['icon'] . ' ' . htmlspecialchars($item['name']) . '</a> ';
715 } else {
716 $allowedTables .= '<span>' . htmlspecialchars($item['name']) . '</span> ';
717 }
718 } elseif ($key === 'name') {
719 $allowedTables .= '<span>' . htmlspecialchars($item) . '</span> ';
720 }
721 }
722 $allowedTables .= '</div>';
723 }
724 // Allowed
725 $allowedList = '';
726 if (is_array($params['allowed']) && !empty($params['allowed'])) {
727 foreach ($params['allowed'] as $item) {
728 $allowedList .= '<span class="label label-success">' . strtoupper($item) . '</span> ';
729 }
730 }
731 // Disallowed
732 $disallowedList = '';
733 if (is_array($params['disallowed']) && !empty($params['disallowed'])) {
734 foreach ($params['disallowed'] as $item) {
735 $disallowedList .= '<span class="label label-danger">' . strtoupper($item) . '</span> ';
736 }
737 }
738 // Rightbox
739 $rightbox = ($params['rightbox'] ?: '');
740
741 // Hook: dbFileIcons_postProcess (requested by FAL-team for use with the "fal" extension)
742 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tceforms.php']['dbFileIcons'])) {
743 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tceforms.php']['dbFileIcons'] as $classRef) {
744 $hookObject = GeneralUtility::getUserObj($classRef);
745 if (!$hookObject instanceof DatabaseFileIconsHookInterface) {
746 throw new \UnexpectedValueException($classRef . ' must implement interface ' . DatabaseFileIconsHookInterface::class, 1290167704);
747 }
748 $additionalParams = array(
749 'mode' => $mode,
750 'allowed' => $allowed,
751 'itemArray' => $itemArray,
752 'onFocus' => $onFocus,
753 'table' => $table,
754 'field' => $field,
755 'uid' => $uid,
756 'config' => $GLOBALS['TCA'][$table]['columns'][$field]
757 );
758 $hookObject->dbFileIcons_postProcess($params, $selector, $thumbnails, $icons, $rightbox, $fName, $uidList, $additionalParams, $this);
759 }
760 }
761
762 // Output
763 $str = '
764 ' . ($params['headers']['selector'] ? '<label>' . $params['headers']['selector'] . '</label>' : '') . '
765 <div class="form-wizards-wrap form-wizards-aside">
766 <div class="form-wizards-element">
767 ' . $selector . '
768 ' . (!$params['noList'] && !empty($allowedTables) ? $allowedTables : '') . '
769 ' . (!$params['noList'] && (!empty($allowedList) || !empty($disallowedList))
770 ? '<div class="help-block">' . $allowedList . $disallowedList . ' </div>'
771 : '') . '
772 </div>
773 ' . (!empty($icons['L']) ? '<div class="form-wizards-items"><div class="btn-group-vertical">' . implode('', $icons['L']) . '</div></div>' : '') . '
774 ' . (!empty($icons['R']) ? '<div class="form-wizards-items"><div class="btn-group-vertical">' . implode('', $icons['R']) . '</div></div>' : '') . '
775 </div>
776 ';
777 if ($rightbox) {
778 $str = '
779 <div class="form-multigroup-wrap t3js-formengine-field-group">
780 <div class="form-multigroup-item form-multigroup-element">' . $str . '</div>
781 <div class="form-multigroup-item form-multigroup-element">
782 ' . ($params['headers']['items'] ? '<label>' . $params['headers']['items'] . '</label>' : '') . '
783 ' . ($params['headers']['selectorbox'] ? '<div class="form-multigroup-item-wizard">' . $params['headers']['selectorbox'] . '</div>' : '') . '
784 ' . $rightbox . '
785 </div>
786 </div>
787 ';
788 }
789 $str .= $thumbnails;
790
791 // Creating the hidden field which contains the actual value as a comma list.
792 $str .= '<input type="hidden" name="' . $fName . '" value="' . htmlspecialchars(implode(',', $uidList)) . '" />';
793 return $str;
794 }
795
796 /**
797 * Returns array of elements from clipboard to insert into GROUP element box.
798 *
799 * @param string $allowed Allowed elements, Eg "pages,tt_content", "gif,jpg,jpeg,png
800 * @param string $mode Mode of relations: "db" or "file
801 * @return array Array of elements in values (keys are insignificant), if none found, empty array.
802 */
803 protected function getClipboardElements($allowed, $mode)
804 {
805 if (!is_object($this->clipboard)) {
806 $this->clipboard = GeneralUtility::makeInstance(Clipboard::class);
807 $this->clipboard->initializeClipboard();
808 }
809
810 $output = array();
811 switch ($mode) {
812 case 'file_reference':
813
814 case 'file':
815 $elFromTable = $this->clipboard->elFromTable('_FILE');
816 $allowedExts = GeneralUtility::trimExplode(',', $allowed, true);
817 // If there are a set of allowed extensions, filter the content:
818 if ($allowedExts) {
819 foreach ($elFromTable as $elValue) {
820 $pI = pathinfo($elValue);
821 $ext = strtolower($pI['extension']);
822 if (in_array($ext, $allowedExts)) {
823 $output[] = $elValue;
824 }
825 }
826 } else {
827 // If all is allowed, insert all: (This does NOT respect any disallowed extensions,
828 // but those will be filtered away by the backend TCEmain)
829 $output = $elFromTable;
830 }
831 break;
832 case 'db':
833 $allowedTables = GeneralUtility::trimExplode(',', $allowed, true);
834 // All tables allowed for relation:
835 if (trim($allowedTables[0]) === '*') {
836 $output = $this->clipboard->elFromTable('');
837 } else {
838 // Only some tables, filter them:
839 foreach ($allowedTables as $tablename) {
840 $elFromTable = $this->clipboard->elFromTable($tablename);
841 $output = array_merge($output, $elFromTable);
842 }
843 }
844 $output = array_keys($output);
845 break;
846 }
847
848 return $output;
849 }
850
851 /**
852 * @return LanguageService
853 */
854 protected function getLanguageService()
855 {
856 return $GLOBALS['LANG'];
857 }
858 }