Completed
Push — master ( cd8a61...02d3c6 )
by
unknown
15:26
created

BackendUtility::getPageForRootline()   C

Complexity

Conditions 11
Paths 21

Size

Total Lines 75
Code Lines 62

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 11
eloc 62
c 1
b 0
f 0
nc 21
nop 4
dl 0
loc 75
rs 6.6824

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
 * It is free software; you can redistribute it and/or modify it under
7
 * the terms of the GNU General Public License, either version 2
8
 * of the License, or any later version.
9
 *
10
 * For the full copyright and license information, please read the
11
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
 * The TYPO3 project - inspiring people to share!
14
 */
15
16
namespace TYPO3\CMS\Backend\Utility;
17
18
use Psr\EventDispatcher\EventDispatcherInterface;
19
use Psr\Log\LoggerInterface;
20
use TYPO3\CMS\Backend\Configuration\TypoScript\ConditionMatching\ConditionMatcher;
21
use TYPO3\CMS\Backend\Controller\File\ThumbnailController;
22
use TYPO3\CMS\Backend\Routing\UriBuilder;
23
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
24
use TYPO3\CMS\Core\Cache\CacheManager;
25
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
26
use TYPO3\CMS\Core\Configuration\Event\ModifyLoadedPageTsConfigEvent;
27
use TYPO3\CMS\Core\Configuration\Loader\PageTsConfigLoader;
28
use TYPO3\CMS\Core\Configuration\Parser\PageTsConfigParser;
29
use TYPO3\CMS\Core\Context\Context;
30
use TYPO3\CMS\Core\Context\DateTimeAspect;
31
use TYPO3\CMS\Core\Core\Environment;
32
use TYPO3\CMS\Core\Database\Connection;
33
use TYPO3\CMS\Core\Database\ConnectionPool;
34
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
35
use TYPO3\CMS\Core\Database\Query\QueryHelper;
36
use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
37
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
38
use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
39
use TYPO3\CMS\Core\Database\RelationHandler;
40
use TYPO3\CMS\Core\Domain\Repository\PageRepository;
41
use TYPO3\CMS\Core\Exception\SiteNotFoundException;
42
use TYPO3\CMS\Core\Http\Uri;
43
use TYPO3\CMS\Core\Imaging\Icon;
44
use TYPO3\CMS\Core\Imaging\IconFactory;
45
use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
46
use TYPO3\CMS\Core\Information\Typo3Information;
47
use TYPO3\CMS\Core\Localization\LanguageService;
48
use TYPO3\CMS\Core\Log\LogManager;
49
use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException;
50
use TYPO3\CMS\Core\Resource\ProcessedFile;
51
use TYPO3\CMS\Core\Resource\ResourceFactory;
52
use TYPO3\CMS\Core\Routing\InvalidRouteArgumentsException;
53
use TYPO3\CMS\Core\Routing\RouterInterface;
54
use TYPO3\CMS\Core\Routing\UnableToLinkToPageException;
55
use TYPO3\CMS\Core\Site\SiteFinder;
56
use TYPO3\CMS\Core\Type\Bitmask\Permission;
57
use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser;
58
use TYPO3\CMS\Core\Utility\ArrayUtility;
59
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
60
use TYPO3\CMS\Core\Utility\GeneralUtility;
61
use TYPO3\CMS\Core\Utility\HttpUtility;
62
use TYPO3\CMS\Core\Utility\MathUtility;
63
use TYPO3\CMS\Core\Utility\PathUtility;
64
use TYPO3\CMS\Core\Versioning\VersionState;
65
66
/**
67
 * Standard functions available for the TYPO3 backend.
68
 * You are encouraged to use this class in your own applications (Backend Modules)
69
 * Don't instantiate - call functions with "\TYPO3\CMS\Backend\Utility\BackendUtility::" prefixed the function name.
70
 *
71
 * Call ALL methods without making an object!
72
 * Eg. to get a page-record 51 do this: '\TYPO3\CMS\Backend\Utility\BackendUtility::getRecord('pages',51)'
73
 */
74
class BackendUtility
75
{
76
    /*******************************************
77
     *
78
     * SQL-related, selecting records, searching
79
     *
80
     *******************************************/
81
    /**
82
     * Gets record with uid = $uid from $table
83
     * You can set $field to a list of fields (default is '*')
84
     * Additional WHERE clauses can be added by $where (fx. ' AND some_field = 1')
85
     * Will automatically check if records has been deleted and if so, not return anything.
86
     * $table must be found in $GLOBALS['TCA']
87
     *
88
     * @param string $table Table name present in $GLOBALS['TCA']
89
     * @param int $uid UID of record
90
     * @param string $fields List of fields to select
91
     * @param string $where Additional WHERE clause, eg. ' AND some_field = 0'
92
     * @param bool $useDeleteClause Use the deleteClause to check if a record is deleted (default TRUE)
93
     * @return array|null Returns the row if found, otherwise NULL
94
     */
95
    public static function getRecord($table, $uid, $fields = '*', $where = '', $useDeleteClause = true)
96
    {
97
        // Ensure we have a valid uid (not 0 and not NEWxxxx) and a valid TCA
98
        if ((int)$uid && !empty($GLOBALS['TCA'][$table])) {
99
            $queryBuilder = static::getQueryBuilderForTable($table);
100
101
            // do not use enabled fields here
102
            $queryBuilder->getRestrictions()->removeAll();
103
104
            // should the delete clause be used
105
            if ($useDeleteClause) {
106
                $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
107
            }
108
109
            // set table and where clause
110
            $queryBuilder
111
                ->select(...GeneralUtility::trimExplode(',', $fields, true))
112
                ->from($table)
113
                ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter((int)$uid, \PDO::PARAM_INT)));
114
115
            // add custom where clause
116
            if ($where) {
117
                $queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($where));
118
            }
119
120
            $row = $queryBuilder->execute()->fetch();
121
            if ($row) {
122
                return $row;
123
            }
124
        }
125
        return null;
126
    }
127
128
    /**
129
     * Like getRecord(), but overlays workspace version if any.
130
     *
131
     * @param string $table Table name present in $GLOBALS['TCA']
132
     * @param int $uid UID of record
133
     * @param string $fields List of fields to select
134
     * @param string $where Additional WHERE clause, eg. ' AND some_field = 0'
135
     * @param bool $useDeleteClause Use the deleteClause to check if a record is deleted (default TRUE)
136
     * @param bool $unsetMovePointers If TRUE the function does not return a "pointer" row for moved records in a workspace
137
     * @return array Returns the row if found, otherwise nothing
138
     */
139
    public static function getRecordWSOL(
140
        $table,
141
        $uid,
142
        $fields = '*',
143
        $where = '',
144
        $useDeleteClause = true,
145
        $unsetMovePointers = false
146
    ) {
147
        if ($fields !== '*') {
148
            $internalFields = GeneralUtility::uniqueList($fields . ',uid,pid');
149
            $row = self::getRecord($table, $uid, $internalFields, $where, $useDeleteClause);
150
            self::workspaceOL($table, $row, -99, $unsetMovePointers);
151
            if (is_array($row)) {
152
                foreach ($row as $key => $_) {
153
                    if (!GeneralUtility::inList($fields, $key) && $key[0] !== '_') {
154
                        unset($row[$key]);
155
                    }
156
                }
157
            }
158
        } else {
159
            $row = self::getRecord($table, $uid, $fields, $where, $useDeleteClause);
160
            self::workspaceOL($table, $row, -99, $unsetMovePointers);
161
        }
162
        return $row;
163
    }
164
165
    /**
166
     * Purges computed properties starting with underscore character ('_').
167
     *
168
     * @param array $record
169
     * @return array
170
     * @internal should only be used from within TYPO3 Core
171
     */
172
    public static function purgeComputedPropertiesFromRecord(array $record): array
173
    {
174
        return array_filter(
175
            $record,
176
            function (string $propertyName): bool {
177
                return $propertyName[0] !== '_';
178
            },
179
            ARRAY_FILTER_USE_KEY
180
        );
181
    }
182
183
    /**
184
     * Purges computed property names starting with underscore character ('_').
185
     *
186
     * @param array $propertyNames
187
     * @return array
188
     * @internal should only be used from within TYPO3 Core
189
     */
190
    public static function purgeComputedPropertyNames(array $propertyNames): array
191
    {
192
        return array_filter(
193
            $propertyNames,
194
            function (string $propertyName): bool {
195
                return $propertyName[0] !== '_';
196
            }
197
        );
198
    }
199
200
    /**
201
     * Makes a backwards explode on the $str and returns an array with ($table, $uid).
202
     * Example: tt_content_45 => ['tt_content', 45]
203
     *
204
     * @param string $str [tablename]_[uid] string to explode
205
     * @return array
206
     * @internal should only be used from within TYPO3 Core
207
     */
208
    public static function splitTable_Uid($str)
0 ignored issues
show
Coding Style introduced by
Method name "BackendUtility::splitTable_Uid" is not in camel caps format
Loading history...
209
    {
210
        [$uid, $table] = explode('_', strrev($str), 2);
211
        return [strrev($table), strrev($uid)];
212
    }
213
214
    /**
215
     * Backend implementation of enableFields()
216
     * Notice that "fe_groups" is not selected for - only disabled, starttime and endtime.
217
     * Notice that deleted-fields are NOT filtered - you must ALSO call deleteClause in addition.
218
     * $GLOBALS["SIM_ACCESS_TIME"] is used for date.
219
     *
220
     * @param string $table The table from which to return enableFields WHERE clause. Table name must have a 'ctrl' section in $GLOBALS['TCA'].
221
     * @param bool $inv Means that the query will select all records NOT VISIBLE records (inverted selection)
222
     * @return string WHERE clause part
223
     * @internal should only be used from within TYPO3 Core, but DefaultRestrictionHandler is recommended as alternative
224
     */
225
    public static function BEenableFields($table, $inv = false)
226
    {
227
        $ctrl = $GLOBALS['TCA'][$table]['ctrl'];
228
        $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
229
            ->getConnectionForTable($table)
230
            ->getExpressionBuilder();
231
        $query = $expressionBuilder->andX();
232
        $invQuery = $expressionBuilder->orX();
233
234
        if (is_array($ctrl)) {
235
            if (is_array($ctrl['enablecolumns'])) {
236
                if ($ctrl['enablecolumns']['disabled'] ?? false) {
237
                    $field = $table . '.' . $ctrl['enablecolumns']['disabled'];
238
                    $query->add($expressionBuilder->eq($field, 0));
239
                    $invQuery->add($expressionBuilder->neq($field, 0));
240
                }
241
                if ($ctrl['enablecolumns']['starttime'] ?? false) {
242
                    $field = $table . '.' . $ctrl['enablecolumns']['starttime'];
243
                    $query->add($expressionBuilder->lte($field, (int)$GLOBALS['SIM_ACCESS_TIME']));
244
                    $invQuery->add(
245
                        $expressionBuilder->andX(
246
                            $expressionBuilder->neq($field, 0),
247
                            $expressionBuilder->gt($field, (int)$GLOBALS['SIM_ACCESS_TIME'])
248
                        )
249
                    );
250
                }
251
                if ($ctrl['enablecolumns']['endtime'] ?? false) {
252
                    $field = $table . '.' . $ctrl['enablecolumns']['endtime'];
253
                    $query->add(
254
                        $expressionBuilder->orX(
255
                            $expressionBuilder->eq($field, 0),
256
                            $expressionBuilder->gt($field, (int)$GLOBALS['SIM_ACCESS_TIME'])
257
                        )
258
                    );
259
                    $invQuery->add(
260
                        $expressionBuilder->andX(
261
                            $expressionBuilder->neq($field, 0),
262
                            $expressionBuilder->lte($field, (int)$GLOBALS['SIM_ACCESS_TIME'])
263
                        )
264
                    );
265
                }
266
            }
267
        }
268
269
        if ($query->count() === 0) {
270
            return '';
271
        }
272
273
        return ' AND ' . ($inv ? $invQuery : $query);
274
    }
275
276
    /**
277
     * Fetches the localization for a given record.
278
     *
279
     * @param string $table Table name present in $GLOBALS['TCA']
280
     * @param int $uid The uid of the record
281
     * @param int $language The uid of the language record in sys_language
282
     * @param string $andWhereClause Optional additional WHERE clause (default: '')
283
     * @return mixed Multidimensional array with selected records, empty array if none exists and FALSE if table is not localizable
284
     */
285
    public static function getRecordLocalization($table, $uid, $language, $andWhereClause = '')
286
    {
287
        $recordLocalization = false;
288
289
        if (self::isTableLocalizable($table)) {
290
            $tcaCtrl = $GLOBALS['TCA'][$table]['ctrl'];
291
292
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
293
                ->getQueryBuilderForTable($table);
294
            $queryBuilder->getRestrictions()
295
                ->removeAll()
296
                ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
297
                ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
298
299
            $queryBuilder->select('*')
300
                ->from($table)
301
                ->where(
302
                    $queryBuilder->expr()->eq(
303
                        $tcaCtrl['translationSource'] ?? $tcaCtrl['transOrigPointerField'],
304
                        $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
305
                    ),
306
                    $queryBuilder->expr()->eq(
307
                        $tcaCtrl['languageField'],
308
                        $queryBuilder->createNamedParameter((int)$language, \PDO::PARAM_INT)
309
                    )
310
                )
311
                ->setMaxResults(1);
312
313
            if ($andWhereClause) {
314
                $queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($andWhereClause));
315
            }
316
317
            $recordLocalization = $queryBuilder->execute()->fetchAll();
318
        }
319
320
        return $recordLocalization;
321
    }
322
323
    /*******************************************
324
     *
325
     * Page tree, TCA related
326
     *
327
     *******************************************/
328
    /**
329
     * Returns what is called the 'RootLine'. That is an array with information about the page records from a page id
330
     * ($uid) and back to the root.
331
     * By default deleted pages are filtered.
332
     * This RootLine will follow the tree all the way to the root. This is opposite to another kind of root line known
333
     * from the frontend where the rootline stops when a root-template is found.
334
     *
335
     * @param int $uid Page id for which to create the root line.
336
     * @param string $clause Clause can be used to select other criteria. It would typically be where-clauses that
337
     *          stops the process if we meet a page, the user has no reading access to.
338
     * @param bool $workspaceOL If TRUE, version overlay is applied. This must be requested specifically because it is
339
     *          usually only wanted when the rootline is used for visual output while for permission checking you want the raw thing!
340
     * @param string[] $additionalFields Additional Fields to select for rootline records
341
     * @return array Root line array, all the way to the page tree root (or as far as $clause allows!)
342
     */
343
    public static function BEgetRootLine($uid, $clause = '', $workspaceOL = false, array $additionalFields = [])
344
    {
345
        $runtimeCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('runtime');
346
        $beGetRootLineCache = $runtimeCache->get('backendUtilityBeGetRootLine') ?: [];
347
        $output = [];
348
        $pid = $uid;
349
        $ident = $pid . '-' . $clause . '-' . $workspaceOL . ($additionalFields ? '-' . md5(implode(',', $additionalFields)) : '');
350
        if (is_array($beGetRootLineCache[$ident] ?? false)) {
351
            $output = $beGetRootLineCache[$ident];
352
        } else {
353
            $loopCheck = 100;
354
            $theRowArray = [];
355
            while ($uid != 0 && $loopCheck) {
356
                $loopCheck--;
357
                $row = self::getPageForRootline($uid, $clause, $workspaceOL, $additionalFields);
358
                if (is_array($row)) {
359
                    $uid = $row['pid'];
360
                    $theRowArray[] = $row;
361
                } else {
362
                    break;
363
                }
364
            }
365
            $fields = [
366
                'uid',
367
                'pid',
368
                'title',
369
                'doktype',
370
                'slug',
371
                'tsconfig_includes',
372
                'TSconfig',
373
                'is_siteroot',
374
                't3ver_oid',
375
                't3ver_wsid',
376
                't3ver_state',
377
                't3ver_stage',
378
                'backend_layout_next_level',
379
                'hidden',
380
                'starttime',
381
                'endtime',
382
                'fe_group',
383
                'nav_hide',
384
                'content_from_pid',
385
                'module',
386
                'extendToSubpages'
387
            ];
388
            $fields = array_merge($fields, $additionalFields);
389
            $rootPage = array_fill_keys($fields, null);
390
            if ($uid == 0) {
391
                $rootPage['uid'] = 0;
392
                $theRowArray[] = $rootPage;
393
            }
394
            $c = count($theRowArray);
395
            foreach ($theRowArray as $val) {
396
                $c--;
397
                $output[$c] = array_intersect_key($val, $rootPage);
398
                if (isset($val['_ORIG_pid'])) {
399
                    $output[$c]['_ORIG_pid'] = $val['_ORIG_pid'];
400
                }
401
            }
402
            $beGetRootLineCache[$ident] = $output;
403
            $runtimeCache->set('backendUtilityBeGetRootLine', $beGetRootLineCache);
404
        }
405
        return $output;
406
    }
407
408
    /**
409
     * Gets the cached page record for the rootline
410
     *
411
     * @param int $uid Page id for which to create the root line.
412
     * @param string $clause Clause can be used to select other criteria. It would typically be where-clauses that stops the process if we meet a page, the user has no reading access to.
413
     * @param bool $workspaceOL If TRUE, version overlay is applied. This must be requested specifically because it is usually only wanted when the rootline is used for visual output while for permission checking you want the raw thing!
414
     * @param string[] $additionalFields AdditionalFields to fetch from the root line
415
     * @return array Cached page record for the rootline
416
     * @see BEgetRootLine
417
     */
418
    protected static function getPageForRootline($uid, $clause, $workspaceOL, array $additionalFields = [])
419
    {
420
        $runtimeCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('runtime');
421
        $pageForRootlineCache = $runtimeCache->get('backendUtilityPageForRootLine') ?: [];
422
        $statementCacheIdent = md5($clause . ($additionalFields ? '-' . implode(',', $additionalFields) : ''));
423
        $ident = $uid . '-' . $workspaceOL . '-' . $statementCacheIdent;
424
        if (is_array($pageForRootlineCache[$ident] ?? false)) {
425
            $row = $pageForRootlineCache[$ident];
426
        } else {
427
            $statement = $runtimeCache->get('getPageForRootlineStatement-' . $statementCacheIdent);
428
            if (!$statement) {
429
                $queryBuilder = static::getQueryBuilderForTable('pages');
430
                $queryBuilder->getRestrictions()
431
                             ->removeAll()
432
                             ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
433
434
                $queryBuilder
435
                    ->select(
436
                        'pid',
437
                        'uid',
438
                        'title',
439
                        'doktype',
440
                        'slug',
441
                        'tsconfig_includes',
442
                        'TSconfig',
443
                        'is_siteroot',
444
                        't3ver_oid',
445
                        't3ver_wsid',
446
                        't3ver_state',
447
                        't3ver_stage',
448
                        'backend_layout_next_level',
449
                        'hidden',
450
                        'starttime',
451
                        'endtime',
452
                        'fe_group',
453
                        'nav_hide',
454
                        'content_from_pid',
455
                        'module',
456
                        'extendToSubpages',
457
                        ...$additionalFields
458
                    )
459
                    ->from('pages')
460
                    ->where(
461
                        $queryBuilder->expr()->eq('uid', $queryBuilder->createPositionalParameter($uid, \PDO::PARAM_INT)),
462
                        QueryHelper::stripLogicalOperatorPrefix($clause)
463
                    );
464
                $statement = $queryBuilder->execute();
465
                $runtimeCache->set('getPageForRootlineStatement-' . $statementCacheIdent, $statement);
466
            } else {
467
                $statement->bindValue(1, (int)$uid);
468
                $statement->execute();
469
            }
470
            $row = $statement->fetch();
471
            $statement->closeCursor();
472
473
            if ($row) {
474
                $newLocation = false;
475
                if ($workspaceOL) {
476
                    self::workspaceOL('pages', $row);
477
                    if (is_array($row) && (int)$row['t3ver_state'] === VersionState::MOVE_POINTER) {
478
                        $newLocation = self::getMovePlaceholder('pages', $row['uid'], 'pid');
479
                    }
480
                }
481
                if (is_array($row)) {
482
                    if ($newLocation !== false) {
483
                        $row['pid'] = $newLocation['pid'];
484
                    } else {
485
                        self::fixVersioningPid('pages', $row);
486
                    }
487
                    $pageForRootlineCache[$ident] = $row;
488
                    $runtimeCache->set('backendUtilityPageForRootLine', $pageForRootlineCache);
489
                }
490
            }
491
        }
492
        return $row;
493
    }
494
495
    /**
496
     * Opens the page tree to the specified page id
497
     *
498
     * @param int $pid Page id.
499
     * @param bool $clearExpansion If set, then other open branches are closed.
500
     * @internal should only be used from within TYPO3 Core
501
     */
502
    public static function openPageTree($pid, $clearExpansion)
503
    {
504
        $beUser = static::getBackendUserAuthentication();
505
        // Get current expansion data:
506
        if ($clearExpansion) {
507
            $expandedPages = [];
508
        } else {
509
            $expandedPages = json_decode($beUser->uc['browseTrees']['browsePages'], true);
510
        }
511
        // Get rootline:
512
        $rL = self::BEgetRootLine($pid);
513
        // First, find out what mount index to use (if more than one DB mount exists):
514
        $mountIndex = 0;
515
        $mountKeys = array_flip($beUser->returnWebmounts());
516
        foreach ($rL as $rLDat) {
517
            if (isset($mountKeys[$rLDat['uid']])) {
518
                $mountIndex = $mountKeys[$rLDat['uid']];
519
                break;
520
            }
521
        }
522
        // Traverse rootline and open paths:
523
        foreach ($rL as $rLDat) {
524
            $expandedPages[$mountIndex][$rLDat['uid']] = 1;
525
        }
526
        // Write back:
527
        $beUser->uc['browseTrees']['browsePages'] = json_encode($expandedPages);
528
        $beUser->writeUC();
529
    }
530
531
    /**
532
     * Returns the path (visually) of a page $uid, fx. "/First page/Second page/Another subpage"
533
     * Each part of the path will be limited to $titleLimit characters
534
     * Deleted pages are filtered out.
535
     *
536
     * @param int $uid Page uid for which to create record path
537
     * @param string $clause Clause is additional where clauses, eg.
538
     * @param int $titleLimit Title limit
539
     * @param int $fullTitleLimit Title limit of Full title (typ. set to 1000 or so)
540
     * @return mixed Path of record (string) OR array with short/long title if $fullTitleLimit is set.
541
     */
542
    public static function getRecordPath($uid, $clause, $titleLimit, $fullTitleLimit = 0)
543
    {
544
        if (!$titleLimit) {
545
            $titleLimit = 1000;
546
        }
547
        $output = $fullOutput = '/';
548
        $clause = trim($clause);
549
        if ($clause !== '' && strpos($clause, 'AND') !== 0) {
550
            $clause = 'AND ' . $clause;
551
        }
552
        $data = self::BEgetRootLine($uid, $clause, true);
553
        foreach ($data as $record) {
554
            if ($record['uid'] === 0) {
555
                continue;
556
            }
557
            $output = '/' . GeneralUtility::fixed_lgd_cs(strip_tags($record['title']), $titleLimit) . $output;
558
            if ($fullTitleLimit) {
559
                $fullOutput = '/' . GeneralUtility::fixed_lgd_cs(strip_tags($record['title']), $fullTitleLimit) . $fullOutput;
560
            }
561
        }
562
        if ($fullTitleLimit) {
563
            return [$output, $fullOutput];
564
        }
565
        return $output;
566
    }
567
568
    /**
569
     * Determines whether a table is localizable and has the languageField and transOrigPointerField set in $GLOBALS['TCA'].
570
     *
571
     * @param string $table The table to check
572
     * @return bool Whether a table is localizable
573
     */
574
    public static function isTableLocalizable($table)
575
    {
576
        $isLocalizable = false;
577
        if (isset($GLOBALS['TCA'][$table]['ctrl']) && is_array($GLOBALS['TCA'][$table]['ctrl'])) {
578
            $tcaCtrl = $GLOBALS['TCA'][$table]['ctrl'];
579
            $isLocalizable = isset($tcaCtrl['languageField']) && $tcaCtrl['languageField'] && isset($tcaCtrl['transOrigPointerField']) && $tcaCtrl['transOrigPointerField'];
580
        }
581
        return $isLocalizable;
582
    }
583
584
    /**
585
     * Returns a page record (of page with $id) with an extra field "_thePath" set to the record path IF the WHERE clause, $perms_clause, selects the record. Thus is works as an access check that returns a page record if access was granted, otherwise not.
586
     * If $id is zero a pseudo root-page with "_thePath" set is returned IF the current BE_USER is admin.
587
     * In any case ->isInWebMount must return TRUE for the user (regardless of $perms_clause)
588
     *
589
     * @param int $id Page uid for which to check read-access
590
     * @param string $perms_clause This is typically a value generated with static::getBackendUserAuthentication()->getPagePermsClause(1);
591
     * @return array|bool Returns page record if OK, otherwise FALSE.
592
     */
593
    public static function readPageAccess($id, $perms_clause)
594
    {
595
        if ((string)$id !== '') {
596
            $id = (int)$id;
597
            if (!$id) {
598
                if (static::getBackendUserAuthentication()->isAdmin()) {
599
                    return ['_thePath' => '/'];
600
                }
601
            } else {
602
                $pageinfo = self::getRecord('pages', $id, '*', $perms_clause);
603
                if ($pageinfo['uid'] && static::getBackendUserAuthentication()->isInWebMount($pageinfo, $perms_clause)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression static::getBackendUserAu...ageinfo, $perms_clause) 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...
604
                    self::workspaceOL('pages', $pageinfo);
605
                    if (is_array($pageinfo)) {
606
                        self::fixVersioningPid('pages', $pageinfo);
607
                        [$pageinfo['_thePath'], $pageinfo['_thePathFull']] = self::getRecordPath((int)$pageinfo['uid'], $perms_clause, 15, 1000);
608
                        return $pageinfo;
609
                    }
610
                }
611
            }
612
        }
613
        return false;
614
    }
615
616
    /**
617
     * Returns the "type" value of $rec from $table which can be used to look up the correct "types" rendering section in $GLOBALS['TCA']
618
     * If no "type" field is configured in the "ctrl"-section of the $GLOBALS['TCA'] for the table, zero is used.
619
     * If zero is not an index in the "types" section of $GLOBALS['TCA'] for the table, then the $fieldValue returned will default to 1 (no matter if that is an index or not)
620
     *
621
     * Note: This method is very similar to the type determination of FormDataProvider/DatabaseRecordTypeValue,
622
     * however, it has two differences:
623
     * 1) The method in TCEForms also takes care of localization (which is difficult to do here as the whole infrastructure for language overlays is only in TCEforms).
624
     * 2) The $row array looks different in TCEForms, as in there it's not the raw record but the prepared data from other providers is handled, which changes e.g. how "select"
625
     * and "group" field values are stored, which makes different processing of the "foreign pointer field" type field variant necessary.
626
     *
627
     * @param string $table Table name present in TCA
628
     * @param array $row Record from $table
629
     * @throws \RuntimeException
630
     * @return string Field value
631
     */
632
    public static function getTCAtypeValue($table, $row)
633
    {
634
        $typeNum = 0;
635
        if ($GLOBALS['TCA'][$table]) {
636
            $field = $GLOBALS['TCA'][$table]['ctrl']['type'];
637
            if (strpos($field, ':') !== false) {
638
                [$pointerField, $foreignTableTypeField] = explode(':', $field);
639
                // Get field value from database if field is not in the $row array
640
                if (!isset($row[$pointerField])) {
641
                    $localRow = self::getRecord($table, $row['uid'], $pointerField);
642
                    $foreignUid = $localRow[$pointerField];
643
                } else {
644
                    $foreignUid = $row[$pointerField];
645
                }
646
                if ($foreignUid) {
647
                    $fieldConfig = $GLOBALS['TCA'][$table]['columns'][$pointerField]['config'];
648
                    $relationType = $fieldConfig['type'];
649
                    if ($relationType === 'select') {
650
                        $foreignTable = $fieldConfig['foreign_table'];
651
                    } elseif ($relationType === 'group') {
652
                        $allowedTables = explode(',', $fieldConfig['allowed']);
653
                        $foreignTable = $allowedTables[0];
654
                    } else {
655
                        throw new \RuntimeException(
656
                            'TCA foreign field pointer fields are only allowed to be used with group or select field types.',
657
                            1325862240
658
                        );
659
                    }
660
                    $foreignRow = self::getRecord($foreignTable, $foreignUid, $foreignTableTypeField);
661
                    if ($foreignRow[$foreignTableTypeField]) {
662
                        $typeNum = $foreignRow[$foreignTableTypeField];
663
                    }
664
                }
665
            } else {
666
                $typeNum = $row[$field];
667
            }
668
            // If that value is an empty string, set it to "0" (zero)
669
            if (empty($typeNum)) {
670
                $typeNum = 0;
671
            }
672
        }
673
        // If current typeNum doesn't exist, set it to 0 (or to 1 for historical reasons, if 0 doesn't exist)
674
        if (!isset($GLOBALS['TCA'][$table]['types'][$typeNum]) || !$GLOBALS['TCA'][$table]['types'][$typeNum]) {
675
            $typeNum = isset($GLOBALS['TCA'][$table]['types']['0']) ? 0 : 1;
676
        }
677
        // Force to string. Necessary for eg '-1' to be recognized as a type value.
678
        $typeNum = (string)$typeNum;
679
        return $typeNum;
680
    }
681
682
    /*******************************************
683
     *
684
     * TypoScript related
685
     *
686
     *******************************************/
687
    /**
688
     * Returns the Page TSconfig for page with id, $id
689
     *
690
     * @param int $id Page uid for which to create Page TSconfig
691
     * @return array Page TSconfig
692
     * @see \TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser
693
     */
694
    public static function getPagesTSconfig($id)
695
    {
696
        $id = (int)$id;
697
698
        $cache = self::getRuntimeCache();
699
        $pagesTsConfigIdToHash = $cache->get('pagesTsConfigIdToHash' . $id);
700
        if ($pagesTsConfigIdToHash !== false) {
701
            return $cache->get('pagesTsConfigHashToContent' . $pagesTsConfigIdToHash);
702
        }
703
704
        $rootLine = self::BEgetRootLine($id, '', true);
705
        // Order correctly
706
        ksort($rootLine);
707
708
        try {
709
            $site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId($id);
710
        } catch (SiteNotFoundException $exception) {
711
            $site = null;
712
        }
713
714
        // Load PageTS from all pages of the rootLine
715
        $pageTs = GeneralUtility::makeInstance(PageTsConfigLoader::class)->load($rootLine);
716
717
        // Parse the PageTS into an array, also applying conditions
718
        $parser = GeneralUtility::makeInstance(
719
            PageTsConfigParser::class,
720
            GeneralUtility::makeInstance(TypoScriptParser::class),
721
            GeneralUtility::makeInstance(CacheManager::class)->getCache('hash')
722
        );
723
        $matcher = GeneralUtility::makeInstance(ConditionMatcher::class, null, $id, $rootLine);
724
        $tsConfig = $parser->parse($pageTs, $matcher, $site);
725
        $cacheHash = md5(json_encode($tsConfig));
726
727
        // Get User TSconfig overlay, if no backend user is logged-in, this needs to be checked as well
728
        if (static::getBackendUserAuthentication()) {
729
            $userTSconfig = static::getBackendUserAuthentication()->getTSConfig() ?? [];
730
        } else {
731
            $userTSconfig = [];
732
        }
733
734
        if (is_array($userTSconfig['page.'] ?? null)) {
735
            // Override page TSconfig with user TSconfig
736
            ArrayUtility::mergeRecursiveWithOverrule($tsConfig, $userTSconfig['page.']);
737
            $cacheHash .= '_user' . static::getBackendUserAuthentication()->user['uid'];
738
        }
739
740
        // Many pages end up with the same ts config. To reduce memory usage, the cache
741
        // entries are a linked list: One or more pids point to content hashes which then
742
        // contain the cached content.
743
        $cache->set('pagesTsConfigHashToContent' . $cacheHash, $tsConfig, ['pagesTsConfig']);
744
        $cache->set('pagesTsConfigIdToHash' . $id, $cacheHash, ['pagesTsConfig']);
745
746
        return $tsConfig;
747
    }
748
749
    /**
750
     * Returns the non-parsed Page TSconfig for page with id, $id
751
     *
752
     * @param int $id Page uid for which to create Page TSconfig
753
     * @param array $rootLine If $rootLine is an array, that is used as rootline, otherwise rootline is just calculated
754
     * @return array Non-parsed Page TSconfig
755
     */
756
    public static function getRawPagesTSconfig($id, array $rootLine = null)
757
    {
758
        trigger_error('BackendUtility::getRawPagesTSconfig will be removed in TYPO3 v11.0. Use PageTsConfigLoader instead.', E_USER_DEPRECATED);
759
        if (!is_array($rootLine)) {
760
            $rootLine = self::BEgetRootLine($id, '', true);
761
        }
762
763
        // Order correctly
764
        ksort($rootLine);
765
        $tsDataArray = [];
766
        // Setting default configuration
767
        $tsDataArray['defaultPageTSconfig'] = $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultPageTSconfig'];
768
        foreach ($rootLine as $k => $v) {
769
            if (trim($v['tsconfig_includes'])) {
770
                $includeTsConfigFileList = GeneralUtility::trimExplode(',', $v['tsconfig_includes'], true);
771
                // Traversing list
772
                foreach ($includeTsConfigFileList as $key => $includeTsConfigFile) {
773
                    if (strpos($includeTsConfigFile, 'EXT:') === 0) {
774
                        [$includeTsConfigFileExtensionKey, $includeTsConfigFilename] = explode(
775
                            '/',
776
                            substr($includeTsConfigFile, 4),
777
                            2
778
                        );
779
                        if ((string)$includeTsConfigFileExtensionKey !== ''
780
                            && ExtensionManagementUtility::isLoaded($includeTsConfigFileExtensionKey)
781
                            && (string)$includeTsConfigFilename !== ''
782
                        ) {
783
                            $extensionPath = ExtensionManagementUtility::extPath($includeTsConfigFileExtensionKey);
784
                            $includeTsConfigFileAndPath = PathUtility::getCanonicalPath($extensionPath . $includeTsConfigFilename);
785
                            if (strpos($includeTsConfigFileAndPath, $extensionPath) === 0 && file_exists($includeTsConfigFileAndPath)) {
786
                                $tsDataArray['uid_' . $v['uid'] . '_static_' . $key] = file_get_contents($includeTsConfigFileAndPath);
787
                            }
788
                        }
789
                    }
790
                }
791
            }
792
            $tsDataArray['uid_' . $v['uid']] = $v['TSconfig'];
793
        }
794
795
        $eventDispatcher = GeneralUtility::getContainer()->get(EventDispatcherInterface::class);
796
        $event = $eventDispatcher->dispatch(new ModifyLoadedPageTsConfigEvent($tsDataArray, $rootLine));
797
        return TypoScriptParser::checkIncludeLines_array($event->getTsConfig());
798
    }
799
800
    /*******************************************
801
     *
802
     * Users / Groups related
803
     *
804
     *******************************************/
805
    /**
806
     * Returns an array with be_users records of all user NOT DELETED sorted by their username
807
     * Keys in the array is the be_users uid
808
     *
809
     * @param string $fields Optional $fields list (default: username,usergroup,usergroup_cached_list,uid) can be used to set the selected fields
810
     * @param string $where Optional $where clause (fx. "AND username='pete'") can be used to limit query
811
     * @return array
812
     * @internal should only be used from within TYPO3 Core, use a direct SQL query instead to ensure proper DBAL where statements
813
     */
814
    public static function getUserNames($fields = 'username,usergroup,usergroup_cached_list,uid', $where = '')
815
    {
816
        return self::getRecordsSortedByTitle(
817
            GeneralUtility::trimExplode(',', $fields, true),
818
            'be_users',
819
            'username',
820
            'AND pid=0 ' . $where
821
        );
822
    }
823
824
    /**
825
     * Returns an array with be_groups records (title, uid) of all groups NOT DELETED sorted by their title
826
     *
827
     * @param string $fields Field list
828
     * @param string $where WHERE clause
829
     * @return array
830
     * @internal should only be used from within TYPO3 Core, use a direct SQL query instead to ensure proper DBAL where statements
831
     */
832
    public static function getGroupNames($fields = 'title,uid', $where = '')
833
    {
834
        return self::getRecordsSortedByTitle(
835
            GeneralUtility::trimExplode(',', $fields, true),
836
            'be_groups',
837
            'title',
838
            'AND pid=0 ' . $where
839
        );
840
    }
841
842
    /**
843
     * Returns an array of all non-deleted records of a table sorted by a given title field.
844
     * The value of the title field will be replaced by the return value
845
     * of self::getRecordTitle() before the sorting is performed.
846
     *
847
     * @param array $fields Fields to select
848
     * @param string $table Table name
849
     * @param string $titleField Field that will contain the record title
850
     * @param string $where Additional where clause
851
     * @return array Array of sorted records
852
     */
853
    protected static function getRecordsSortedByTitle(array $fields, $table, $titleField, $where = '')
854
    {
855
        $fieldsIndex = array_flip($fields);
856
        // Make sure the titleField is amongst the fields when getting sorted
857
        $fieldsIndex[$titleField] = 1;
858
859
        $result = [];
860
861
        $queryBuilder = static::getQueryBuilderForTable($table);
862
        $queryBuilder->getRestrictions()
863
            ->removeAll()
864
            ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
865
866
        $res = $queryBuilder
867
            ->select('*')
868
            ->from($table)
869
            ->where(QueryHelper::stripLogicalOperatorPrefix($where))
870
            ->execute();
871
872
        while ($record = $res->fetch()) {
873
            // store the uid, because it might be unset if it's not among the requested $fields
874
            $recordId = $record['uid'];
875
            $record[$titleField] = self::getRecordTitle($table, $record);
876
877
            // include only the requested fields in the result
878
            $result[$recordId] = array_intersect_key($record, $fieldsIndex);
879
        }
880
881
        // sort records by $sortField. This is not done in the query because the title might have been overwritten by
882
        // self::getRecordTitle();
883
        return ArrayUtility::sortArraysByKey($result, $titleField);
884
    }
885
886
    /**
887
     * Returns the array $usernames with the names of all users NOT IN $groupArray changed to the uid (hides the usernames!).
888
     * If $excludeBlindedFlag is set, then these records are unset from the array $usernames
889
     * Takes $usernames (array made by \TYPO3\CMS\Backend\Utility\BackendUtility::getUserNames()) and a $groupArray (array with the groups a certain user is member of) as input
890
     *
891
     * @param array $usernames User names
892
     * @param array $groupArray Group names
893
     * @param bool $excludeBlindedFlag If $excludeBlindedFlag is set, then these records are unset from the array $usernames
894
     * @return array User names, blinded
895
     * @internal
896
     */
897
    public static function blindUserNames($usernames, $groupArray, $excludeBlindedFlag = false)
898
    {
899
        if (is_array($usernames) && is_array($groupArray)) {
0 ignored issues
show
introduced by
The condition is_array($groupArray) is always true.
Loading history...
900
            foreach ($usernames as $uid => $row) {
901
                $userN = $uid;
902
                $set = 0;
903
                if ($row['uid'] != static::getBackendUserAuthentication()->user['uid']) {
904
                    foreach ($groupArray as $v) {
905
                        if ($v && GeneralUtility::inList($row['usergroup_cached_list'], $v)) {
906
                            $userN = $row['username'];
907
                            $set = 1;
908
                        }
909
                    }
910
                } else {
911
                    $userN = $row['username'];
912
                    $set = 1;
913
                }
914
                $usernames[$uid]['username'] = $userN;
915
                if ($excludeBlindedFlag && !$set) {
916
                    unset($usernames[$uid]);
917
                }
918
            }
919
        }
920
        return $usernames;
921
    }
922
923
    /**
924
     * Corresponds to blindUserNames but works for groups instead
925
     *
926
     * @param array $groups Group names
927
     * @param array $groupArray Group names (reference)
928
     * @param bool $excludeBlindedFlag If $excludeBlindedFlag is set, then these records are unset from the array $usernames
929
     * @return array
930
     * @internal
931
     */
932
    public static function blindGroupNames($groups, $groupArray, $excludeBlindedFlag = false)
933
    {
934
        if (is_array($groups) && is_array($groupArray)) {
0 ignored issues
show
introduced by
The condition is_array($groupArray) is always true.
Loading history...
935
            foreach ($groups as $uid => $row) {
936
                $groupN = $uid;
937
                $set = 0;
938
                if (in_array($uid, $groupArray, false)) {
939
                    $groupN = $row['title'];
940
                    $set = 1;
941
                }
942
                $groups[$uid]['title'] = $groupN;
943
                if ($excludeBlindedFlag && !$set) {
944
                    unset($groups[$uid]);
945
                }
946
            }
947
        }
948
        return $groups;
949
    }
950
951
    /*******************************************
952
     *
953
     * Output related
954
     *
955
     *******************************************/
956
    /**
957
     * Returns the difference in days between input $tstamp and $EXEC_TIME
958
     *
959
     * @param int $tstamp Time stamp, seconds
960
     * @return int
961
     */
962
    public static function daysUntil($tstamp)
963
    {
964
        $delta_t = $tstamp - $GLOBALS['EXEC_TIME'];
965
        return ceil($delta_t / (3600 * 24));
966
    }
967
968
    /**
969
     * Returns $tstamp formatted as "ddmmyy" (According to $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'])
970
     *
971
     * @param int $tstamp Time stamp, seconds
972
     * @return string Formatted time
973
     */
974
    public static function date($tstamp)
975
    {
976
        return date($GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'], (int)$tstamp);
977
    }
978
979
    /**
980
     * Returns $tstamp formatted as "ddmmyy hhmm" (According to $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] AND $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'])
981
     *
982
     * @param int $value Time stamp, seconds
983
     * @return string Formatted time
984
     */
985
    public static function datetime($value)
986
    {
987
        return date(
988
            $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] . ' ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'],
989
            $value
990
        );
991
    }
992
993
    /**
994
     * Returns $value (in seconds) formatted as hh:mm:ss
995
     * For instance $value = 3600 + 60*2 + 3 should return "01:02:03"
996
     *
997
     * @param int $value Time stamp, seconds
998
     * @param bool $withSeconds Output hh:mm:ss. If FALSE: hh:mm
999
     * @return string Formatted time
1000
     */
1001
    public static function time($value, $withSeconds = true)
1002
    {
1003
        return gmdate('H:i' . ($withSeconds ? ':s' : ''), (int)$value);
1004
    }
1005
1006
    /**
1007
     * Returns the "age" in minutes / hours / days / years of the number of $seconds inputted.
1008
     *
1009
     * @param int $seconds Seconds could be the difference of a certain timestamp and time()
1010
     * @param string $labels Labels should be something like ' min| hrs| days| yrs| min| hour| day| year'. This value is typically delivered by this function call: $GLOBALS["LANG"]->sL("LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.minutesHoursDaysYears")
1011
     * @return string Formatted time
1012
     */
1013
    public static function calcAge($seconds, $labels = 'min|hrs|days|yrs|min|hour|day|year')
1014
    {
1015
        $labelArr = GeneralUtility::trimExplode('|', $labels, true);
1016
        $absSeconds = abs($seconds);
1017
        $sign = $seconds < 0 ? -1 : 1;
1018
        if ($absSeconds < 3600) {
1019
            $val = round($absSeconds / 60);
1020
            $seconds = $sign * $val . ' ' . ($val == 1 ? $labelArr[4] : $labelArr[0]);
1021
        } elseif ($absSeconds < 24 * 3600) {
1022
            $val = round($absSeconds / 3600);
1023
            $seconds = $sign * $val . ' ' . ($val == 1 ? $labelArr[5] : $labelArr[1]);
1024
        } elseif ($absSeconds < 365 * 24 * 3600) {
1025
            $val = round($absSeconds / (24 * 3600));
1026
            $seconds = $sign * $val . ' ' . ($val == 1 ? $labelArr[6] : $labelArr[2]);
1027
        } else {
1028
            $val = round($absSeconds / (365 * 24 * 3600));
1029
            $seconds = $sign * $val . ' ' . ($val == 1 ? $labelArr[7] : $labelArr[3]);
1030
        }
1031
        return $seconds;
1032
    }
1033
1034
    /**
1035
     * Returns a formatted timestamp if $tstamp is set.
1036
     * The date/datetime will be followed by the age in parenthesis.
1037
     *
1038
     * @param int $tstamp Time stamp, seconds
1039
     * @param int $prefix 1/-1 depending on polarity of age.
1040
     * @param string $date $date=="date" will yield "dd:mm:yy" formatting, otherwise "dd:mm:yy hh:mm
1041
     * @return string
1042
     */
1043
    public static function dateTimeAge($tstamp, $prefix = 1, $date = '')
1044
    {
1045
        if (!$tstamp) {
1046
            return '';
1047
        }
1048
        $label = static::getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.minutesHoursDaysYears');
1049
        $age = ' (' . self::calcAge($prefix * ($GLOBALS['EXEC_TIME'] - $tstamp), $label) . ')';
1050
        return ($date === 'date' ? self::date($tstamp) : self::datetime($tstamp)) . $age;
1051
    }
1052
1053
    /**
1054
     * Resolves file references for a given record.
1055
     *
1056
     * @param string $tableName Name of the table of the record
1057
     * @param string $fieldName Name of the field of the record
1058
     * @param array $element Record data
1059
     * @param int|null $workspaceId Workspace to fetch data for
1060
     * @return \TYPO3\CMS\Core\Resource\FileReference[]|null
1061
     */
1062
    public static function resolveFileReferences($tableName, $fieldName, $element, $workspaceId = null)
1063
    {
1064
        if (empty($GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'])) {
1065
            return null;
1066
        }
1067
        $configuration = $GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'];
1068
        if (empty($configuration['type']) || $configuration['type'] !== 'inline'
1069
            || empty($configuration['foreign_table']) || $configuration['foreign_table'] !== 'sys_file_reference'
1070
        ) {
1071
            return null;
1072
        }
1073
1074
        $fileReferences = [];
1075
        /** @var RelationHandler $relationHandler */
1076
        $relationHandler = GeneralUtility::makeInstance(RelationHandler::class);
1077
        if ($workspaceId !== null) {
1078
            $relationHandler->setWorkspaceId($workspaceId);
1079
        }
1080
        $relationHandler->start(
1081
            $element[$fieldName],
1082
            $configuration['foreign_table'],
1083
            $configuration['MM'] ?? '',
1084
            $element['uid'],
1085
            $tableName,
1086
            $configuration
1087
        );
1088
        $relationHandler->processDeletePlaceholder();
1089
        $referenceUids = $relationHandler->tableArray[$configuration['foreign_table']];
1090
1091
        foreach ($referenceUids as $referenceUid) {
1092
            try {
1093
                $fileReference = GeneralUtility::makeInstance(ResourceFactory::class)->getFileReferenceObject(
1094
                    $referenceUid,
1095
                    [],
1096
                    $workspaceId === 0
1097
                );
1098
                $fileReferences[$fileReference->getUid()] = $fileReference;
1099
            } catch (FileDoesNotExistException $e) {
1100
                /**
1101
                 * We just catch the exception here
1102
                 * Reasoning: There is nothing an editor or even admin could do
1103
                 */
1104
            } catch (\InvalidArgumentException $e) {
1105
                /**
1106
                 * The storage does not exist anymore
1107
                 * Log the exception message for admins as they maybe can restore the storage
1108
                 */
1109
                self::getLogger()->error($e->getMessage(), ['table' => $tableName, 'fieldName' => $fieldName, 'referenceUid' => $referenceUid, 'exception' => $e]);
1110
            }
1111
        }
1112
1113
        return $fileReferences;
1114
    }
1115
1116
    /**
1117
     * Returns a linked image-tag for thumbnail(s)/fileicons/truetype-font-previews from a database row with sys_file_references
1118
     * All $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] extension are made to thumbnails + ttf file (renders font-example)
1119
     * Thumbnails are linked to ShowItemController (/thumbnails route)
1120
     *
1121
     * @param array $row Row is the database row from the table, $table.
1122
     * @param string $table Table name for $row (present in TCA)
1123
     * @param string $field Field is pointing to the connecting field of sys_file_references
1124
     * @param string $backPath Back path prefix for image tag src="" field
1125
     * @param string $thumbScript UNUSED since FAL
1126
     * @param string $uploaddir UNUSED since FAL
1127
     * @param int $abs UNUSED
1128
     * @param string $tparams Optional: $tparams is additional attributes for the image tags
1129
     * @param int|string $size Optional: $size is [w]x[h] of the thumbnail. 64 is default.
1130
     * @param bool $linkInfoPopup Whether to wrap with a link opening the info popup
1131
     * @return string Thumbnail image tag.
1132
     */
1133
    public static function thumbCode(
1134
        $row,
1135
        $table,
1136
        $field,
1137
        $backPath = '',
1138
        $thumbScript = '',
1139
        $uploaddir = null,
1140
        $abs = 0,
1141
        $tparams = '',
1142
        $size = '',
1143
        $linkInfoPopup = true
1144
    ) {
1145
        // Check and parse the size parameter
1146
        $size = trim($size);
1147
        $sizeParts = [64, 64];
1148
        if ($size) {
1149
            $sizeParts = explode('x', $size . 'x' . $size);
1150
        }
1151
        $thumbData = '';
1152
        $fileReferences = static::resolveFileReferences($table, $field, $row);
1153
        // FAL references
1154
        $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
1155
        if ($fileReferences !== null) {
1156
            foreach ($fileReferences as $fileReferenceObject) {
1157
                // Do not show previews of hidden references
1158
                if ($fileReferenceObject->getProperty('hidden')) {
1159
                    continue;
1160
                }
1161
                $fileObject = $fileReferenceObject->getOriginalFile();
1162
1163
                if ($fileObject->isMissing()) {
1164
                    $thumbData .= '<span class="label label-danger">'
1165
                        . htmlspecialchars(
1166
                            static::getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.file_missing')
1167
                        )
1168
                        . '</span>&nbsp;' . htmlspecialchars($fileObject->getName()) . '<br />';
1169
                    continue;
1170
                }
1171
1172
                // Preview web image or media elements
1173
                if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['thumbnails']
1174
                    && GeneralUtility::inList(
1175
                        $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'],
1176
                        $fileReferenceObject->getExtension()
1177
                    )
1178
                ) {
1179
                    $cropVariantCollection = CropVariantCollection::create((string)$fileReferenceObject->getProperty('crop'));
1180
                    $cropArea = $cropVariantCollection->getCropArea();
1181
                    $imageUrl = self::getThumbnailUrl($fileObject->getUid(), [
1182
                        'width' => $sizeParts[0],
1183
                        'height' => $sizeParts[1] . 'c',
1184
                        'crop' => $cropArea->isEmpty() ? null : $cropArea->makeAbsoluteBasedOnFile($fileReferenceObject),
1185
                        '_context' => $cropArea->isEmpty() ? ProcessedFile::CONTEXT_IMAGEPREVIEW : ProcessedFile::CONTEXT_IMAGECROPSCALEMASK
1186
                    ]);
1187
                    $attributes = [
1188
                        'src' => $imageUrl,
1189
                        'width' => (int)$sizeParts[0],
1190
                        'height' => (int)$sizeParts[1],
1191
                        'alt' => $fileReferenceObject->getName(),
1192
                    ];
1193
                    $imgTag = '<img ' . GeneralUtility::implodeAttributes($attributes, true) . $tparams . '/>';
1194
                } else {
1195
                    // Icon
1196
                    $imgTag = '<span title="' . htmlspecialchars($fileObject->getName()) . '">'
1197
                        . $iconFactory->getIconForResource($fileObject, Icon::SIZE_SMALL)->render()
1198
                        . '</span>';
1199
                }
1200
                if ($linkInfoPopup) {
1201
                    // @todo Should we add requireJsModule again (should be loaded in most/all cases)
1202
                    // loadRequireJsModule('TYPO3/CMS/Backend/ActionDispatcher');
1203
                    $attributes = GeneralUtility::implodeAttributes([
1204
                        'data-dispatch-action' => 'TYPO3.InfoWindow.showItem',
1205
                        'data-dispatch-args-list' => '_FILE,' . (int)$fileObject->getUid(),
1206
                    ], true);
1207
                    $thumbData .= '<a href="#" ' . $attributes . '>' . $imgTag . '</a> ';
1208
                } else {
1209
                    $thumbData .= $imgTag;
1210
                }
1211
            }
1212
        }
1213
        return $thumbData;
1214
    }
1215
1216
    /**
1217
     * @param int $fileId
1218
     * @param array $configuration
1219
     * @return string
1220
     * @throws \TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException
1221
     */
1222
    public static function getThumbnailUrl(int $fileId, array $configuration): string
1223
    {
1224
        $parameters = json_encode([
1225
            'fileId' => $fileId,
1226
            'configuration' => $configuration
1227
        ]);
1228
        $uriParameters = [
1229
            'parameters' => $parameters,
1230
            'hmac' => GeneralUtility::hmac(
1231
                $parameters,
1232
                ThumbnailController::class
1233
            ),
1234
        ];
1235
        return (string)GeneralUtility::makeInstance(UriBuilder::class)
1236
            ->buildUriFromRoute('thumbnails', $uriParameters);
1237
    }
1238
1239
    /**
1240
     * Returns title-attribute information for a page-record informing about id, doktype, hidden, starttime, endtime, fe_group etc.
1241
     *
1242
     * @param array $row Input must be a page row ($row) with the proper fields set (be sure - send the full range of fields for the table)
1243
     * @param string $perms_clause This is used to get the record path of the shortcut page, if any (and doktype==4)
1244
     * @param bool $includeAttrib If $includeAttrib is set, then the 'title=""' attribute is wrapped about the return value, which is in any case htmlspecialchar()'ed already
1245
     * @return string
1246
     */
1247
    public static function titleAttribForPages($row, $perms_clause = '', $includeAttrib = true)
1248
    {
1249
        $lang = static::getLanguageService();
1250
        $parts = [];
1251
        $parts[] = 'id=' . $row['uid'];
1252
        if ($row['uid'] === 0) {
1253
            $out = htmlspecialchars($parts[0]);
1254
            return $includeAttrib ? 'title="' . $out . '"' : $out;
1255
        }
1256
        switch (VersionState::cast($row['t3ver_state'])) {
1257
            case new VersionState(VersionState::NEW_PLACEHOLDER):
1258
                $parts[] = 'PLH WSID#' . $row['t3ver_wsid'];
1259
                break;
1260
            case new VersionState(VersionState::DELETE_PLACEHOLDER):
1261
                $parts[] = 'Deleted element!';
1262
                break;
1263
            case new VersionState(VersionState::MOVE_PLACEHOLDER):
1264
                $parts[] = 'OLD LOCATION (Move Placeholder) WSID#' . $row['t3ver_wsid'];
1265
                break;
1266
            case new VersionState(VersionState::MOVE_POINTER):
1267
                $parts[] = 'NEW LOCATION (Move-to Pointer) WSID#' . $row['t3ver_wsid'];
1268
                break;
1269
            case new VersionState(VersionState::NEW_PLACEHOLDER_VERSION):
1270
                $parts[] = 'New element!';
1271
                break;
1272
        }
1273
        if ($row['doktype'] == PageRepository::DOKTYPE_LINK) {
1274
            $parts[] = $lang->sL($GLOBALS['TCA']['pages']['columns']['url']['label']) . ' ' . $row['url'];
1275
        } elseif ($row['doktype'] == PageRepository::DOKTYPE_SHORTCUT) {
1276
            if ($perms_clause) {
1277
                $label = self::getRecordPath((int)$row['shortcut'], $perms_clause, 20);
1278
            } else {
1279
                $row['shortcut'] = (int)$row['shortcut'];
1280
                $lRec = self::getRecordWSOL('pages', $row['shortcut'], 'title');
1281
                $label = $lRec['title'] . ' (id=' . $row['shortcut'] . ')';
1282
            }
1283
            if ($row['shortcut_mode'] != PageRepository::SHORTCUT_MODE_NONE) {
1284
                $label .= ', ' . $lang->sL($GLOBALS['TCA']['pages']['columns']['shortcut_mode']['label']) . ' '
1285
                    . $lang->sL(self::getLabelFromItemlist('pages', 'shortcut_mode', $row['shortcut_mode']));
1286
            }
1287
            $parts[] = $lang->sL($GLOBALS['TCA']['pages']['columns']['shortcut']['label']) . ' ' . $label;
0 ignored issues
show
Bug introduced by
Are you sure $label of type array<integer,string>|string can be used in concatenation? ( Ignorable by Annotation )

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

1287
            $parts[] = $lang->sL($GLOBALS['TCA']['pages']['columns']['shortcut']['label']) . ' ' . /** @scrutinizer ignore-type */ $label;
Loading history...
1288
        } elseif ($row['doktype'] == PageRepository::DOKTYPE_MOUNTPOINT) {
1289
            if ((int)$row['mount_pid'] > 0) {
1290
                if ($perms_clause) {
1291
                    $label = self::getRecordPath((int)$row['mount_pid'], $perms_clause, 20);
1292
                } else {
1293
                    $lRec = self::getRecordWSOL('pages', (int)$row['mount_pid'], 'title');
1294
                    $label = $lRec['title'] . ' (id=' . $row['mount_pid'] . ')';
1295
                }
1296
                $parts[] = $lang->sL($GLOBALS['TCA']['pages']['columns']['mount_pid']['label']) . ' ' . $label;
1297
                if ($row['mount_pid_ol']) {
1298
                    $parts[] = $lang->sL($GLOBALS['TCA']['pages']['columns']['mount_pid_ol']['label']);
1299
                }
1300
            } else {
1301
                $parts[] = $lang->sL('LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:no_mount_pid');
1302
            }
1303
        }
1304
        if ($row['nav_hide']) {
1305
            $parts[] = $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:pages.nav_hide');
1306
        }
1307
        if ($row['hidden']) {
1308
            $parts[] = $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.hidden');
1309
        }
1310
        if ($row['starttime']) {
1311
            $parts[] = $lang->sL($GLOBALS['TCA']['pages']['columns']['starttime']['label'])
1312
                . ' ' . self::dateTimeAge($row['starttime'], -1, 'date');
1313
        }
1314
        if ($row['endtime']) {
1315
            $parts[] = $lang->sL($GLOBALS['TCA']['pages']['columns']['endtime']['label']) . ' '
1316
                . self::dateTimeAge($row['endtime'], -1, 'date');
1317
        }
1318
        if ($row['fe_group']) {
1319
            $fe_groups = [];
1320
            foreach (GeneralUtility::intExplode(',', $row['fe_group']) as $fe_group) {
1321
                if ($fe_group < 0) {
1322
                    $fe_groups[] = $lang->sL(self::getLabelFromItemlist('pages', 'fe_group', $fe_group));
1323
                } else {
1324
                    $lRec = self::getRecordWSOL('fe_groups', $fe_group, 'title');
1325
                    $fe_groups[] = $lRec['title'];
1326
                }
1327
            }
1328
            $label = implode(', ', $fe_groups);
1329
            $parts[] = $lang->sL($GLOBALS['TCA']['pages']['columns']['fe_group']['label']) . ' ' . $label;
1330
        }
1331
        $out = htmlspecialchars(implode(' - ', $parts));
1332
        return $includeAttrib ? 'title="' . $out . '"' : $out;
1333
    }
1334
1335
    /**
1336
     * Returns the combined markup for Bootstraps tooltips
1337
     *
1338
     * @param array $row
1339
     * @param string $table
1340
     * @return string
1341
     */
1342
    public static function getRecordToolTip(array $row, $table = 'pages')
1343
    {
1344
        $toolTipText = self::getRecordIconAltText($row, $table);
1345
        $toolTipCode = 'data-toggle="tooltip" data-title=" '
1346
            . str_replace(' - ', '<br>', $toolTipText)
1347
            . '" data-html="true" data-placement="right"';
1348
        return $toolTipCode;
1349
    }
1350
1351
    /**
1352
     * Returns title-attribute information for ANY record (from a table defined in TCA of course)
1353
     * The included information depends on features of the table, but if hidden, starttime, endtime and fe_group fields are configured for, information about the record status in regard to these features are is included.
1354
     * "pages" table can be used as well and will return the result of ->titleAttribForPages() for that page.
1355
     *
1356
     * @param array $row Table row; $row is a row from the table, $table
1357
     * @param string $table Table name
1358
     * @return string
1359
     */
1360
    public static function getRecordIconAltText($row, $table = 'pages')
1361
    {
1362
        if ($table === 'pages') {
1363
            $out = self::titleAttribForPages($row, '', 0);
1364
        } else {
1365
            $out = !empty(trim($GLOBALS['TCA'][$table]['ctrl']['descriptionColumn'])) ? $row[$GLOBALS['TCA'][$table]['ctrl']['descriptionColumn']] . ' ' : '';
1366
            $ctrl = $GLOBALS['TCA'][$table]['ctrl']['enablecolumns'];
1367
            // Uid is added
1368
            $out .= 'id=' . $row['uid'];
1369
            if (static::isTableWorkspaceEnabled($table)) {
1370
                switch (VersionState::cast($row['t3ver_state'])) {
1371
                    case new VersionState(VersionState::NEW_PLACEHOLDER):
1372
                        $out .= ' - PLH WSID#' . $row['t3ver_wsid'];
1373
                        break;
1374
                    case new VersionState(VersionState::DELETE_PLACEHOLDER):
1375
                        $out .= ' - Deleted element!';
1376
                        break;
1377
                    case new VersionState(VersionState::MOVE_PLACEHOLDER):
1378
                        $out .= ' - OLD LOCATION (Move Placeholder) WSID#' . $row['t3ver_wsid'];
1379
                        break;
1380
                    case new VersionState(VersionState::MOVE_POINTER):
1381
                        $out .= ' - NEW LOCATION (Move-to Pointer) WSID#' . $row['t3ver_wsid'];
1382
                        break;
1383
                    case new VersionState(VersionState::NEW_PLACEHOLDER_VERSION):
1384
                        $out .= ' - New element!';
1385
                        break;
1386
                }
1387
            }
1388
            // Hidden
1389
            $lang = static::getLanguageService();
1390
            if ($ctrl['disabled']) {
1391
                $out .= $row[$ctrl['disabled']] ? ' - ' . $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.hidden') : '';
1392
            }
1393
            if ($ctrl['starttime']) {
1394
                if ($row[$ctrl['starttime']] > $GLOBALS['EXEC_TIME']) {
1395
                    $out .= ' - ' . $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.starttime') . ':' . self::date($row[$ctrl['starttime']]) . ' (' . self::daysUntil($row[$ctrl['starttime']]) . ' ' . $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.days') . ')';
1396
                }
1397
            }
1398
            if ($row[$ctrl['endtime']]) {
1399
                $out .= ' - ' . $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.endtime') . ': ' . self::date($row[$ctrl['endtime']]) . ' (' . self::daysUntil($row[$ctrl['endtime']]) . ' ' . $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.days') . ')';
1400
            }
1401
        }
1402
        return htmlspecialchars($out);
1403
    }
1404
1405
    /**
1406
     * Returns the label of the first found entry in an "items" array from $GLOBALS['TCA'] (tablename = $table/fieldname = $col) where the value is $key
1407
     *
1408
     * @param string $table Table name, present in $GLOBALS['TCA']
1409
     * @param string $col Field name, present in $GLOBALS['TCA']
1410
     * @param string $key items-array value to match
1411
     * @return string Label for item entry
1412
     */
1413
    public static function getLabelFromItemlist($table, $col, $key)
1414
    {
1415
        // Check, if there is an "items" array:
1416
        if (is_array($GLOBALS['TCA'][$table]['columns'][$col]['config']['items'] ?? false)) {
1417
            // Traverse the items-array...
1418
            foreach ($GLOBALS['TCA'][$table]['columns'][$col]['config']['items'] as $v) {
1419
                // ... and return the first found label where the value was equal to $key
1420
                if ((string)$v[1] === (string)$key) {
1421
                    return $v[0];
1422
                }
1423
            }
1424
        }
1425
        return '';
1426
    }
1427
1428
    /**
1429
     * Return the label of a field by additionally checking TsConfig values
1430
     *
1431
     * @param int $pageId Page id
1432
     * @param string $table Table name
1433
     * @param string $column Field Name
1434
     * @param string $key item value
1435
     * @return string Label for item entry
1436
     */
1437
    public static function getLabelFromItemListMerged($pageId, $table, $column, $key)
1438
    {
1439
        $pageTsConfig = static::getPagesTSconfig($pageId);
1440
        $label = '';
1441
        if (isset($pageTsConfig['TCEFORM.'])
1442
            && \is_array($pageTsConfig['TCEFORM.'])
1443
            && \is_array($pageTsConfig['TCEFORM.'][$table . '.'])
1444
            && \is_array($pageTsConfig['TCEFORM.'][$table . '.'][$column . '.'])
1445
        ) {
1446
            if (\is_array($pageTsConfig['TCEFORM.'][$table . '.'][$column . '.']['addItems.'])
1447
                && isset($pageTsConfig['TCEFORM.'][$table . '.'][$column . '.']['addItems.'][$key])
1448
            ) {
1449
                $label = $pageTsConfig['TCEFORM.'][$table . '.'][$column . '.']['addItems.'][$key];
1450
            } elseif (\is_array($pageTsConfig['TCEFORM.'][$table . '.'][$column . '.']['altLabels.'])
1451
                && isset($pageTsConfig['TCEFORM.'][$table . '.'][$column . '.']['altLabels.'][$key])
1452
            ) {
1453
                $label = $pageTsConfig['TCEFORM.'][$table . '.'][$column . '.']['altLabels.'][$key];
1454
            }
1455
        }
1456
        if (empty($label)) {
1457
            $tcaValue = self::getLabelFromItemlist($table, $column, $key);
1458
            if (!empty($tcaValue)) {
1459
                $label = $tcaValue;
1460
            }
1461
        }
1462
        return $label;
1463
    }
1464
1465
    /**
1466
     * Splits the given key with commas and returns the list of all the localized items labels, separated by a comma.
1467
     * NOTE: this does not take itemsProcFunc into account
1468
     *
1469
     * @param string $table Table name, present in TCA
1470
     * @param string $column Field name
1471
     * @param string $keyList Key or comma-separated list of keys.
1472
     * @param array $columnTsConfig page TSConfig for $column (TCEMAIN.<table>.<column>)
1473
     * @return string Comma-separated list of localized labels
1474
     */
1475
    public static function getLabelsFromItemsList($table, $column, $keyList, array $columnTsConfig = [])
1476
    {
1477
        // Check if there is an "items" array
1478
        if (
1479
            !isset($GLOBALS['TCA'][$table]['columns'][$column]['config']['items'])
1480
            || !is_array($GLOBALS['TCA'][$table]['columns'][$column]['config']['items'])
1481
            || $keyList === ''
1482
        ) {
1483
            return '';
1484
        }
1485
1486
        $keys = GeneralUtility::trimExplode(',', $keyList, true);
1487
        $labels = [];
1488
        // Loop on all selected values
1489
        foreach ($keys as $key) {
1490
            $label = null;
1491
            if ($columnTsConfig) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $columnTsConfig 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...
1492
                // Check if label has been defined or redefined via pageTsConfig
1493
                if (isset($columnTsConfig['addItems.'][$key])) {
1494
                    $label = $columnTsConfig['addItems.'][$key];
1495
                } elseif (isset($columnTsConfig['altLabels.'][$key])) {
1496
                    $label = $columnTsConfig['altLabels.'][$key];
1497
                }
1498
            }
1499
            if ($label === null) {
1500
                // Otherwise lookup the label in TCA items list
1501
                foreach ($GLOBALS['TCA'][$table]['columns'][$column]['config']['items'] as $itemConfiguration) {
1502
                    [$currentLabel, $currentKey] = $itemConfiguration;
1503
                    if ((string)$key === (string)$currentKey) {
1504
                        $label = $currentLabel;
1505
                        break;
1506
                    }
1507
                }
1508
            }
1509
            if ($label !== null) {
1510
                $labels[] = static::getLanguageService()->sL($label);
1511
            }
1512
        }
1513
        return implode(', ', $labels);
1514
    }
1515
1516
    /**
1517
     * Returns the label-value for fieldname $col in table, $table
1518
     * If $printAllWrap is set (to a "wrap") then it's wrapped around the $col value IF THE COLUMN $col DID NOT EXIST in TCA!, eg. $printAllWrap = '<strong>|</strong>' and the fieldname was 'not_found_field' then the return value would be '<strong>not_found_field</strong>'
1519
     *
1520
     * @param string $table Table name, present in $GLOBALS['TCA']
1521
     * @param string $col Field name
1522
     * @return string or NULL if $col is not found in the TCA table
1523
     */
1524
    public static function getItemLabel($table, $col)
1525
    {
1526
        // Check if column exists
1527
        if (is_array($GLOBALS['TCA'][$table]) && is_array($GLOBALS['TCA'][$table]['columns'][$col])) {
1528
            return $GLOBALS['TCA'][$table]['columns'][$col]['label'];
1529
        }
1530
1531
        return null;
1532
    }
1533
1534
    /**
1535
     * Returns the "title"-value in record, $row, from table, $table
1536
     * The field(s) from which the value is taken is determined by the "ctrl"-entries 'label', 'label_alt' and 'label_alt_force'
1537
     *
1538
     * @param string $table Table name, present in TCA
1539
     * @param array $row Row from table
1540
     * @param bool $prep If set, result is prepared for output: The output is cropped to a limited length (depending on BE_USER->uc['titleLen']) and if no value is found for the title, '<em>[No title]</em>' is returned (localized). Further, the output is htmlspecialchars()'ed
1541
     * @param bool $forceResult If set, the function always returns an output. If no value is found for the title, '[No title]' is returned (localized).
1542
     * @return string
1543
     */
1544
    public static function getRecordTitle($table, $row, $prep = false, $forceResult = true)
1545
    {
1546
        $params = [];
1547
        $recordTitle = '';
1548
        if (isset($GLOBALS['TCA'][$table]) && is_array($GLOBALS['TCA'][$table])) {
1549
            // If configured, call userFunc
1550
            if (!empty($GLOBALS['TCA'][$table]['ctrl']['label_userFunc'])) {
1551
                $params['table'] = $table;
1552
                $params['row'] = $row;
1553
                $params['title'] = '';
1554
                $params['options'] = $GLOBALS['TCA'][$table]['ctrl']['label_userFunc_options'] ?? [];
1555
1556
                // Create NULL-reference
1557
                $null = null;
1558
                GeneralUtility::callUserFunction($GLOBALS['TCA'][$table]['ctrl']['label_userFunc'], $params, $null);
1559
                $recordTitle = $params['title'];
1560
            } else {
1561
                // No userFunc: Build label
1562
                $recordTitle = self::getProcessedValue(
1563
                    $table,
1564
                    $GLOBALS['TCA'][$table]['ctrl']['label'],
1565
                    $row[$GLOBALS['TCA'][$table]['ctrl']['label']],
1566
                    0,
1567
                    0,
1568
                    false,
1569
                    $row['uid'],
1570
                    $forceResult
1571
                );
1572
                if (!empty($GLOBALS['TCA'][$table]['ctrl']['label_alt'])
1573
                    && (!empty($GLOBALS['TCA'][$table]['ctrl']['label_alt_force']) || (string)$recordTitle === '')
1574
                ) {
1575
                    $altFields = GeneralUtility::trimExplode(',', $GLOBALS['TCA'][$table]['ctrl']['label_alt'], true);
1576
                    $tA = [];
1577
                    if (!empty($recordTitle)) {
1578
                        $tA[] = $recordTitle;
1579
                    }
1580
                    foreach ($altFields as $fN) {
1581
                        $recordTitle = trim(strip_tags($row[$fN]));
1582
                        if ((string)$recordTitle !== '') {
1583
                            $recordTitle = self::getProcessedValue($table, $fN, $recordTitle, 0, 0, false, $row['uid']);
1584
                            if (!$GLOBALS['TCA'][$table]['ctrl']['label_alt_force']) {
1585
                                break;
1586
                            }
1587
                            $tA[] = $recordTitle;
1588
                        }
1589
                    }
1590
                    if ($GLOBALS['TCA'][$table]['ctrl']['label_alt_force']) {
1591
                        $recordTitle = implode(', ', $tA);
1592
                    }
1593
                }
1594
            }
1595
            // If the current result is empty, set it to '[No title]' (localized) and prepare for output if requested
1596
            if ($prep || $forceResult) {
1597
                if ($prep) {
1598
                    $recordTitle = self::getRecordTitlePrep($recordTitle);
1599
                }
1600
                if (trim($recordTitle) === '') {
1601
                    $recordTitle = self::getNoRecordTitle($prep);
1602
                }
1603
            }
1604
        }
1605
1606
        return $recordTitle;
1607
    }
1608
1609
    /**
1610
     * Crops a title string to a limited length and if it really was cropped, wrap it in a <span title="...">|</span>,
1611
     * which offers a tooltip with the original title when moving mouse over it.
1612
     *
1613
     * @param string $title The title string to be cropped
1614
     * @param int $titleLength Crop title after this length - if not set, BE_USER->uc['titleLen'] is used
1615
     * @return string The processed title string, wrapped in <span title="...">|</span> if cropped
1616
     */
1617
    public static function getRecordTitlePrep($title, $titleLength = 0)
1618
    {
1619
        // If $titleLength is not a valid positive integer, use BE_USER->uc['titleLen']:
1620
        if (!$titleLength || !MathUtility::canBeInterpretedAsInteger($titleLength) || $titleLength < 0) {
1621
            $titleLength = static::getBackendUserAuthentication()->uc['titleLen'];
1622
        }
1623
        $titleOrig = htmlspecialchars($title);
1624
        $title = htmlspecialchars(GeneralUtility::fixed_lgd_cs($title, $titleLength));
1625
        // If title was cropped, offer a tooltip:
1626
        if ($titleOrig != $title) {
1627
            $title = '<span title="' . $titleOrig . '">' . $title . '</span>';
1628
        }
1629
        return $title;
1630
    }
1631
1632
    /**
1633
     * Get a localized [No title] string, wrapped in <em>|</em> if $prep is TRUE.
1634
     *
1635
     * @param bool $prep Wrap result in <em>|</em>
1636
     * @return string Localized [No title] string
1637
     */
1638
    public static function getNoRecordTitle($prep = false)
1639
    {
1640
        $noTitle = '[' .
1641
            htmlspecialchars(static::getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.no_title'))
1642
            . ']';
1643
        if ($prep) {
1644
            $noTitle = '<em>' . $noTitle . '</em>';
1645
        }
1646
        return $noTitle;
1647
    }
1648
1649
    /**
1650
     * Returns a human readable output of a value from a record
1651
     * For instance a database record relation would be looked up to display the title-value of that record. A checkbox with a "1" value would be "Yes", etc.
1652
     * $table/$col is tablename and fieldname
1653
     * REMEMBER to pass the output through htmlspecialchars() if you output it to the browser! (To protect it from XSS attacks and be XHTML compliant)
1654
     *
1655
     * @param string $table Table name, present in TCA
1656
     * @param string $col Field name, present in TCA
1657
     * @param string $value The value of that field from a selected record
1658
     * @param int $fixed_lgd_chars The max amount of characters the value may occupy
1659
     * @param bool $defaultPassthrough Flag means that values for columns that has no conversion will just be pass through directly (otherwise cropped to 200 chars or returned as "N/A")
1660
     * @param bool $noRecordLookup If set, no records will be looked up, UIDs are just shown.
1661
     * @param int $uid Uid of the current record
1662
     * @param bool $forceResult If BackendUtility::getRecordTitle is used to process the value, this parameter is forwarded.
1663
     * @param int $pid Optional page uid is used to evaluate page TSConfig for the given field
1664
     * @throws \InvalidArgumentException
1665
     * @return string|null
1666
     */
1667
    public static function getProcessedValue(
1668
        $table,
1669
        $col,
1670
        $value,
1671
        $fixed_lgd_chars = 0,
1672
        $defaultPassthrough = false,
1673
        $noRecordLookup = false,
1674
        $uid = 0,
1675
        $forceResult = true,
1676
        $pid = 0
1677
    ) {
1678
        if ($col === 'uid') {
1679
            // uid is not in TCA-array
1680
            return $value;
1681
        }
1682
        // Check if table and field is configured
1683
        if (!isset($GLOBALS['TCA'][$table]['columns'][$col]) || !is_array($GLOBALS['TCA'][$table]['columns'][$col])) {
1684
            return null;
1685
        }
1686
        // Depending on the fields configuration, make a meaningful output value.
1687
        $theColConf = $GLOBALS['TCA'][$table]['columns'][$col]['config'] ?? [];
1688
        /*****************
1689
         *HOOK: pre-processing the human readable output from a record
1690
         ****************/
1691
        $referenceObject = new \stdClass();
1692
        $referenceObject->table = $table;
1693
        $referenceObject->fieldName = $col;
1694
        $referenceObject->uid = $uid;
1695
        $referenceObject->value = &$value;
1696
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['preProcessValue'] ?? [] as $_funcRef) {
1697
            GeneralUtility::callUserFunction($_funcRef, $theColConf, $referenceObject);
1698
        }
1699
1700
        $l = '';
1701
        $lang = static::getLanguageService();
1702
        switch ((string)($theColConf['type'] ?? '')) {
1703
            case 'radio':
1704
                $l = self::getLabelFromItemlist($table, $col, $value);
1705
                $l = $lang->sL($l);
1706
                break;
1707
            case 'inline':
1708
            case 'select':
1709
                if (!empty($theColConf['MM'])) {
1710
                    if ($uid) {
1711
                        // Display the title of MM related records in lists
1712
                        if ($noRecordLookup) {
1713
                            $MMfields = [];
1714
                            $MMfields[] = $theColConf['foreign_table'] . '.uid';
1715
                        } else {
1716
                            $MMfields = [$theColConf['foreign_table'] . '.' . $GLOBALS['TCA'][$theColConf['foreign_table']]['ctrl']['label']];
1717
                            if (isset($GLOBALS['TCA'][$theColConf['foreign_table']]['ctrl']['label_alt'])) {
1718
                                foreach (GeneralUtility::trimExplode(
1719
                                    ',',
1720
                                    $GLOBALS['TCA'][$theColConf['foreign_table']]['ctrl']['label_alt'],
1721
                                    true
1722
                                ) as $f) {
1723
                                    $MMfields[] = $theColConf['foreign_table'] . '.' . $f;
1724
                                }
1725
                            }
1726
                        }
1727
                        /** @var RelationHandler $dbGroup */
1728
                        $dbGroup = GeneralUtility::makeInstance(RelationHandler::class);
1729
                        $dbGroup->start(
1730
                            $value,
1731
                            $theColConf['foreign_table'],
1732
                            $theColConf['MM'],
1733
                            $uid,
1734
                            $table,
1735
                            $theColConf
1736
                        );
1737
                        $selectUids = $dbGroup->tableArray[$theColConf['foreign_table']];
1738
                        if (is_array($selectUids) && !empty($selectUids)) {
1739
                            $queryBuilder = static::getQueryBuilderForTable($theColConf['foreign_table']);
1740
                            $queryBuilder->getRestrictions()
1741
                                ->removeAll()
1742
                                ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1743
1744
                            $result = $queryBuilder
1745
                                ->select('uid', ...$MMfields)
1746
                                ->from($theColConf['foreign_table'])
1747
                                ->where(
1748
                                    $queryBuilder->expr()->in(
1749
                                        'uid',
1750
                                        $queryBuilder->createNamedParameter($selectUids, Connection::PARAM_INT_ARRAY)
1751
                                    )
1752
                                )
1753
                                ->execute();
1754
1755
                            $mmlA = [];
1756
                            while ($MMrow = $result->fetch()) {
1757
                                // Keep sorting of $selectUids
1758
                                $selectedUid = array_search($MMrow['uid'], $selectUids);
1759
                                $mmlA[$selectedUid] = $MMrow['uid'];
1760
                                if (!$noRecordLookup) {
1761
                                    $mmlA[$selectedUid] = static::getRecordTitle(
1762
                                        $theColConf['foreign_table'],
1763
                                        $MMrow,
1764
                                        false,
1765
                                        $forceResult
1766
                                    );
1767
                                }
1768
                            }
1769
1770
                            if (!empty($mmlA)) {
1771
                                ksort($mmlA);
1772
                                $l = implode('; ', $mmlA);
1773
                            } else {
1774
                                $l = $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:notAvailableAbbreviation');
1775
                            }
1776
                        } else {
1777
                            $l = $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:notAvailableAbbreviation');
1778
                        }
1779
                    } else {
1780
                        $l = $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:notAvailableAbbreviation');
1781
                    }
1782
                } else {
1783
                    $columnTsConfig = [];
1784
                    if ($pid) {
1785
                        $pageTsConfig = self::getPagesTSconfig($pid);
1786
                        if (isset($pageTsConfig['TCEFORM.'][$table . '.'][$col . '.']) && is_array($pageTsConfig['TCEFORM.'][$table . '.'][$col . '.'])) {
1787
                            $columnTsConfig = $pageTsConfig['TCEFORM.'][$table . '.'][$col . '.'];
1788
                        }
1789
                    }
1790
                    $l = self::getLabelsFromItemsList($table, $col, $value, $columnTsConfig);
1791
                    if (!empty($theColConf['foreign_table']) && !$l && !empty($GLOBALS['TCA'][$theColConf['foreign_table']])) {
1792
                        if ($noRecordLookup) {
1793
                            $l = $value;
1794
                        } else {
1795
                            $rParts = [];
1796
                            if ($uid && isset($theColConf['foreign_field']) && $theColConf['foreign_field'] !== '') {
1797
                                $queryBuilder = static::getQueryBuilderForTable($theColConf['foreign_table']);
1798
                                $queryBuilder->getRestrictions()
1799
                                    ->removeAll()
1800
                                    ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1801
                                    ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
1802
                                $constraints = [
1803
                                    $queryBuilder->expr()->eq(
1804
                                        $theColConf['foreign_field'],
1805
                                        $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
1806
                                    )
1807
                                ];
1808
1809
                                if (!empty($theColConf['foreign_table_field'])) {
1810
                                    $constraints[] = $queryBuilder->expr()->eq(
1811
                                        $theColConf['foreign_table_field'],
1812
                                        $queryBuilder->createNamedParameter($table, \PDO::PARAM_STR)
1813
                                    );
1814
                                }
1815
1816
                                // Add additional where clause if foreign_match_fields are defined
1817
                                $foreignMatchFields = [];
1818
                                if (is_array($theColConf['foreign_match_fields'])) {
1819
                                    $foreignMatchFields = $theColConf['foreign_match_fields'];
1820
                                }
1821
1822
                                foreach ($foreignMatchFields as $matchField => $matchValue) {
1823
                                    $constraints[] = $queryBuilder->expr()->eq(
1824
                                        $matchField,
1825
                                        $queryBuilder->createNamedParameter($matchValue)
1826
                                    );
1827
                                }
1828
1829
                                $result = $queryBuilder
1830
                                    ->select('*')
1831
                                    ->from($theColConf['foreign_table'])
1832
                                    ->where(...$constraints)
1833
                                    ->execute();
1834
1835
                                while ($record = $result->fetch()) {
1836
                                    $rParts[] = $record['uid'];
1837
                                }
1838
                            }
1839
                            if (empty($rParts)) {
1840
                                $rParts = GeneralUtility::trimExplode(',', $value, true);
1841
                            }
1842
                            $lA = [];
1843
                            foreach ($rParts as $rVal) {
1844
                                $rVal = (int)$rVal;
1845
                                $r = self::getRecordWSOL($theColConf['foreign_table'], $rVal);
1846
                                if (is_array($r)) {
1847
                                    $lA[] = $lang->sL($theColConf['foreign_table_prefix'])
1848
                                        . self::getRecordTitle($theColConf['foreign_table'], $r, false, $forceResult);
1849
                                } else {
1850
                                    $lA[] = $rVal ? '[' . $rVal . '!]' : '';
1851
                                }
1852
                            }
1853
                            $l = implode(', ', $lA);
1854
                        }
1855
                    }
1856
                    if (empty($l) && !empty($value)) {
1857
                        // Use plain database value when label is empty
1858
                        $l = $value;
1859
                    }
1860
                }
1861
                break;
1862
            case 'group':
1863
                // resolve the titles for DB records
1864
                if (isset($theColConf['internal_type']) && $theColConf['internal_type'] === 'db') {
1865
                    if (isset($theColConf['MM']) && $theColConf['MM']) {
1866
                        if ($uid) {
1867
                            // Display the title of MM related records in lists
1868
                            if ($noRecordLookup) {
1869
                                $MMfields = [];
1870
                                $MMfields[] = $theColConf['foreign_table'] . '.uid';
1871
                            } else {
1872
                                $MMfields = [$theColConf['foreign_table'] . '.' . $GLOBALS['TCA'][$theColConf['foreign_table']]['ctrl']['label']];
1873
                                $altLabelFields = explode(
1874
                                    ',',
1875
                                    $GLOBALS['TCA'][$theColConf['foreign_table']]['ctrl']['label_alt']
1876
                                );
1877
                                foreach ($altLabelFields as $f) {
1878
                                    $f = trim($f);
1879
                                    if ($f !== '') {
1880
                                        $MMfields[] = $theColConf['foreign_table'] . '.' . $f;
1881
                                    }
1882
                                }
1883
                            }
1884
                            /** @var RelationHandler $dbGroup */
1885
                            $dbGroup = GeneralUtility::makeInstance(RelationHandler::class);
1886
                            $dbGroup->start(
1887
                                $value,
1888
                                $theColConf['foreign_table'],
1889
                                $theColConf['MM'],
1890
                                $uid,
1891
                                $table,
1892
                                $theColConf
1893
                            );
1894
                            $selectUids = $dbGroup->tableArray[$theColConf['foreign_table']];
1895
                            if (!empty($selectUids) && is_array($selectUids)) {
1896
                                $queryBuilder = static::getQueryBuilderForTable($theColConf['foreign_table']);
1897
                                $queryBuilder->getRestrictions()
1898
                                    ->removeAll()
1899
                                    ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1900
1901
                                $result = $queryBuilder
1902
                                    ->select('uid', ...$MMfields)
1903
                                    ->from($theColConf['foreign_table'])
1904
                                    ->where(
1905
                                        $queryBuilder->expr()->in(
1906
                                            'uid',
1907
                                            $queryBuilder->createNamedParameter(
1908
                                                $selectUids,
1909
                                                Connection::PARAM_INT_ARRAY
1910
                                            )
1911
                                        )
1912
                                    )
1913
                                    ->execute();
1914
1915
                                $mmlA = [];
1916
                                while ($MMrow = $result->fetch()) {
1917
                                    // Keep sorting of $selectUids
1918
                                    $selectedUid = array_search($MMrow['uid'], $selectUids);
1919
                                    $mmlA[$selectedUid] = $MMrow['uid'];
1920
                                    if (!$noRecordLookup) {
1921
                                        $mmlA[$selectedUid] = static::getRecordTitle(
1922
                                            $theColConf['foreign_table'],
1923
                                            $MMrow,
1924
                                            false,
1925
                                            $forceResult
1926
                                        );
1927
                                    }
1928
                                }
1929
1930
                                if (!empty($mmlA)) {
1931
                                    ksort($mmlA);
1932
                                    $l = implode('; ', $mmlA);
1933
                                } else {
1934
                                    $l = $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:notAvailableAbbreviation');
1935
                                }
1936
                            } else {
1937
                                $l = $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:notAvailableAbbreviation');
1938
                            }
1939
                        } else {
1940
                            $l = $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:notAvailableAbbreviation');
1941
                        }
1942
                    } else {
1943
                        $finalValues = [];
1944
                        $relationTableName = $theColConf['allowed'];
1945
                        $explodedValues = GeneralUtility::trimExplode(',', $value, true);
1946
1947
                        foreach ($explodedValues as $explodedValue) {
1948
                            if (MathUtility::canBeInterpretedAsInteger($explodedValue)) {
1949
                                $relationTableNameForField = $relationTableName;
1950
                            } else {
1951
                                [$relationTableNameForField, $explodedValue] = self::splitTable_Uid($explodedValue);
1952
                            }
1953
1954
                            $relationRecord = static::getRecordWSOL($relationTableNameForField, $explodedValue);
0 ignored issues
show
Bug introduced by
$explodedValue of type string is incompatible with the type integer expected by parameter $uid of TYPO3\CMS\Backend\Utilit...tility::getRecordWSOL(). ( Ignorable by Annotation )

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

1954
                            $relationRecord = static::getRecordWSOL($relationTableNameForField, /** @scrutinizer ignore-type */ $explodedValue);
Loading history...
1955
                            $finalValues[] = static::getRecordTitle($relationTableNameForField, $relationRecord);
1956
                        }
1957
                        $l = implode(', ', $finalValues);
1958
                    }
1959
                } else {
1960
                    $l = implode(', ', GeneralUtility::trimExplode(',', $value, true));
1961
                }
1962
                break;
1963
            case 'check':
1964
                if (!is_array($theColConf['items'])) {
1965
                    $l = $value ? $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:yes') : $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:no');
1966
                } elseif (count($theColConf['items']) === 1) {
1967
                    reset($theColConf['items']);
1968
                    $invertStateDisplay = current($theColConf['items'])['invertStateDisplay'] ?? false;
1969
                    if ($invertStateDisplay) {
1970
                        $value = !$value;
1971
                    }
1972
                    $l = $value ? $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:yes') : $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:no');
1973
                } else {
1974
                    $lA = [];
1975
                    foreach ($theColConf['items'] as $key => $val) {
1976
                        if ($value & 2 ** $key) {
1977
                            $lA[] = $lang->sL($val[0]);
1978
                        }
1979
                    }
1980
                    $l = implode(', ', $lA);
1981
                }
1982
                break;
1983
            case 'input':
1984
                // Hide value 0 for dates, but show it for everything else
1985
                // todo: phpstan states that $value always exists and is not nullable. At the moment, this is a false
1986
                //       positive as null can be passed into this method via $value. As soon as more strict types are
1987
                //       used, this isset check must be replaced with a more appropriate check.
1988
                if (isset($value)) {
1989
                    $dateTimeFormats = QueryHelper::getDateTimeFormats();
1990
1991
                    if (GeneralUtility::inList($theColConf['eval'] ?? '', 'date')) {
1992
                        // Handle native date field
1993
                        if (isset($theColConf['dbType']) && $theColConf['dbType'] === 'date') {
1994
                            $value = $value === $dateTimeFormats['date']['empty'] ? 0 : (int)strtotime($value);
1995
                        } else {
1996
                            $value = (int)$value;
1997
                        }
1998
                        if (!empty($value)) {
1999
                            $ageSuffix = '';
2000
                            $dateColumnConfiguration = $GLOBALS['TCA'][$table]['columns'][$col]['config'];
2001
                            $ageDisplayKey = 'disableAgeDisplay';
2002
2003
                            // generate age suffix as long as not explicitly suppressed
2004
                            if (!isset($dateColumnConfiguration[$ageDisplayKey])
2005
                                // non typesafe comparison on intention
2006
                                || $dateColumnConfiguration[$ageDisplayKey] == false
2007
                            ) {
2008
                                $ageSuffix = ' (' . ($GLOBALS['EXEC_TIME'] - $value > 0 ? '-' : '')
2009
                                    . self::calcAge(
2010
                                        abs($GLOBALS['EXEC_TIME'] - $value),
0 ignored issues
show
Bug introduced by
It seems like abs($GLOBALS['EXEC_TIME'] - $value) can also be of type double; however, parameter $seconds of TYPO3\CMS\Backend\Utilit...ckendUtility::calcAge() does only seem to accept integer, 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

2010
                                        /** @scrutinizer ignore-type */ abs($GLOBALS['EXEC_TIME'] - $value),
Loading history...
2011
                                        $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.minutesHoursDaysYears')
2012
                                    )
2013
                                    . ')';
2014
                            }
2015
2016
                            $l = self::date($value) . $ageSuffix;
2017
                        }
2018
                    } elseif (GeneralUtility::inList($theColConf['eval'] ?? '', 'time')) {
2019
                        // Handle native time field
2020
                        if (isset($theColConf['dbType']) && $theColConf['dbType'] === 'time') {
2021
                            $value = $value === $dateTimeFormats['time']['empty'] ? 0 : (int)strtotime('1970-01-01 ' . $value);
2022
                        } else {
2023
                            $value = (int)$value;
2024
                        }
2025
                        if (!empty($value)) {
2026
                            $l = gmdate('H:i', (int)$value);
2027
                        }
2028
                    } elseif (GeneralUtility::inList($theColConf['eval'] ?? '', 'timesec')) {
2029
                        // Handle native time field
2030
                        if (isset($theColConf['dbType']) && $theColConf['dbType'] === 'time') {
2031
                            $value = $value === $dateTimeFormats['time']['empty'] ? 0 : (int)strtotime('1970-01-01 ' . $value);
2032
                        } else {
2033
                            $value = (int)$value;
2034
                        }
2035
                        if (!empty($value)) {
2036
                            $l = gmdate('H:i:s', (int)$value);
2037
                        }
2038
                    } elseif (GeneralUtility::inList($theColConf['eval'] ?? '', 'datetime')) {
2039
                        // Handle native datetime field
2040
                        if (isset($theColConf['dbType']) && $theColConf['dbType'] === 'datetime') {
2041
                            $value = $value === $dateTimeFormats['datetime']['empty'] ? 0 : (int)strtotime($value);
2042
                        } else {
2043
                            $value = (int)$value;
2044
                        }
2045
                        if (!empty($value)) {
2046
                            $l = self::datetime($value);
2047
                        }
2048
                    } else {
2049
                        $l = $value;
2050
                    }
2051
                }
2052
                break;
2053
            case 'flex':
2054
                $l = strip_tags($value);
2055
                break;
2056
            default:
2057
                if ($defaultPassthrough) {
2058
                    $l = $value;
2059
                } elseif (isset($theColConf['MM'])) {
2060
                    $l = $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:notAvailableAbbreviation');
2061
                } elseif ($value) {
2062
                    $l = GeneralUtility::fixed_lgd_cs(strip_tags($value), 200);
2063
                }
2064
        }
2065
        // If this field is a password field, then hide the password by changing it to a random number of asterisk (*)
2066
        if (!empty($theColConf['eval']) && stripos($theColConf['eval'], 'password') !== false) {
2067
            $l = '';
2068
            $randomNumber = random_int(5, 12);
2069
            for ($i = 0; $i < $randomNumber; $i++) {
2070
                $l .= '*';
2071
            }
2072
        }
2073
        /*****************
2074
         *HOOK: post-processing the human readable output from a record
2075
         ****************/
2076
        $null = null;
2077
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['postProcessValue'] ?? [] as $_funcRef) {
2078
            $params = [
2079
                'value' => $l,
2080
                'colConf' => $theColConf
2081
            ];
2082
            $l = GeneralUtility::callUserFunction($_funcRef, $params, $null);
2083
        }
2084
        if ($fixed_lgd_chars) {
2085
            return GeneralUtility::fixed_lgd_cs($l, $fixed_lgd_chars);
2086
        }
2087
        return $l;
2088
    }
2089
2090
    /**
2091
     * Same as ->getProcessedValue() but will go easy on fields like "tstamp" and "pid" which are not configured in TCA - they will be formatted by this function instead.
2092
     *
2093
     * @param string $table Table name, present in TCA
2094
     * @param string $fN Field name
2095
     * @param string $fV Field value
2096
     * @param int $fixed_lgd_chars The max amount of characters the value may occupy
2097
     * @param int $uid Uid of the current record
2098
     * @param bool $forceResult If BackendUtility::getRecordTitle is used to process the value, this parameter is forwarded.
2099
     * @param int $pid Optional page uid is used to evaluate page TSConfig for the given field
2100
     * @return string
2101
     * @see getProcessedValue()
2102
     */
2103
    public static function getProcessedValueExtra(
2104
        $table,
2105
        $fN,
2106
        $fV,
2107
        $fixed_lgd_chars = 0,
2108
        $uid = 0,
2109
        $forceResult = true,
2110
        $pid = 0
2111
    ) {
2112
        $fVnew = self::getProcessedValue($table, $fN, $fV, $fixed_lgd_chars, 1, 0, $uid, $forceResult, $pid);
2113
        if (!isset($fVnew)) {
2114
            if (is_array($GLOBALS['TCA'][$table])) {
2115
                if ($fN == $GLOBALS['TCA'][$table]['ctrl']['tstamp'] || $fN == $GLOBALS['TCA'][$table]['ctrl']['crdate']) {
2116
                    $fVnew = self::datetime($fV);
0 ignored issues
show
Bug introduced by
$fV of type string is incompatible with the type integer expected by parameter $value of TYPO3\CMS\Backend\Utilit...kendUtility::datetime(). ( Ignorable by Annotation )

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

2116
                    $fVnew = self::datetime(/** @scrutinizer ignore-type */ $fV);
Loading history...
2117
                } elseif ($fN === 'pid') {
2118
                    // Fetches the path with no regard to the users permissions to select pages.
2119
                    $fVnew = self::getRecordPath($fV, '1=1', 20);
0 ignored issues
show
Bug introduced by
$fV of type string is incompatible with the type integer expected by parameter $uid of TYPO3\CMS\Backend\Utilit...tility::getRecordPath(). ( Ignorable by Annotation )

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

2119
                    $fVnew = self::getRecordPath(/** @scrutinizer ignore-type */ $fV, '1=1', 20);
Loading history...
2120
                } else {
2121
                    $fVnew = $fV;
2122
                }
2123
            }
2124
        }
2125
        return $fVnew;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $fVnew also could return the type array<integer,string> which is incompatible with the documented return type string.
Loading history...
2126
    }
2127
2128
    /**
2129
     * Returns fields for a table, $table, which would typically be interesting to select
2130
     * This includes uid, the fields defined for title, icon-field.
2131
     * Returned as a list ready for query ($prefix can be set to eg. "pages." if you are selecting from the pages table and want the table name prefixed)
2132
     *
2133
     * @param string $table Table name, present in $GLOBALS['TCA']
2134
     * @param string $prefix Table prefix
2135
     * @param array $fields Preset fields (must include prefix if that is used)
2136
     * @return string List of fields.
2137
     * @internal should only be used from within TYPO3 Core
2138
     */
2139
    public static function getCommonSelectFields($table, $prefix = '', $fields = [])
2140
    {
2141
        $fields[] = $prefix . 'uid';
2142
        if (isset($GLOBALS['TCA'][$table]['ctrl']['label']) && $GLOBALS['TCA'][$table]['ctrl']['label'] != '') {
2143
            $fields[] = $prefix . $GLOBALS['TCA'][$table]['ctrl']['label'];
2144
        }
2145
        if (!empty($GLOBALS['TCA'][$table]['ctrl']['label_alt'])) {
2146
            $secondFields = GeneralUtility::trimExplode(',', $GLOBALS['TCA'][$table]['ctrl']['label_alt'], true);
2147
            foreach ($secondFields as $fieldN) {
2148
                $fields[] = $prefix . $fieldN;
2149
            }
2150
        }
2151
        if (static::isTableWorkspaceEnabled($table)) {
2152
            $fields[] = $prefix . 't3ver_state';
2153
            $fields[] = $prefix . 't3ver_wsid';
2154
            $fields[] = $prefix . 't3ver_count';
2155
        }
2156
        if (!empty($GLOBALS['TCA'][$table]['ctrl']['selicon_field'])) {
2157
            $fields[] = $prefix . $GLOBALS['TCA'][$table]['ctrl']['selicon_field'];
2158
        }
2159
        if (!empty($GLOBALS['TCA'][$table]['ctrl']['typeicon_column'])) {
2160
            $fields[] = $prefix . $GLOBALS['TCA'][$table]['ctrl']['typeicon_column'];
2161
        }
2162
        if (!empty($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled'])) {
2163
            $fields[] = $prefix . $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled'];
2164
        }
2165
        if (!empty($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['starttime'])) {
2166
            $fields[] = $prefix . $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['starttime'];
2167
        }
2168
        if (!empty($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['endtime'])) {
2169
            $fields[] = $prefix . $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['endtime'];
2170
        }
2171
        if (!empty($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['fe_group'])) {
2172
            $fields[] = $prefix . $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['fe_group'];
2173
        }
2174
        return implode(',', array_unique($fields));
2175
    }
2176
2177
    /*******************************************
2178
     *
2179
     * Backend Modules API functions
2180
     *
2181
     *******************************************/
2182
2183
    /**
2184
     * Returns CSH help text (description), if configured for, as an array (title, description)
2185
     *
2186
     * @param string $table Table name
2187
     * @param string $field Field name
2188
     * @return array With keys 'description' (raw, as available in locallang), 'title' (optional), 'moreInfo'
2189
     * @internal should only be used from within TYPO3 Core
2190
     */
2191
    public static function helpTextArray($table, $field)
2192
    {
2193
        if (!isset($GLOBALS['TCA_DESCR'][$table]['columns'])) {
2194
            static::getLanguageService()->loadSingleTableDescription($table);
2195
        }
2196
        $output = [
2197
            'description' => null,
2198
            'title' => null,
2199
            'moreInfo' => false
2200
        ];
2201
        if (isset($GLOBALS['TCA_DESCR'][$table]['columns'][$field]) && is_array($GLOBALS['TCA_DESCR'][$table]['columns'][$field])) {
2202
            $data = $GLOBALS['TCA_DESCR'][$table]['columns'][$field];
2203
            // Add alternative title, if defined
2204
            if ($data['alttitle']) {
2205
                $output['title'] = $data['alttitle'];
2206
            }
2207
            // If we have more information to show and access to the cshmanual
2208
            if (($data['image_descr'] || $data['seeAlso'] || $data['details'] || $data['syntax'])
2209
                && static::getBackendUserAuthentication()->check('modules', 'help_CshmanualCshmanual')
2210
            ) {
2211
                $output['moreInfo'] = true;
2212
            }
2213
            // Add description
2214
            if ($data['description']) {
2215
                $output['description'] = $data['description'];
2216
            }
2217
        }
2218
        return $output;
2219
    }
2220
2221
    /**
2222
     * Returns CSH help text
2223
     *
2224
     * @param string $table Table name
2225
     * @param string $field Field name
2226
     * @return string HTML content for help text
2227
     * @see cshItem()
2228
     * @internal should only be used from within TYPO3 Core
2229
     */
2230
    public static function helpText($table, $field)
2231
    {
2232
        $helpTextArray = self::helpTextArray($table, $field);
2233
        $output = '';
2234
        $arrow = '';
2235
        // Put header before the rest of the text
2236
        if ($helpTextArray['title'] !== null) {
2237
            $output .= '<h2>' . $helpTextArray['title'] . '</h2>';
2238
        }
2239
        // Add see also arrow if we have more info
2240
        if ($helpTextArray['moreInfo']) {
2241
            /** @var IconFactory $iconFactory */
2242
            $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
2243
            $arrow = $iconFactory->getIcon('actions-view-go-forward', Icon::SIZE_SMALL)->render();
2244
        }
2245
        // Wrap description and arrow in p tag
2246
        if ($helpTextArray['description'] !== null || $arrow) {
2247
            $output .= '<p class="help-short">' . nl2br(htmlspecialchars($helpTextArray['description'])) . $arrow . '</p>';
2248
        }
2249
        return $output;
2250
    }
2251
2252
    /**
2253
     * API function that wraps the text / html in help text, so if a user hovers over it
2254
     * the help text will show up
2255
     *
2256
     * @param string $table The table name for which the help should be shown
2257
     * @param string $field The field name for which the help should be shown
2258
     * @param string $text The text which should be wrapped with the help text
2259
     * @param array $overloadHelpText Array with text to overload help text
2260
     * @return string the HTML code ready to render
2261
     * @internal should only be used from within TYPO3 Core
2262
     */
2263
    public static function wrapInHelp($table, $field, $text = '', array $overloadHelpText = [])
2264
    {
2265
        // Initialize some variables
2266
        $helpText = '';
2267
        $abbrClassAdd = '';
2268
        $hasHelpTextOverload = !empty($overloadHelpText);
2269
        // Get the help text that should be shown on hover
2270
        if (!$hasHelpTextOverload) {
2271
            $helpText = self::helpText($table, $field);
2272
        }
2273
        // If there's a help text or some overload information, proceed with preparing an output
2274
        if (!empty($helpText) || $hasHelpTextOverload) {
2275
            // If no text was given, just use the regular help icon
2276
            if ($text == '') {
2277
                $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
2278
                $text = $iconFactory->getIcon('actions-system-help-open', Icon::SIZE_SMALL)->render();
2279
                $abbrClassAdd = ' help-teaser-icon';
2280
            }
2281
            $text = '<abbr class="help-teaser' . $abbrClassAdd . '">' . $text . '</abbr>';
2282
            $wrappedText = '<span class="help-link" href="#" data-table="' . $table . '" data-field="' . $field . '"';
2283
            // The overload array may provide a title and a description
2284
            // If either one is defined, add them to the "data" attributes
2285
            if ($hasHelpTextOverload) {
2286
                if (isset($overloadHelpText['title'])) {
2287
                    $wrappedText .= ' data-title="' . htmlspecialchars($overloadHelpText['title']) . '"';
2288
                }
2289
                if (isset($overloadHelpText['description'])) {
2290
                    $wrappedText .= ' data-description="' . htmlspecialchars($overloadHelpText['description']) . '"';
2291
                }
2292
            }
2293
            $wrappedText .= '>' . $text . '</span>';
2294
            return $wrappedText;
2295
        }
2296
        return $text;
2297
    }
2298
2299
    /**
2300
     * API for getting CSH icons/text for use in backend modules.
2301
     * TCA_DESCR will be loaded if it isn't already
2302
     *
2303
     * @param string $table Table name ('_MOD_'+module name)
2304
     * @param string $field Field name (CSH locallang main key)
2305
     * @param string $_ (unused)
2306
     * @param string $wrap Wrap code for icon-mode, splitted by "|". Not used for full-text mode.
2307
     * @return string HTML content for help text
2308
     */
2309
    public static function cshItem($table, $field, $_ = '', $wrap = '')
2310
    {
2311
        static::getLanguageService()->loadSingleTableDescription($table);
2312
        if (is_array($GLOBALS['TCA_DESCR'][$table])
2313
            && is_array($GLOBALS['TCA_DESCR'][$table]['columns'][$field])
2314
        ) {
2315
            // Creating short description
2316
            $output = self::wrapInHelp($table, $field);
2317
            if ($output && $wrap) {
2318
                $wrParts = explode('|', $wrap);
2319
                $output = $wrParts[0] . $output . $wrParts[1];
2320
            }
2321
            return $output;
2322
        }
2323
        return '';
2324
    }
2325
2326
    /**
2327
     * Returns a JavaScript string (for an onClick handler) which will load the EditDocumentController script that shows the form for editing of the record(s) you have send as params.
2328
     * REMEMBER to always htmlspecialchar() content in href-properties to ampersands get converted to entities (XHTML requirement and XSS precaution)
2329
     *
2330
     * @param string $params Parameters sent along to EditDocumentController. This requires a much more details description which you must seek in Inside TYPO3s documentation of the FormEngine API. And example could be '&edit[pages][123] = edit' which will show edit form for page record 123.
2331
     * @param string $_ (unused)
2332
     * @param string $requestUri An optional returnUrl you can set - automatically set to REQUEST_URI.
2333
     *
2334
     * @return string
2335
     * @deprecated will be removed in TYPO3 v11.
2336
     */
2337
    public static function editOnClick($params, $_ = '', $requestUri = '')
2338
    {
2339
        trigger_error(__METHOD__ . ' has been marked as deprecated and will be removed in TYPO3 v11. Consider using regular links and use the UriBuilder API instead.', E_USER_DEPRECATED);
2340
        if ($requestUri == -1) {
2341
            $returnUrl = 'T3_THIS_LOCATION';
2342
        } else {
2343
            $returnUrl = GeneralUtility::quoteJSvalue(rawurlencode($requestUri ?: GeneralUtility::getIndpEnv('REQUEST_URI')));
2344
        }
2345
        $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
2346
        return 'window.location.href=' . GeneralUtility::quoteJSvalue((string)$uriBuilder->buildUriFromRoute('record_edit') . $params . '&returnUrl=') . '+' . $returnUrl . '; return false;';
2347
    }
2348
2349
    /**
2350
     * Returns a JavaScript string for viewing the page id, $id
2351
     * It will re-use any window already open.
2352
     *
2353
     * @param int $pageUid Page UID
2354
     * @param string $backPath Must point back to TYPO3_mainDir (where the site is assumed to be one level above)
2355
     * @param array|null $rootLine If root line is supplied the function will look for the first found domain record and use that URL instead (if found)
2356
     * @param string $anchorSection Optional anchor to the URL
2357
     * @param string $alternativeUrl An alternative URL that, if set, will ignore other parameters except $switchFocus: It will return the window.open command wrapped around this URL!
2358
     * @param string $additionalGetVars Additional GET variables.
2359
     * @param bool $switchFocus If TRUE, then the preview window will gain the focus.
2360
     * @return string
2361
     */
2362
    public static function viewOnClick(
2363
        $pageUid,
2364
        $backPath = '',
2365
        $rootLine = null,
2366
        $anchorSection = '',
2367
        $alternativeUrl = '',
2368
        $additionalGetVars = '',
2369
        $switchFocus = true
2370
    ) {
2371
        try {
2372
            $previewUrl = self::getPreviewUrl(
2373
                $pageUid,
2374
                $backPath,
2375
                $rootLine,
2376
                $anchorSection,
2377
                $alternativeUrl,
2378
                $additionalGetVars,
2379
                $switchFocus
2380
            );
2381
        } catch (UnableToLinkToPageException $e) {
2382
            return '';
2383
        }
2384
2385
        $onclickCode = 'var previewWin = window.open(' . GeneralUtility::quoteJSvalue($previewUrl) . ',\'newTYPO3frontendWindow\');'
2386
            . ($switchFocus ? 'previewWin.focus();' : '') . LF
2387
            . 'if (previewWin.location.href === ' . GeneralUtility::quoteJSvalue($previewUrl) . ') { previewWin.location.reload(); };';
2388
2389
        return $onclickCode;
2390
    }
2391
2392
    /**
2393
     * Returns the preview url
2394
     *
2395
     * It will detect the correct domain name if needed and provide the link with the right back path.
2396
     *
2397
     * @param int $pageUid Page UID
2398
     * @param string $backPath Must point back to TYPO3_mainDir (where the site is assumed to be one level above)
2399
     * @param array|null $rootLine If root line is supplied the function will look for the first found domain record and use that URL instead (if found)
2400
     * @param string $anchorSection Optional anchor to the URL
2401
     * @param string $alternativeUrl An alternative URL that, if set, will ignore other parameters except $switchFocus: It will return the window.open command wrapped around this URL!
2402
     * @param string $additionalGetVars Additional GET variables.
2403
     * @param bool $switchFocus If TRUE, then the preview window will gain the focus.
2404
     * @return string
2405
     */
2406
    public static function getPreviewUrl(
2407
        $pageUid,
2408
        $backPath = '',
2409
        $rootLine = null,
2410
        $anchorSection = '',
2411
        $alternativeUrl = '',
2412
        $additionalGetVars = '',
2413
        &$switchFocus = true
2414
    ): string {
2415
        $viewScript = '/index.php?id=';
2416
        if ($alternativeUrl) {
2417
            $viewScript = $alternativeUrl;
2418
        }
2419
2420
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['viewOnClickClass'] ?? [] as $className) {
2421
            $hookObj = GeneralUtility::makeInstance($className);
2422
            if (method_exists($hookObj, 'preProcess')) {
2423
                $hookObj->preProcess(
2424
                    $pageUid,
2425
                    $backPath,
2426
                    $rootLine,
2427
                    $anchorSection,
2428
                    $viewScript,
2429
                    $additionalGetVars,
2430
                    $switchFocus
2431
                );
2432
            }
2433
        }
2434
2435
        // If there is an alternative URL or the URL has been modified by a hook, use that one.
2436
        if ($alternativeUrl || $viewScript !== '/index.php?id=') {
2437
            $previewUrl = $viewScript;
2438
        } else {
2439
            $permissionClause = $GLOBALS['BE_USER']->getPagePermsClause(Permission::PAGE_SHOW);
2440
            $pageInfo = self::readPageAccess($pageUid, $permissionClause);
2441
            // prepare custom context for link generation (to allow for example time based previews)
2442
            $context = clone GeneralUtility::makeInstance(Context::class);
2443
            $additionalGetVars .= self::ADMCMD_previewCmds($pageInfo, $context);
2444
2445
            // Build the URL with a site as prefix, if configured
2446
            $siteFinder = GeneralUtility::makeInstance(SiteFinder::class);
2447
            // Check if the page (= its rootline) has a site attached, otherwise just keep the URL as is
2448
            $rootLine = $rootLine ?? BackendUtility::BEgetRootLine($pageUid);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
2449
            try {
2450
                $site = $siteFinder->getSiteByPageId((int)$pageUid, $rootLine);
2451
            } catch (SiteNotFoundException $e) {
2452
                throw new UnableToLinkToPageException('The page ' . $pageUid . ' had no proper connection to a site, no link could be built.', 1559794919);
2453
            }
2454
            // Create a multi-dimensional array out of the additional get vars
2455
            $additionalQueryParams = [];
2456
            parse_str($additionalGetVars, $additionalQueryParams);
2457
            if (isset($additionalQueryParams['L'])) {
2458
                $additionalQueryParams['_language'] = $additionalQueryParams['_language'] ?? $additionalQueryParams['L'];
2459
                unset($additionalQueryParams['L']);
2460
            }
2461
            try {
2462
                $previewUrl = (string)$site->getRouter($context)->generateUri(
2463
                    $pageUid,
2464
                    $additionalQueryParams,
2465
                    $anchorSection,
2466
                    RouterInterface::ABSOLUTE_URL
2467
                );
2468
            } catch (\InvalidArgumentException | InvalidRouteArgumentsException $e) {
2469
                throw new UnableToLinkToPageException('The page ' . $pageUid . ' had no proper connection to a site, no link could be built.', 1559794914);
2470
            }
2471
        }
2472
2473
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['viewOnClickClass'] ?? [] as $className) {
2474
            $hookObj = GeneralUtility::makeInstance($className);
2475
            if (method_exists($hookObj, 'postProcess')) {
2476
                $previewUrl = $hookObj->postProcess(
2477
                    $previewUrl,
2478
                    $pageUid,
2479
                    $rootLine,
2480
                    $anchorSection,
2481
                    $viewScript,
2482
                    $additionalGetVars,
2483
                    $switchFocus
2484
                );
2485
            }
2486
        }
2487
2488
        return $previewUrl;
2489
    }
2490
2491
    /**
2492
     * Makes click menu link (context sensitive menu)
2493
     *
2494
     * Returns $str wrapped in a link which will activate the context sensitive
2495
     * menu for the record ($table/$uid) or file ($table = file)
2496
     * The link will load the top frame with the parameter "&item" which is the table, uid
2497
     * and context arguments imploded by "|": rawurlencode($table.'|'.$uid.'|'.$context)
2498
     *
2499
     * @param string $content String to be wrapped in link, typ. image tag.
2500
     * @param string $table Table name/File path. If the icon is for a database
2501
     * record, enter the tablename from $GLOBALS['TCA']. If a file then enter
2502
     * the absolute filepath
2503
     * @param int|string $uid If icon is for database record this is the UID for the
2504
     * record from $table or identifier for sys_file record
2505
     * @param string $context Set tree if menu is called from tree view
2506
     * @param string $_addParams NOT IN USE
2507
     * @param string $_enDisItems NOT IN USE
2508
     * @param bool $returnTagParameters If set, will return only the onclick
2509
     * JavaScript, not the whole link.
2510
     *
2511
     * @return string The link wrapped input string.
2512
     */
2513
    public static function wrapClickMenuOnIcon(
2514
        $content,
2515
        $table,
2516
        $uid = 0,
2517
        $context = '',
2518
        $_addParams = '',
2519
        $_enDisItems = '',
2520
        $returnTagParameters = false
2521
    ) {
2522
        $tagParameters = [
2523
            'class' => 't3js-contextmenutrigger',
2524
            'data-table' => $table,
2525
            'data-uid' => $uid,
2526
            'data-context' => $context
2527
        ];
2528
2529
        if ($returnTagParameters) {
2530
            return $tagParameters;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $tagParameters returns the type array<string,integer|string> which is incompatible with the documented return type string.
Loading history...
2531
        }
2532
        return '<a href="#" ' . GeneralUtility::implodeAttributes($tagParameters, true) . '>' . $content . '</a>';
2533
    }
2534
2535
    /**
2536
     * Returns a URL with a command to TYPO3 Datahandler
2537
     *
2538
     * @param string $parameters Set of GET params to send. Example: "&cmd[tt_content][123][move]=456" or "&data[tt_content][123][hidden]=1&data[tt_content][123][title]=Hello%20World
2539
     * @param string|int $redirectUrl Redirect URL, default is to use GeneralUtility::getIndpEnv('REQUEST_URI'), -1 means to generate an URL for JavaScript using T3_THIS_LOCATION
2540
     * @return string
2541
     */
2542
    public static function getLinkToDataHandlerAction($parameters, $redirectUrl = '')
2543
    {
2544
        $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
2545
        $url = (string)$uriBuilder->buildUriFromRoute('tce_db') . $parameters . '&redirect=';
2546
        if ((int)$redirectUrl === -1) {
2547
            trigger_error('Generating URLs to DataHandler for JavaScript click handlers is deprecated. Consider using the href attribute instead.', E_USER_DEPRECATED);
2548
            $url = GeneralUtility::quoteJSvalue($url) . '+T3_THIS_LOCATION';
2549
        } else {
2550
            $url .= rawurlencode($redirectUrl ?: GeneralUtility::getIndpEnv('REQUEST_URI'));
2551
        }
2552
        return $url;
2553
    }
2554
2555
    /**
2556
     * Builds the frontend view domain for a given page ID with a given root
2557
     * line.
2558
     *
2559
     * @param int $pageId The page ID to use, must be > 0
2560
     * @param array|null $rootLine The root line structure to use
2561
     * @return string The full domain including the protocol http:// or https://, but without the trailing '/'
2562
     * @deprecated since TYPO3 v10.0, will be removed in TYPO3 v11.0. Use PageRouter instead.
2563
     */
2564
    public static function getViewDomain($pageId, $rootLine = null)
2565
    {
2566
        trigger_error('BackendUtility::getViewDomain() will be removed in TYPO3 v11.0. Use a Site and its PageRouter to link to a page directly', E_USER_DEPRECATED);
2567
        $domain = rtrim(GeneralUtility::getIndpEnv('TYPO3_SITE_URL'), '/');
2568
        if (!is_array($rootLine)) {
2569
            $rootLine = self::BEgetRootLine($pageId);
2570
        }
2571
        // Checks alternate domains
2572
        if (!empty($rootLine)) {
2573
            try {
2574
                $site = GeneralUtility::makeInstance(SiteFinder::class)
2575
                    ->getSiteByPageId((int)$pageId, $rootLine);
2576
                $uri = $site->getBase();
2577
            } catch (SiteNotFoundException $e) {
2578
                // Just use the current domain
2579
                $uri = new Uri($domain);
2580
                // Append port number if lockSSLPort is not the standard port 443
2581
                $portNumber = (int)$GLOBALS['TYPO3_CONF_VARS']['BE']['lockSSLPort'];
2582
                if ($portNumber > 0 && $portNumber !== 443 && $portNumber < 65536 && $uri->getScheme() === 'https') {
2583
                    $uri = $uri->withPort((int)$portNumber);
2584
                }
2585
            }
2586
            return (string)$uri;
2587
        }
2588
        return $domain;
2589
    }
2590
2591
    /**
2592
     * Returns a selector box "function menu" for a module
2593
     * See Inside TYPO3 for details about how to use / make Function menus
2594
     *
2595
     * @param mixed $mainParams The "&id=" parameter value to be sent to the module, but it can be also a parameter array which will be passed instead of the &id=...
2596
     * @param string $elementName The form elements name, probably something like "SET[...]
2597
     * @param string $currentValue The value to be selected currently.
2598
     * @param array $menuItems An array with the menu items for the selector box
2599
     * @param string $script The script to send the &id to, if empty it's automatically found
2600
     * @param string $addParams Additional parameters to pass to the script.
2601
     * @return string HTML code for selector box
2602
     */
2603
    public static function getFuncMenu(
2604
        $mainParams,
2605
        $elementName,
2606
        $currentValue,
2607
        $menuItems,
2608
        $script = '',
2609
        $addParams = ''
2610
    ) {
2611
        if (!is_array($menuItems) || count($menuItems) <= 1) {
0 ignored issues
show
introduced by
The condition is_array($menuItems) is always true.
Loading history...
2612
            return '';
2613
        }
2614
        $scriptUrl = self::buildScriptUrl($mainParams, $addParams, $script);
2615
        $options = [];
2616
        foreach ($menuItems as $value => $label) {
2617
            $options[] = '<option value="'
2618
                . htmlspecialchars($value) . '"'
2619
                . ((string)$currentValue === (string)$value ? ' selected="selected"' : '') . '>'
2620
                . htmlspecialchars($label, ENT_COMPAT, 'UTF-8', false) . '</option>';
2621
        }
2622
        $dataMenuIdentifier = str_replace(['SET[', ']'], '', $elementName);
2623
        $dataMenuIdentifier = GeneralUtility::camelCaseToLowerCaseUnderscored($dataMenuIdentifier);
2624
        $dataMenuIdentifier = str_replace('_', '-', $dataMenuIdentifier);
2625
        if (!empty($options)) {
2626
            // @todo Should we add requireJsModule again (should be loaded in most/all cases)
2627
            // loadRequireJsModule('TYPO3/CMS/Backend/GlobalEventHandler');
2628
            $attributes = GeneralUtility::implodeAttributes([
2629
                'name' => $elementName,
2630
                'class' => 'form-control',
2631
                'data-menu-identifier' => $dataMenuIdentifier,
2632
                'data-global-event' => 'change',
2633
                'data-action-navigate' => '$data=~s/$value/',
2634
                'data-navigate-value' => $scriptUrl . '&' . $elementName . '=${value}',
2635
            ], true);
2636
            return sprintf(
2637
                '<select %s>%s</select>',
2638
                $attributes,
2639
                implode('', $options)
2640
            );
2641
        }
2642
        return '';
2643
    }
2644
2645
    /**
2646
     * Returns a selector box to switch the view
2647
     * Based on BackendUtility::getFuncMenu() but done as new function because it has another purpose.
2648
     * Mingling with getFuncMenu would harm the docHeader Menu.
2649
     *
2650
     * @param mixed $mainParams The "&id=" parameter value to be sent to the module, but it can be also a parameter array which will be passed instead of the &id=...
2651
     * @param string $elementName The form elements name, probably something like "SET[...]
2652
     * @param string $currentValue The value to be selected currently.
2653
     * @param array $menuItems An array with the menu items for the selector box
2654
     * @param string $script The script to send the &id to, if empty it's automatically found
2655
     * @param string $addParams Additional parameters to pass to the script.
2656
     * @return string HTML code for selector box
2657
     */
2658
    public static function getDropdownMenu(
2659
        $mainParams,
2660
        $elementName,
2661
        $currentValue,
2662
        $menuItems,
2663
        $script = '',
2664
        $addParams = ''
2665
    ) {
2666
        if (!is_array($menuItems) || count($menuItems) <= 1) {
0 ignored issues
show
introduced by
The condition is_array($menuItems) is always true.
Loading history...
2667
            return '';
2668
        }
2669
        $scriptUrl = self::buildScriptUrl($mainParams, $addParams, $script);
2670
        $options = [];
2671
        foreach ($menuItems as $value => $label) {
2672
            $options[] = '<option value="'
2673
                . htmlspecialchars($value) . '"'
2674
                . ((string)$currentValue === (string)$value ? ' selected="selected"' : '') . '>'
2675
                . htmlspecialchars($label, ENT_COMPAT, 'UTF-8', false) . '</option>';
2676
        }
2677
        $dataMenuIdentifier = str_replace(['SET[', ']'], '', $elementName);
2678
        $dataMenuIdentifier = GeneralUtility::camelCaseToLowerCaseUnderscored($dataMenuIdentifier);
2679
        $dataMenuIdentifier = str_replace('_', '-', $dataMenuIdentifier);
2680
        if (!empty($options)) {
2681
            // @todo Should we add requireJsModule again (should be loaded in most/all cases)
2682
            // loadRequireJsModule('TYPO3/CMS/Backend/GlobalEventHandler');
2683
            $onChange = 'window.location.href = ' . GeneralUtility::quoteJSvalue($scriptUrl . '&' . $elementName . '=') . '+this.options[this.selectedIndex].value;';
0 ignored issues
show
Unused Code introduced by
The assignment to $onChange is dead and can be removed.
Loading history...
2684
            $attributes = GeneralUtility::implodeAttributes([
2685
                'name' => $elementName,
2686
                'data-menu-identifier' => $dataMenuIdentifier,
2687
                'data-global-event' => 'change',
2688
                'data-action-navigate' => '$data=~s/$value/',
2689
                'data-navigate-value' => $scriptUrl . '&' . $elementName . '=${value}',
2690
            ], true);
2691
            return '
2692
			<div class="form-group">
2693
				<!-- Function Menu of module -->
2694
				<select class="form-control input-sm" ' . $attributes . '>
2695
					' . implode(LF, $options) . '
2696
				</select>
2697
			</div>
2698
						';
2699
        }
2700
        return '';
2701
    }
2702
2703
    /**
2704
     * Checkbox function menu.
2705
     * Works like ->getFuncMenu() but takes no $menuItem array since this is a simple checkbox.
2706
     *
2707
     * @param mixed $mainParams $id is the "&id=" parameter value to be sent to the module, but it can be also a parameter array which will be passed instead of the &id=...
2708
     * @param string $elementName The form elements name, probably something like "SET[...]
2709
     * @param string $currentValue The value to be selected currently.
2710
     * @param string $script The script to send the &id to, if empty it's automatically found
2711
     * @param string $addParams Additional parameters to pass to the script.
2712
     * @param string $tagParams Additional attributes for the checkbox input tag
2713
     * @return string HTML code for checkbox
2714
     * @see getFuncMenu()
2715
     */
2716
    public static function getFuncCheck(
2717
        $mainParams,
2718
        $elementName,
2719
        $currentValue,
2720
        $script = '',
2721
        $addParams = '',
2722
        $tagParams = ''
2723
    ) {
2724
        // @todo Should we add requireJsModule again (should be loaded in most/all cases)
2725
        // loadRequireJsModule('TYPO3/CMS/Backend/GlobalEventHandler');
2726
        $scriptUrl = self::buildScriptUrl($mainParams, $addParams, $script);
2727
        $attributes = GeneralUtility::implodeAttributes([
2728
            'type' => 'checkbox',
2729
            'class' => 'checkbox',
2730
            'name' => $elementName,
2731
            'value' => 1,
2732
            'data-global-event' => 'change',
2733
            'data-action-navigate' => '$data=~s/$value/',
2734
            'data-navigate-value' => sprintf('%s&%s=${value}', $scriptUrl, $elementName),
2735
        ], true);
2736
        return
2737
            '<input ' . $attributes .
2738
            ($currentValue ? ' checked="checked"' : '') .
2739
            ($tagParams ? ' ' . $tagParams : '') .
2740
            ' />';
2741
    }
2742
2743
    /**
2744
     * Input field function menu
2745
     * Works like ->getFuncMenu() / ->getFuncCheck() but displays an input field instead which updates the script "onchange"
2746
     *
2747
     * @param mixed $mainParams $id is the "&id=" parameter value to be sent to the module, but it can be also a parameter array which will be passed instead of the &id=...
2748
     * @param string $elementName The form elements name, probably something like "SET[...]
2749
     * @param string $currentValue The value to be selected currently.
2750
     * @param int $size Relative size of input field, max is 48
2751
     * @param string $script The script to send the &id to, if empty it's automatically found
2752
     * @param string $addParams Additional parameters to pass to the script.
2753
     * @return string HTML code for input text field.
2754
     * @see getFuncMenu()
2755
     */
2756
    public static function getFuncInput(
2757
        $mainParams,
2758
        $elementName,
2759
        $currentValue,
2760
        $size = 10,
2761
        $script = '',
2762
        $addParams = ''
2763
    ) {
2764
        $scriptUrl = self::buildScriptUrl($mainParams, $addParams, $script);
2765
        $onChange = 'window.location.href = ' . GeneralUtility::quoteJSvalue($scriptUrl . '&' . $elementName . '=') . '+escape(this.value);';
2766
        return '<input type="text" class="form-control" name="' . $elementName . '" value="' . htmlspecialchars($currentValue) . '" onchange="' . htmlspecialchars($onChange) . '" />';
2767
    }
2768
2769
    /**
2770
     * Builds the URL to the current script with given arguments
2771
     *
2772
     * @param mixed $mainParams $id is the "&id=" parameter value to be sent to the module, but it can be also a parameter array which will be passed instead of the &id=...
2773
     * @param string $addParams Additional parameters to pass to the script.
2774
     * @param string $script The script to send the &id to, if empty it's automatically found
2775
     * @return string The complete script URL
2776
     */
2777
    protected static function buildScriptUrl($mainParams, $addParams, $script = '')
2778
    {
2779
        if (!is_array($mainParams)) {
2780
            $mainParams = ['id' => $mainParams];
2781
        }
2782
        if (!$script) {
2783
            $script = PathUtility::basename(Environment::getCurrentScript());
2784
        }
2785
2786
        if ($routePath = GeneralUtility::_GP('route')) {
2787
            $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
2788
            $scriptUrl = (string)$uriBuilder->buildUriFromRoutePath($routePath, $mainParams);
2789
            $scriptUrl .= $addParams;
2790
        } else {
2791
            $scriptUrl = $script . HttpUtility::buildQueryString($mainParams, '?') . $addParams;
2792
        }
2793
2794
        return $scriptUrl;
2795
    }
2796
2797
    /**
2798
     * Call to update the page tree frame (or something else..?) after
2799
     * use 'updatePageTree' as a first parameter will set the page tree to be updated.
2800
     *
2801
     * @param string $set Key to set the update signal. When setting, this value contains strings telling WHAT to set. At this point it seems that the value "updatePageTree" is the only one it makes sense to set. If empty, all update signals will be removed.
2802
     * @param mixed $params Additional information for the update signal, used to only refresh a branch of the tree
2803
     * @see BackendUtility::getUpdateSignalCode()
2804
     */
2805
    public static function setUpdateSignal($set = '', $params = '')
2806
    {
2807
        $beUser = static::getBackendUserAuthentication();
2808
        $modData = $beUser->getModuleData(
2809
            \TYPO3\CMS\Backend\Utility\BackendUtility::class . '::getUpdateSignal',
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
2810
            'ses'
2811
        );
2812
        if ($set) {
2813
            $modData[$set] = [
2814
                'set' => $set,
2815
                'parameter' => $params
2816
            ];
2817
        } else {
2818
            // clear the module data
2819
            $modData = [];
2820
        }
2821
        $beUser->pushModuleData(\TYPO3\CMS\Backend\Utility\BackendUtility::class . '::getUpdateSignal', $modData);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
2822
    }
2823
2824
    /**
2825
     * Call to update the page tree frame (or something else..?) if this is set by the function
2826
     * setUpdateSignal(). It will return some JavaScript that does the update
2827
     *
2828
     * @return string HTML javascript code
2829
     * @see BackendUtility::setUpdateSignal()
2830
     */
2831
    public static function getUpdateSignalCode()
2832
    {
2833
        $signals = [];
2834
        $modData = static::getBackendUserAuthentication()->getModuleData(
2835
            \TYPO3\CMS\Backend\Utility\BackendUtility::class . '::getUpdateSignal',
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
2836
            'ses'
2837
        );
2838
        if (empty($modData)) {
2839
            return '';
2840
        }
2841
        // Hook: Allows to let TYPO3 execute your JS code
2842
        $updateSignals = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['updateSignalHook'] ?? [];
2843
        // Loop through all setUpdateSignals and get the JS code
2844
        foreach ($modData as $set => $val) {
2845
            if (isset($updateSignals[$set])) {
2846
                $params = ['set' => $set, 'parameter' => $val['parameter'], 'JScode' => ''];
2847
                $ref = null;
2848
                GeneralUtility::callUserFunction($updateSignals[$set], $params, $ref);
2849
                $signals[] = $params['JScode'];
2850
            } else {
2851
                switch ($set) {
2852
                    case 'updatePageTree':
2853
                        $signals[] = '
2854
								if (top && top.TYPO3.Backend && top.TYPO3.Backend.NavigationContainer.PageTree) {
2855
									top.TYPO3.Backend.NavigationContainer.PageTree.refreshTree();
2856
								}
2857
							';
2858
                        break;
2859
                    case 'updateFolderTree':
2860
                        $signals[] = '
2861
								if (top && top.nav_frame && top.nav_frame.location) {
2862
									top.nav_frame.location.reload(true);
2863
								}';
2864
                        break;
2865
                    case 'updateModuleMenu':
2866
                        $signals[] = '
2867
								if (top && top.TYPO3.ModuleMenu && top.TYPO3.ModuleMenu.App) {
2868
									top.TYPO3.ModuleMenu.App.refreshMenu();
2869
								}';
2870
                        break;
2871
                    case 'updateTopbar':
2872
                        $signals[] = '
2873
								if (top && top.TYPO3.Backend && top.TYPO3.Backend.Topbar) {
2874
									top.TYPO3.Backend.Topbar.refresh();
2875
								}';
2876
                        break;
2877
                }
2878
            }
2879
        }
2880
        $content = implode(LF, $signals);
2881
        // For backwards compatibility, should be replaced
2882
        self::setUpdateSignal();
2883
        return $content;
2884
    }
2885
2886
    /**
2887
     * Returns an array which is most backend modules becomes MOD_SETTINGS containing values from function menus etc. determining the function of the module.
2888
     * This is kind of session variable management framework for the backend users.
2889
     * If a key from MOD_MENU is set in the CHANGED_SETTINGS array (eg. a value is passed to the script from the outside), this value is put into the settings-array
2890
     * Ultimately, see Inside TYPO3 for how to use this function in relation to your modules.
2891
     *
2892
     * @param array $MOD_MENU MOD_MENU is an array that defines the options in menus.
2893
     * @param array $CHANGED_SETTINGS CHANGED_SETTINGS represents the array used when passing values to the script from the menus.
2894
     * @param string $modName modName is the name of this module. Used to get the correct module data.
2895
     * @param string $type If type is 'ses' then the data is stored as session-lasting data. This means that it'll be wiped out the next time the user logs in.
2896
     * @param string $dontValidateList dontValidateList can be used to list variables that should not be checked if their value is found in the MOD_MENU array. Used for dynamically generated menus.
2897
     * @param string $setDefaultList List of default values from $MOD_MENU to set in the output array (only if the values from MOD_MENU are not arrays)
2898
     * @throws \RuntimeException
2899
     * @return array The array $settings, which holds a key for each MOD_MENU key and the values of each key will be within the range of values for each menuitem
2900
     */
2901
    public static function getModuleData(
2902
        $MOD_MENU,
2903
        $CHANGED_SETTINGS,
2904
        $modName,
2905
        $type = '',
2906
        $dontValidateList = '',
2907
        $setDefaultList = ''
2908
    ) {
2909
        if ($modName && is_string($modName)) {
2910
            // Getting stored user-data from this module:
2911
            $beUser = static::getBackendUserAuthentication();
2912
            $settings = $beUser->getModuleData($modName, $type);
2913
            $changed = 0;
2914
            if (!is_array($settings)) {
2915
                $changed = 1;
2916
                $settings = [];
2917
            }
2918
            if (is_array($MOD_MENU)) {
0 ignored issues
show
introduced by
The condition is_array($MOD_MENU) is always true.
Loading history...
2919
                foreach ($MOD_MENU as $key => $var) {
2920
                    // If a global var is set before entering here. eg if submitted, then it's substituting the current value the array.
2921
                    if (is_array($CHANGED_SETTINGS) && isset($CHANGED_SETTINGS[$key])) {
2922
                        if (is_array($CHANGED_SETTINGS[$key])) {
2923
                            $serializedSettings = serialize($CHANGED_SETTINGS[$key]);
2924
                            if ((string)$settings[$key] !== $serializedSettings) {
2925
                                $settings[$key] = $serializedSettings;
2926
                                $changed = 1;
2927
                            }
2928
                        } else {
2929
                            if ((string)$settings[$key] !== (string)$CHANGED_SETTINGS[$key]) {
2930
                                $settings[$key] = $CHANGED_SETTINGS[$key];
2931
                                $changed = 1;
2932
                            }
2933
                        }
2934
                    }
2935
                    // If the $var is an array, which denotes the existence of a menu, we check if the value is permitted
2936
                    if (is_array($var) && (!$dontValidateList || !GeneralUtility::inList($dontValidateList, $key))) {
2937
                        // If the setting is an array or not present in the menu-array, MOD_MENU, then the default value is inserted.
2938
                        if (is_array($settings[$key]) || !isset($MOD_MENU[$key][$settings[$key]])) {
2939
                            $settings[$key] = (string)key($var);
2940
                            $changed = 1;
2941
                        }
2942
                    }
2943
                    // Sets default values (only strings/checkboxes, not menus)
2944
                    if ($setDefaultList && !is_array($var)) {
2945
                        if (GeneralUtility::inList($setDefaultList, $key) && !isset($settings[$key])) {
2946
                            $settings[$key] = (string)$var;
2947
                        }
2948
                    }
2949
                }
2950
            } else {
2951
                throw new \RuntimeException('No menu', 1568119229);
2952
            }
2953
            if ($changed) {
2954
                $beUser->pushModuleData($modName, $settings);
2955
            }
2956
            return $settings;
2957
        }
2958
        throw new \RuntimeException('Wrong module name "' . $modName . '"', 1568119221);
2959
    }
2960
2961
    /*******************************************
2962
     *
2963
     * Core
2964
     *
2965
     *******************************************/
2966
    /**
2967
     * Unlock or Lock a record from $table with $uid
2968
     * If $table and $uid is not set, then all locking for the current BE_USER is removed!
2969
     *
2970
     * @param string $table Table name
2971
     * @param int $uid Record uid
2972
     * @param int $pid Record pid
2973
     * @internal
2974
     */
2975
    public static function lockRecords($table = '', $uid = 0, $pid = 0)
2976
    {
2977
        $beUser = static::getBackendUserAuthentication();
2978
        if (isset($beUser->user['uid'])) {
2979
            $userId = (int)$beUser->user['uid'];
2980
            if ($table && $uid) {
2981
                $fieldsValues = [
2982
                    'userid' => $userId,
2983
                    'feuserid' => 0,
2984
                    'tstamp' => $GLOBALS['EXEC_TIME'],
2985
                    'record_table' => $table,
2986
                    'record_uid' => $uid,
2987
                    'username' => $beUser->user['username'],
2988
                    'record_pid' => $pid
2989
                ];
2990
                GeneralUtility::makeInstance(ConnectionPool::class)
2991
                    ->getConnectionForTable('sys_lockedrecords')
2992
                    ->insert(
2993
                        'sys_lockedrecords',
2994
                        $fieldsValues
2995
                    );
2996
            } else {
2997
                GeneralUtility::makeInstance(ConnectionPool::class)
2998
                    ->getConnectionForTable('sys_lockedrecords')
2999
                    ->delete(
3000
                        'sys_lockedrecords',
3001
                        ['userid' => (int)$userId]
3002
                    );
3003
            }
3004
        }
3005
    }
3006
3007
    /**
3008
     * Returns information about whether the record from table, $table, with uid, $uid is currently locked
3009
     * (edited by another user - which should issue a warning).
3010
     * Notice: Locking is not strictly carried out since locking is abandoned when other backend scripts
3011
     * are activated - which means that a user CAN have a record "open" without having it locked.
3012
     * So this just serves as a warning that counts well in 90% of the cases, which should be sufficient.
3013
     *
3014
     * @param string $table Table name
3015
     * @param int $uid Record uid
3016
     * @return array|bool
3017
     * @internal
3018
     */
3019
    public static function isRecordLocked($table, $uid)
3020
    {
3021
        $runtimeCache = self::getRuntimeCache();
3022
        $cacheId = 'backend-recordLocked';
3023
        $recordLockedCache = $runtimeCache->get($cacheId);
3024
        if ($recordLockedCache !== false) {
3025
            $lockedRecords = $recordLockedCache;
3026
        } else {
3027
            $lockedRecords = [];
3028
3029
            $queryBuilder = static::getQueryBuilderForTable('sys_lockedrecords');
3030
            $result = $queryBuilder
3031
                ->select('*')
3032
                ->from('sys_lockedrecords')
3033
                ->where(
3034
                    $queryBuilder->expr()->neq(
3035
                        'sys_lockedrecords.userid',
3036
                        $queryBuilder->createNamedParameter(
3037
                            static::getBackendUserAuthentication()->user['uid'],
3038
                            \PDO::PARAM_INT
3039
                        )
3040
                    ),
3041
                    $queryBuilder->expr()->gt(
3042
                        'sys_lockedrecords.tstamp',
3043
                        $queryBuilder->createNamedParameter(
3044
                            $GLOBALS['EXEC_TIME'] - 2 * 3600,
3045
                            \PDO::PARAM_INT
3046
                        )
3047
                    )
3048
                )
3049
                ->execute();
3050
3051
            $lang = static::getLanguageService();
3052
            while ($row = $result->fetch()) {
3053
                // Get the type of the user that locked this record:
3054
                if ($row['userid']) {
3055
                    $userTypeLabel = 'beUser';
3056
                } elseif ($row['feuserid']) {
3057
                    $userTypeLabel = 'feUser';
3058
                } else {
3059
                    $userTypeLabel = 'user';
3060
                }
3061
                $userType = $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.' . $userTypeLabel);
3062
                // Get the username (if available):
3063
                if ($row['username']) {
3064
                    $userName = $row['username'];
3065
                } else {
3066
                    $userName = $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.unknownUser');
3067
                }
3068
                $lockedRecords[$row['record_table'] . ':' . $row['record_uid']] = $row;
3069
                $lockedRecords[$row['record_table'] . ':' . $row['record_uid']]['msg'] = sprintf(
3070
                    $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.lockedRecordUser'),
3071
                    $userType,
3072
                    $userName,
3073
                    self::calcAge(
3074
                        $GLOBALS['EXEC_TIME'] - $row['tstamp'],
3075
                        $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.minutesHoursDaysYears')
3076
                    )
3077
                );
3078
                if ($row['record_pid'] && !isset($lockedRecords[$row['record_table'] . ':' . $row['record_pid']])) {
3079
                    $lockedRecords['pages:' . $row['record_pid']]['msg'] = sprintf(
3080
                        $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.lockedRecordUser_content'),
3081
                        $userType,
3082
                        $userName,
3083
                        self::calcAge(
3084
                            $GLOBALS['EXEC_TIME'] - $row['tstamp'],
3085
                            $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.minutesHoursDaysYears')
3086
                        )
3087
                    );
3088
                }
3089
            }
3090
            $runtimeCache->set($cacheId, $lockedRecords);
3091
        }
3092
3093
        return $lockedRecords[$table . ':' . $uid] ?? false;
3094
    }
3095
3096
    /**
3097
     * Returns TSConfig for the TCEFORM object in Page TSconfig.
3098
     * Used in TCEFORMs
3099
     *
3100
     * @param string $table Table name present in TCA
3101
     * @param array $row Row from table
3102
     * @return array
3103
     */
3104
    public static function getTCEFORM_TSconfig($table, $row)
0 ignored issues
show
Coding Style introduced by
Method name "BackendUtility::getTCEFORM_TSconfig" is not in camel caps format
Loading history...
3105
    {
3106
        self::fixVersioningPid($table, $row);
3107
        $res = [];
3108
        // Get main config for the table
3109
        [$TScID, $cPid] = self::getTSCpid($table, $row['uid'], $row['pid']);
3110
        if ($TScID >= 0) {
3111
            $tsConfig = static::getPagesTSconfig($TScID)['TCEFORM.'][$table . '.'] ?? [];
3112
            $typeVal = self::getTCAtypeValue($table, $row);
3113
            foreach ($tsConfig as $key => $val) {
3114
                if (is_array($val)) {
3115
                    $fieldN = substr($key, 0, -1);
3116
                    $res[$fieldN] = $val;
3117
                    unset($res[$fieldN]['types.']);
3118
                    if ((string)$typeVal !== '' && is_array($val['types.'][$typeVal . '.'])) {
3119
                        ArrayUtility::mergeRecursiveWithOverrule($res[$fieldN], $val['types.'][$typeVal . '.']);
3120
                    }
3121
                }
3122
            }
3123
        }
3124
        $res['_CURRENT_PID'] = $cPid;
3125
        $res['_THIS_UID'] = $row['uid'];
3126
        // So the row will be passed to foreign_table_where_query()
3127
        $res['_THIS_ROW'] = $row;
3128
        return $res;
3129
    }
3130
3131
    /**
3132
     * Find the real PID of the record (with $uid from $table).
3133
     * This MAY be impossible if the pid is set as a reference to the former record or a page (if two records are created at one time).
3134
     * NOTICE: Make sure that the input PID is never negative because the record was an offline version!
3135
     * Therefore, you should always use BackendUtility::fixVersioningPid($table,$row); on the data you input before calling this function!
3136
     *
3137
     * @param string $table Table name
3138
     * @param int $uid Record uid
3139
     * @param int $pid Record pid, could be negative then pointing to a record from same table whose pid to find and return
3140
     * @return int
3141
     * @internal
3142
     * @see \TYPO3\CMS\Core\DataHandling\DataHandler::copyRecord()
3143
     * @see \TYPO3\CMS\Backend\Utility\BackendUtility::getTSCpid()
3144
     */
3145
    public static function getTSconfig_pidValue($table, $uid, $pid)
0 ignored issues
show
Coding Style introduced by
Method name "BackendUtility::getTSconfig_pidValue" is not in camel caps format
Loading history...
3146
    {
3147
        // If pid is an integer this takes precedence in our lookup.
3148
        if (MathUtility::canBeInterpretedAsInteger($pid)) {
3149
            $thePidValue = (int)$pid;
3150
            // If ref to another record, look that record up.
3151
            if ($thePidValue < 0) {
3152
                $pidRec = self::getRecord($table, abs($thePidValue), 'pid');
0 ignored issues
show
Bug introduced by
It seems like abs($thePidValue) can also be of type double; however, parameter $uid of TYPO3\CMS\Backend\Utilit...endUtility::getRecord() does only seem to accept integer, 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

3152
                $pidRec = self::getRecord($table, /** @scrutinizer ignore-type */ abs($thePidValue), 'pid');
Loading history...
3153
                $thePidValue = is_array($pidRec) ? $pidRec['pid'] : -2;
3154
            }
3155
        } else {
3156
            // Try to fetch the record pid from uid. If the uid is 'NEW...' then this will of course return nothing
3157
            $rr = self::getRecord($table, $uid);
3158
            $thePidValue = null;
3159
            if (is_array($rr)) {
3160
                // First check if the pid is -1 which means it is a workspaced element. Get the "real" record:
3161
                if ($rr['pid'] == '-1') {
3162
                    $rr = self::getRecord($table, $rr['t3ver_oid'], 'pid');
3163
                    if (is_array($rr)) {
3164
                        $thePidValue = $rr['pid'];
3165
                    }
3166
                } else {
3167
                    // Returning the "pid" of the record
3168
                    $thePidValue = $rr['pid'];
3169
                }
3170
            }
3171
            if (!$thePidValue) {
3172
                // Returns -1 if the record with this pid was not found.
3173
                $thePidValue = -1;
3174
            }
3175
        }
3176
        return $thePidValue;
3177
    }
3178
3179
    /**
3180
     * Return the real pid of a record and caches the result.
3181
     * The non-cached method needs database queries to do the job, so this method
3182
     * can be used if code sometimes calls the same record multiple times to save
3183
     * some queries. This should not be done if the calling code may change the
3184
     * same record meanwhile.
3185
     *
3186
     * @param string $table Tablename
3187
     * @param string $uid UID value
3188
     * @param string $pid PID value
3189
     * @return array Array of two integers; first is the real PID of a record, second is the PID value for TSconfig.
3190
     */
3191
    public static function getTSCpidCached($table, $uid, $pid)
3192
    {
3193
        $runtimeCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('runtime');
3194
        $firstLevelCache = $runtimeCache->get('backendUtilityTscPidCached') ?: [];
3195
        $key = $table . ':' . $uid . ':' . $pid;
3196
        if (!isset($firstLevelCache[$key])) {
3197
            $firstLevelCache[$key] = static::getTSCpid($table, $uid, $pid);
0 ignored issues
show
Bug introduced by
$pid of type string is incompatible with the type integer expected by parameter $pid of TYPO3\CMS\Backend\Utilit...endUtility::getTSCpid(). ( Ignorable by Annotation )

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

3197
            $firstLevelCache[$key] = static::getTSCpid($table, $uid, /** @scrutinizer ignore-type */ $pid);
Loading history...
Bug introduced by
$uid of type string is incompatible with the type integer expected by parameter $uid of TYPO3\CMS\Backend\Utilit...endUtility::getTSCpid(). ( Ignorable by Annotation )

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

3197
            $firstLevelCache[$key] = static::getTSCpid($table, /** @scrutinizer ignore-type */ $uid, $pid);
Loading history...
3198
            $runtimeCache->set('backendUtilityTscPidCached', $firstLevelCache);
3199
        }
3200
        return $firstLevelCache[$key];
3201
    }
3202
3203
    /**
3204
     * Returns the REAL pid of the record, if possible. If both $uid and $pid is strings, then pid=-1 is returned as an error indication.
3205
     *
3206
     * @param string $table Table name
3207
     * @param int $uid Record uid
3208
     * @param int $pid Record pid
3209
     * @return array Array of two integers; first is the REAL PID of a record and if its a new record negative values are resolved to the true PID,
3210
     * second value is the PID value for TSconfig (uid if table is pages, otherwise the pid)
3211
     * @internal
3212
     * @see \TYPO3\CMS\Core\DataHandling\DataHandler::setHistory()
3213
     * @see \TYPO3\CMS\Core\DataHandling\DataHandler::process_datamap()
3214
     */
3215
    public static function getTSCpid($table, $uid, $pid)
3216
    {
3217
        // If pid is negative (referring to another record) the pid of the other record is fetched and returned.
3218
        $cPid = self::getTSconfig_pidValue($table, $uid, $pid);
3219
        // $TScID is the id of $table = pages, else it's the pid of the record.
3220
        $TScID = $table === 'pages' && MathUtility::canBeInterpretedAsInteger($uid) ? $uid : $cPid;
3221
        return [$TScID, $cPid];
3222
    }
3223
3224
    /**
3225
     * Returns soft-reference parser for the softRef processing type
3226
     * Usage: $softRefObj = BackendUtility::softRefParserObj('[parser key]');
3227
     *
3228
     * @param string $spKey softRef parser key
3229
     * @return mixed If available, returns Soft link parser object, otherwise false.
3230
     * @internal should only be used from within TYPO3 Core
3231
     */
3232
    public static function softRefParserObj($spKey)
3233
    {
3234
        $className = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['softRefParser'][$spKey] ?? false;
3235
        if ($className) {
3236
            return GeneralUtility::makeInstance($className);
3237
        }
3238
        return false;
3239
    }
3240
3241
    /**
3242
     * Gets an instance of the runtime cache.
3243
     *
3244
     * @return FrontendInterface
3245
     */
3246
    protected static function getRuntimeCache()
3247
    {
3248
        return GeneralUtility::makeInstance(CacheManager::class)->getCache('runtime');
3249
    }
3250
3251
    /**
3252
     * Returns array of soft parser references
3253
     *
3254
     * @param string $parserList softRef parser list
3255
     * @return array|bool Array where the parser key is the key and the value is the parameter string, FALSE if no parsers were found
3256
     * @throws \InvalidArgumentException
3257
     * @internal should only be used from within TYPO3 Core
3258
     */
3259
    public static function explodeSoftRefParserList($parserList)
3260
    {
3261
        // Return immediately if list is blank:
3262
        if ((string)$parserList === '') {
3263
            return false;
3264
        }
3265
3266
        $runtimeCache = self::getRuntimeCache();
3267
        $cacheId = 'backend-softRefList-' . md5($parserList);
3268
        $parserListCache = $runtimeCache->get($cacheId);
3269
        if ($parserListCache !== false) {
3270
            return $parserListCache;
3271
        }
3272
3273
        // Otherwise parse the list:
3274
        $keyList = GeneralUtility::trimExplode(',', $parserList, true);
3275
        $output = [];
3276
        foreach ($keyList as $val) {
3277
            $reg = [];
3278
            if (preg_match('/^([[:alnum:]_-]+)\\[(.*)\\]$/', $val, $reg)) {
3279
                $output[$reg[1]] = GeneralUtility::trimExplode(';', $reg[2], true);
3280
            } else {
3281
                $output[$val] = '';
3282
            }
3283
        }
3284
        $runtimeCache->set($cacheId, $output);
3285
        return $output;
3286
    }
3287
3288
    /**
3289
     * Returns TRUE if $modName is set and is found as a main- or submodule in $TBE_MODULES array
3290
     *
3291
     * @param string $modName Module name
3292
     * @return bool
3293
     */
3294
    public static function isModuleSetInTBE_MODULES($modName)
0 ignored issues
show
Coding Style introduced by
Method name "BackendUtility::isModuleSetInTBE_MODULES" is not in camel caps format
Loading history...
3295
    {
3296
        $loaded = [];
3297
        foreach ($GLOBALS['TBE_MODULES'] as $mkey => $list) {
3298
            $loaded[$mkey] = 1;
3299
            if (!is_array($list) && trim($list)) {
3300
                $subList = GeneralUtility::trimExplode(',', $list, true);
3301
                foreach ($subList as $skey) {
3302
                    $loaded[$mkey . '_' . $skey] = 1;
3303
                }
3304
            }
3305
        }
3306
        return $modName && isset($loaded[$modName]);
3307
    }
3308
3309
    /**
3310
     * Counting references to a record/file
3311
     *
3312
     * @param string $table Table name (or "_FILE" if its a file)
3313
     * @param string $ref Reference: If table, then int-uid, if _FILE, then file reference (relative to Environment::getPublicPath())
3314
     * @param string $msg Message with %s, eg. "There were %s records pointing to this file!
3315
     * @param string|null $count Reference count
3316
     * @return string Output string (or int count value if no msg string specified)
3317
     */
3318
    public static function referenceCount($table, $ref, $msg = '', $count = null)
3319
    {
3320
        if ($count === null) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
3321
3322
            // Build base query
3323
            $queryBuilder = static::getQueryBuilderForTable('sys_refindex');
3324
            $queryBuilder
3325
                ->count('*')
3326
                ->from('sys_refindex')
3327
                ->where(
3328
                    $queryBuilder->expr()->eq('ref_table', $queryBuilder->createNamedParameter($table, \PDO::PARAM_STR)),
3329
                    $queryBuilder->expr()->eq('deleted', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
3330
                );
3331
3332
            // Look up the path:
3333
            if ($table === '_FILE') {
3334
                if (!GeneralUtility::isFirstPartOfStr($ref, Environment::getPublicPath())) {
3335
                    return '';
3336
                }
3337
3338
                $ref = PathUtility::stripPathSitePrefix($ref);
3339
                $queryBuilder->andWhere(
3340
                    $queryBuilder->expr()->eq('ref_string', $queryBuilder->createNamedParameter($ref, \PDO::PARAM_STR))
3341
                );
3342
            } else {
3343
                $queryBuilder->andWhere(
3344
                    $queryBuilder->expr()->eq('ref_uid', $queryBuilder->createNamedParameter($ref, \PDO::PARAM_INT))
3345
                );
3346
                if ($table === 'sys_file') {
3347
                    $queryBuilder->andWhere($queryBuilder->expr()->neq('tablename', $queryBuilder->quote('sys_file_metadata')));
3348
                }
3349
            }
3350
3351
            $count = $queryBuilder->execute()->fetchColumn(0);
3352
        }
3353
3354
        if ($count) {
3355
            return $msg ? sprintf($msg, $count) : $count;
3356
        }
3357
        return $msg ? '' : 0;
3358
    }
3359
3360
    /**
3361
     * Counting translations of records
3362
     *
3363
     * @param string $table Table name
3364
     * @param string $ref Reference: the record's uid
3365
     * @param string $msg Message with %s, eg. "This record has %s translation(s) which will be deleted, too!
3366
     * @return string Output string (or int count value if no msg string specified)
3367
     */
3368
    public static function translationCount($table, $ref, $msg = '')
3369
    {
3370
        $count = null;
3371
        if ($GLOBALS['TCA'][$table]['ctrl']['languageField']
3372
            && $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']
3373
        ) {
3374
            $queryBuilder = static::getQueryBuilderForTable($table);
3375
            $queryBuilder->getRestrictions()
3376
                ->removeAll()
3377
                ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
3378
3379
            $count = (int)$queryBuilder
3380
                ->count('*')
3381
                ->from($table)
3382
                ->where(
3383
                    $queryBuilder->expr()->eq(
3384
                        $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
3385
                        $queryBuilder->createNamedParameter($ref, \PDO::PARAM_INT)
3386
                    ),
3387
                    $queryBuilder->expr()->neq(
3388
                        $GLOBALS['TCA'][$table]['ctrl']['languageField'],
3389
                        $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
3390
                    )
3391
                )
3392
                ->execute()
3393
                ->fetchColumn(0);
3394
        }
3395
3396
        if ($count && $msg) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $count 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...
3397
            return sprintf($msg, $count);
3398
        }
3399
3400
        if ($count) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $count 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...
3401
            return $msg ? sprintf($msg, $count) : $count;
3402
        }
3403
        return $msg ? '' : 0;
3404
    }
3405
3406
    /*******************************************
3407
     *
3408
     * Workspaces / Versioning
3409
     *
3410
     *******************************************/
3411
    /**
3412
     * Select all versions of a record, ordered by latest created version (uid DESC)
3413
     *
3414
     * @param string $table Table name to select from
3415
     * @param int $uid Record uid for which to find versions.
3416
     * @param string $fields Field list to select
3417
     * @param int|null $workspace Search in workspace ID and Live WS, if 0 search only in LiveWS, if NULL search in all WS.
3418
     * @param bool $includeDeletedRecords If set, deleted-flagged versions are included! (Only for clean-up script!)
3419
     * @param array $row The current record
3420
     * @return array|null Array of versions of table/uid
3421
     * @internal should only be used from within TYPO3 Core
3422
     */
3423
    public static function selectVersionsOfRecord(
3424
        $table,
3425
        $uid,
3426
        $fields = '*',
3427
        $workspace = 0,
3428
        $includeDeletedRecords = false,
3429
        $row = null
3430
    ) {
3431
        $realPid = 0;
3432
        $outputRows = [];
3433
        if (static::isTableWorkspaceEnabled($table)) {
3434
            if (is_array($row) && !$includeDeletedRecords) {
3435
                $row['_CURRENT_VERSION'] = true;
3436
                $realPid = $row['pid'];
3437
                $outputRows[] = $row;
3438
            } else {
3439
                // Select UID version:
3440
                $row = self::getRecord($table, $uid, $fields, '', !$includeDeletedRecords);
3441
                // Add rows to output array:
3442
                if ($row) {
3443
                    $row['_CURRENT_VERSION'] = true;
3444
                    $realPid = $row['pid'];
3445
                    $outputRows[] = $row;
3446
                }
3447
            }
3448
3449
            $queryBuilder = static::getQueryBuilderForTable($table);
3450
            $queryBuilder->getRestrictions()->removeAll();
3451
3452
            // build fields to select
3453
            $queryBuilder->select(...GeneralUtility::trimExplode(',', $fields));
3454
3455
            $queryBuilder
3456
                ->from($table)
3457
                ->where(
3458
                    $queryBuilder->expr()->neq('uid', $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)),
3459
                    $queryBuilder->expr()->eq('t3ver_oid', $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT))
3460
                )
3461
                ->orderBy('uid', 'DESC');
3462
3463
            if (!$includeDeletedRecords) {
3464
                $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
3465
            }
3466
3467
            if ($workspace === 0) {
3468
                // Only in Live WS
3469
                $queryBuilder->andWhere(
3470
                    $queryBuilder->expr()->eq(
3471
                        't3ver_wsid',
3472
                        $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
3473
                    )
3474
                );
3475
            } elseif ($workspace !== null) {
3476
                // In Live WS and Workspace with given ID
3477
                $queryBuilder->andWhere(
3478
                    $queryBuilder->expr()->in(
3479
                        't3ver_wsid',
3480
                        $queryBuilder->createNamedParameter([0, (int)$workspace], Connection::PARAM_INT_ARRAY)
3481
                    )
3482
                );
3483
            }
3484
3485
            $rows = $queryBuilder->execute()->fetchAll();
3486
3487
            // Add rows to output array:
3488
            if (is_array($rows)) {
0 ignored issues
show
introduced by
The condition is_array($rows) is always true.
Loading history...
3489
                $outputRows = array_merge($outputRows, $rows);
3490
            }
3491
            // Set real-pid:
3492
            foreach ($outputRows as $idx => $oRow) {
3493
                $outputRows[$idx]['_REAL_PID'] = $realPid;
3494
            }
3495
            return $outputRows;
3496
        }
3497
        return null;
3498
    }
3499
3500
    /**
3501
     * Find page-tree PID for versionized record
3502
     * Will look if the "pid" value of the input record is -1 and if the table supports versioning - if so,
3503
     * it will translate the -1 PID into the PID of the original record
3504
     * Used whenever you are tracking something back, like making the root line.
3505
     * Will only translate if the workspace of the input record matches that of the current user (unless flag set)
3506
     * Principle; Record offline! => Find online?
3507
     *
3508
     * If the record had its pid corrected to the online versions pid, then "_ORIG_pid" is set
3509
     * to the original pid value (-1 of course). The field "_ORIG_pid" is used by various other functions
3510
     * to detect if a record was in fact in a versionized branch.
3511
     *
3512
     * @param string $table Table name
3513
     * @param array $rr Record array passed by reference. As minimum, "pid" and "uid" fields must exist! "t3ver_oid" and "t3ver_wsid" is nice and will save you a DB query.
3514
     * @param bool $ignoreWorkspaceMatch Ignore workspace match
3515
     * @see PageRepository::fixVersioningPid()
3516
     * @internal should only be used from within TYPO3 Core
3517
     */
3518
    public static function fixVersioningPid($table, &$rr, $ignoreWorkspaceMatch = false)
3519
    {
3520
        if (!ExtensionManagementUtility::isLoaded('workspaces')) {
3521
            return;
3522
        }
3523
        if (!static::isTableWorkspaceEnabled($table)) {
3524
            return;
3525
        }
3526
        // Check that the input record is an offline version from a table that supports versioning:
3527
        if (is_array($rr)) {
0 ignored issues
show
introduced by
The condition is_array($rr) is always true.
Loading history...
3528
            // Check values for t3ver_oid and t3ver_wsid:
3529
            if (isset($rr['t3ver_oid']) && isset($rr['t3ver_wsid'])) {
3530
                // If "t3ver_oid" is already a field, just set this:
3531
                $oid = $rr['t3ver_oid'];
3532
                $wsid = $rr['t3ver_wsid'];
3533
            } else {
3534
                $oid = 0;
3535
                $wsid = 0;
3536
                // Otherwise we have to expect "uid" to be in the record and look up based on this:
3537
                $newPidRec = self::getRecord($table, $rr['uid'], 't3ver_oid,t3ver_wsid');
3538
                if (is_array($newPidRec)) {
3539
                    $oid = $newPidRec['t3ver_oid'];
3540
                    $wsid = $newPidRec['t3ver_wsid'];
3541
                }
3542
            }
3543
            // If ID of current online version is found, look up the PID value of that:
3544
            if ($oid
3545
                && ($ignoreWorkspaceMatch || (static::getBackendUserAuthentication() instanceof BackendUserAuthentication && (int)$wsid === (int)static::getBackendUserAuthentication()->workspace))
3546
            ) {
3547
                $oidRec = self::getRecord($table, $oid, 'pid');
3548
                if (is_array($oidRec)) {
3549
                    $rr['_ORIG_pid'] = $rr['pid'];
3550
                    $rr['pid'] = $oidRec['pid'];
3551
                }
3552
                // Use target PID in case of move pointer
3553
                if (
3554
                    !isset($rr['t3ver_state'])
3555
                    || VersionState::cast($rr['t3ver_state'])->equals(VersionState::MOVE_POINTER)
3556
                ) {
3557
                    $movePlaceholder = self::getMovePlaceholder($table, $oid, 'pid');
3558
                    if ($movePlaceholder) {
3559
                        $rr['_ORIG_pid'] = $rr['pid'];
3560
                        $rr['pid'] = $movePlaceholder['pid'];
3561
                    }
3562
                }
3563
            }
3564
        }
3565
    }
3566
3567
    /**
3568
     * Workspace Preview Overlay
3569
     * Generally ALWAYS used when records are selected based on uid or pid.
3570
     * If records are selected on other fields than uid or pid (eg. "email = ....")
3571
     * then usage might produce undesired results and that should be evaluated on individual basis.
3572
     * Principle; Record online! => Find offline?
3573
     * Recently, this function has been modified so it MAY set $row to FALSE.
3574
     * This happens if a version overlay with the move-id pointer is found in which case we would like a backend preview.
3575
     * In other words, you should check if the input record is still an array afterwards when using this function.
3576
     *
3577
     * @param string $table Table name
3578
     * @param array $row Record array passed by reference. As minimum, the "uid" and  "pid" fields must exist! Fake fields cannot exist since the fields in the array is used as field names in the SQL look up. It would be nice to have fields like "t3ver_state" and "t3ver_mode_id" as well to avoid a new lookup inside movePlhOL().
3579
     * @param int $wsid Workspace ID, if not specified will use static::getBackendUserAuthentication()->workspace
3580
     * @param bool $unsetMovePointers If TRUE the function does not return a "pointer" row for moved records in a workspace
3581
     * @see fixVersioningPid()
3582
     */
3583
    public static function workspaceOL($table, &$row, $wsid = -99, $unsetMovePointers = false)
3584
    {
3585
        if (!ExtensionManagementUtility::isLoaded('workspaces')) {
3586
            return;
3587
        }
3588
        // If this is FALSE the placeholder is shown raw in the backend.
3589
        // I don't know if this move can be useful for users to toggle. Technically it can help debugging.
3590
        $previewMovePlaceholders = true;
3591
        // Initialize workspace ID
3592
        if ($wsid == -99 && static::getBackendUserAuthentication() instanceof BackendUserAuthentication) {
3593
            $wsid = static::getBackendUserAuthentication()->workspace;
3594
        }
3595
        // Check if workspace is different from zero and record is set:
3596
        if ($wsid !== 0 && is_array($row)) {
3597
            // Check if input record is a move-placeholder and if so, find the pointed-to live record:
3598
            $movePldSwap = null;
3599
            $orig_uid = 0;
3600
            $orig_pid = 0;
3601
            if ($previewMovePlaceholders) {
0 ignored issues
show
introduced by
The condition $previewMovePlaceholders is always true.
Loading history...
3602
                $orig_uid = $row['uid'];
3603
                $orig_pid = $row['pid'];
3604
                $movePldSwap = self::movePlhOL($table, $row);
3605
            }
3606
            $wsAlt = self::getWorkspaceVersionOfRecord(
3607
                $wsid,
3608
                $table,
3609
                $row['uid'],
3610
                implode(',', static::purgeComputedPropertyNames(array_keys($row)))
3611
            );
3612
            // If version was found, swap the default record with that one.
3613
            if (is_array($wsAlt)) {
3614
                // Check if this is in move-state:
3615
                if ($previewMovePlaceholders && !$movePldSwap && static::isTableWorkspaceEnabled($table) && $unsetMovePointers) {
3616
                    // Only for WS ver 2... (moving)
3617
                    // If t3ver_state is not found, then find it... (but we like best if it is here...)
3618
                    if (!isset($wsAlt['t3ver_state'])) {
3619
                        $stateRec = self::getRecord($table, $wsAlt['uid'], 't3ver_state');
3620
                        $versionState = VersionState::cast($stateRec['t3ver_state']);
3621
                    } else {
3622
                        $versionState = VersionState::cast($wsAlt['t3ver_state']);
3623
                    }
3624
                    if ($versionState->equals(VersionState::MOVE_POINTER)) {
3625
                        // @todo Same problem as frontend in versionOL(). See TODO point there.
3626
                        $row = false;
3627
                        return;
3628
                    }
3629
                }
3630
                // Always correct PID from -1 to what it should be
3631
                if (isset($wsAlt['pid'])) {
3632
                    // Keep the old (-1) - indicates it was a version.
3633
                    $wsAlt['_ORIG_pid'] = $wsAlt['pid'];
3634
                    // Set in the online versions PID.
3635
                    $wsAlt['pid'] = $row['pid'];
3636
                }
3637
                // For versions of single elements or page+content, swap UID and PID
3638
                $wsAlt['_ORIG_uid'] = $wsAlt['uid'];
3639
                $wsAlt['uid'] = $row['uid'];
3640
                // Backend css class:
3641
                $wsAlt['_CSSCLASS'] = 'ver-element';
3642
                // Changing input record to the workspace version alternative:
3643
                $row = $wsAlt;
3644
            }
3645
            // If the original record was a move placeholder, the uid and pid of that is preserved here:
3646
            if ($movePldSwap) {
3647
                $row['_MOVE_PLH'] = true;
3648
                $row['_MOVE_PLH_uid'] = $orig_uid;
3649
                $row['_MOVE_PLH_pid'] = $orig_pid;
3650
                // For display; To make the icon right for the placeholder vs. the original
3651
                $row['t3ver_state'] = (string)new VersionState(VersionState::MOVE_PLACEHOLDER);
3652
            }
3653
        }
3654
    }
3655
3656
    /**
3657
     * Checks if record is a move-placeholder (t3ver_state==VersionState::MOVE_PLACEHOLDER) and if so
3658
     * it will set $row to be the pointed-to live record (and return TRUE)
3659
     *
3660
     * @param string $table Table name
3661
     * @param array $row Row (passed by reference) - must be online record!
3662
     * @return bool TRUE if overlay is made.
3663
     * @see PageRepository::movePlhOl()
3664
     * @internal should only be used from within TYPO3 Core
3665
     */
3666
    public static function movePlhOL($table, &$row)
3667
    {
3668
        if (static::isTableWorkspaceEnabled($table)) {
3669
            // If t3ver_move_id or t3ver_state is not found, then find it... (but we like best if it is here...)
3670
            if (!isset($row['t3ver_move_id']) || !isset($row['t3ver_state'])) {
3671
                $moveIDRec = self::getRecord($table, $row['uid'], 't3ver_move_id, t3ver_state');
3672
                $moveID = $moveIDRec['t3ver_move_id'];
3673
                $versionState = VersionState::cast($moveIDRec['t3ver_state']);
3674
            } else {
3675
                $moveID = $row['t3ver_move_id'];
3676
                $versionState = VersionState::cast($row['t3ver_state']);
3677
            }
3678
            // Find pointed-to record.
3679
            if ($versionState->equals(VersionState::MOVE_PLACEHOLDER) && $moveID) {
3680
                if ($origRow = self::getRecord(
3681
                    $table,
3682
                    $moveID,
3683
                    implode(',', static::purgeComputedPropertyNames(array_keys($row)))
3684
                )) {
3685
                    $row = $origRow;
3686
                    return true;
3687
                }
3688
            }
3689
        }
3690
        return false;
3691
    }
3692
3693
    /**
3694
     * Select the workspace version of a record, if exists
3695
     *
3696
     * @param int $workspace Workspace ID
3697
     * @param string $table Table name to select from
3698
     * @param int $uid Record uid for which to find workspace version.
3699
     * @param string $fields Field list to select
3700
     * @return array|bool If found, return record, otherwise false
3701
     */
3702
    public static function getWorkspaceVersionOfRecord($workspace, $table, $uid, $fields = '*')
3703
    {
3704
        if (ExtensionManagementUtility::isLoaded('workspaces')) {
3705
            if ($workspace !== 0 && self::isTableWorkspaceEnabled($table)) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
3706
3707
                // Select workspace version of record:
3708
                $queryBuilder = static::getQueryBuilderForTable($table);
3709
                $queryBuilder->getRestrictions()
3710
                    ->removeAll()
3711
                    ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
3712
3713
                // build fields to select
3714
                $queryBuilder->select(...GeneralUtility::trimExplode(',', $fields));
3715
3716
                $row = $queryBuilder
3717
                    ->from($table)
3718
                    ->where(
3719
                        $queryBuilder->expr()->eq(
3720
                            't3ver_oid',
3721
                            $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
3722
                        ),
3723
                        $queryBuilder->expr()->eq(
3724
                            't3ver_wsid',
3725
                            $queryBuilder->createNamedParameter($workspace, \PDO::PARAM_INT)
3726
                        )
3727
                    )
3728
                    ->execute()
3729
                    ->fetch();
3730
3731
                return $row;
3732
            }
3733
        }
3734
        return false;
3735
    }
3736
3737
    /**
3738
     * Returns live version of record
3739
     *
3740
     * @param string $table Table name
3741
     * @param int $uid Record UID of draft, offline version
3742
     * @param string $fields Field list, default is *
3743
     * @return array|null If found, the record, otherwise NULL
3744
     */
3745
    public static function getLiveVersionOfRecord($table, $uid, $fields = '*')
3746
    {
3747
        $liveVersionId = self::getLiveVersionIdOfRecord($table, $uid);
3748
        if ($liveVersionId !== null) {
3749
            return self::getRecord($table, $liveVersionId, $fields);
3750
        }
3751
        return null;
3752
    }
3753
3754
    /**
3755
     * Gets the id of the live version of a record.
3756
     *
3757
     * @param string $table Name of the table
3758
     * @param int $uid Uid of the offline/draft record
3759
     * @return int|null The id of the live version of the record (or NULL if nothing was found)
3760
     * @internal should only be used from within TYPO3 Core
3761
     */
3762
    public static function getLiveVersionIdOfRecord($table, $uid)
3763
    {
3764
        if (!ExtensionManagementUtility::isLoaded('workspaces')) {
3765
            return null;
3766
        }
3767
        $liveVersionId = null;
3768
        if (self::isTableWorkspaceEnabled($table)) {
3769
            $currentRecord = self::getRecord($table, $uid, 'pid,t3ver_oid');
3770
            if (is_array($currentRecord) && (int)$currentRecord['t3ver_oid'] > 0) {
3771
                $liveVersionId = $currentRecord['t3ver_oid'];
3772
            }
3773
        }
3774
        return $liveVersionId;
3775
    }
3776
3777
    /**
3778
     * Will return where clause de-selecting new(/deleted)-versions from other workspaces.
3779
     * If in live-workspace, don't show "MOVE-TO-PLACEHOLDERS" records if versioningWS is 2 (allows moving)
3780
     *
3781
     * @param string $table Table name
3782
     * @return string Where clause if applicable.
3783
     * @internal should only be used from within TYPO3 Core
3784
     */
3785
    public static function versioningPlaceholderClause($table)
3786
    {
3787
        if (static::isTableWorkspaceEnabled($table) && static::getBackendUserAuthentication() instanceof BackendUserAuthentication) {
3788
            $currentWorkspace = (int)static::getBackendUserAuthentication()->workspace;
3789
            return ' AND (' . $table . '.t3ver_state <= ' . new VersionState(VersionState::DEFAULT_STATE) . ' OR ' . $table . '.t3ver_wsid = ' . $currentWorkspace . ')';
3790
        }
3791
        return '';
3792
    }
3793
3794
    /**
3795
     * Get additional where clause to select records of a specific workspace (includes live as well).
3796
     *
3797
     * @param string $table Table name
3798
     * @param int $workspaceId Workspace ID
3799
     * @return string Workspace where clause
3800
     * @internal should only be used from within TYPO3 Core
3801
     */
3802
    public static function getWorkspaceWhereClause($table, $workspaceId = null)
3803
    {
3804
        $whereClause = '';
3805
        if (self::isTableWorkspaceEnabled($table) && static::getBackendUserAuthentication() instanceof BackendUserAuthentication) {
3806
            if ($workspaceId === null) {
3807
                $workspaceId = static::getBackendUserAuthentication()->workspace;
3808
            }
3809
            $workspaceId = (int)$workspaceId;
3810
            $comparison = $workspaceId === 0 ? '=' : '>';
3811
            $whereClause = ' AND ' . $table . '.t3ver_wsid=' . $workspaceId . ' AND ' . $table . '.t3ver_oid' . $comparison . '0';
3812
        }
3813
        return $whereClause;
3814
    }
3815
3816
    /**
3817
     * Performs mapping of new uids to new versions UID in case of import inside a workspace.
3818
     *
3819
     * @param string $table Table name
3820
     * @param int $uid Record uid (of live record placeholder)
3821
     * @return int Uid of offline version if any, otherwise live uid.
3822
     * @internal should only be used from within TYPO3 Core
3823
     */
3824
    public static function wsMapId($table, $uid)
3825
    {
3826
        $wsRec = null;
3827
        if (static::getBackendUserAuthentication() instanceof BackendUserAuthentication) {
0 ignored issues
show
introduced by
static::getBackendUserAuthentication() is always a sub-type of TYPO3\CMS\Core\Authentic...ckendUserAuthentication.
Loading history...
3828
            $wsRec = self::getWorkspaceVersionOfRecord(
3829
                static::getBackendUserAuthentication()->workspace,
3830
                $table,
3831
                $uid,
3832
                'uid'
3833
            );
3834
        }
3835
        return is_array($wsRec) ? $wsRec['uid'] : $uid;
3836
    }
3837
3838
    /**
3839
     * Returns move placeholder of online (live) version
3840
     *
3841
     * @param string $table Table name
3842
     * @param int $uid Record UID of online version
3843
     * @param string $fields Field list, default is *
3844
     * @param int|null $workspace The workspace to be used
3845
     * @return array|bool If found, the record, otherwise false
3846
     * @internal should only be used from within TYPO3 Core
3847
     */
3848
    public static function getMovePlaceholder($table, $uid, $fields = '*', $workspace = null)
3849
    {
3850
        if ($workspace === null && static::getBackendUserAuthentication() instanceof BackendUserAuthentication) {
3851
            $workspace = static::getBackendUserAuthentication()->workspace;
3852
        }
3853
        if ((int)$workspace !== 0 && static::isTableWorkspaceEnabled($table)) {
3854
            // Select workspace version of record:
3855
            $queryBuilder = static::getQueryBuilderForTable($table);
3856
            $queryBuilder->getRestrictions()
3857
                ->removeAll()
3858
                ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
3859
3860
            $row = $queryBuilder
3861
                ->select(...GeneralUtility::trimExplode(',', $fields, true))
3862
                ->from($table)
3863
                ->where(
3864
                    $queryBuilder->expr()->eq(
3865
                        't3ver_state',
3866
                        $queryBuilder->createNamedParameter(
3867
                            (string)new VersionState(VersionState::MOVE_PLACEHOLDER),
3868
                            \PDO::PARAM_INT
3869
                        )
3870
                    ),
3871
                    $queryBuilder->expr()->eq(
3872
                        't3ver_move_id',
3873
                        $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
3874
                    ),
3875
                    $queryBuilder->expr()->eq(
3876
                        't3ver_wsid',
3877
                        $queryBuilder->createNamedParameter($workspace, \PDO::PARAM_INT)
3878
                    )
3879
                )
3880
                ->execute()
3881
                ->fetch();
3882
3883
            return $row ?: false;
3884
        }
3885
        return false;
3886
    }
3887
3888
    /*******************************************
3889
     *
3890
     * Miscellaneous
3891
     *
3892
     *******************************************/
3893
    /**
3894
     * Prints TYPO3 Copyright notice for About Modules etc. modules.
3895
     *
3896
     * Warning:
3897
     * DO NOT prevent this notice from being shown in ANY WAY.
3898
     * According to the GPL license an interactive application must show such a notice on start-up ('If the program is interactive, make it output a short notice... ' - see GPL.txt)
3899
     * Therefore preventing this notice from being properly shown is a violation of the license, regardless of whether you remove it or use a stylesheet to obstruct the display.
3900
     *
3901
     * @return string Text/Image (HTML) for copyright notice.
3902
     * @deprecated since TYPO3 v10.2, will be removed in TYPO3 v11.0
3903
     */
3904
    public static function TYPO3_copyRightNotice()
0 ignored issues
show
Coding Style introduced by
Method name "BackendUtility::TYPO3_copyRightNotice" is not in camel caps format
Loading history...
3905
    {
3906
        trigger_error('BackendUtility::TYPO3_copyRightNotice() will be removed in TYPO3 v11.0, use the Typo3Information PHP class instead.', E_USER_DEPRECATED);
3907
        $copyrightGenerator = GeneralUtility::makeInstance(Typo3Information::class, static::getLanguageService());
3908
        return $copyrightGenerator->getCopyrightNotice();
3909
    }
3910
3911
    /**
3912
     * Creates ADMCMD parameters for the "viewpage" extension / frontend
3913
     *
3914
     * @param array $pageInfo Page record
3915
     * @param \TYPO3\CMS\Core\Context\Context $context
3916
     * @return string Query-parameters
3917
     * @internal
3918
     */
3919
    public static function ADMCMD_previewCmds($pageInfo, Context $context)
0 ignored issues
show
Coding Style introduced by
Method name "BackendUtility::ADMCMD_previewCmds" is not in camel caps format
Loading history...
3920
    {
3921
        $simUser = '';
3922
        $simTime = '';
3923
        if ($pageInfo['fe_group'] > 0) {
3924
            $simUser = '&ADMCMD_simUser=' . $pageInfo['fe_group'];
3925
        } elseif ((int)$pageInfo['fe_group'] === -2) {
3926
            // -2 means "show at any login". We simulate first available fe_group.
3927
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
3928
                ->getQueryBuilderForTable('fe_groups');
3929
            $queryBuilder->getRestrictions()
3930
                ->removeAll()
3931
                ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
3932
                ->add(GeneralUtility::makeInstance(HiddenRestriction::class));
3933
3934
            $activeFeGroupRow = $queryBuilder->select('uid')
3935
                ->from('fe_groups')
3936
                ->execute()
3937
                ->fetch();
3938
3939
            if (!empty($activeFeGroupRow)) {
3940
                $simUser = '&ADMCMD_simUser=' . $activeFeGroupRow['uid'];
3941
            }
3942
        }
3943
        $startTime = (int)$pageInfo['starttime'];
3944
        $endTime = (int)$pageInfo['endtime'];
3945
        if ($startTime > $GLOBALS['EXEC_TIME']) {
3946
            // simulate access time to ensure PageRepository will find the page and in turn PageRouter will generate
3947
            // an URL for it
3948
            $dateAspect = GeneralUtility::makeInstance(DateTimeAspect::class, new \DateTimeImmutable('@' . $startTime));
3949
            $context->setAspect('date', $dateAspect);
3950
            $simTime = '&ADMCMD_simTime=' . $startTime;
3951
        }
3952
        if ($endTime < $GLOBALS['EXEC_TIME'] && $endTime !== 0) {
3953
            // Set access time to page's endtime subtracted one second to ensure PageRepository will find the page and
3954
            // in turn PageRouter will generate an URL for it
3955
            $dateAspect = GeneralUtility::makeInstance(
3956
                DateTimeAspect::class,
3957
                new \DateTimeImmutable('@' . ($endTime - 1))
3958
            );
3959
            $context->setAspect('date', $dateAspect);
3960
            $simTime = '&ADMCMD_simTime=' . ($endTime - 1);
3961
        }
3962
        return $simUser . $simTime;
3963
    }
3964
3965
    /**
3966
     * Returns the name of the backend script relative to the TYPO3 main directory.
3967
     *
3968
     * @param string $interface Name of the backend interface  (backend, frontend) to look up the script name for. If no interface is given, the interface for the current backend user is used.
3969
     * @return string The name of the backend script relative to the TYPO3 main directory.
3970
     * @internal should only be used from within TYPO3 Core
3971
     */
3972
    public static function getBackendScript($interface = '')
3973
    {
3974
        if (!$interface) {
3975
            $interface = static::getBackendUserAuthentication()->uc['interfaceSetup'];
3976
        }
3977
        switch ($interface) {
3978
            case 'frontend':
3979
                $script = '../.';
3980
                break;
3981
            case 'backend':
3982
            default:
3983
                $script = (string)GeneralUtility::makeInstance(UriBuilder::class)->buildUriFromRoute('main');
3984
        }
3985
        return $script;
3986
    }
3987
3988
    /**
3989
     * Determines whether a table is enabled for workspaces.
3990
     *
3991
     * @param string $table Name of the table to be checked
3992
     * @return bool
3993
     */
3994
    public static function isTableWorkspaceEnabled($table)
3995
    {
3996
        return !empty($GLOBALS['TCA'][$table]['ctrl']['versioningWS']);
3997
    }
3998
3999
    /**
4000
     * Gets the TCA configuration of a field.
4001
     *
4002
     * @param string $table Name of the table
4003
     * @param string $field Name of the field
4004
     * @return array
4005
     */
4006
    public static function getTcaFieldConfiguration($table, $field)
4007
    {
4008
        $configuration = [];
4009
        if (isset($GLOBALS['TCA'][$table]['columns'][$field]['config'])) {
4010
            $configuration = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
4011
        }
4012
        return $configuration;
4013
    }
4014
4015
    /**
4016
     * Whether to ignore restrictions on a web-mount of a table.
4017
     * The regular behaviour is that records to be accessed need to be
4018
     * in a valid user's web-mount.
4019
     *
4020
     * @param string $table Name of the table
4021
     * @return bool
4022
     */
4023
    public static function isWebMountRestrictionIgnored($table)
4024
    {
4025
        return !empty($GLOBALS['TCA'][$table]['ctrl']['security']['ignoreWebMountRestriction']);
4026
    }
4027
4028
    /**
4029
     * Whether to ignore restrictions on root-level records.
4030
     * The regular behaviour is that records on the root-level (page-id 0)
4031
     * only can be accessed by admin users.
4032
     *
4033
     * @param string $table Name of the table
4034
     * @return bool
4035
     */
4036
    public static function isRootLevelRestrictionIgnored($table)
4037
    {
4038
        return !empty($GLOBALS['TCA'][$table]['ctrl']['security']['ignoreRootLevelRestriction']);
4039
    }
4040
4041
    /**
4042
     * @param string $table
4043
     * @return Connection
4044
     */
4045
    protected static function getConnectionForTable($table)
4046
    {
4047
        return GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
4048
    }
4049
4050
    /**
4051
     * @param string $table
4052
     * @return QueryBuilder
4053
     */
4054
    protected static function getQueryBuilderForTable($table)
4055
    {
4056
        return GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
4057
    }
4058
4059
    /**
4060
     * @return LoggerInterface
4061
     */
4062
    protected static function getLogger()
4063
    {
4064
        return GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__);
4065
    }
4066
4067
    /**
4068
     * @return LanguageService
4069
     */
4070
    protected static function getLanguageService()
4071
    {
4072
        return $GLOBALS['LANG'];
4073
    }
4074
4075
    /**
4076
     * @return BackendUserAuthentication
4077
     */
4078
    protected static function getBackendUserAuthentication()
4079
    {
4080
        return $GLOBALS['BE_USER'] ?? null;
4081
    }
4082
}
4083