Passed
Push — master ( cc6329...bce4af )
by
unknown
166:34 queued 142:45
created

ElementInformationController::getRecordActions()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 36
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 19
dl 0
loc 36
rs 9.6333
c 0
b 0
f 0
cc 4
nc 3
nop 3
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the TYPO3 CMS project.
7
 *
8
 * It is free software; you can redistribute it and/or modify it under
9
 * the terms of the GNU General Public License, either version 2
10
 * of the License, or any later version.
11
 *
12
 * For the full copyright and license information, please read the
13
 * LICENSE.txt file that was distributed with this source code.
14
 *
15
 * The TYPO3 project - inspiring people to share!
16
 */
17
18
namespace TYPO3\CMS\Backend\Controller\ContentElement;
19
20
use Doctrine\DBAL\Connection;
21
use Exception;
22
use Psr\Http\Message\ResponseInterface;
23
use Psr\Http\Message\ServerRequestInterface;
24
use TYPO3\CMS\Backend\Backend\Avatar\Avatar;
25
use TYPO3\CMS\Backend\Form\FormDataCompiler;
26
use TYPO3\CMS\Backend\Form\FormDataGroup\TcaDatabaseRecord;
27
use TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException;
28
use TYPO3\CMS\Backend\Routing\PreviewUriBuilder;
29
use TYPO3\CMS\Backend\Routing\UriBuilder;
30
use TYPO3\CMS\Backend\Template\ModuleTemplate;
31
use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
32
use TYPO3\CMS\Backend\Utility\BackendUtility;
33
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
34
use TYPO3\CMS\Core\Database\ConnectionPool;
35
use TYPO3\CMS\Core\Http\HtmlResponse;
36
use TYPO3\CMS\Core\Imaging\Icon;
37
use TYPO3\CMS\Core\Imaging\IconFactory;
38
use TYPO3\CMS\Core\Localization\LanguageService;
39
use TYPO3\CMS\Core\Resource\AbstractFile;
40
use TYPO3\CMS\Core\Resource\File;
41
use TYPO3\CMS\Core\Resource\Folder;
42
use TYPO3\CMS\Core\Resource\Index\MetaDataRepository;
43
use TYPO3\CMS\Core\Resource\Rendering\RendererRegistry;
44
use TYPO3\CMS\Core\Resource\ResourceFactory;
45
use TYPO3\CMS\Core\Type\Bitmask\Permission;
46
use TYPO3\CMS\Core\Utility\GeneralUtility;
47
use TYPO3\CMS\Core\Utility\PathUtility;
48
use TYPO3\CMS\Fluid\View\StandaloneView;
49
50
/**
51
 * Script Class for showing information about an item.
52
 * @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
53
 */
54
class ElementInformationController
55
{
56
    /**
57
     * Record table name
58
     *
59
     * @var string
60
     */
61
    protected $table;
62
63
    /**
64
     * Record uid
65
     *
66
     * @var int
67
     */
68
    protected $uid;
69
70
    /**
71
     * @var string
72
     */
73
    protected $permsClause;
74
75
    /**
76
     * @var bool
77
     */
78
    protected $access = false;
79
80
    /**
81
     * Which type of element:
82
     * - "file"
83
     * - "db"
84
     *
85
     * @var string
86
     */
87
    protected $type = '';
88
89
    /**
90
     * @var ModuleTemplate
91
     */
92
    protected $moduleTemplate;
93
94
    /**
95
     * For type "db": Set to page record of the parent page of the item set
96
     * (if type="db")
97
     *
98
     * @var array
99
     */
100
    protected $pageInfo;
101
102
    /**
103
     * Database records identified by table/uid
104
     *
105
     * @var array
106
     */
107
    protected $row;
108
109
    /**
110
     * @var \TYPO3\CMS\Core\Resource\File|null
111
     */
112
    protected $fileObject;
113
114
    /**
115
     * @var Folder
116
     */
117
    protected $folderObject;
118
119
    protected IconFactory $iconFactory;
120
    protected UriBuilder $uriBuilder;
121
    protected ModuleTemplateFactory $moduleTemplateFactory;
122
123
    public function __construct(
124
        IconFactory $iconFactory,
125
        UriBuilder $uriBuilder,
126
        ModuleTemplateFactory $moduleTemplateFactory
127
    ) {
128
        $this->iconFactory = $iconFactory;
129
        $this->uriBuilder = $uriBuilder;
130
        $this->moduleTemplateFactory = $moduleTemplateFactory;
131
    }
132
133
    /**
134
     * Injects the request object for the current request or subrequest
135
     * As this controller goes only through the main() method, it is rather simple for now
136
     *
137
     * @param ServerRequestInterface $request the current request
138
     * @return ResponseInterface the response with the content
139
     */
140
    public function mainAction(ServerRequestInterface $request): ResponseInterface
141
    {
142
        $this->init($request);
143
        $this->main($request);
144
        return new HtmlResponse($this->moduleTemplate->renderContent());
145
    }
146
147
    /**
148
     * Determines if table/uid point to database record or file and
149
     * if user has access to view information
150
     *
151
     * @param ServerRequestInterface $request
152
     */
153
    protected function init(ServerRequestInterface $request): void
154
    {
155
        $this->moduleTemplate = $this->moduleTemplateFactory->create($request);
156
        $this->moduleTemplate->getDocHeaderComponent()->disable();
157
        $queryParams = $request->getQueryParams();
158
159
        $this->table = $queryParams['table'] ?? null;
160
        $this->uid = $queryParams['uid'] ?? null;
161
162
        $this->permsClause = $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW);
163
164
        if (isset($GLOBALS['TCA'][$this->table])) {
165
            $this->initDatabaseRecord();
166
        } elseif ($this->table === '_FILE' || $this->table === '_FOLDER' || $this->table === 'sys_file') {
167
            $this->initFileOrFolderRecord();
168
        }
169
    }
170
171
    /**
172
     * Init database records (table)
173
     */
174
    protected function initDatabaseRecord(): void
175
    {
176
        $this->type = 'db';
177
        $this->uid = (int)$this->uid;
178
179
        // Check permissions and uid value:
180
        if ($this->uid && $this->getBackendUser()->check('tables_select', $this->table)) {
181
            if ((string)$this->table === 'pages') {
182
                $this->pageInfo = BackendUtility::readPageAccess($this->uid, $this->permsClause) ?: [];
183
                $this->access = $this->pageInfo !== [];
184
                $this->row = $this->pageInfo;
185
            } else {
186
                $this->row = BackendUtility::getRecordWSOL($this->table, $this->uid);
187
                if ($this->row) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->row of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
188
                    if (!empty($this->row['t3ver_oid'])) {
189
                        // Make $this->uid the uid of the versioned record, while $this->row['uid'] is live record uid
190
                        $this->uid = (int)$this->row['_ORIG_uid'];
191
                    }
192
                    $this->pageInfo = BackendUtility::readPageAccess((int)$this->row['pid'], $this->permsClause) ?: [];
193
                    $this->access = $this->pageInfo !== [];
194
                }
195
            }
196
        }
197
    }
198
199
    /**
200
     * Init file/folder parameters
201
     */
202
    protected function initFileOrFolderRecord(): void
203
    {
204
        $fileOrFolderObject = GeneralUtility::makeInstance(ResourceFactory::class)->retrieveFileOrFolderObject($this->uid);
205
206
        if ($fileOrFolderObject instanceof Folder) {
207
            $this->folderObject = $fileOrFolderObject;
208
            $this->access = $this->folderObject->checkActionPermission('read');
209
            $this->type = 'folder';
210
        } elseif ($fileOrFolderObject instanceof File) {
211
            $this->fileObject = $fileOrFolderObject;
212
            $this->access = $this->fileObject->checkActionPermission('read');
213
            $this->type = 'file';
214
            $this->table = 'sys_file';
215
216
            try {
217
                $this->row = BackendUtility::getRecordWSOL($this->table, $fileOrFolderObject->getUid());
218
            } catch (Exception $e) {
219
                $this->row = [];
220
            }
221
        }
222
    }
223
224
    /**
225
     * Compiles the whole content to be outputted, which is then set as content to the moduleTemplate
226
     * There is a hook to do a custom rendering of a record.
227
     *
228
     * @param ServerRequestInterface $request
229
     */
230
    protected function main(ServerRequestInterface $request): void
231
    {
232
        $content = '';
233
234
        // Rendering of the output via fluid
235
        $view = GeneralUtility::makeInstance(StandaloneView::class);
236
        $view->setTemplateRootPaths([GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Templates')]);
237
        $view->setPartialRootPaths([GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Partials')]);
238
        $view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName(
239
            'EXT:backend/Resources/Private/Templates/ContentElement/ElementInformation.html'
240
        ));
241
242
        if ($this->access) {
243
            // render type by user func
244
            $typeRendered = false;
245
            foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/show_item.php']['typeRendering'] ?? [] as $className) {
246
                $typeRenderObj = GeneralUtility::makeInstance($className);
247
                if (is_object($typeRenderObj) && method_exists($typeRenderObj, 'isValid') && method_exists($typeRenderObj, 'render')) {
248
                    if ($typeRenderObj->isValid($this->type, $this)) {
249
                        $content .= $typeRenderObj->render($this->type, $this);
250
                        $typeRendered = true;
251
                        break;
252
                    }
253
                }
254
            }
255
256
            if (!$typeRendered) {
257
                $view->assign('accessAllowed', true);
258
                $view->assignMultiple($this->getPageTitle());
259
                $view->assignMultiple($this->getPreview());
260
                $view->assignMultiple($this->getPropertiesForTable());
261
                $view->assignMultiple($this->getReferences($request));
262
                $view->assign('returnUrl', GeneralUtility::sanitizeLocalUrl($request->getQueryParams()['returnUrl']));
263
                $view->assign('maxTitleLength', $this->getBackendUser()->uc['titleLen'] ?? 20);
264
                $content .= $view->render();
265
            }
266
        } else {
267
            $content .= $view->render();
268
        }
269
270
        $this->moduleTemplate->setContent($content);
271
    }
272
273
    /**
274
     * Get page title with icon, table title and record title
275
     *
276
     * @return array
277
     */
278
    protected function getPageTitle(): array
279
    {
280
        $pageTitle = [
281
            'title' => BackendUtility::getRecordTitle($this->table, $this->row)
282
        ];
283
        if ($this->type === 'folder') {
284
            $pageTitle['title'] = htmlspecialchars($this->folderObject->getName());
285
            $pageTitle['table'] = $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:folder');
286
            $pageTitle['icon'] = $this->iconFactory->getIconForResource($this->folderObject, Icon::SIZE_SMALL)->render();
287
        } elseif ($this->type === 'file') {
288
            $pageTitle['table'] = $this->getLanguageService()->sL($GLOBALS['TCA'][$this->table]['ctrl']['title']);
289
            $pageTitle['icon'] = $this->iconFactory->getIconForResource($this->fileObject, Icon::SIZE_SMALL)->render();
0 ignored issues
show
Bug introduced by
It seems like $this->fileObject can also be of type null; however, parameter $resource of TYPO3\CMS\Core\Imaging\I...y::getIconForResource() does only seem to accept TYPO3\CMS\Core\Resource\ResourceInterface, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

289
            $pageTitle['icon'] = $this->iconFactory->getIconForResource(/** @scrutinizer ignore-type */ $this->fileObject, Icon::SIZE_SMALL)->render();
Loading history...
290
        } else {
291
            $pageTitle['table'] = $this->getLanguageService()->sL($GLOBALS['TCA'][$this->table]['ctrl']['title']);
292
            $pageTitle['icon'] = $this->iconFactory->getIconForRecord($this->table, $this->row, Icon::SIZE_SMALL);
293
        }
294
        $this->moduleTemplate->setTitle($pageTitle['table'] . ': ' . $pageTitle['title']);
295
        return $pageTitle;
296
    }
297
298
    /**
299
     * Get preview for current record
300
     *
301
     * @return array
302
     */
303
    protected function getPreview(): array
304
    {
305
        $preview = [];
306
        // Perhaps @todo in future: Also display preview for records - without fileObject
307
        if (!$this->fileObject) {
308
            return $preview;
309
        }
310
311
        // check if file is marked as missing
312
        if ($this->fileObject->isMissing()) {
313
            $preview['missingFile'] = $this->fileObject->getName();
314
        } else {
315
            $rendererRegistry = GeneralUtility::makeInstance(RendererRegistry::class);
316
            $fileRenderer = $rendererRegistry->getRenderer($this->fileObject);
317
            $preview['url'] = PathUtility::getAbsoluteWebPath($this->fileObject->getPublicUrl() ?? '');
318
319
            $width = '590m';
320
            $height = '400m';
321
322
            // Check if there is a FileRenderer
323
            if ($fileRenderer !== null) {
324
                $preview['fileRenderer'] = $fileRenderer->render($this->fileObject, $width, $height);
325
            // else check if we can create an Image preview
326
            } elseif ($this->fileObject->isImage()) {
327
                $preview['fileObject'] = $this->fileObject;
328
                $preview['width'] = $width;
329
                $preview['height'] = $height;
330
            }
331
        }
332
        return $preview;
333
    }
334
335
    /**
336
     * Get property array for html table
337
     *
338
     * @return array
339
     */
340
    protected function getPropertiesForTable(): array
341
    {
342
        $lang = $this->getLanguageService();
343
        $propertiesForTable = [];
344
        $propertiesForTable['extraFields'] = $this->getExtraFields();
345
346
        // Traverse the list of fields to display for the record:
347
        $fieldList = $this->getFieldList($this->table, (int)$this->row['uid']);
348
349
        foreach ($fieldList as $name) {
350
            $name = trim($name);
351
            $uid = $this->row['uid'];
352
353
            if (!isset($GLOBALS['TCA'][$this->table]['columns'][$name])) {
354
                continue;
355
            }
356
357
            // @todo Add meaningful information for mfa field. For the time being we don't display anything at all.
358
            if ($this->type === 'db' && $name === 'mfa' && in_array($this->table, ['be_users', 'fe_users'], true)) {
359
                continue;
360
            }
361
362
            // not a real field -> skip
363
            if ($this->type === 'file' && $name === 'fileinfo') {
364
                continue;
365
            }
366
367
            $isExcluded = !(!$GLOBALS['TCA'][$this->table]['columns'][$name]['exclude'] || $this->getBackendUser()->check('non_exclude_fields', $this->table . ':' . $name));
368
            if ($isExcluded) {
369
                continue;
370
            }
371
            $label = $lang->sL(BackendUtility::getItemLabel($this->table, $name));
372
            $label = $label ?: $name;
373
374
            $propertiesForTable['fields'][] = [
375
                'fieldValue' => BackendUtility::getProcessedValue($this->table, $name, $this->row[$name], 0, false, false, $uid),
376
                'fieldLabel' => htmlspecialchars($label)
377
            ];
378
        }
379
380
        // additional information for folders and files
381
        if ($this->folderObject instanceof Folder || $this->fileObject instanceof File) {
0 ignored issues
show
introduced by
$this->folderObject is always a sub-type of TYPO3\CMS\Core\Resource\Folder.
Loading history...
382
            // storage
383
            if ($this->folderObject instanceof Folder) {
384
                $propertiesForTable['fields']['storage'] = [
385
                    'fieldValue' => $this->folderObject->getStorage()->getName(),
386
                    'fieldLabel' => htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_file.storage'))
387
                ];
388
            }
389
390
            // folder
391
            $resourceObject = $this->fileObject ?: $this->folderObject;
392
            $propertiesForTable['fields']['folder'] = [
393
                'fieldValue' => $resourceObject->getParentFolder()->getReadablePath(),
394
                'fieldLabel' => htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:folder'))
395
            ];
396
397
            if ($this->fileObject instanceof File) {
398
                // show file dimensions for images
399
                if ($this->fileObject->getType() === AbstractFile::FILETYPE_IMAGE) {
400
                    $propertiesForTable['fields']['width'] = [
401
                        'fieldValue' => $this->fileObject->getProperty('width') . 'px',
402
                        'fieldLabel' => htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.width'))
403
                    ];
404
                    $propertiesForTable['fields']['height'] = [
405
                        'fieldValue' => $this->fileObject->getProperty('height') . 'px',
406
                        'fieldLabel' => htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.height'))
407
                    ];
408
                }
409
410
                // file size
411
                $propertiesForTable['fields']['size'] = [
412
                    'fieldValue' => GeneralUtility::formatSize((int)$this->fileObject->getProperty('size'), htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:byteSizeUnits'))),
413
                    'fieldLabel' => $lang->sL(BackendUtility::getItemLabel($this->table, 'size'))
414
                ];
415
416
                // show the metadata of a file as well
417
                $table = 'sys_file_metadata';
418
                $metaDataRepository = GeneralUtility::makeInstance(MetaDataRepository::class);
419
                /** @var array<string, string> $metaData */
420
                $metaData = $metaDataRepository->findByFileUid($this->row['uid']);
421
                $allowedFields = $this->getFieldList($table, (int)$metaData['uid']);
422
423
                foreach ($metaData as $name => $value) {
424
                    if (in_array($name, $allowedFields, true)) {
425
                        if (!isset($GLOBALS['TCA'][$table]['columns'][$name])) {
426
                            continue;
427
                        }
428
429
                        $isExcluded = !(!$GLOBALS['TCA'][$table]['columns'][$name]['exclude'] || $this->getBackendUser()->check('non_exclude_fields', $table . ':' . $name));
430
                        if ($isExcluded) {
431
                            continue;
432
                        }
433
434
                        $label = $lang->sL(BackendUtility::getItemLabel($table, $name));
435
                        $label = $label ?: $name;
436
437
                        $propertiesForTable['fields'][] = [
438
                            'fieldValue' => BackendUtility::getProcessedValue($table, $name, $metaData[$name], 0, false, false, (int)$metaData['uid']),
439
                            'fieldLabel' => htmlspecialchars($label)
440
                        ];
441
                    }
442
                }
443
            }
444
        }
445
446
        return $propertiesForTable;
447
    }
448
449
    /**
450
     * Get the list of fields that should be shown for the given table
451
     *
452
     * @param string $table
453
     * @param int $uid
454
     * @return array
455
     */
456
    protected function getFieldList(string $table, int $uid): array
457
    {
458
        $formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
459
        $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
460
        $formDataCompilerInput = [
461
            'command' => 'edit',
462
            'tableName' => $table,
463
            'vanillaUid' => $uid,
464
        ];
465
        try {
466
            $result = $formDataCompiler->compile($formDataCompilerInput);
467
            $fieldList = array_unique(array_values($result['columnsToProcess']));
468
469
            $ctrlKeysOfUnneededFields = ['origUid', 'transOrigPointerField', 'transOrigDiffSourceField'];
470
            foreach ($ctrlKeysOfUnneededFields as $field) {
471
                if (($key = array_search($GLOBALS['TCA'][$table]['ctrl'][$field], $fieldList, true)) !== false) {
472
                    unset($fieldList[$key]);
473
                }
474
            }
475
        } catch (Exception $exception) {
476
            $fieldList = [];
477
        }
478
479
        $searchFields = GeneralUtility::trimExplode(',', $GLOBALS['TCA'][$table]['ctrl']['searchFields']);
480
481
        return array_unique(array_merge($fieldList, $searchFields));
482
    }
483
484
    /**
485
     * Get the extra fields (uid, timestamps, creator) for the table
486
     *
487
     * @return array
488
     */
489
    protected function getExtraFields(): array
490
    {
491
        $lang = $this->getLanguageService();
492
        $keyLabelPair = [];
493
        if (in_array($this->type, ['folder', 'file'], true)) {
494
            if ($this->type === 'file') {
495
                $keyLabelPair['uid'] = [
496
                    'value' => BackendUtility::getProcessedValueExtra($this->table, 'uid', $this->row['uid']),
497
                    'fieldLabel' => rtrim(htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:show_item.php.uid')), ':'),
498
                ];
499
                $keyLabelPair['creation_date'] = [
500
                    'value' => BackendUtility::datetime($this->row['creation_date']),
501
                    'fieldLabel' => rtrim(htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.creationDate')), ':'),
502
                    'isDatetime' => true,
503
                ];
504
                $keyLabelPair['modification_date'] = [
505
                    'value' => BackendUtility::datetime($this->row['modification_date']),
506
                    'fieldLabel' => rtrim(htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.timestamp')), ':'),
507
                    'isDatetime' => true,
508
                ];
509
            }
510
        } else {
511
            $keyLabelPair['uid'] = [
512
                'value' => BackendUtility::getProcessedValueExtra($this->table, 'uid', $this->row['uid']),
513
                'fieldLabel' => rtrim(htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:show_item.php.uid')), ':'),
514
            ];
515
            foreach (['crdate' => 'creationDate', 'tstamp' => 'timestamp', 'cruser_id' => 'creationUserId'] as $field => $label) {
516
                if (isset($GLOBALS['TCA'][$this->table]['ctrl'][$field])) {
517
                    if ($field === 'crdate' || $field === 'tstamp') {
518
                        $keyLabelPair[$field] = [
519
                            'value' => BackendUtility::datetime($this->row[$GLOBALS['TCA'][$this->table]['ctrl'][$field]]),
520
                            'fieldLabel' => rtrim(htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.' . $label)), ':'),
521
                            'isDatetime' => true,
522
                        ];
523
                    }
524
                    if ($field === 'cruser_id') {
525
                        $rowValue = BackendUtility::getProcessedValueExtra($this->table, $GLOBALS['TCA'][$this->table]['ctrl'][$field], $this->row[$GLOBALS['TCA'][$this->table]['ctrl'][$field]]);
526
                        if ($rowValue) {
527
                            $creatorRecord = BackendUtility::getRecord('be_users', (int)$rowValue);
528
                            if ($creatorRecord) {
529
                                /** @var Avatar $avatar */
530
                                $avatar = GeneralUtility::makeInstance(Avatar::class);
531
                                $creatorRecord['icon'] = $avatar->render($creatorRecord);
532
                                $rowValue = $creatorRecord;
533
                                $keyLabelPair['creatorRecord'] = [
534
                                    'value' => $rowValue,
535
                                    'fieldLabel' => rtrim(htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.' . $label)), ':'),
536
                                ];
537
                            }
538
                        }
539
                    }
540
                }
541
            }
542
        }
543
        return $keyLabelPair;
544
    }
545
546
    /**
547
     * Get references section (references from and references to current record)
548
     *
549
     * @param ServerRequestInterface $request
550
     * @return array
551
     */
552
    protected function getReferences(ServerRequestInterface $request): array
553
    {
554
        $references = [];
555
        switch ($this->type) {
556
            case 'db': {
557
                $references['refLines'] = $this->makeRef($this->table, $this->uid, $request);
558
                $references['refFromLines'] = $this->makeRefFrom($this->table, $this->uid, $request);
559
                break;
560
            }
561
562
            case 'file': {
563
                if ($this->fileObject && $this->fileObject->isIndexed()) {
564
                    $references['refLines'] = $this->makeRef('_FILE', $this->fileObject, $request);
565
                }
566
                break;
567
            }
568
        }
569
        return $references;
570
    }
571
572
    /**
573
     * Get field name for specified table/column name
574
     *
575
     * @param string $tableName Table name
576
     * @param string $fieldName Column name
577
     * @return string label
578
     */
579
    protected function getLabelForTableColumn($tableName, $fieldName): string
580
    {
581
        if ($GLOBALS['TCA'][$tableName]['columns'][$fieldName]['label'] !== null) {
582
            $field = $this->getLanguageService()->sL($GLOBALS['TCA'][$tableName]['columns'][$fieldName]['label']);
583
            if (trim($field) === '') {
584
                $field = $fieldName;
585
            }
586
        } else {
587
            $field = $fieldName;
588
        }
589
        return $field;
590
    }
591
592
    /**
593
     * Returns the record actions
594
     *
595
     * @param string $table
596
     * @param int $uid
597
     * @param ServerRequestInterface $request
598
     * @return array
599
     * @throws RouteNotFoundException
600
     */
601
    protected function getRecordActions($table, $uid, ServerRequestInterface $request): array
602
    {
603
        if ($table === '' || $uid < 0) {
604
            return [];
605
        }
606
607
        $actions = [];
608
        // Edit button
609
        $urlParameters = [
610
            'edit' => [
611
                $table => [
612
                    $uid => 'edit'
613
                ]
614
            ],
615
            'returnUrl' => $request->getAttribute('normalizedParams')->getRequestUri()
616
        ];
617
        $actions['recordEditUrl'] = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
618
619
        // History button
620
        $urlParameters = [
621
            'element' => $table . ':' . $uid,
622
            'returnUrl' => $request->getAttribute('normalizedParams')->getRequestUri()
623
        ];
624
        $actions['recordHistoryUrl'] = (string)$this->uriBuilder->buildUriFromRoute('record_history', $urlParameters);
625
626
        if ($table === 'pages') {
627
            // Recordlist button
628
            $actions['webListUrl'] = (string)$this->uriBuilder->buildUriFromRoute('web_list', ['id' => $uid, 'returnUrl' => $request->getAttribute('normalizedParams')->getRequestUri()]);
629
630
            $previewUriBuilder = PreviewUriBuilder::create((int)$uid)
631
                ->withRootLine(BackendUtility::BEgetRootLine($uid));
632
            // View page button (`previewUrlAttributes` is the substitute for previous `viewOnClick`)
633
            $actions['previewUrlAttributes'] = $previewUriBuilder->serializeDispatcherAttributes();
634
        }
635
636
        return $actions;
637
    }
638
639
    /**
640
     * Make reference display
641
     *
642
     * @param string $table Table name
643
     * @param int|\TYPO3\CMS\Core\Resource\File $ref Filename or uid
644
     * @param ServerRequestInterface $request
645
     * @return array
646
     * @throws RouteNotFoundException
647
     */
648
    protected function makeRef($table, $ref, ServerRequestInterface $request): array
649
    {
650
        $refLines = [];
651
        $lang = $this->getLanguageService();
652
        // Files reside in sys_file table
653
        if ($table === '_FILE') {
654
            $selectTable = 'sys_file';
655
            $selectUid = $ref->getUid();
656
        } else {
657
            $selectTable = $table;
658
            $selectUid = $ref;
659
        }
660
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
661
            ->getQueryBuilderForTable('sys_refindex');
662
663
        $predicates = [
664
            $queryBuilder->expr()->eq(
665
                'ref_table',
666
                $queryBuilder->createNamedParameter($selectTable, \PDO::PARAM_STR)
667
            ),
668
            $queryBuilder->expr()->eq(
669
                'ref_uid',
670
                $queryBuilder->createNamedParameter($selectUid, \PDO::PARAM_INT)
671
            )
672
        ];
673
674
        $backendUser = $this->getBackendUser();
675
        if (!$backendUser->isAdmin()) {
676
            $allowedSelectTables = GeneralUtility::trimExplode(',', $backendUser->groupData['tables_select']);
677
            $predicates[] = $queryBuilder->expr()->in(
678
                'tablename',
679
                $queryBuilder->createNamedParameter($allowedSelectTables, Connection::PARAM_STR_ARRAY)
680
            );
681
        }
682
683
        $rows = $queryBuilder
684
            ->select('*')
685
            ->from('sys_refindex')
686
            ->where(...$predicates)
687
            ->execute()
688
            ->fetchAll();
689
690
        // Compile information for title tag:
691
        foreach ($rows as $row) {
692
            if ($row['tablename'] === 'sys_file_reference') {
693
                $row = $this->transformFileReferenceToRecordReference($row);
694
                if ($row['tablename'] === null || $row['recuid'] === null) {
695
                    return [];
696
                }
697
            }
698
699
            $line = [];
700
            $record = BackendUtility::getRecordWSOL($row['tablename'], $row['recuid']);
701
            if ($record) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $record of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
702
                if (!$this->canAccessPage($row['tablename'], $record)) {
703
                    continue;
704
                }
705
                $parentRecord = BackendUtility::getRecord('pages', $record['pid']);
706
                $parentRecordTitle = is_array($parentRecord)
707
                    ? BackendUtility::getRecordTitle('pages', $parentRecord)
708
                    : '';
709
                $urlParameters = [
710
                    'edit' => [
711
                        $row['tablename'] => [
712
                            $row['recuid'] => 'edit'
713
                        ]
714
                    ],
715
                    'returnUrl' => $request->getAttribute('normalizedParams')->getRequestUri()
716
                ];
717
                $url = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
718
                $line['url'] = $url;
719
                $line['icon'] = $this->iconFactory->getIconForRecord($row['tablename'], $record, Icon::SIZE_SMALL)->render();
720
                $line['row'] = $row;
721
                $line['record'] = $record;
722
                $line['recordTitle'] = BackendUtility::getRecordTitle($row['tablename'], $record, false, true);
723
                $line['parentRecordTitle'] = $parentRecordTitle;
724
                $line['title'] = $lang->sL($GLOBALS['TCA'][$row['tablename']]['ctrl']['title']);
725
                $line['labelForTableColumn'] = $this->getLabelForTableColumn($row['tablename'], $row['field']);
726
                $line['path'] = BackendUtility::getRecordPath($record['pid'], '', 0, 0);
727
                $line['actions'] = $this->getRecordActions($row['tablename'], $row['recuid'], $request);
728
            } else {
729
                $line['row'] = $row;
730
                $line['title'] = $lang->sL($GLOBALS['TCA'][$row['tablename']]['ctrl']['title']) ?: $row['tablename'];
731
                $line['labelForTableColumn'] = $this->getLabelForTableColumn($row['tablename'], $row['field']);
732
            }
733
            $refLines[] = $line;
734
        }
735
        return $refLines;
736
    }
737
738
    /**
739
     * Make reference display (what this elements points to)
740
     *
741
     * @param string $table Table name
742
     * @param int $ref Filename or uid
743
     * @param ServerRequestInterface $request
744
     * @return array
745
     */
746
    protected function makeRefFrom($table, $ref, ServerRequestInterface $request): array
747
    {
748
        $refFromLines = [];
749
        $lang = $this->getLanguageService();
750
751
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
752
            ->getQueryBuilderForTable('sys_refindex');
753
754
        $predicates = [
755
            $queryBuilder->expr()->eq(
756
                'tablename',
757
                $queryBuilder->createNamedParameter($table, \PDO::PARAM_STR)
758
            ),
759
            $queryBuilder->expr()->eq(
760
                'recuid',
761
                $queryBuilder->createNamedParameter($ref, \PDO::PARAM_INT)
762
            )
763
        ];
764
765
        $backendUser = $this->getBackendUser();
766
        if (!$backendUser->isAdmin()) {
767
            $allowedSelectTables = GeneralUtility::trimExplode(',', $backendUser->groupData['tables_select']);
768
            $predicates[] = $queryBuilder->expr()->in(
769
                'ref_table',
770
                $queryBuilder->createNamedParameter($allowedSelectTables, Connection::PARAM_STR_ARRAY)
771
            );
772
        }
773
774
        $rows = $queryBuilder
775
            ->select('*')
776
            ->from('sys_refindex')
777
            ->where(...$predicates)
778
            ->execute()
779
            ->fetchAll();
780
781
        // Compile information for title tag:
782
        foreach ($rows as $row) {
783
            $line = [];
784
            $record = BackendUtility::getRecordWSOL($row['ref_table'], $row['ref_uid']);
785
            if ($record) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $record of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
786
                if (!$this->canAccessPage($row['ref_table'], $record)) {
787
                    continue;
788
                }
789
                $urlParameters = [
790
                    'edit' => [
791
                        $row['ref_table'] => [
792
                            $row['ref_uid'] => 'edit'
793
                        ]
794
                    ],
795
                    'returnUrl' => $request->getAttribute('normalizedParams')->getRequestUri()
796
                ];
797
                $url = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
798
                $line['url'] = $url;
799
                $line['icon'] = $this->iconFactory->getIconForRecord($row['tablename'], $record, Icon::SIZE_SMALL)->render();
800
                $line['row'] = $row;
801
                $line['record'] = $record;
802
                $line['recordTitle'] = BackendUtility::getRecordTitle($row['ref_table'], $record, false, true);
803
                $line['title'] = $lang->sL($GLOBALS['TCA'][$row['ref_table']]['ctrl']['title']);
804
                $line['labelForTableColumn'] = $this->getLabelForTableColumn($table, $row['field']);
805
                $line['path'] = BackendUtility::getRecordPath($record['pid'], '', 0, 0);
806
                $line['actions'] = $this->getRecordActions($row['ref_table'], $row['ref_uid'], $request);
807
            } else {
808
                $line['row'] = $row;
809
                $line['title'] = $lang->sL($GLOBALS['TCA'][$row['ref_table']]['ctrl']['title']);
810
                $line['labelForTableColumn'] = $this->getLabelForTableColumn($table, $row['field']);
811
            }
812
            $refFromLines[] = $line;
813
        }
814
        return $refFromLines;
815
    }
816
817
    /**
818
     * Convert FAL file reference (sys_file_reference) to reference index (sys_refindex) table format
819
     *
820
     * @param array $referenceRecord
821
     * @return array
822
     */
823
    protected function transformFileReferenceToRecordReference(array $referenceRecord): array
824
    {
825
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
826
            ->getQueryBuilderForTable('sys_file_reference');
827
        $queryBuilder->getRestrictions()->removeAll();
828
        $fileReference = $queryBuilder
829
            ->select('*')
830
            ->from('sys_file_reference')
831
            ->where(
832
                $queryBuilder->expr()->eq(
833
                    'uid',
834
                    $queryBuilder->createNamedParameter($referenceRecord['recuid'], \PDO::PARAM_INT)
835
                )
836
            )
837
            ->execute()
838
            ->fetch();
839
840
        return [
841
            'recuid' => $fileReference['uid_foreign'],
842
            'tablename' => $fileReference['tablenames'],
843
            'field' => $fileReference['fieldname'],
844
            'flexpointer' => '',
845
            'softref_key' => '',
846
            'sorting' => $fileReference['sorting_foreign']
847
        ];
848
    }
849
850
    /**
851
     * @param string $tableName Name of the table
852
     * @param array $record Record to be checked (ensure pid is resolved for workspaces)
853
     * @return bool
854
     */
855
    protected function canAccessPage(string $tableName, array $record): bool
856
    {
857
        $recordPid = (int)($tableName === 'pages' ? $record['uid'] : $record['pid']);
858
        return $this->getBackendUser()->isInWebMount($tableName === 'pages' ? $record : $record['pid'])
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->getBackendUser()-...ecord : $record['pid']) of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
859
            || $recordPid === 0 && !empty($GLOBALS['TCA'][$tableName]['ctrl']['security']['ignoreRootLevelRestriction']);
860
    }
861
862
    /**
863
     * @return LanguageService
864
     */
865
    protected function getLanguageService(): LanguageService
866
    {
867
        return $GLOBALS['LANG'];
868
    }
869
870
    /**
871
     * @return BackendUserAuthentication
872
     */
873
    protected function getBackendUser(): BackendUserAuthentication
874
    {
875
        return $GLOBALS['BE_USER'];
876
    }
877
}
878