9abbac774aea6e40b3f02ad86c3863200920d35c
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Form / Element / GroupElement.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\Utility\BackendUtility;
18 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
19 use TYPO3\CMS\Core\Imaging\Icon;
20 use TYPO3\CMS\Core\Messaging\FlashMessage;
21 use TYPO3\CMS\Core\Messaging\FlashMessageService;
22 use TYPO3\CMS\Core\Resource\ProcessedFile;
23 use TYPO3\CMS\Core\Resource\ResourceFactory;
24 use TYPO3\CMS\Core\Utility\GeneralUtility;
25 use TYPO3\CMS\Core\Utility\MathUtility;
26
27 /**
28 * Generation of TCEform elements of the type "group"
29 */
30 class GroupElement extends AbstractFormElement
31 {
32 /**
33 * This will render a selector box into which elements from either
34 * the file system or database can be inserted. Relations.
35 *
36 * @return array As defined in initializeResultArray() of AbstractNode
37 * @throws \RuntimeException
38 */
39 public function render()
40 {
41 $table = $this->data['tableName'];
42 $fieldName = $this->data['fieldName'];
43 $row = $this->data['databaseRow'];
44 $parameterArray = $this->data['parameterArray'];
45 $config = $parameterArray['fieldConf']['config'];
46 $show_thumbs = $config['show_thumbs'];
47 $resultArray = $this->initializeResultArray();
48
49 $size = isset($config['size']) ? (int)$config['size'] : $this->minimumInputWidth;
50 $maxitems = MathUtility::forceIntegerInRange($config['maxitems'], 0);
51 if (!$maxitems) {
52 $maxitems = 100000;
53 }
54 $minitems = MathUtility::forceIntegerInRange($config['minitems'], 0);
55 $thumbnails = [];
56 $allowed = GeneralUtility::trimExplode(',', $config['allowed'], true);
57 $disallowed = GeneralUtility::trimExplode(',', $config['disallowed'], true);
58 $disabled = $config['readOnly'];
59 $info = [];
60 $parameterArray['itemFormElID_file'] = $parameterArray['itemFormElID'] . '_files';
61
62 // whether the list and delete controls should be disabled
63 $noList = isset($config['disable_controls']) && GeneralUtility::inList($config['disable_controls'], 'list');
64 $noDelete = isset($config['disable_controls']) && GeneralUtility::inList($config['disable_controls'], 'delete');
65
66 // "Extra" configuration; Returns configuration for the field based on settings found in the "types" fieldlist.
67 $specConf = BackendUtility::getSpecConfParts($parameterArray['fieldConf']['defaultExtras']);
68
69 // Register properties in required elements / validation
70 $attributes['data-formengine-validation-rules'] = htmlspecialchars(
71 $this->getValidationDataAsJsonString(
72 [
73 'minitems' => $minitems,
74 'maxitems' => $maxitems
75 ]
76 )
77 );
78
79 // If maxitems==1 then automatically replace the current item (in list and file selector)
80 if ($maxitems === 1) {
81 $resultArray['additionalJavaScriptPost'][] =
82 'TBE_EDITOR.clearBeforeSettingFormValueFromBrowseWin[' . GeneralUtility::quoteJSvalue($parameterArray['itemFormElName']) . '] = {
83 itemFormElID_file: ' . GeneralUtility::quoteJSvalue($parameterArray['itemFormElID_file']) . '
84 }';
85 $parameterArray['fieldChangeFunc']['TBE_EDITOR_fieldChanged'] = 'setFormValueManipulate(' . GeneralUtility::quoteJSvalue($parameterArray['itemFormElName'])
86 . ', \'Remove\'); ' . $parameterArray['fieldChangeFunc']['TBE_EDITOR_fieldChanged'];
87 } elseif ($noList) {
88 // If the list controls have been removed and the maximum number is reached, remove the first entry to avoid "write once" field
89 $parameterArray['fieldChangeFunc']['TBE_EDITOR_fieldChanged'] = 'setFormValueManipulate(' . GeneralUtility::quoteJSvalue($parameterArray['itemFormElName'])
90 . ', \'RemoveFirstIfFull\', ' . GeneralUtility::quoteJSvalue($maxitems) . '); ' . $parameterArray['fieldChangeFunc']['TBE_EDITOR_fieldChanged'];
91 }
92
93 $html = '<input type="hidden" class="t3js-group-hidden-field" data-formengine-input-name="' . htmlspecialchars($parameterArray['itemFormElName']) . '" value="' . ($config['multiple'] ? 1 : 0) . '"' . $disabled . ' />';
94
95 // Define parameters for all types below
96 $commonParameters = [
97 'size' => $size,
98 'dontShowMoveIcons' => isset($config['hideMoveIcons']) || $maxitems <= 1,
99 'autoSizeMax' => MathUtility::forceIntegerInRange($config['autoSizeMax'], 0),
100 'maxitems' => $maxitems,
101 'style' => isset($config['selectedListStyle'])
102 ? ' style="' . htmlspecialchars($config['selectedListStyle']) . '"'
103 : '',
104 'readOnly' => $disabled,
105 'noBrowser' => $noList || isset($config['disable_controls']) && GeneralUtility::inList($config['disable_controls'], 'browser'),
106 'noList' => $noList,
107 'hideAllowedTables' => GeneralUtility::inList($config['disable_controls'], 'allowedTables'),
108 ];
109
110 // Acting according to either "file" or "db" type:
111 switch ((string)$config['internal_type']) {
112 case 'file_reference':
113 $config['uploadfolder'] = '';
114 // Fall through
115 case 'file':
116 // Creating string showing allowed types:
117 if (empty($allowed)) {
118 $allowed = ['*'];
119 }
120 // Making the array of file items:
121 $itemArray = GeneralUtility::trimExplode(',', $parameterArray['itemFormElValue'], true);
122 $fileFactory = ResourceFactory::getInstance();
123 // Correct the filename for the FAL items
124 foreach ($itemArray as &$fileItem) {
125 list($fileUid, $fileLabel) = explode('|', $fileItem);
126 if (MathUtility::canBeInterpretedAsInteger($fileUid)) {
127 $fileObject = $fileFactory->getFileObject($fileUid);
128 $fileLabel = $fileObject->getName();
129 }
130 $fileItem = $fileUid . '|' . $fileLabel;
131 }
132 // Showing thumbnails:
133 if ($show_thumbs) {
134 foreach ($itemArray as $imgRead) {
135 $imgP = explode('|', $imgRead);
136 $imgPath = rawurldecode($imgP[0]);
137 // FAL icon production
138 if (MathUtility::canBeInterpretedAsInteger($imgP[0])) {
139 $fileObject = $fileFactory->getFileObject($imgP[0]);
140 if ($fileObject->isMissing()) {
141 $thumbnails[] = [
142 'message' => '<span class="label label-danger">'
143 . htmlspecialchars(static::getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:warning.file_missing'))
144 . '</span>&nbsp;' . htmlspecialchars($fileObject->getName()) . '<br />'
145 ];
146 } elseif (GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'], $fileObject->getExtension())) {
147 $thumbnails[] = [
148 'name' => htmlspecialchars($fileObject->getName()),
149 'image' => $fileObject->process(ProcessedFile::CONTEXT_IMAGEPREVIEW, [])->getPublicUrl(true)
150 ];
151 } else {
152 $name = htmlspecialchars($fileObject->getName());
153 // Icon
154 $thumbnails[] = [
155 'name' => $name,
156 'image' => '<span title="' . $name . '">' . $this->iconFactory->getIconForResource($fileObject, Icon::SIZE_SMALL) . '</span>'
157 ];
158 }
159 } else {
160 $rowCopy = [];
161 $rowCopy[$fieldName] = $imgPath;
162 try {
163 $thumbnails[] = [
164 'name' => $imgPath,
165 'image' => BackendUtility::thumbCode(
166 $rowCopy,
167 $table,
168 $fieldName,
169 '',
170 '',
171 $config['uploadfolder'],
172 0,
173 ' align="middle"'
174 )
175 ];
176 } catch (\Exception $exception) {
177 /** @var $flashMessage FlashMessage */
178 $message = $exception->getMessage();
179 $flashMessage = GeneralUtility::makeInstance(
180 FlashMessage::class,
181 $message, '', FlashMessage::ERROR, true
182 );
183 /** @var $flashMessageService FlashMessageService */
184 $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
185 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
186 $defaultFlashMessageQueue->enqueue($flashMessage);
187 $logMessage = $message . ' (' . $table . ':' . $row['uid'] . ')';
188 GeneralUtility::sysLog($logMessage, 'core', GeneralUtility::SYSLOG_SEVERITY_WARNING);
189 }
190 }
191 }
192 }
193 // Creating the element:
194 $params = array_merge($commonParameters, [
195 'allowed' => $allowed,
196 'disallowed' => $disallowed,
197 'thumbnails' => $thumbnails,
198 'noDelete' => $noDelete
199 ]);
200 $html .= $this->dbFileIcons(
201 $parameterArray['itemFormElName'],
202 'file',
203 implode(',', $allowed),
204 $itemArray,
205 '',
206 $params,
207 null,
208 '',
209 '',
210 '',
211 $config
212 );
213 if (!$disabled && !(isset($config['disable_controls']) && GeneralUtility::inList($config['disable_controls'], 'upload'))) {
214 // Adding the upload field:
215 $isDirectFileUploadEnabled = (bool)$this->getBackendUserAuthentication()->uc['edit_docModuleUpload'];
216 if ($isDirectFileUploadEnabled && $config['uploadfolder']) {
217 // Insert the multiple attribute to enable HTML5 multiple file upload
218 $multipleAttribute = '';
219 $multipleFilenameSuffix = '';
220 if (isset($config['maxitems']) && $config['maxitems'] > 1) {
221 $multipleAttribute = ' multiple="multiple"';
222 $multipleFilenameSuffix = '[]';
223 }
224 $html .= '
225 <div id="' . $parameterArray['itemFormElID_file'] . '">
226 <input type="file"' . $multipleAttribute . '
227 name="data_files' . $this->data['elementBaseName'] . $multipleFilenameSuffix . '"
228 size="35" onchange="' . implode('', $parameterArray['fieldChangeFunc']) . '"
229 />
230 </div>';
231 }
232 }
233 break;
234 case 'folder':
235 // If the element is of the internal type "folder":
236 // Array of folder items:
237 $itemArray = GeneralUtility::trimExplode(',', $parameterArray['itemFormElValue'], true);
238 // Creating the element:
239 $params = $commonParameters;
240 $html .= $this->dbFileIcons(
241 $parameterArray['itemFormElName'],
242 'folder',
243 '',
244 $itemArray,
245 '',
246 $params
247 );
248 break;
249 case 'db':
250 // If the element is of the internal type "db":
251 // Creating string showing allowed types:
252 $languageService = $this->getLanguageService();
253
254 $allowedTables = [];
255 if ($allowed[0] === '*') {
256 $allowedTables = [
257 'name' => htmlspecialchars($languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.allTables'))
258 ];
259 } elseif ($allowed) {
260 foreach ($allowed as $allowedTable) {
261 $allowedTables[] = [
262 // @todo: access to globals!
263 'name' => htmlspecialchars($languageService->sL($GLOBALS['TCA'][$allowedTable]['ctrl']['title'])),
264 'icon' => $this->iconFactory->getIconForRecord($allowedTable, [], Icon::SIZE_SMALL)->render(),
265 'onClick' => 'setFormValueOpenBrowser(\'db\', ' . GeneralUtility::quoteJSvalue($parameterArray['itemFormElName'] . '|||' . $allowedTable) . '); return false;'
266 ];
267 }
268 }
269 $perms_clause = $this->getBackendUserAuthentication()->getPagePermsClause(1);
270 $itemArray = [];
271
272 // Thumbnails:
273 // @todo: this is data processing - must be extracted
274 $temp_itemArray = GeneralUtility::trimExplode(',', $parameterArray['itemFormElValue'], true);
275 foreach ($temp_itemArray as $dbRead) {
276 $recordParts = explode('|', $dbRead);
277 list($this_table, $this_uid) = BackendUtility::splitTable_Uid($recordParts[0]);
278
279 $itemArray[] = ['table' => $this_table, 'id' => $this_uid];
280 if (!$disabled && $show_thumbs) {
281 if (empty($this_table)) {
282 throw new \RuntimeException(
283 'Table name could not been determined for field "' . $fieldName . '" in table "' . $table . '". ' .
284 'This should never happen since the table name should have been already prepared in the DataProvider TcaGroup. ' .
285 'Maybe the prepared values have been set to an invalid value by a user defined data provider.',
286 1468149217
287 );
288 }
289 $rr = BackendUtility::getRecordWSOL($this_table, $this_uid);
290 $thumbnails[] = [
291 'name' => BackendUtility::getRecordTitle($this_table, $rr, true),
292 'image' => $this->iconFactory->getIconForRecord($this_table, $rr, Icon::SIZE_SMALL)->render(),
293 'path' => BackendUtility::getRecordPath($rr['pid'], $perms_clause, 15),
294 'uid' => $rr['uid'],
295 'table' => $this_table
296 ];
297 }
298 }
299 // Creating the element:
300 $params = array_merge($commonParameters, [
301 'info' => $info,
302 'allowedTables' => $allowedTables,
303 'thumbnails' => $thumbnails,
304 ]);
305 $html .= $this->dbFileIcons(
306 $parameterArray['itemFormElName'],
307 'db',
308 implode(',', $allowed),
309 $itemArray,
310 '',
311 $params,
312 null,
313 $table,
314 $fieldName,
315 $row['uid'],
316 $config
317 );
318 break;
319 }
320 // Wizards:
321 if (!$disabled) {
322 $html = $this->renderWizards(
323 [$html],
324 $config['wizards'],
325 $table,
326 $row,
327 $fieldName,
328 $parameterArray,
329 $parameterArray['itemFormElName'],
330 $specConf
331 );
332 }
333 $resultArray['html'] = $html;
334 return $resultArray;
335 }
336
337 /**
338 * @return BackendUserAuthentication
339 */
340 protected function getBackendUserAuthentication()
341 {
342 return $GLOBALS['BE_USER'];
343 }
344 }