Total Complexity | 130 |
Total Lines | 640 |
Duplicated Lines | 0 % |
Changes | 1 | ||
Bugs | 0 | Features | 0 |
Complex classes like InlineRecordContainer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use InlineRecordContainer, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
47 | class InlineRecordContainer extends AbstractContainer |
||
48 | { |
||
49 | /** |
||
50 | * Inline data array used for JSON output |
||
51 | * |
||
52 | * @var array |
||
53 | */ |
||
54 | protected $inlineData = []; |
||
55 | |||
56 | /** |
||
57 | * @var InlineStackProcessor |
||
58 | */ |
||
59 | protected $inlineStackProcessor; |
||
60 | |||
61 | /** |
||
62 | * Array containing instances of hook classes called once for IRRE objects |
||
63 | * |
||
64 | * @var array |
||
65 | */ |
||
66 | protected $hookObjects = []; |
||
67 | |||
68 | /** |
||
69 | * @var IconFactory |
||
70 | */ |
||
71 | protected $iconFactory; |
||
72 | |||
73 | /** |
||
74 | * Default constructor |
||
75 | * |
||
76 | * @param NodeFactory $nodeFactory |
||
77 | * @param array $data |
||
78 | */ |
||
79 | public function __construct(NodeFactory $nodeFactory, array $data) |
||
85 | } |
||
86 | |||
87 | /** |
||
88 | * Entry method |
||
89 | * |
||
90 | * @return array As defined in initializeResultArray() of AbstractNode |
||
91 | * @throws AccessDeniedContentEditException |
||
92 | */ |
||
93 | public function render() |
||
94 | { |
||
95 | $data = $this->data; |
||
96 | $this->inlineData = $data['inlineData']; |
||
97 | |||
98 | $inlineStackProcessor = $this->inlineStackProcessor; |
||
99 | $inlineStackProcessor->initializeByGivenStructure($data['inlineStructure']); |
||
100 | |||
101 | $record = $data['databaseRow']; |
||
102 | $inlineConfig = $data['inlineParentConfig']; |
||
103 | $foreignTable = $inlineConfig['foreign_table']; |
||
104 | |||
105 | $resultArray = $this->initializeResultArray(); |
||
106 | |||
107 | // Send a mapping information to the browser via JSON: |
||
108 | // e.g. data[<curTable>][<curId>][<curField>] => data-<pid>-<parentTable>-<parentId>-<parentField>-<curTable>-<curId>-<curField> |
||
109 | $formPrefix = $inlineStackProcessor->getCurrentStructureFormPrefix(); |
||
110 | $domObjectId = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($data['inlineFirstPid']); |
||
111 | $this->inlineData['map'][$formPrefix] = $domObjectId; |
||
112 | |||
113 | $resultArray['inlineData'] = $this->inlineData; |
||
114 | |||
115 | // Get the current naming scheme for DOM name/id attributes: |
||
116 | $appendFormFieldNames = '[' . $foreignTable . '][' . ($record['uid'] ?? 0) . ']'; |
||
117 | $objectId = $domObjectId . '-' . $foreignTable . '-' . ($record['uid'] ?? 0); |
||
118 | $classes = []; |
||
119 | $html = ''; |
||
120 | $combinationHtml = ''; |
||
121 | $isNewRecord = $data['command'] === 'new'; |
||
122 | $hiddenField = ''; |
||
123 | if (isset($data['processedTca']['ctrl']['enablecolumns']['disabled'])) { |
||
124 | $hiddenField = $data['processedTca']['ctrl']['enablecolumns']['disabled']; |
||
125 | } |
||
126 | if (!$data['isInlineDefaultLanguageRecordInLocalizedParentContext']) { |
||
127 | if ($isNewRecord || $data['isInlineChildExpanded']) { |
||
128 | // Render full content ONLY IF this is an AJAX request, a new record, or the record is not collapsed |
||
129 | $combinationHtml = ''; |
||
130 | if (isset($data['combinationChild'])) { |
||
131 | $combinationChild = $this->renderCombinationChild($data, $appendFormFieldNames); |
||
132 | $combinationHtml = $combinationChild['html']; |
||
133 | $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $combinationChild, false); |
||
134 | } |
||
135 | $childArray = $this->renderChild($data); |
||
136 | $html = $childArray['html']; |
||
137 | $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $childArray, false); |
||
138 | } else { |
||
139 | // This class is the marker for the JS-function to check if the full content has already been loaded |
||
140 | $classes[] = 't3js-not-loaded'; |
||
141 | } |
||
142 | if ($isNewRecord) { |
||
143 | // Add pid of record as hidden field |
||
144 | $html .= '<input type="hidden" name="data' . htmlspecialchars($appendFormFieldNames) |
||
145 | . '[pid]" value="' . htmlspecialchars($record['pid']) . '"/>'; |
||
146 | // Tell DataHandler this record is expanded |
||
147 | $ucFieldName = 'uc[inlineView]' |
||
148 | . '[' . $data['inlineTopMostParentTableName'] . ']' |
||
149 | . '[' . $data['inlineTopMostParentUid'] . ']' |
||
150 | . $appendFormFieldNames; |
||
151 | $html .= '<input type="hidden" name="' . htmlspecialchars($ucFieldName) |
||
152 | . '" value="' . (int)$data['isInlineChildExpanded'] . '" />'; |
||
153 | } else { |
||
154 | // Set additional field for processing for saving |
||
155 | $html .= '<input type="hidden" name="cmd' . htmlspecialchars($appendFormFieldNames) |
||
156 | . '[delete]" value="1" disabled="disabled" />'; |
||
157 | if (!$data['isInlineChildExpanded'] && !empty($hiddenField)) { |
||
158 | $checked = !empty($record[$hiddenField]) ? ' checked="checked"' : ''; |
||
159 | $html .= '<input type="checkbox" data-formengine-input-name="data' |
||
160 | . htmlspecialchars($appendFormFieldNames) |
||
161 | . '[' . htmlspecialchars($hiddenField) . ']" value="1"' . $checked . ' />'; |
||
162 | $html .= '<input type="input" name="data' . htmlspecialchars($appendFormFieldNames) |
||
163 | . '[' . htmlspecialchars($hiddenField) . ']" value="' . htmlspecialchars($record[$hiddenField]) . '" />'; |
||
164 | } |
||
165 | } |
||
166 | // If this record should be shown collapsed |
||
167 | $classes[] = $data['isInlineChildExpanded'] ? 'panel-visible' : 'panel-collapsed'; |
||
168 | } |
||
169 | $hiddenFieldHtml = implode(LF, $resultArray['additionalHiddenFields'] ?? []); |
||
170 | |||
171 | if ($inlineConfig['renderFieldsOnly'] ?? false) { |
||
172 | // Render "body" part only |
||
173 | $html .= $combinationHtml; |
||
174 | } else { |
||
175 | // Render header row and content (if expanded) |
||
176 | if ($data['isInlineDefaultLanguageRecordInLocalizedParentContext']) { |
||
177 | $classes[] = 't3-form-field-container-inline-placeHolder'; |
||
178 | } |
||
179 | if (!empty($hiddenField) && isset($record[$hiddenField]) && (int)$record[$hiddenField]) { |
||
180 | $classes[] = 't3-form-field-container-inline-hidden'; |
||
181 | } |
||
182 | if ($isNewRecord) { |
||
183 | $classes[] = 'inlineIsNewRecord'; |
||
184 | } |
||
185 | |||
186 | $originalUniqueValue = ''; |
||
187 | if (isset($record['uid'], $data['inlineData']['unique'][$domObjectId . '-' . $foreignTable]['used'][$record['uid']])) { |
||
188 | $uniqueValueValues = $data['inlineData']['unique'][$domObjectId . '-' . $foreignTable]['used'][$record['uid']]; |
||
189 | // in case of site_language we don't have the full form engine options, so fallbacks need to be taken into account |
||
190 | $originalUniqueValue = ($uniqueValueValues['table'] ?? $foreignTable) . '_'; |
||
191 | // @todo In what circumstance would $uniqueValueValues be an array that lacks a 'uid' key? Unclear, but |
||
192 | // it breaks the string concatenation. This is a hacky workaround for type safety only. |
||
193 | $uVV = ($uniqueValueValues['uid'] ?? $uniqueValueValues); |
||
194 | if (is_array($uVV)) { |
||
195 | $uVV = implode(',', $uVV); |
||
196 | } |
||
197 | $originalUniqueValue .= $uVV; |
||
198 | } |
||
199 | |||
200 | // The hashed object id needs a non-numeric prefix, the value is used as ID selector in JavaScript |
||
201 | $hashedObjectId = 'hash-' . md5($objectId); |
||
202 | $containerAttributes = [ |
||
203 | 'id' => $objectId . '_div', |
||
204 | 'class' => 'form-irre-object panel panel-default panel-condensed ' . trim(implode(' ', $classes)), |
||
205 | 'data-object-uid' => $record['uid'] ?? 0, |
||
206 | 'data-object-id' => $objectId, |
||
207 | 'data-object-id-hash' => $hashedObjectId, |
||
208 | 'data-object-parent-group' => $domObjectId . '-' . $foreignTable, |
||
209 | 'data-field-name' => $appendFormFieldNames, |
||
210 | 'data-topmost-parent-table' => $data['inlineTopMostParentTableName'], |
||
211 | 'data-topmost-parent-uid' => $data['inlineTopMostParentUid'], |
||
212 | 'data-table-unique-original-value' => $originalUniqueValue, |
||
213 | 'data-placeholder-record' => $data['isInlineDefaultLanguageRecordInLocalizedParentContext'] ? '1' : '0' |
||
214 | ]; |
||
215 | |||
216 | $ariaExpanded = ($data['isInlineChildExpanded'] ?? false) ? 'true' : 'false'; |
||
217 | $ariaControls = htmlspecialchars($objectId . '_fields', ENT_QUOTES | ENT_HTML5); |
||
218 | $ariaAttributesString = 'aria-expanded="' . $ariaExpanded . '" aria-controls="' . $ariaControls . '"'; |
||
219 | $html = ' |
||
220 | <div ' . GeneralUtility::implodeAttributes($containerAttributes, true) . '> |
||
221 | <div class="panel-heading" data-bs-toggle="formengine-inline" id="' . htmlspecialchars($hashedObjectId, ENT_QUOTES | ENT_HTML5) . '_header" data-expandSingle="' . (($inlineConfig['appearance']['expandSingle'] ?? false) ? 1 : 0) . '"> |
||
222 | <div class="form-irre-header"> |
||
223 | <div class="form-irre-header-cell form-irre-header-icon"> |
||
224 | <span class="caret"></span> |
||
225 | </div> |
||
226 | ' . $this->renderForeignRecordHeader($data, $ariaAttributesString) . ' |
||
227 | </div> |
||
228 | </div> |
||
229 | <div class="panel-collapse" id="' . $ariaControls . '">' . $html . $hiddenFieldHtml . $combinationHtml . '</div> |
||
230 | </div>'; |
||
231 | } |
||
232 | |||
233 | $resultArray['html'] = $html; |
||
234 | return $resultArray; |
||
235 | } |
||
236 | |||
237 | /** |
||
238 | * Render inner child |
||
239 | * |
||
240 | * @param array $data |
||
241 | * @return array Result array |
||
242 | */ |
||
243 | protected function renderChild(array $data) |
||
244 | { |
||
245 | $domObjectId = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($data['inlineFirstPid']); |
||
246 | $data['tabAndInlineStack'][] = [ |
||
247 | 'inline', |
||
248 | $domObjectId . '-' . $data['tableName'] . '-' . $data['databaseRow']['uid'], |
||
249 | ]; |
||
250 | // @todo: ugly construct ... |
||
251 | $data['inlineData'] = $this->inlineData; |
||
252 | $data['renderType'] = 'fullRecordContainer'; |
||
253 | return $this->nodeFactory->create($data)->render(); |
||
254 | } |
||
255 | |||
256 | /** |
||
257 | * Render child child |
||
258 | * |
||
259 | * Render a table with FormEngine, that occurs on an intermediate table but should be editable directly, |
||
260 | * so two tables are combined (the intermediate table with attributes and the sub-embedded table). |
||
261 | * -> This is a direct embedding over two levels! |
||
262 | * |
||
263 | * @param array $data |
||
264 | * @param string $appendFormFieldNames The [<table>][<uid>] of the parent record (the intermediate table) |
||
265 | * @return array Result array |
||
266 | */ |
||
267 | protected function renderCombinationChild(array $data, $appendFormFieldNames) |
||
268 | { |
||
269 | $childData = $data['combinationChild']; |
||
270 | $parentConfig = $data['inlineParentConfig']; |
||
271 | |||
272 | // If field is set to readOnly, set all fields of the relation to readOnly as well |
||
273 | if (isset($parentConfig['readOnly']) && $parentConfig['readOnly']) { |
||
274 | foreach ($childData['processedTca']['columns'] as $columnName => $columnConfiguration) { |
||
275 | $childData['processedTca']['columns'][$columnName]['config']['readOnly'] = true; |
||
276 | } |
||
277 | } |
||
278 | |||
279 | $resultArray = $this->initializeResultArray(); |
||
280 | |||
281 | // Display Warning FlashMessage if it is not suppressed |
||
282 | if (!isset($parentConfig['appearance']['suppressCombinationWarning']) || empty($parentConfig['appearance']['suppressCombinationWarning'])) { |
||
283 | $combinationWarningMessage = 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.inline_use_combination'; |
||
284 | if (!empty($parentConfig['appearance']['overwriteCombinationWarningMessage'])) { |
||
285 | $combinationWarningMessage = $parentConfig['appearance']['overwriteCombinationWarningMessage']; |
||
286 | } |
||
287 | $message = $this->getLanguageService()->sL($combinationWarningMessage); |
||
288 | $markup = []; |
||
289 | // @TODO: This is not a FlashMessage! The markup must be changed and special CSS |
||
290 | // @TODO: should be created, in order to prevent confusion. |
||
291 | $markup[] = '<div class="alert alert-warning">'; |
||
292 | $markup[] = ' <div class="media">'; |
||
293 | $markup[] = ' <div class="media-left">'; |
||
294 | $markup[] = ' <span class="fa-stack fa-lg">'; |
||
295 | $markup[] = ' <i class="fa fa-circle fa-stack-2x"></i>'; |
||
296 | $markup[] = ' <i class="fa fa-exclamation fa-stack-1x"></i>'; |
||
297 | $markup[] = ' </span>'; |
||
298 | $markup[] = ' </div>'; |
||
299 | $markup[] = ' <div class="media-body">'; |
||
300 | $markup[] = ' <div class="alert-message">' . htmlspecialchars($message) . '</div>'; |
||
301 | $markup[] = ' </div>'; |
||
302 | $markup[] = ' </div>'; |
||
303 | $markup[] = '</div>'; |
||
304 | $resultArray['html'] = implode(LF, $markup); |
||
305 | } |
||
306 | |||
307 | $childArray = $this->renderChild($childData); |
||
308 | $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $childArray); |
||
309 | |||
310 | // If this is a new record, add a pid value to store this record and the pointer value for the intermediate table |
||
311 | if ($childData['command'] === 'new') { |
||
312 | $comboFormFieldName = 'data[' . $childData['tableName'] . '][' . $childData['databaseRow']['uid'] . '][pid]'; |
||
313 | $resultArray['html'] .= '<input type="hidden" name="' . htmlspecialchars($comboFormFieldName) . '" value="' . htmlspecialchars($childData['databaseRow']['pid']) . '" />'; |
||
314 | } |
||
315 | // If the foreign_selector field is also responsible for uniqueness, tell the browser the uid of the "other" side of the relation |
||
316 | if ($childData['command'] === 'new' || $parentConfig['foreign_unique'] === $parentConfig['foreign_selector']) { |
||
317 | $parentFormFieldName = 'data' . $appendFormFieldNames . '[' . $parentConfig['foreign_selector'] . ']'; |
||
318 | $resultArray['html'] .= '<input type="hidden" name="' . htmlspecialchars($parentFormFieldName) . '" value="' . htmlspecialchars($childData['databaseRow']['uid']) . '" />'; |
||
319 | } |
||
320 | |||
321 | return $resultArray; |
||
322 | } |
||
323 | |||
324 | /** |
||
325 | * Renders the HTML header for a foreign record, such as the title, toggle-function, drag'n'drop, etc. |
||
326 | * Later on the command-icons are inserted here. |
||
327 | * |
||
328 | * @param array $data Current data |
||
329 | * @param string $ariaAttributesString HTML aria attributes for the collapse button |
||
330 | * @return string The HTML code of the header |
||
331 | */ |
||
332 | protected function renderForeignRecordHeader(array $data, string $ariaAttributesString) |
||
333 | { |
||
334 | $languageService = $this->getLanguageService(); |
||
335 | $inlineConfig = $data['inlineParentConfig']; |
||
336 | $foreignTable = $inlineConfig['foreign_table']; |
||
337 | $rec = $data['databaseRow']; |
||
338 | // Init: |
||
339 | $domObjectId = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($data['inlineFirstPid']); |
||
340 | $objectId = $domObjectId . '-' . $foreignTable . '-' . ($rec['uid'] ?? 0); |
||
341 | |||
342 | $recordTitle = $data['recordTitle']; |
||
343 | if (!empty($recordTitle)) { |
||
344 | // The user function may return HTML, therefore we can't escape it |
||
345 | if (empty($data['processedTca']['ctrl']['formattedLabel_userFunc'])) { |
||
346 | $recordTitle = BackendUtility::getRecordTitlePrep($recordTitle); |
||
347 | } |
||
348 | } else { |
||
349 | $recordTitle = '<em>[' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.no_title')) . ']</em>'; |
||
350 | } |
||
351 | |||
352 | $altText = BackendUtility::getRecordIconAltText($rec, $foreignTable); |
||
353 | |||
354 | $iconImg = '<span title="' . $altText . '" id="' . htmlspecialchars($objectId) . '_icon">' . $this->iconFactory->getIconForRecord($foreignTable, $rec, Icon::SIZE_SMALL)->render() . '</span>'; |
||
355 | $label = '<span id="' . $objectId . '_label">' . $recordTitle . '</span>'; |
||
356 | $ctrl = $this->renderForeignRecordHeaderControl($data); |
||
357 | $thumbnail = false; |
||
358 | |||
359 | // Renders a thumbnail for the header |
||
360 | if (($GLOBALS['TYPO3_CONF_VARS']['GFX']['thumbnails'] ?? false) && !empty($inlineConfig['appearance']['headerThumbnail']['field'])) { |
||
361 | $fieldValue = $rec[$inlineConfig['appearance']['headerThumbnail']['field']]; |
||
362 | $fileUid = $fieldValue[0]['uid']; |
||
363 | |||
364 | if (!empty($fileUid)) { |
||
365 | try { |
||
366 | $fileObject = GeneralUtility::makeInstance(ResourceFactory::class)->getFileObject($fileUid); |
||
367 | } catch (\InvalidArgumentException $e) { |
||
368 | $fileObject = null; |
||
369 | } |
||
370 | if ($fileObject && $fileObject->isMissing()) { |
||
371 | $thumbnail .= '<span class="label label-danger">' |
||
372 | . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.file_missing')) |
||
373 | . '</span> ' . htmlspecialchars($fileObject->getName()) . '<br />'; |
||
374 | } elseif ($fileObject) { |
||
375 | $imageSetup = $inlineConfig['appearance']['headerThumbnail'] ?? []; |
||
376 | unset($imageSetup['field']); |
||
377 | $cropVariantCollection = CropVariantCollection::create($rec['crop'] ?? ''); |
||
378 | if (!$cropVariantCollection->getCropArea()->isEmpty()) { |
||
379 | $imageSetup['crop'] = $cropVariantCollection->getCropArea()->makeAbsoluteBasedOnFile($fileObject); |
||
380 | } |
||
381 | $imageSetup = array_merge(['maxWidth' => '145', 'maxHeight' => '45'], $imageSetup); |
||
382 | |||
383 | if (($GLOBALS['TYPO3_CONF_VARS']['GFX']['thumbnails'] ?? false) && $fileObject->isImage()) { |
||
384 | $processedImage = $fileObject->process(ProcessedFile::CONTEXT_IMAGECROPSCALEMASK, $imageSetup); |
||
385 | // Only use a thumbnail if the processing process was successful by checking if image width is set |
||
386 | if ($processedImage->getProperty('width')) { |
||
387 | $imageUrl = $processedImage->getPublicUrl() ?? ''; |
||
388 | $thumbnail = '<img src="' . PathUtility::getAbsoluteWebPath($imageUrl) . '" ' . |
||
389 | 'width="' . $processedImage->getProperty('width') . '" ' . |
||
390 | 'height="' . $processedImage->getProperty('height') . '" ' . |
||
391 | 'alt="' . htmlspecialchars($altText) . '" ' . |
||
392 | 'title="' . htmlspecialchars($altText) . '">'; |
||
393 | } |
||
394 | } else { |
||
395 | $thumbnail = ''; |
||
396 | } |
||
397 | } |
||
398 | } |
||
399 | } |
||
400 | |||
401 | if (!empty($inlineConfig['appearance']['headerThumbnail']['field']) && $thumbnail) { |
||
402 | $mediaContainer = '<div class="form-irre-header-thumbnail" id="' . $objectId . '_thumbnailcontainer">' . $thumbnail . '</div>'; |
||
403 | } else { |
||
404 | $mediaContainer = '<div class="form-irre-header-icon" id="' . $objectId . '_iconcontainer">' . $iconImg . '</div>'; |
||
405 | } |
||
406 | $header = '<button class="form-irre-header-cell form-irre-header-button" ' . $ariaAttributesString . '>' . |
||
407 | $mediaContainer . |
||
408 | '<div class="form-irre-header-body">' . $label . '</div>' . |
||
409 | '</button>' . |
||
410 | '<div class="form-irre-header-cell form-irre-header-control t3js-formengine-irre-control">' . $ctrl . '</div>'; |
||
411 | |||
412 | return $header; |
||
413 | } |
||
414 | |||
415 | /** |
||
416 | * Render the control-icons for a record header (create new, sorting, delete, disable/enable). |
||
417 | * Most of the parts are copy&paste from TYPO3\CMS\Recordlist\RecordList\DatabaseRecordList and |
||
418 | * modified for the JavaScript calls here |
||
419 | * |
||
420 | * @param array $data Current data |
||
421 | * @return string The HTML code with the control-icons |
||
422 | */ |
||
423 | protected function renderForeignRecordHeaderControl(array $data) |
||
424 | { |
||
425 | $rec = $data['databaseRow']; |
||
426 | $rec += [ |
||
427 | 'uid' => 0, |
||
428 | 'table_local' => '', |
||
429 | 'sys_language_uid' => '', |
||
430 | ]; |
||
431 | $inlineConfig = $data['inlineParentConfig']; |
||
432 | $foreignTable = $inlineConfig['foreign_table']; |
||
433 | $languageService = $this->getLanguageService(); |
||
434 | $backendUser = $this->getBackendUserAuthentication(); |
||
435 | // Initialize: |
||
436 | $cells = [ |
||
437 | 'edit' => '', |
||
438 | 'hide' => '', |
||
439 | 'delete' => '', |
||
440 | 'info' => '', |
||
441 | 'new' => '', |
||
442 | 'sort.up' => '', |
||
443 | 'sort.down' => '', |
||
444 | 'dragdrop' => '', |
||
445 | 'localize' => '', |
||
446 | 'locked' => '', |
||
447 | ]; |
||
448 | $isNewItem = strpos($rec['uid'], 'NEW') === 0; |
||
449 | $isParentReadOnly = isset($inlineConfig['readOnly']) && $inlineConfig['readOnly']; |
||
450 | $isParentExisting = MathUtility::canBeInterpretedAsInteger($data['inlineParentUid']); |
||
451 | $tcaTableCtrl = $GLOBALS['TCA'][$foreignTable]['ctrl']; |
||
452 | $tcaTableCols = $GLOBALS['TCA'][$foreignTable]['columns']; |
||
453 | $isPagesTable = $foreignTable === 'pages'; |
||
454 | $isSysFileReferenceTable = $foreignTable === 'sys_file_reference'; |
||
455 | $enableManualSorting = ($tcaTableCtrl['sortby'] ?? false) |
||
456 | || ($inlineConfig['MM'] ?? false) |
||
457 | || (!($data['isOnSymmetricSide'] ?? false) && ($inlineConfig['foreign_sortby'] ?? false)) |
||
458 | || (($data['isOnSymmetricSide'] ?? false) && ($inlineConfig['symmetric_sortby'] ?? false)); |
||
459 | $calcPerms = new Permission($backendUser->calcPerms(BackendUtility::readPageAccess((int)($data['parentPageRow']['uid'] ?? 0), $backendUser->getPagePermsClause(Permission::PAGE_SHOW)))); |
||
|
|||
460 | // If the listed table is 'pages' we have to request the permission settings for each page: |
||
461 | $localCalcPerms = new Permission(Permission::NOTHING); |
||
462 | if ($isPagesTable) { |
||
463 | $localCalcPerms = new Permission($backendUser->calcPerms(BackendUtility::getRecord('pages', $rec['uid']))); |
||
464 | } |
||
465 | // This expresses the edit permissions for this particular element: |
||
466 | $permsEdit = ($isPagesTable && $localCalcPerms->editPagePermissionIsGranted()) || (!$isPagesTable && $calcPerms->editContentPermissionIsGranted()); |
||
467 | // Controls: Defines which controls should be shown |
||
468 | $enabledControls = $inlineConfig['appearance']['enabledControls']; |
||
469 | // Hook: Can disable/enable single controls for specific child records: |
||
470 | foreach ($this->hookObjects as $hookObj) { |
||
471 | /** @var InlineElementHookInterface $hookObj */ |
||
472 | $hookObj->renderForeignRecordHeaderControl_preProcess($data['inlineParentUid'], $foreignTable, $rec, $inlineConfig, $data['isInlineDefaultLanguageRecordInLocalizedParentContext'], $enabledControls); |
||
473 | } |
||
474 | if ($data['isInlineDefaultLanguageRecordInLocalizedParentContext']) { |
||
475 | $cells['localize'] = '<span title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:localize.isLocalizable')) . '"> |
||
476 | ' . $this->iconFactory->getIcon('actions-edit-localize-status-low', Icon::SIZE_SMALL)->render() . ' |
||
477 | </span>'; |
||
478 | } |
||
479 | // "Info": (All records) |
||
480 | // @todo: hardcoded sys_file! |
||
481 | if ($rec['table_local'] === 'sys_file') { |
||
482 | $uid = $rec['uid_local'][0]['uid']; |
||
483 | $table = '_FILE'; |
||
484 | } else { |
||
485 | $uid = $rec['uid']; |
||
486 | $table = $foreignTable; |
||
487 | } |
||
488 | if ($enabledControls['info']) { |
||
489 | if ($isNewItem) { |
||
490 | $cells['info'] = '<span class="btn btn-default disabled">' . $this->iconFactory->getIcon('empty-empty', Icon::SIZE_SMALL)->render() . '</span>'; |
||
491 | } else { |
||
492 | $cells['info'] = ' |
||
493 | <button type="button" class="btn btn-default" data-action="infowindow" data-info-table="' . htmlspecialchars($table) . '" data-info-uid="' . htmlspecialchars($uid) . '" title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:showInfo')) . '"> |
||
494 | ' . $this->iconFactory->getIcon('actions-document-info', Icon::SIZE_SMALL)->render() . ' |
||
495 | </button>'; |
||
496 | } |
||
497 | } |
||
498 | // If the table is NOT a read-only table, then show these links: |
||
499 | if (!$isParentReadOnly && !($tcaTableCtrl['readOnly'] ?? false) && !($data['isInlineDefaultLanguageRecordInLocalizedParentContext'] ?? false)) { |
||
500 | // "New record after" link (ONLY if the records in the table are sorted by a "sortby"-row or if default values can depend on previous record): |
||
501 | if (($enabledControls['new'] ?? false) && ($enableManualSorting || ($tcaTableCtrl['useColumnsForDefaultValues'] ?? false))) { |
||
502 | if ((!$isPagesTable && $calcPerms->editContentPermissionIsGranted()) || ($isPagesTable && $calcPerms->createPagePermissionIsGranted())) { |
||
503 | $style = ''; |
||
504 | if ($inlineConfig['inline']['inlineNewButtonStyle'] ?? false) { |
||
505 | $style = ' style="' . $inlineConfig['inline']['inlineNewButtonStyle'] . '"'; |
||
506 | } |
||
507 | $cells['new'] = ' |
||
508 | <button type="button" class="btn btn-default t3js-create-new-button" data-record-uid="' . htmlspecialchars($rec['uid']) . '" title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:new' . ($isPagesTable ? 'Page' : 'Record'))) . '" ' . $style . '> |
||
509 | ' . $this->iconFactory->getIcon('actions-' . ($isPagesTable ? 'page-new' : 'add'), Icon::SIZE_SMALL)->render() . ' |
||
510 | </button>'; |
||
511 | } |
||
512 | } |
||
513 | // "Up/Down" links |
||
514 | if ($enabledControls['sort'] && $permsEdit && $enableManualSorting) { |
||
515 | // Up |
||
516 | $icon = 'actions-move-up'; |
||
517 | $class = ''; |
||
518 | if ($inlineConfig['inline']['first'] == $rec['uid']) { |
||
519 | $class = ' disabled'; |
||
520 | $icon = 'empty-empty'; |
||
521 | } |
||
522 | $cells['sort.up'] = ' |
||
523 | <button type="button" class="btn btn-default' . $class . '" data-action="sort" data-direction="up" title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:moveUp')) . '"> |
||
524 | ' . $this->iconFactory->getIcon($icon, Icon::SIZE_SMALL)->render() . ' |
||
525 | </button>'; |
||
526 | // Down |
||
527 | $icon = 'actions-move-down'; |
||
528 | $class = ''; |
||
529 | if ($inlineConfig['inline']['last'] == $rec['uid']) { |
||
530 | $class = ' disabled'; |
||
531 | $icon = 'empty-empty'; |
||
532 | } |
||
533 | |||
534 | $cells['sort.down'] = ' |
||
535 | <button type="button" class="btn btn-default' . $class . '" data-action="sort" data-direction="down" title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:moveDown')) . '"> |
||
536 | ' . $this->iconFactory->getIcon($icon, Icon::SIZE_SMALL)->render() . ' |
||
537 | </button>'; |
||
538 | } |
||
539 | // "Edit" link: |
||
540 | if (($rec['table_local'] === 'sys_file') && !$isNewItem && $backendUser->check('tables_modify', 'sys_file_metadata')) { |
||
541 | $sys_language_uid = 0; |
||
542 | if (!empty($rec['sys_language_uid'])) { |
||
543 | $sys_language_uid = $rec['sys_language_uid'][0]; |
||
544 | } |
||
545 | $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) |
||
546 | ->getQueryBuilderForTable('sys_file_metadata'); |
||
547 | $recordInDatabase = $queryBuilder |
||
548 | ->select('uid') |
||
549 | ->from('sys_file_metadata') |
||
550 | ->where( |
||
551 | $queryBuilder->expr()->eq( |
||
552 | 'file', |
||
553 | $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT) |
||
554 | ), |
||
555 | $queryBuilder->expr()->eq( |
||
556 | 'sys_language_uid', |
||
557 | $queryBuilder->createNamedParameter($sys_language_uid, \PDO::PARAM_INT) |
||
558 | ) |
||
559 | ) |
||
560 | ->setMaxResults(1) |
||
561 | ->execute() |
||
562 | ->fetch(); |
||
563 | if (!empty($recordInDatabase)) { |
||
564 | $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class); |
||
565 | $url = (string)$uriBuilder->buildUriFromRoute('record_edit', [ |
||
566 | 'edit[sys_file_metadata][' . (int)$recordInDatabase['uid'] . ']' => 'edit', |
||
567 | 'returnUrl' => $this->data['returnUrl'] |
||
568 | ]); |
||
569 | $title = $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.editMetadata'); |
||
570 | $cells['edit'] = ' |
||
571 | <a class="btn btn-default" href="' . htmlspecialchars($url) . '" title="' . htmlspecialchars($title) . '"> |
||
572 | ' . $this->iconFactory->getIcon('actions-open', Icon::SIZE_SMALL)->render() . ' |
||
573 | </a>'; |
||
574 | } |
||
575 | } |
||
576 | // "Delete" link: |
||
577 | if ($enabledControls['delete'] && (($isPagesTable && $localCalcPerms->deletePagePermissionIsGranted()) |
||
578 | || (!$isPagesTable && $calcPerms->editContentPermissionIsGranted()) |
||
579 | || ($isSysFileReferenceTable && $calcPerms->editPagePermissionIsGranted())) |
||
580 | ) { |
||
581 | $title = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:delete')); |
||
582 | $icon = $this->iconFactory->getIcon('actions-edit-delete', Icon::SIZE_SMALL)->render(); |
||
583 | $cells['delete'] = '<button type="button" class="btn btn-default t3js-editform-delete-inline-record" title="' . $title . '">' . $icon . '</button>'; |
||
584 | } |
||
585 | |||
586 | // "Hide/Unhide" links: |
||
587 | $hiddenField = $tcaTableCtrl['enablecolumns']['disabled'] ?? ''; |
||
588 | if (($enabledControls['hide'] ?? false) |
||
589 | && $permsEdit |
||
590 | && $hiddenField |
||
591 | && $tcaTableCols[$hiddenField] |
||
592 | && (!($tcaTableCols[$hiddenField]['exclude'] ?? false) || $backendUser->check('non_exclude_fields', $foreignTable . ':' . $hiddenField)) |
||
593 | ) { |
||
594 | if ($rec[$hiddenField]) { |
||
595 | $title = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:unHide' . ($isPagesTable ? 'Page' : ''))); |
||
596 | $cells['hide'] = ' |
||
597 | <button type="button" class="btn btn-default t3js-toggle-visibility-button" data-hidden-field="' . htmlspecialchars($hiddenField) . '" title="' . $title . '"> |
||
598 | ' . $this->iconFactory->getIcon('actions-edit-unhide', Icon::SIZE_SMALL)->render() . ' |
||
599 | </button>'; |
||
600 | } else { |
||
601 | $title = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:hide' . ($isPagesTable ? 'Page' : ''))); |
||
602 | $cells['hide'] = ' |
||
603 | <button type="button" class="btn btn-default t3js-toggle-visibility-button" data-hidden-field="' . htmlspecialchars($hiddenField) . '" title="' . $title . '"> |
||
604 | ' . $this->iconFactory->getIcon('actions-edit-hide', Icon::SIZE_SMALL)->render() . ' |
||
605 | </button>'; |
||
606 | } |
||
607 | } |
||
608 | // Drag&Drop Sorting: Sortable handler for script.aculo.us |
||
609 | if (($enabledControls['dragdrop'] ?? false) && $permsEdit && $enableManualSorting && ($inlineConfig['appearance']['useSortable'] ?? false)) { |
||
610 | $cells['dragdrop'] = ' |
||
611 | <span class="btn btn-default sortableHandle" data-id="' . htmlspecialchars($rec['uid']) . '" title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.move')) . '"> |
||
612 | ' . $this->iconFactory->getIcon('actions-move-move', Icon::SIZE_SMALL)->render() . ' |
||
613 | </span>'; |
||
614 | } |
||
615 | } elseif (($data['isInlineDefaultLanguageRecordInLocalizedParentContext'] ?? false) && $isParentExisting) { |
||
616 | if (($enabledControls['localize'] ?? false) && ($data['isInlineDefaultLanguageRecordInLocalizedParentContext'] ?? false)) { |
||
617 | $cells['localize'] = ' |
||
618 | <button type="button" class="btn btn-default t3js-synchronizelocalize-button" data-type="' . htmlspecialchars($rec['uid']) . '" title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:localize')) . '"> |
||
619 | ' . $this->iconFactory->getIcon('actions-document-localize', Icon::SIZE_SMALL)->render() . ' |
||
620 | </button>'; |
||
621 | } |
||
622 | } |
||
623 | // If the record is edit-locked by another user, we will show a little warning sign: |
||
624 | if ($lockInfo = BackendUtility::isRecordLocked($foreignTable, $rec['uid'])) { |
||
625 | $cells['locked'] = ' |
||
626 | <button type="button" class="btn btn-default" data-bs-toggle="tooltip" title="' . htmlspecialchars($lockInfo['msg']) . '"> |
||
627 | ' . $this->iconFactory->getIcon('warning-in-use', Icon::SIZE_SMALL)->render() . ' |
||
628 | </button>'; |
||
629 | } |
||
630 | // Hook: Post-processing of single controls for specific child records: |
||
631 | foreach ($this->hookObjects as $hookObj) { |
||
632 | $hookObj->renderForeignRecordHeaderControl_postProcess($data['inlineParentUid'], $foreignTable, $rec, $inlineConfig, $data['isInlineDefaultLanguageRecordInLocalizedParentContext'], $cells); |
||
633 | } |
||
634 | |||
635 | $out = ''; |
||
636 | if (!empty($cells['edit']) || !empty($cells['hide']) || !empty($cells['delete'])) { |
||
637 | $out .= '<div class="btn-group btn-group-sm" role="group">' . $cells['edit'] . $cells['hide'] . $cells['delete'] . '</div>'; |
||
638 | unset($cells['edit'], $cells['hide'], $cells['delete']); |
||
639 | } |
||
640 | if (!empty($cells['info']) || !empty($cells['new']) || !empty($cells['sort.up']) || !empty($cells['sort.down']) || !empty($cells['dragdrop'])) { |
||
641 | $out .= '<div class="btn-group btn-group-sm" role="group">' . $cells['info'] . $cells['new'] . $cells['sort.up'] . $cells['sort.down'] . $cells['dragdrop'] . '</div>'; |
||
642 | unset($cells['info'], $cells['new'], $cells['sort.up'], $cells['sort.down'], $cells['dragdrop']); |
||
643 | } |
||
644 | if (!empty($cells['localize'])) { |
||
645 | $out .= '<div class="btn-group btn-group-sm" role="group">' . $cells['localize'] . '</div>'; |
||
646 | unset($cells['localize']); |
||
647 | } |
||
648 | if (!empty($cells)) { |
||
649 | $out .= ' <div class="btn-group btn-group-sm" role="group">' . implode('', $cells) . '</div>'; |
||
650 | } |
||
651 | return $out; |
||
652 | } |
||
653 | |||
654 | /** |
||
655 | * Initialized the hook objects for this class. |
||
656 | * Each hook object has to implement the interface |
||
657 | * \TYPO3\CMS\Backend\Form\Element\InlineElementHookInterface |
||
658 | * |
||
659 | * @throws \UnexpectedValueException |
||
660 | */ |
||
661 | protected function initHookObjects() |
||
670 | } |
||
671 | } |
||
672 | |||
673 | /** |
||
674 | * @return BackendUserAuthentication |
||
675 | */ |
||
676 | protected function getBackendUserAuthentication() |
||
679 | } |
||
680 | |||
681 | /** |
||
682 | * @return LanguageService |
||
683 | */ |
||
684 | protected function getLanguageService() |
||
687 | } |
||
688 | } |
||
689 |