Passed
Push — master ( d52baa...42f7fa )
by
unknown
17:28
created

BackendUtility::blindGroupNames()   B

Complexity

Conditions 7
Paths 2

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 11
nc 2
nop 3
dl 0
loc 17
rs 8.8333
c 0
b 0
f 0

1 Method

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

1145
            $parts[] = $lang->sL($GLOBALS['TCA']['pages']['columns']['shortcut']['label']) . ' ' . /** @scrutinizer ignore-type */ $label;
Loading history...
1146
        } elseif ($row['doktype'] == PageRepository::DOKTYPE_MOUNTPOINT) {
1147
            if ((int)$row['mount_pid'] > 0) {
1148
                if ($perms_clause) {
1149
                    $label = self::getRecordPath((int)$row['mount_pid'], $perms_clause, 20);
1150
                } else {
1151
                    $lRec = self::getRecordWSOL('pages', (int)$row['mount_pid'], 'title');
1152
                    $label = $lRec['title'] . ' (id=' . $row['mount_pid'] . ')';
1153
                }
1154
                $parts[] = $lang->sL($GLOBALS['TCA']['pages']['columns']['mount_pid']['label']) . ' ' . $label;
1155
                if ($row['mount_pid_ol']) {
1156
                    $parts[] = $lang->sL($GLOBALS['TCA']['pages']['columns']['mount_pid_ol']['label']);
1157
                }
1158
            } else {
1159
                $parts[] = $lang->sL('LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:no_mount_pid');
1160
            }
1161
        }
1162
        if ($row['nav_hide']) {
1163
            $parts[] = $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:pages.nav_hide');
1164
        }
1165
        if ($row['hidden']) {
1166
            $parts[] = $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.hidden');
1167
        }
1168
        if ($row['starttime']) {
1169
            $parts[] = $lang->sL($GLOBALS['TCA']['pages']['columns']['starttime']['label'])
1170
                . ' ' . self::dateTimeAge($row['starttime'], -1, 'date');
1171
        }
1172
        if ($row['endtime']) {
1173
            $parts[] = $lang->sL($GLOBALS['TCA']['pages']['columns']['endtime']['label']) . ' '
1174
                . self::dateTimeAge($row['endtime'], -1, 'date');
1175
        }
1176
        if ($row['fe_group']) {
1177
            $fe_groups = [];
1178
            foreach (GeneralUtility::intExplode(',', $row['fe_group']) as $fe_group) {
1179
                if ($fe_group < 0) {
1180
                    $fe_groups[] = $lang->sL(self::getLabelFromItemlist('pages', 'fe_group', (string)$fe_group));
1181
                } else {
1182
                    $lRec = self::getRecordWSOL('fe_groups', $fe_group, 'title');
1183
                    $fe_groups[] = $lRec['title'];
1184
                }
1185
            }
1186
            $label = implode(', ', $fe_groups);
1187
            $parts[] = $lang->sL($GLOBALS['TCA']['pages']['columns']['fe_group']['label']) . ' ' . $label;
1188
        }
1189
        $out = htmlspecialchars(implode(' - ', $parts));
1190
        return $includeAttrib ? 'title="' . $out . '"' : $out;
1191
    }
1192
1193
    /**
1194
     * Returns the combined markup for Bootstraps tooltips
1195
     *
1196
     * @param array $row
1197
     * @param string $table
1198
     * @return string
1199
     */
1200
    public static function getRecordToolTip(array $row, $table = 'pages')
1201
    {
1202
        $toolTipText = self::getRecordIconAltText($row, $table);
1203
        $toolTipCode = 'data-toggle="tooltip" data-title=" '
1204
            . str_replace(' - ', '<br>', $toolTipText)
1205
            . '" data-html="true" data-placement="right"';
1206
        return $toolTipCode;
1207
    }
1208
1209
    /**
1210
     * Returns title-attribute information for ANY record (from a table defined in TCA of course)
1211
     * 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.
1212
     * "pages" table can be used as well and will return the result of ->titleAttribForPages() for that page.
1213
     *
1214
     * @param array $row Table row; $row is a row from the table, $table
1215
     * @param string $table Table name
1216
     * @return string
1217
     */
1218
    public static function getRecordIconAltText($row, $table = 'pages')
1219
    {
1220
        if ($table === 'pages') {
1221
            $out = self::titleAttribForPages($row, '', false);
1222
        } else {
1223
            $out = !empty(trim($GLOBALS['TCA'][$table]['ctrl']['descriptionColumn'])) ? $row[$GLOBALS['TCA'][$table]['ctrl']['descriptionColumn']] . ' ' : '';
1224
            $ctrl = $GLOBALS['TCA'][$table]['ctrl']['enablecolumns'];
1225
            // Uid is added
1226
            $out .= 'id=' . $row['uid'];
1227
            if (static::isTableWorkspaceEnabled($table)) {
1228
                switch (VersionState::cast($row['t3ver_state'])) {
1229
                    case new VersionState(VersionState::DELETE_PLACEHOLDER):
1230
                        $out .= ' - Deleted element!';
1231
                        break;
1232
                    case new VersionState(VersionState::MOVE_POINTER):
1233
                        $out .= ' - NEW LOCATION (Move-to Pointer) WSID#' . $row['t3ver_wsid'];
1234
                        break;
1235
                    case new VersionState(VersionState::NEW_PLACEHOLDER):
1236
                        $out .= ' - New element!';
1237
                        break;
1238
                }
1239
            }
1240
            // Hidden
1241
            $lang = static::getLanguageService();
1242
            if ($ctrl['disabled']) {
1243
                $out .= $row[$ctrl['disabled']] ? ' - ' . $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.hidden') : '';
1244
            }
1245
            if ($ctrl['starttime']) {
1246
                if ($row[$ctrl['starttime']] > $GLOBALS['EXEC_TIME']) {
1247
                    $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') . ')';
1248
                }
1249
            }
1250
            if ($row[$ctrl['endtime']]) {
1251
                $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') . ')';
1252
            }
1253
        }
1254
        return htmlspecialchars($out);
1255
    }
1256
1257
    /**
1258
     * Returns the label of the first found entry in an "items" array from $GLOBALS['TCA'] (tablename = $table/fieldname = $col) where the value is $key
1259
     *
1260
     * @param string $table Table name, present in $GLOBALS['TCA']
1261
     * @param string $col Field name, present in $GLOBALS['TCA']
1262
     * @param string $key items-array value to match
1263
     * @return string Label for item entry
1264
     */
1265
    public static function getLabelFromItemlist($table, $col, $key)
1266
    {
1267
        // Check, if there is an "items" array:
1268
        if (is_array($GLOBALS['TCA'][$table]['columns'][$col]['config']['items'] ?? false)) {
1269
            // Traverse the items-array...
1270
            foreach ($GLOBALS['TCA'][$table]['columns'][$col]['config']['items'] as $v) {
1271
                // ... and return the first found label where the value was equal to $key
1272
                if ((string)$v[1] === (string)$key) {
1273
                    return $v[0];
1274
                }
1275
            }
1276
        }
1277
        return '';
1278
    }
1279
1280
    /**
1281
     * Return the label of a field by additionally checking TsConfig values
1282
     *
1283
     * @param int $pageId Page id
1284
     * @param string $table Table name
1285
     * @param string $column Field Name
1286
     * @param string $key item value
1287
     * @return string Label for item entry
1288
     */
1289
    public static function getLabelFromItemListMerged($pageId, $table, $column, $key)
1290
    {
1291
        $pageTsConfig = static::getPagesTSconfig($pageId);
1292
        $label = '';
1293
        if (isset($pageTsConfig['TCEFORM.'])
1294
            && \is_array($pageTsConfig['TCEFORM.'])
1295
            && \is_array($pageTsConfig['TCEFORM.'][$table . '.'])
1296
            && \is_array($pageTsConfig['TCEFORM.'][$table . '.'][$column . '.'])
1297
        ) {
1298
            if (\is_array($pageTsConfig['TCEFORM.'][$table . '.'][$column . '.']['addItems.'])
1299
                && isset($pageTsConfig['TCEFORM.'][$table . '.'][$column . '.']['addItems.'][$key])
1300
            ) {
1301
                $label = $pageTsConfig['TCEFORM.'][$table . '.'][$column . '.']['addItems.'][$key];
1302
            } elseif (\is_array($pageTsConfig['TCEFORM.'][$table . '.'][$column . '.']['altLabels.'])
1303
                && isset($pageTsConfig['TCEFORM.'][$table . '.'][$column . '.']['altLabels.'][$key])
1304
            ) {
1305
                $label = $pageTsConfig['TCEFORM.'][$table . '.'][$column . '.']['altLabels.'][$key];
1306
            }
1307
        }
1308
        if (empty($label)) {
1309
            $tcaValue = self::getLabelFromItemlist($table, $column, $key);
1310
            if (!empty($tcaValue)) {
1311
                $label = $tcaValue;
1312
            }
1313
        }
1314
        return $label;
1315
    }
1316
1317
    /**
1318
     * Splits the given key with commas and returns the list of all the localized items labels, separated by a comma.
1319
     * NOTE: this does not take itemsProcFunc into account
1320
     *
1321
     * @param string $table Table name, present in TCA
1322
     * @param string $column Field name
1323
     * @param string $keyList Key or comma-separated list of keys.
1324
     * @param array $columnTsConfig page TSConfig for $column (TCEMAIN.<table>.<column>)
1325
     * @return string Comma-separated list of localized labels
1326
     */
1327
    public static function getLabelsFromItemsList($table, $column, $keyList, array $columnTsConfig = [])
1328
    {
1329
        // Check if there is an "items" array
1330
        if (
1331
            !isset($GLOBALS['TCA'][$table]['columns'][$column]['config']['items'])
1332
            || !is_array($GLOBALS['TCA'][$table]['columns'][$column]['config']['items'])
1333
            || $keyList === ''
1334
        ) {
1335
            return '';
1336
        }
1337
1338
        $keys = GeneralUtility::trimExplode(',', $keyList, true);
1339
        $labels = [];
1340
        // Loop on all selected values
1341
        foreach ($keys as $key) {
1342
            $label = null;
1343
            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...
1344
                // Check if label has been defined or redefined via pageTsConfig
1345
                if (isset($columnTsConfig['addItems.'][$key])) {
1346
                    $label = $columnTsConfig['addItems.'][$key];
1347
                } elseif (isset($columnTsConfig['altLabels.'][$key])) {
1348
                    $label = $columnTsConfig['altLabels.'][$key];
1349
                }
1350
            }
1351
            if ($label === null) {
1352
                // Otherwise lookup the label in TCA items list
1353
                foreach ($GLOBALS['TCA'][$table]['columns'][$column]['config']['items'] as $itemConfiguration) {
1354
                    [$currentLabel, $currentKey] = $itemConfiguration;
1355
                    if ((string)$key === (string)$currentKey) {
1356
                        $label = $currentLabel;
1357
                        break;
1358
                    }
1359
                }
1360
            }
1361
            if ($label !== null) {
1362
                $labels[] = static::getLanguageService()->sL($label);
1363
            }
1364
        }
1365
        return implode(', ', $labels);
1366
    }
1367
1368
    /**
1369
     * Returns the label-value for fieldname $col in table, $table
1370
     * 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>'
1371
     *
1372
     * @param string $table Table name, present in $GLOBALS['TCA']
1373
     * @param string $col Field name
1374
     * @return string or NULL if $col is not found in the TCA table
1375
     */
1376
    public static function getItemLabel($table, $col)
1377
    {
1378
        // Check if column exists
1379
        if (is_array($GLOBALS['TCA'][$table]) && is_array($GLOBALS['TCA'][$table]['columns'][$col])) {
1380
            return $GLOBALS['TCA'][$table]['columns'][$col]['label'];
1381
        }
1382
1383
        return null;
1384
    }
1385
1386
    /**
1387
     * Returns the "title"-value in record, $row, from table, $table
1388
     * The field(s) from which the value is taken is determined by the "ctrl"-entries 'label', 'label_alt' and 'label_alt_force'
1389
     *
1390
     * @param string $table Table name, present in TCA
1391
     * @param array $row Row from table
1392
     * @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
1393
     * @param bool $forceResult If set, the function always returns an output. If no value is found for the title, '[No title]' is returned (localized).
1394
     * @return string
1395
     */
1396
    public static function getRecordTitle($table, $row, $prep = false, $forceResult = true)
1397
    {
1398
        $params = [];
1399
        $recordTitle = '';
1400
        if (isset($GLOBALS['TCA'][$table]) && is_array($GLOBALS['TCA'][$table])) {
1401
            // If configured, call userFunc
1402
            if (!empty($GLOBALS['TCA'][$table]['ctrl']['label_userFunc'])) {
1403
                $params['table'] = $table;
1404
                $params['row'] = $row;
1405
                $params['title'] = '';
1406
                $params['options'] = $GLOBALS['TCA'][$table]['ctrl']['label_userFunc_options'] ?? [];
1407
1408
                // Create NULL-reference
1409
                $null = null;
1410
                GeneralUtility::callUserFunction($GLOBALS['TCA'][$table]['ctrl']['label_userFunc'], $params, $null);
1411
                $recordTitle = $params['title'];
1412
            } else {
1413
                // No userFunc: Build label
1414
                $recordTitle = self::getProcessedValue(
1415
                    $table,
1416
                    $GLOBALS['TCA'][$table]['ctrl']['label'],
1417
                    $row[$GLOBALS['TCA'][$table]['ctrl']['label']],
1418
                    0,
1419
                    false,
1420
                    false,
1421
                    $row['uid'],
1422
                    $forceResult
1423
                ) ?? '';
1424
                if (!empty($GLOBALS['TCA'][$table]['ctrl']['label_alt'])
1425
                    && (!empty($GLOBALS['TCA'][$table]['ctrl']['label_alt_force']) || (string)$recordTitle === '')
1426
                ) {
1427
                    $altFields = GeneralUtility::trimExplode(',', $GLOBALS['TCA'][$table]['ctrl']['label_alt'], true);
1428
                    $tA = [];
1429
                    if (!empty($recordTitle)) {
1430
                        $tA[] = $recordTitle;
1431
                    }
1432
                    foreach ($altFields as $fN) {
1433
                        $recordTitle = trim(strip_tags($row[$fN]));
1434
                        if ((string)$recordTitle !== '') {
1435
                            $recordTitle = self::getProcessedValue($table, $fN, $recordTitle, 0, false, false, $row['uid']);
1436
                            if (!$GLOBALS['TCA'][$table]['ctrl']['label_alt_force']) {
1437
                                break;
1438
                            }
1439
                            $tA[] = $recordTitle;
1440
                        }
1441
                    }
1442
                    if ($GLOBALS['TCA'][$table]['ctrl']['label_alt_force']) {
1443
                        $recordTitle = implode(', ', $tA);
1444
                    }
1445
                }
1446
            }
1447
            // If the current result is empty, set it to '[No title]' (localized) and prepare for output if requested
1448
            if ($prep || $forceResult) {
1449
                if ($prep) {
1450
                    $recordTitle = self::getRecordTitlePrep($recordTitle);
1451
                }
1452
                if (trim($recordTitle) === '') {
1453
                    $recordTitle = self::getNoRecordTitle($prep);
1454
                }
1455
            }
1456
        }
1457
1458
        return $recordTitle;
1459
    }
1460
1461
    /**
1462
     * Crops a title string to a limited length and if it really was cropped, wrap it in a <span title="...">|</span>,
1463
     * which offers a tooltip with the original title when moving mouse over it.
1464
     *
1465
     * @param string $title The title string to be cropped
1466
     * @param int $titleLength Crop title after this length - if not set, BE_USER->uc['titleLen'] is used
1467
     * @return string The processed title string, wrapped in <span title="...">|</span> if cropped
1468
     */
1469
    public static function getRecordTitlePrep($title, $titleLength = 0)
1470
    {
1471
        // If $titleLength is not a valid positive integer, use BE_USER->uc['titleLen']:
1472
        if (!$titleLength || !MathUtility::canBeInterpretedAsInteger($titleLength) || $titleLength < 0) {
1473
            $titleLength = static::getBackendUserAuthentication()->uc['titleLen'];
1474
        }
1475
        $titleOrig = htmlspecialchars($title);
1476
        $title = htmlspecialchars(GeneralUtility::fixed_lgd_cs($title, $titleLength));
1477
        // If title was cropped, offer a tooltip:
1478
        if ($titleOrig != $title) {
1479
            $title = '<span title="' . $titleOrig . '">' . $title . '</span>';
1480
        }
1481
        return $title;
1482
    }
1483
1484
    /**
1485
     * Get a localized [No title] string, wrapped in <em>|</em> if $prep is TRUE.
1486
     *
1487
     * @param bool $prep Wrap result in <em>|</em>
1488
     * @return string Localized [No title] string
1489
     */
1490
    public static function getNoRecordTitle($prep = false)
1491
    {
1492
        $noTitle = '[' .
1493
            htmlspecialchars(static::getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.no_title'))
1494
            . ']';
1495
        if ($prep) {
1496
            $noTitle = '<em>' . $noTitle . '</em>';
1497
        }
1498
        return $noTitle;
1499
    }
1500
1501
    /**
1502
     * Returns a human readable output of a value from a record
1503
     * 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.
1504
     * $table/$col is tablename and fieldname
1505
     * REMEMBER to pass the output through htmlspecialchars() if you output it to the browser! (To protect it from XSS attacks and be XHTML compliant)
1506
     *
1507
     * @param string $table Table name, present in TCA
1508
     * @param string $col Field name, present in TCA
1509
     * @param string $value The value of that field from a selected record
1510
     * @param int $fixed_lgd_chars The max amount of characters the value may occupy
1511
     * @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")
1512
     * @param bool $noRecordLookup If set, no records will be looked up, UIDs are just shown.
1513
     * @param int $uid Uid of the current record
1514
     * @param bool $forceResult If BackendUtility::getRecordTitle is used to process the value, this parameter is forwarded.
1515
     * @param int $pid Optional page uid is used to evaluate page TSConfig for the given field
1516
     * @throws \InvalidArgumentException
1517
     * @return string|null
1518
     */
1519
    public static function getProcessedValue(
1520
        $table,
1521
        $col,
1522
        $value,
1523
        $fixed_lgd_chars = 0,
1524
        $defaultPassthrough = false,
1525
        $noRecordLookup = false,
1526
        $uid = 0,
1527
        $forceResult = true,
1528
        $pid = 0
1529
    ) {
1530
        if ($col === 'uid') {
1531
            // uid is not in TCA-array
1532
            return $value;
1533
        }
1534
        // Check if table and field is configured
1535
        if (!isset($GLOBALS['TCA'][$table]['columns'][$col]) || !is_array($GLOBALS['TCA'][$table]['columns'][$col])) {
1536
            return null;
1537
        }
1538
        // Depending on the fields configuration, make a meaningful output value.
1539
        $theColConf = $GLOBALS['TCA'][$table]['columns'][$col]['config'] ?? [];
1540
        /*****************
1541
         *HOOK: pre-processing the human readable output from a record
1542
         ****************/
1543
        $referenceObject = new \stdClass();
1544
        $referenceObject->table = $table;
1545
        $referenceObject->fieldName = $col;
1546
        $referenceObject->uid = $uid;
1547
        $referenceObject->value = &$value;
1548
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['preProcessValue'] ?? [] as $_funcRef) {
1549
            GeneralUtility::callUserFunction($_funcRef, $theColConf, $referenceObject);
1550
        }
1551
1552
        $l = '';
1553
        $lang = static::getLanguageService();
1554
        switch ((string)($theColConf['type'] ?? '')) {
1555
            case 'radio':
1556
                $l = self::getLabelFromItemlist($table, $col, $value);
1557
                $l = $lang->sL($l);
1558
                break;
1559
            case 'inline':
1560
            case 'select':
1561
                if (!empty($theColConf['MM'])) {
1562
                    if ($uid) {
1563
                        // Display the title of MM related records in lists
1564
                        if ($noRecordLookup) {
1565
                            $MMfields = [];
1566
                            $MMfields[] = $theColConf['foreign_table'] . '.uid';
1567
                        } else {
1568
                            $MMfields = [$theColConf['foreign_table'] . '.' . $GLOBALS['TCA'][$theColConf['foreign_table']]['ctrl']['label']];
1569
                            if (isset($GLOBALS['TCA'][$theColConf['foreign_table']]['ctrl']['label_alt'])) {
1570
                                foreach (GeneralUtility::trimExplode(
1571
                                    ',',
1572
                                    $GLOBALS['TCA'][$theColConf['foreign_table']]['ctrl']['label_alt'],
1573
                                    true
1574
                                ) as $f) {
1575
                                    $MMfields[] = $theColConf['foreign_table'] . '.' . $f;
1576
                                }
1577
                            }
1578
                        }
1579
                        /** @var RelationHandler $dbGroup */
1580
                        $dbGroup = GeneralUtility::makeInstance(RelationHandler::class);
1581
                        $dbGroup->start(
1582
                            $value,
1583
                            $theColConf['foreign_table'],
1584
                            $theColConf['MM'],
1585
                            $uid,
1586
                            $table,
1587
                            $theColConf
1588
                        );
1589
                        $selectUids = $dbGroup->tableArray[$theColConf['foreign_table']];
1590
                        if (is_array($selectUids) && !empty($selectUids)) {
1591
                            $queryBuilder = static::getQueryBuilderForTable($theColConf['foreign_table']);
1592
                            $queryBuilder->getRestrictions()
1593
                                ->removeAll()
1594
                                ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1595
1596
                            $result = $queryBuilder
1597
                                ->select('uid', ...$MMfields)
1598
                                ->from($theColConf['foreign_table'])
1599
                                ->where(
1600
                                    $queryBuilder->expr()->in(
1601
                                        'uid',
1602
                                        $queryBuilder->createNamedParameter($selectUids, Connection::PARAM_INT_ARRAY)
1603
                                    )
1604
                                )
1605
                                ->execute();
1606
1607
                            $mmlA = [];
1608
                            while ($MMrow = $result->fetch()) {
1609
                                // Keep sorting of $selectUids
1610
                                $selectedUid = array_search($MMrow['uid'], $selectUids);
1611
                                $mmlA[$selectedUid] = $MMrow['uid'];
1612
                                if (!$noRecordLookup) {
1613
                                    $mmlA[$selectedUid] = static::getRecordTitle(
1614
                                        $theColConf['foreign_table'],
1615
                                        $MMrow,
1616
                                        false,
1617
                                        $forceResult
1618
                                    );
1619
                                }
1620
                            }
1621
1622
                            if (!empty($mmlA)) {
1623
                                ksort($mmlA);
1624
                                $l = implode('; ', $mmlA);
1625
                            } else {
1626
                                $l = $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:notAvailableAbbreviation');
1627
                            }
1628
                        } else {
1629
                            $l = $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:notAvailableAbbreviation');
1630
                        }
1631
                    } else {
1632
                        $l = $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:notAvailableAbbreviation');
1633
                    }
1634
                } else {
1635
                    $columnTsConfig = [];
1636
                    if ($pid) {
1637
                        $pageTsConfig = self::getPagesTSconfig($pid);
1638
                        if (isset($pageTsConfig['TCEFORM.'][$table . '.'][$col . '.']) && is_array($pageTsConfig['TCEFORM.'][$table . '.'][$col . '.'])) {
1639
                            $columnTsConfig = $pageTsConfig['TCEFORM.'][$table . '.'][$col . '.'];
1640
                        }
1641
                    }
1642
                    $l = self::getLabelsFromItemsList($table, $col, $value, $columnTsConfig);
1643
                    if (!empty($theColConf['foreign_table']) && !$l && !empty($GLOBALS['TCA'][$theColConf['foreign_table']])) {
1644
                        if ($noRecordLookup) {
1645
                            $l = $value;
1646
                        } else {
1647
                            $rParts = [];
1648
                            if ($uid && isset($theColConf['foreign_field']) && $theColConf['foreign_field'] !== '') {
1649
                                $queryBuilder = static::getQueryBuilderForTable($theColConf['foreign_table']);
1650
                                $queryBuilder->getRestrictions()
1651
                                    ->removeAll()
1652
                                    ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1653
                                    ->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, static::getBackendUserAuthentication()->workspace));
1654
                                $constraints = [
1655
                                    $queryBuilder->expr()->eq(
1656
                                        $theColConf['foreign_field'],
1657
                                        $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
1658
                                    )
1659
                                ];
1660
1661
                                if (!empty($theColConf['foreign_table_field'])) {
1662
                                    $constraints[] = $queryBuilder->expr()->eq(
1663
                                        $theColConf['foreign_table_field'],
1664
                                        $queryBuilder->createNamedParameter($table, \PDO::PARAM_STR)
1665
                                    );
1666
                                }
1667
1668
                                // Add additional where clause if foreign_match_fields are defined
1669
                                $foreignMatchFields = [];
1670
                                if (is_array($theColConf['foreign_match_fields'])) {
1671
                                    $foreignMatchFields = $theColConf['foreign_match_fields'];
1672
                                }
1673
1674
                                foreach ($foreignMatchFields as $matchField => $matchValue) {
1675
                                    $constraints[] = $queryBuilder->expr()->eq(
1676
                                        $matchField,
1677
                                        $queryBuilder->createNamedParameter($matchValue)
1678
                                    );
1679
                                }
1680
1681
                                $result = $queryBuilder
1682
                                    ->select('*')
1683
                                    ->from($theColConf['foreign_table'])
1684
                                    ->where(...$constraints)
1685
                                    ->execute();
1686
1687
                                while ($record = $result->fetch()) {
1688
                                    $rParts[] = $record['uid'];
1689
                                }
1690
                            }
1691
                            if (empty($rParts)) {
1692
                                $rParts = GeneralUtility::trimExplode(',', $value, true);
1693
                            }
1694
                            $lA = [];
1695
                            foreach ($rParts as $rVal) {
1696
                                $rVal = (int)$rVal;
1697
                                $r = self::getRecordWSOL($theColConf['foreign_table'], $rVal);
1698
                                if (is_array($r)) {
1699
                                    $lA[] = $lang->sL($theColConf['foreign_table_prefix'])
1700
                                        . self::getRecordTitle($theColConf['foreign_table'], $r, false, $forceResult);
1701
                                } else {
1702
                                    $lA[] = $rVal ? '[' . $rVal . '!]' : '';
1703
                                }
1704
                            }
1705
                            $l = implode(', ', $lA);
1706
                        }
1707
                    }
1708
                    if (empty($l) && !empty($value)) {
1709
                        // Use plain database value when label is empty
1710
                        $l = $value;
1711
                    }
1712
                }
1713
                break;
1714
            case 'group':
1715
                // resolve the titles for DB records
1716
                if (isset($theColConf['internal_type']) && $theColConf['internal_type'] === 'db') {
1717
                    if (isset($theColConf['MM']) && $theColConf['MM']) {
1718
                        if ($uid) {
1719
                            // Display the title of MM related records in lists
1720
                            if ($noRecordLookup) {
1721
                                $MMfields = [];
1722
                                $MMfields[] = $theColConf['foreign_table'] . '.uid';
1723
                            } else {
1724
                                $MMfields = [$theColConf['foreign_table'] . '.' . $GLOBALS['TCA'][$theColConf['foreign_table']]['ctrl']['label']];
1725
                                $altLabelFields = explode(
1726
                                    ',',
1727
                                    $GLOBALS['TCA'][$theColConf['foreign_table']]['ctrl']['label_alt']
1728
                                );
1729
                                foreach ($altLabelFields as $f) {
1730
                                    $f = trim($f);
1731
                                    if ($f !== '') {
1732
                                        $MMfields[] = $theColConf['foreign_table'] . '.' . $f;
1733
                                    }
1734
                                }
1735
                            }
1736
                            /** @var RelationHandler $dbGroup */
1737
                            $dbGroup = GeneralUtility::makeInstance(RelationHandler::class);
1738
                            $dbGroup->start(
1739
                                $value,
1740
                                $theColConf['foreign_table'],
1741
                                $theColConf['MM'],
1742
                                $uid,
1743
                                $table,
1744
                                $theColConf
1745
                            );
1746
                            $selectUids = $dbGroup->tableArray[$theColConf['foreign_table']];
1747
                            if (!empty($selectUids) && is_array($selectUids)) {
1748
                                $queryBuilder = static::getQueryBuilderForTable($theColConf['foreign_table']);
1749
                                $queryBuilder->getRestrictions()
1750
                                    ->removeAll()
1751
                                    ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1752
1753
                                $result = $queryBuilder
1754
                                    ->select('uid', ...$MMfields)
1755
                                    ->from($theColConf['foreign_table'])
1756
                                    ->where(
1757
                                        $queryBuilder->expr()->in(
1758
                                            'uid',
1759
                                            $queryBuilder->createNamedParameter(
1760
                                                $selectUids,
1761
                                                Connection::PARAM_INT_ARRAY
1762
                                            )
1763
                                        )
1764
                                    )
1765
                                    ->execute();
1766
1767
                                $mmlA = [];
1768
                                while ($MMrow = $result->fetch()) {
1769
                                    // Keep sorting of $selectUids
1770
                                    $selectedUid = array_search($MMrow['uid'], $selectUids);
1771
                                    $mmlA[$selectedUid] = $MMrow['uid'];
1772
                                    if (!$noRecordLookup) {
1773
                                        $mmlA[$selectedUid] = static::getRecordTitle(
1774
                                            $theColConf['foreign_table'],
1775
                                            $MMrow,
1776
                                            false,
1777
                                            $forceResult
1778
                                        );
1779
                                    }
1780
                                }
1781
1782
                                if (!empty($mmlA)) {
1783
                                    ksort($mmlA);
1784
                                    $l = implode('; ', $mmlA);
1785
                                } else {
1786
                                    $l = $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:notAvailableAbbreviation');
1787
                                }
1788
                            } else {
1789
                                $l = $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:notAvailableAbbreviation');
1790
                            }
1791
                        } else {
1792
                            $l = $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:notAvailableAbbreviation');
1793
                        }
1794
                    } else {
1795
                        $finalValues = [];
1796
                        $relationTableName = $theColConf['allowed'];
1797
                        $explodedValues = GeneralUtility::trimExplode(',', $value, true);
1798
1799
                        foreach ($explodedValues as $explodedValue) {
1800
                            if (MathUtility::canBeInterpretedAsInteger($explodedValue)) {
1801
                                $relationTableNameForField = $relationTableName;
1802
                            } else {
1803
                                [$relationTableNameForField, $explodedValue] = self::splitTable_Uid($explodedValue);
1804
                            }
1805
1806
                            $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

1806
                            $relationRecord = static::getRecordWSOL($relationTableNameForField, /** @scrutinizer ignore-type */ $explodedValue);
Loading history...
1807
                            $finalValues[] = static::getRecordTitle($relationTableNameForField, $relationRecord);
1808
                        }
1809
                        $l = implode(', ', $finalValues);
1810
                    }
1811
                } else {
1812
                    $l = implode(', ', GeneralUtility::trimExplode(',', $value, true));
1813
                }
1814
                break;
1815
            case 'check':
1816
                if (!is_array($theColConf['items'])) {
1817
                    $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');
1818
                } elseif (count($theColConf['items']) === 1) {
1819
                    reset($theColConf['items']);
1820
                    $invertStateDisplay = current($theColConf['items'])['invertStateDisplay'] ?? false;
1821
                    if ($invertStateDisplay) {
1822
                        $value = !$value;
1823
                    }
1824
                    $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');
1825
                } else {
1826
                    $lA = [];
1827
                    foreach ($theColConf['items'] as $key => $val) {
1828
                        if ($value & 2 ** $key) {
1829
                            $lA[] = $lang->sL($val[0]);
1830
                        }
1831
                    }
1832
                    $l = implode(', ', $lA);
1833
                }
1834
                break;
1835
            case 'input':
1836
                // Hide value 0 for dates, but show it for everything else
1837
                // todo: phpstan states that $value always exists and is not nullable. At the moment, this is a false
1838
                //       positive as null can be passed into this method via $value. As soon as more strict types are
1839
                //       used, this isset check must be replaced with a more appropriate check.
1840
                if (isset($value)) {
1841
                    $dateTimeFormats = QueryHelper::getDateTimeFormats();
1842
1843
                    if (GeneralUtility::inList($theColConf['eval'] ?? '', 'date')) {
1844
                        // Handle native date field
1845
                        if (isset($theColConf['dbType']) && $theColConf['dbType'] === 'date') {
1846
                            $value = $value === $dateTimeFormats['date']['empty'] ? 0 : (int)strtotime($value);
1847
                        } else {
1848
                            $value = (int)$value;
1849
                        }
1850
                        if (!empty($value)) {
1851
                            $ageSuffix = '';
1852
                            $dateColumnConfiguration = $GLOBALS['TCA'][$table]['columns'][$col]['config'];
1853
                            $ageDisplayKey = 'disableAgeDisplay';
1854
1855
                            // generate age suffix as long as not explicitly suppressed
1856
                            if (!isset($dateColumnConfiguration[$ageDisplayKey])
1857
                                // non typesafe comparison on intention
1858
                                || $dateColumnConfiguration[$ageDisplayKey] == false
1859
                            ) {
1860
                                $ageSuffix = ' (' . ($GLOBALS['EXEC_TIME'] - $value > 0 ? '-' : '')
1861
                                    . self::calcAge(
1862
                                        (int)abs($GLOBALS['EXEC_TIME'] - $value),
1863
                                        $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.minutesHoursDaysYears')
1864
                                    )
1865
                                    . ')';
1866
                            }
1867
1868
                            $l = self::date($value) . $ageSuffix;
1869
                        }
1870
                    } elseif (GeneralUtility::inList($theColConf['eval'] ?? '', 'time')) {
1871
                        // Handle native time field
1872
                        if (isset($theColConf['dbType']) && $theColConf['dbType'] === 'time') {
1873
                            $value = $value === $dateTimeFormats['time']['empty'] ? 0 : (int)strtotime('1970-01-01 ' . $value);
1874
                        } else {
1875
                            $value = (int)$value;
1876
                        }
1877
                        if (!empty($value)) {
1878
                            $l = gmdate('H:i', (int)$value);
1879
                        }
1880
                    } elseif (GeneralUtility::inList($theColConf['eval'] ?? '', 'timesec')) {
1881
                        // Handle native time field
1882
                        if (isset($theColConf['dbType']) && $theColConf['dbType'] === 'time') {
1883
                            $value = $value === $dateTimeFormats['time']['empty'] ? 0 : (int)strtotime('1970-01-01 ' . $value);
1884
                        } else {
1885
                            $value = (int)$value;
1886
                        }
1887
                        if (!empty($value)) {
1888
                            $l = gmdate('H:i:s', (int)$value);
1889
                        }
1890
                    } elseif (GeneralUtility::inList($theColConf['eval'] ?? '', 'datetime')) {
1891
                        // Handle native datetime field
1892
                        if (isset($theColConf['dbType']) && $theColConf['dbType'] === 'datetime') {
1893
                            $value = $value === $dateTimeFormats['datetime']['empty'] ? 0 : (int)strtotime($value);
1894
                        } else {
1895
                            $value = (int)$value;
1896
                        }
1897
                        if (!empty($value)) {
1898
                            $l = self::datetime($value);
1899
                        }
1900
                    } else {
1901
                        $l = $value;
1902
                    }
1903
                }
1904
                break;
1905
            case 'flex':
1906
                $l = strip_tags($value);
1907
                break;
1908
            default:
1909
                if ($defaultPassthrough) {
1910
                    $l = $value;
1911
                } elseif (isset($theColConf['MM'])) {
1912
                    $l = $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:notAvailableAbbreviation');
1913
                } elseif ($value) {
1914
                    $l = GeneralUtility::fixed_lgd_cs(strip_tags($value), 200);
1915
                }
1916
        }
1917
        // If this field is a password field, then hide the password by changing it to a random number of asterisk (*)
1918
        if (!empty($theColConf['eval']) && stripos($theColConf['eval'], 'password') !== false) {
1919
            $l = '';
1920
            $randomNumber = random_int(5, 12);
1921
            for ($i = 0; $i < $randomNumber; $i++) {
1922
                $l .= '*';
1923
            }
1924
        }
1925
        /*****************
1926
         *HOOK: post-processing the human readable output from a record
1927
         ****************/
1928
        $null = null;
1929
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['postProcessValue'] ?? [] as $_funcRef) {
1930
            $params = [
1931
                'value' => $l,
1932
                'colConf' => $theColConf
1933
            ];
1934
            $l = GeneralUtility::callUserFunction($_funcRef, $params, $null);
1935
        }
1936
        if ($fixed_lgd_chars) {
1937
            return GeneralUtility::fixed_lgd_cs($l, $fixed_lgd_chars);
1938
        }
1939
        return $l;
1940
    }
1941
1942
    /**
1943
     * 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.
1944
     *
1945
     * @param string $table Table name, present in TCA
1946
     * @param string $fN Field name
1947
     * @param string $fV Field value
1948
     * @param int $fixed_lgd_chars The max amount of characters the value may occupy
1949
     * @param int $uid Uid of the current record
1950
     * @param bool $forceResult If BackendUtility::getRecordTitle is used to process the value, this parameter is forwarded.
1951
     * @param int $pid Optional page uid is used to evaluate page TSConfig for the given field
1952
     * @return string
1953
     * @see getProcessedValue()
1954
     */
1955
    public static function getProcessedValueExtra(
1956
        $table,
1957
        $fN,
1958
        $fV,
1959
        $fixed_lgd_chars = 0,
1960
        $uid = 0,
1961
        $forceResult = true,
1962
        $pid = 0
1963
    ) {
1964
        $fVnew = self::getProcessedValue($table, $fN, $fV, $fixed_lgd_chars, true, false, $uid, $forceResult, $pid);
1965
        if (!isset($fVnew)) {
1966
            if (is_array($GLOBALS['TCA'][$table])) {
1967
                if ($fN == $GLOBALS['TCA'][$table]['ctrl']['tstamp'] || $fN == $GLOBALS['TCA'][$table]['ctrl']['crdate']) {
1968
                    $fVnew = self::datetime((int)$fV);
1969
                } elseif ($fN === 'pid') {
1970
                    // Fetches the path with no regard to the users permissions to select pages.
1971
                    $fVnew = self::getRecordPath((int)$fV, '1=1', 20);
1972
                } else {
1973
                    $fVnew = $fV;
1974
                }
1975
            }
1976
        }
1977
        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...
1978
    }
1979
1980
    /**
1981
     * Returns fields for a table, $table, which would typically be interesting to select
1982
     * This includes uid, the fields defined for title, icon-field.
1983
     * 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)
1984
     *
1985
     * @param string $table Table name, present in $GLOBALS['TCA']
1986
     * @param string $prefix Table prefix
1987
     * @param array $fields Preset fields (must include prefix if that is used)
1988
     * @return string List of fields.
1989
     * @internal should only be used from within TYPO3 Core
1990
     */
1991
    public static function getCommonSelectFields($table, $prefix = '', $fields = [])
1992
    {
1993
        $fields[] = $prefix . 'uid';
1994
        if (isset($GLOBALS['TCA'][$table]['ctrl']['label']) && $GLOBALS['TCA'][$table]['ctrl']['label'] != '') {
1995
            $fields[] = $prefix . $GLOBALS['TCA'][$table]['ctrl']['label'];
1996
        }
1997
        if (!empty($GLOBALS['TCA'][$table]['ctrl']['label_alt'])) {
1998
            $secondFields = GeneralUtility::trimExplode(',', $GLOBALS['TCA'][$table]['ctrl']['label_alt'], true);
1999
            foreach ($secondFields as $fieldN) {
2000
                $fields[] = $prefix . $fieldN;
2001
            }
2002
        }
2003
        if (static::isTableWorkspaceEnabled($table)) {
2004
            $fields[] = $prefix . 't3ver_state';
2005
            $fields[] = $prefix . 't3ver_wsid';
2006
        }
2007
        if (!empty($GLOBALS['TCA'][$table]['ctrl']['selicon_field'])) {
2008
            $fields[] = $prefix . $GLOBALS['TCA'][$table]['ctrl']['selicon_field'];
2009
        }
2010
        if (!empty($GLOBALS['TCA'][$table]['ctrl']['typeicon_column'])) {
2011
            $fields[] = $prefix . $GLOBALS['TCA'][$table]['ctrl']['typeicon_column'];
2012
        }
2013
        if (!empty($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled'])) {
2014
            $fields[] = $prefix . $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled'];
2015
        }
2016
        if (!empty($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['starttime'])) {
2017
            $fields[] = $prefix . $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['starttime'];
2018
        }
2019
        if (!empty($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['endtime'])) {
2020
            $fields[] = $prefix . $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['endtime'];
2021
        }
2022
        if (!empty($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['fe_group'])) {
2023
            $fields[] = $prefix . $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['fe_group'];
2024
        }
2025
        return implode(',', array_unique($fields));
2026
    }
2027
2028
    /*******************************************
2029
     *
2030
     * Backend Modules API functions
2031
     *
2032
     *******************************************/
2033
2034
    /**
2035
     * Returns CSH help text (description), if configured for, as an array (title, description)
2036
     *
2037
     * @param string $table Table name
2038
     * @param string $field Field name
2039
     * @return array With keys 'description' (raw, as available in locallang), 'title' (optional), 'moreInfo'
2040
     * @internal should only be used from within TYPO3 Core
2041
     */
2042
    public static function helpTextArray($table, $field)
2043
    {
2044
        if (!isset($GLOBALS['TCA_DESCR'][$table]['columns'])) {
2045
            static::getLanguageService()->loadSingleTableDescription($table);
2046
        }
2047
        $output = [
2048
            'description' => null,
2049
            'title' => null,
2050
            'moreInfo' => false
2051
        ];
2052
        if (isset($GLOBALS['TCA_DESCR'][$table]['columns'][$field]) && is_array($GLOBALS['TCA_DESCR'][$table]['columns'][$field])) {
2053
            $data = $GLOBALS['TCA_DESCR'][$table]['columns'][$field];
2054
            // Add alternative title, if defined
2055
            if ($data['alttitle']) {
2056
                $output['title'] = $data['alttitle'];
2057
            }
2058
            // If we have more information to show and access to the cshmanual
2059
            if (($data['image_descr'] || $data['seeAlso'] || $data['details'] || $data['syntax'])
2060
                && static::getBackendUserAuthentication()->check('modules', 'help_CshmanualCshmanual')
2061
            ) {
2062
                $output['moreInfo'] = true;
2063
            }
2064
            // Add description
2065
            if ($data['description']) {
2066
                $output['description'] = $data['description'];
2067
            }
2068
        }
2069
        return $output;
2070
    }
2071
2072
    /**
2073
     * Returns CSH help text
2074
     *
2075
     * @param string $table Table name
2076
     * @param string $field Field name
2077
     * @return string HTML content for help text
2078
     * @see cshItem()
2079
     * @internal should only be used from within TYPO3 Core
2080
     */
2081
    public static function helpText($table, $field)
2082
    {
2083
        $helpTextArray = self::helpTextArray($table, $field);
2084
        $output = '';
2085
        $arrow = '';
2086
        // Put header before the rest of the text
2087
        if ($helpTextArray['title'] !== null) {
2088
            $output .= '<h2>' . $helpTextArray['title'] . '</h2>';
2089
        }
2090
        // Add see also arrow if we have more info
2091
        if ($helpTextArray['moreInfo']) {
2092
            /** @var IconFactory $iconFactory */
2093
            $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
2094
            $arrow = $iconFactory->getIcon('actions-view-go-forward', Icon::SIZE_SMALL)->render();
2095
        }
2096
        // Wrap description and arrow in p tag
2097
        if ($helpTextArray['description'] !== null || $arrow) {
2098
            $output .= '<p class="help-short">' . nl2br(htmlspecialchars($helpTextArray['description'])) . $arrow . '</p>';
2099
        }
2100
        return $output;
2101
    }
2102
2103
    /**
2104
     * API function that wraps the text / html in help text, so if a user hovers over it
2105
     * the help text will show up
2106
     *
2107
     * @param string $table The table name for which the help should be shown
2108
     * @param string $field The field name for which the help should be shown
2109
     * @param string $text The text which should be wrapped with the help text
2110
     * @param array $overloadHelpText Array with text to overload help text
2111
     * @return string the HTML code ready to render
2112
     * @internal should only be used from within TYPO3 Core
2113
     */
2114
    public static function wrapInHelp($table, $field, $text = '', array $overloadHelpText = [])
2115
    {
2116
        // Initialize some variables
2117
        $helpText = '';
2118
        $abbrClassAdd = '';
2119
        $hasHelpTextOverload = !empty($overloadHelpText);
2120
        // Get the help text that should be shown on hover
2121
        if (!$hasHelpTextOverload) {
2122
            $helpText = self::helpText($table, $field);
2123
        }
2124
        // If there's a help text or some overload information, proceed with preparing an output
2125
        if (!empty($helpText) || $hasHelpTextOverload) {
2126
            // If no text was given, just use the regular help icon
2127
            if ($text == '') {
2128
                $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
2129
                $text = $iconFactory->getIcon('actions-system-help-open', Icon::SIZE_SMALL)->render();
2130
                $abbrClassAdd = ' help-teaser-icon';
2131
            }
2132
            $text = '<abbr class="help-teaser' . $abbrClassAdd . '">' . $text . '</abbr>';
2133
            $wrappedText = '<span class="help-link" data-table="' . $table . '" data-field="' . $field . '"';
2134
            // The overload array may provide a title and a description
2135
            // If either one is defined, add them to the "data" attributes
2136
            if ($hasHelpTextOverload) {
2137
                if (isset($overloadHelpText['title'])) {
2138
                    $wrappedText .= ' data-title="' . htmlspecialchars($overloadHelpText['title']) . '"';
2139
                }
2140
                if (isset($overloadHelpText['description'])) {
2141
                    $wrappedText .= ' data-description="' . htmlspecialchars($overloadHelpText['description']) . '"';
2142
                }
2143
            }
2144
            $wrappedText .= '>' . $text . '</span>';
2145
            return $wrappedText;
2146
        }
2147
        return $text;
2148
    }
2149
2150
    /**
2151
     * API for getting CSH icons/text for use in backend modules.
2152
     * TCA_DESCR will be loaded if it isn't already
2153
     *
2154
     * @param string $table Table name ('_MOD_'+module name)
2155
     * @param string $field Field name (CSH locallang main key)
2156
     * @param string $_ (unused)
2157
     * @param string $wrap Wrap code for icon-mode, splitted by "|". Not used for full-text mode.
2158
     * @return string HTML content for help text
2159
     */
2160
    public static function cshItem($table, $field, $_ = '', $wrap = '')
2161
    {
2162
        static::getLanguageService()->loadSingleTableDescription($table);
2163
        if (is_array($GLOBALS['TCA_DESCR'][$table])
2164
            && is_array($GLOBALS['TCA_DESCR'][$table]['columns'][$field])
2165
        ) {
2166
            // Creating short description
2167
            $output = self::wrapInHelp($table, $field);
2168
            if ($output && $wrap) {
2169
                $wrParts = explode('|', $wrap);
2170
                $output = $wrParts[0] . $output . $wrParts[1];
2171
            }
2172
            return $output;
2173
        }
2174
        return '';
2175
    }
2176
2177
    /**
2178
     * Returns a JavaScript string for viewing the page id, $id
2179
     * It will re-use any window already open.
2180
     *
2181
     * @param int $pageUid Page UID
2182
     * @param string $backPath Must point back to TYPO3_mainDir (where the site is assumed to be one level above)
2183
     * @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)
2184
     * @param string $anchorSection Optional anchor to the URL
2185
     * @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!
2186
     * @param string $additionalGetVars Additional GET variables.
2187
     * @param bool $switchFocus If TRUE, then the preview window will gain the focus.
2188
     * @return string
2189
     */
2190
    public static function viewOnClick(
2191
        $pageUid,
2192
        $backPath = '',
2193
        $rootLine = null,
2194
        $anchorSection = '',
2195
        $alternativeUrl = '',
2196
        $additionalGetVars = '',
2197
        $switchFocus = true
2198
    ) {
2199
        try {
2200
            $previewUrl = self::getPreviewUrl(
2201
                $pageUid,
2202
                $backPath,
2203
                $rootLine,
2204
                $anchorSection,
2205
                $alternativeUrl,
2206
                $additionalGetVars,
2207
                $switchFocus
2208
            );
2209
        } catch (UnableToLinkToPageException $e) {
2210
            return '';
2211
        }
2212
2213
        $onclickCode = 'var previewWin = window.open(' . GeneralUtility::quoteJSvalue($previewUrl) . ',\'newTYPO3frontendWindow\');'
2214
            . ($switchFocus ? 'previewWin.focus();' : '') . LF
2215
            . 'if (previewWin.location.href === ' . GeneralUtility::quoteJSvalue($previewUrl) . ') { previewWin.location.reload(); };';
2216
2217
        return $onclickCode;
2218
    }
2219
2220
    /**
2221
     * Returns the preview url
2222
     *
2223
     * It will detect the correct domain name if needed and provide the link with the right back path.
2224
     *
2225
     * @param int $pageUid Page UID
2226
     * @param string $backPath Must point back to TYPO3_mainDir (where the site is assumed to be one level above)
2227
     * @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)
2228
     * @param string $anchorSection Optional anchor to the URL
2229
     * @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!
2230
     * @param string $additionalGetVars Additional GET variables.
2231
     * @param bool $switchFocus If TRUE, then the preview window will gain the focus.
2232
     * @return string
2233
     */
2234
    public static function getPreviewUrl(
2235
        $pageUid,
2236
        $backPath = '',
2237
        $rootLine = null,
2238
        $anchorSection = '',
2239
        $alternativeUrl = '',
2240
        $additionalGetVars = '',
2241
        &$switchFocus = true
2242
    ): string {
2243
        $viewScript = '/index.php?id=';
2244
        if ($alternativeUrl) {
2245
            $viewScript = $alternativeUrl;
2246
        }
2247
2248
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['viewOnClickClass'] ?? [] as $className) {
2249
            $hookObj = GeneralUtility::makeInstance($className);
2250
            if (method_exists($hookObj, 'preProcess')) {
2251
                $hookObj->preProcess(
2252
                    $pageUid,
2253
                    $backPath,
2254
                    $rootLine,
2255
                    $anchorSection,
2256
                    $viewScript,
2257
                    $additionalGetVars,
2258
                    $switchFocus
2259
                );
2260
            }
2261
        }
2262
2263
        // If there is an alternative URL or the URL has been modified by a hook, use that one.
2264
        if ($alternativeUrl || $viewScript !== '/index.php?id=') {
2265
            $previewUrl = $viewScript;
2266
        } else {
2267
            $permissionClause = $GLOBALS['BE_USER']->getPagePermsClause(Permission::PAGE_SHOW);
2268
            $pageInfo = self::readPageAccess($pageUid, $permissionClause) ?: [];
2269
            // prepare custom context for link generation (to allow for example time based previews)
2270
            $context = clone GeneralUtility::makeInstance(Context::class);
2271
            $additionalGetVars .= self::ADMCMD_previewCmds($pageInfo, $context);
2272
2273
            // Build the URL with a site as prefix, if configured
2274
            $siteFinder = GeneralUtility::makeInstance(SiteFinder::class);
2275
            // Check if the page (= its rootline) has a site attached, otherwise just keep the URL as is
2276
            $rootLine = $rootLine ?? BackendUtility::BEgetRootLine($pageUid);
2277
            try {
2278
                $site = $siteFinder->getSiteByPageId((int)$pageUid, $rootLine);
2279
            } catch (SiteNotFoundException $e) {
2280
                throw new UnableToLinkToPageException('The page ' . $pageUid . ' had no proper connection to a site, no link could be built.', 1559794919);
2281
            }
2282
            // Create a multi-dimensional array out of the additional get vars
2283
            $additionalQueryParams = [];
2284
            parse_str($additionalGetVars, $additionalQueryParams);
2285
            if (isset($additionalQueryParams['L'])) {
2286
                $additionalQueryParams['_language'] = $additionalQueryParams['_language'] ?? $additionalQueryParams['L'];
2287
                unset($additionalQueryParams['L']);
2288
            }
2289
            try {
2290
                $previewUrl = (string)$site->getRouter($context)->generateUri(
2291
                    $pageUid,
2292
                    $additionalQueryParams,
2293
                    $anchorSection,
2294
                    RouterInterface::ABSOLUTE_URL
2295
                );
2296
            } catch (\InvalidArgumentException | InvalidRouteArgumentsException $e) {
2297
                throw new UnableToLinkToPageException('The page ' . $pageUid . ' had no proper connection to a site, no link could be built.', 1559794914);
2298
            }
2299
        }
2300
2301
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['viewOnClickClass'] ?? [] as $className) {
2302
            $hookObj = GeneralUtility::makeInstance($className);
2303
            if (method_exists($hookObj, 'postProcess')) {
2304
                $previewUrl = $hookObj->postProcess(
2305
                    $previewUrl,
2306
                    $pageUid,
2307
                    $rootLine,
2308
                    $anchorSection,
2309
                    $viewScript,
2310
                    $additionalGetVars,
2311
                    $switchFocus
2312
                );
2313
            }
2314
        }
2315
2316
        return $previewUrl;
2317
    }
2318
2319
    /**
2320
     * Makes click menu link (context sensitive menu)
2321
     *
2322
     * Returns $str wrapped in a link which will activate the context sensitive
2323
     * menu for the record ($table/$uid) or file ($table = file)
2324
     * The link will load the top frame with the parameter "&item" which is the table, uid
2325
     * and context arguments imploded by "|": rawurlencode($table.'|'.$uid.'|'.$context)
2326
     *
2327
     * @param string $content String to be wrapped in link, typ. image tag.
2328
     * @param string $table Table name/File path. If the icon is for a database
2329
     * record, enter the tablename from $GLOBALS['TCA']. If a file then enter
2330
     * the absolute filepath
2331
     * @param int|string $uid If icon is for database record this is the UID for the
2332
     * record from $table or identifier for sys_file record
2333
     * @param string $context Set tree if menu is called from tree view
2334
     * @param string $_addParams NOT IN USE Deprecated since TYPO3 11, will be removed in TYPO3 12.
2335
     * @param string $_enDisItems NOT IN USE Deprecated since TYPO3 11, will be removed in TYPO3 12.
2336
     * @param bool $returnTagParameters If set, will return only the onclick
2337
     * JavaScript, not the whole link. Deprecated since TYPO3 11, will be removed in TYPO3 12.
2338
     *
2339
     * @return string|array The link wrapped input string.
2340
     */
2341
    public static function wrapClickMenuOnIcon(
2342
        $content,
2343
        $table,
2344
        $uid = 0,
2345
        $context = '',
2346
        $_addParams = '',
2347
        $_enDisItems = '',
2348
        $returnTagParameters = false
2349
    ) {
2350
        $tagParameters = self::getClickMenuOnIconTagParameters((string)$table, $uid, (string)$context);
2351
2352
        if ($_addParams !== '') {
2353
            trigger_error('Calling BackendUtility::wrapClickMenuOnIcon() with unused 5th parameter is deprecated and will be removed in v12.', E_USER_DEPRECATED);
2354
        }
2355
        if ($_enDisItems !== '') {
2356
            trigger_error('Calling BackendUtility::wrapClickMenuOnIcon() with unused 6th parameter is deprecated and will be removed in v12.', E_USER_DEPRECATED);
2357
        }
2358
        if ($returnTagParameters) {
2359
            trigger_error('Calling BackendUtility::wrapClickMenuOnIcon() with 7th parameter set to true is deprecated and will be removed in v12. Please use BackendUtility::getClickMenuOnIconTagParameters() instead.', E_USER_DEPRECATED);
2360
            return $tagParameters;
2361
        }
2362
        return '<a href="#" ' . GeneralUtility::implodeAttributes($tagParameters, true) . '>' . $content . '</a>';
2363
    }
2364
2365
    /**
2366
     * @param string $table Table name/File path. If the icon is for a database
2367
     * record, enter the tablename from $GLOBALS['TCA']. If a file then enter
2368
     * the absolute filepath
2369
     * @param int|string $uid If icon is for database record this is the UID for the
2370
     * record from $table or identifier for sys_file record
2371
     * @param string $context Set tree if menu is called from tree view
2372
     * @return array
2373
     */
2374
    public static function getClickMenuOnIconTagParameters(string $table, $uid = 0, string $context = ''): array
2375
    {
2376
        return [
2377
            'class' => 't3js-contextmenutrigger',
2378
            'data-table' => $table,
2379
            'data-uid' => (string)$uid,
2380
            'data-context' => $context
2381
        ];
2382
    }
2383
2384
    /**
2385
     * Returns a URL with a command to TYPO3 Datahandler
2386
     *
2387
     * @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
2388
     * @param string $redirectUrl Redirect URL, default is to use GeneralUtility::getIndpEnv('REQUEST_URI')
2389
     * @return string
2390
     */
2391
    public static function getLinkToDataHandlerAction($parameters, $redirectUrl = '')
2392
    {
2393
        $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
2394
        $url = (string)$uriBuilder->buildUriFromRoute('tce_db') . $parameters . '&redirect=';
2395
        $url .= rawurlencode($redirectUrl ?: GeneralUtility::getIndpEnv('REQUEST_URI'));
2396
        return $url;
2397
    }
2398
2399
    /**
2400
     * Returns a selector box "function menu" for a module
2401
     * See Inside TYPO3 for details about how to use / make Function menus
2402
     *
2403
     * @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=...
2404
     * @param string $elementName The form elements name, probably something like "SET[...]
2405
     * @param string $currentValue The value to be selected currently.
2406
     * @param array $menuItems An array with the menu items for the selector box
2407
     * @param string $script The script to send the &id to, if empty it's automatically found
2408
     * @param string $addParams Additional parameters to pass to the script.
2409
     * @return string HTML code for selector box
2410
     */
2411
    public static function getFuncMenu(
2412
        $mainParams,
2413
        $elementName,
2414
        $currentValue,
2415
        $menuItems,
2416
        $script = '',
2417
        $addParams = ''
2418
    ) {
2419
        if (!is_array($menuItems) || count($menuItems) <= 1) {
0 ignored issues
show
introduced by
The condition is_array($menuItems) is always true.
Loading history...
2420
            return '';
2421
        }
2422
        $scriptUrl = self::buildScriptUrl($mainParams, $addParams, $script);
2423
        $options = [];
2424
        foreach ($menuItems as $value => $label) {
2425
            $options[] = '<option value="'
2426
                . htmlspecialchars($value) . '"'
2427
                . ((string)$currentValue === (string)$value ? ' selected="selected"' : '') . '>'
2428
                . htmlspecialchars($label, ENT_COMPAT, 'UTF-8', false) . '</option>';
2429
        }
2430
        $dataMenuIdentifier = str_replace(['SET[', ']'], '', $elementName);
2431
        $dataMenuIdentifier = GeneralUtility::camelCaseToLowerCaseUnderscored($dataMenuIdentifier);
2432
        $dataMenuIdentifier = str_replace('_', '-', $dataMenuIdentifier);
2433
        if (!empty($options)) {
2434
            // relies on module 'TYPO3/CMS/Backend/ActionDispatcher'
2435
            $attributes = GeneralUtility::implodeAttributes([
2436
                'name' => $elementName,
2437
                'class' => 'form-control',
2438
                'data-menu-identifier' => $dataMenuIdentifier,
2439
                'data-global-event' => 'change',
2440
                'data-action-navigate' => '$data=~s/$value/',
2441
                'data-navigate-value' => $scriptUrl . '&' . $elementName . '=${value}',
2442
            ], true);
2443
            return sprintf(
2444
                '<select %s>%s</select>',
2445
                $attributes,
2446
                implode('', $options)
2447
            );
2448
        }
2449
        return '';
2450
    }
2451
2452
    /**
2453
     * Returns a selector box to switch the view
2454
     * Based on BackendUtility::getFuncMenu() but done as new function because it has another purpose.
2455
     * Mingling with getFuncMenu would harm the docHeader Menu.
2456
     *
2457
     * @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=...
2458
     * @param string $elementName The form elements name, probably something like "SET[...]
2459
     * @param string $currentValue The value to be selected currently.
2460
     * @param array $menuItems An array with the menu items for the selector box
2461
     * @param string $script The script to send the &id to, if empty it's automatically found
2462
     * @param string $addParams Additional parameters to pass to the script.
2463
     * @return string HTML code for selector box
2464
     */
2465
    public static function getDropdownMenu(
2466
        $mainParams,
2467
        $elementName,
2468
        $currentValue,
2469
        $menuItems,
2470
        $script = '',
2471
        $addParams = ''
2472
    ) {
2473
        if (!is_array($menuItems) || count($menuItems) <= 1) {
0 ignored issues
show
introduced by
The condition is_array($menuItems) is always true.
Loading history...
2474
            return '';
2475
        }
2476
        $scriptUrl = self::buildScriptUrl($mainParams, $addParams, $script);
2477
        $options = [];
2478
        foreach ($menuItems as $value => $label) {
2479
            $options[] = '<option value="'
2480
                . htmlspecialchars($value) . '"'
2481
                . ((string)$currentValue === (string)$value ? ' selected="selected"' : '') . '>'
2482
                . htmlspecialchars($label, ENT_COMPAT, 'UTF-8', false) . '</option>';
2483
        }
2484
        $dataMenuIdentifier = str_replace(['SET[', ']'], '', $elementName);
2485
        $dataMenuIdentifier = GeneralUtility::camelCaseToLowerCaseUnderscored($dataMenuIdentifier);
2486
        $dataMenuIdentifier = str_replace('_', '-', $dataMenuIdentifier);
2487
        if (!empty($options)) {
2488
            // relies on module 'TYPO3/CMS/Backend/ActionDispatcher'
2489
            $attributes = GeneralUtility::implodeAttributes([
2490
                'name' => $elementName,
2491
                'data-menu-identifier' => $dataMenuIdentifier,
2492
                'data-global-event' => 'change',
2493
                'data-action-navigate' => '$data=~s/$value/',
2494
                'data-navigate-value' => $scriptUrl . '&' . $elementName . '=${value}',
2495
            ], true);
2496
            return '
2497
			<div class="form-group">
2498
				<!-- Function Menu of module -->
2499
				<select class="form-control input-sm" ' . $attributes . '>
2500
					' . implode(LF, $options) . '
2501
				</select>
2502
			</div>
2503
						';
2504
        }
2505
        return '';
2506
    }
2507
2508
    /**
2509
     * Checkbox function menu.
2510
     * Works like ->getFuncMenu() but takes no $menuItem array since this is a simple checkbox.
2511
     *
2512
     * @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=...
2513
     * @param string $elementName The form elements name, probably something like "SET[...]
2514
     * @param string $currentValue The value to be selected currently.
2515
     * @param string $script The script to send the &id to, if empty it's automatically found
2516
     * @param string $addParams Additional parameters to pass to the script.
2517
     * @param string $tagParams Additional attributes for the checkbox input tag
2518
     * @return string HTML code for checkbox
2519
     * @see getFuncMenu()
2520
     */
2521
    public static function getFuncCheck(
2522
        $mainParams,
2523
        $elementName,
2524
        $currentValue,
2525
        $script = '',
2526
        $addParams = '',
2527
        $tagParams = ''
2528
    ) {
2529
        // relies on module 'TYPO3/CMS/Backend/ActionDispatcher'
2530
        $scriptUrl = self::buildScriptUrl($mainParams, $addParams, $script);
2531
        $attributes = GeneralUtility::implodeAttributes([
2532
            'type' => 'checkbox',
2533
            'class' => 'checkbox',
2534
            'name' => $elementName,
2535
            'value' => '1',
2536
            'data-global-event' => 'change',
2537
            'data-action-navigate' => '$data=~s/$value/',
2538
            'data-navigate-value' => sprintf('%s&%s=${value}', $scriptUrl, $elementName),
2539
            'data-empty-value' => '0',
2540
        ], true);
2541
        return
2542
            '<input ' . $attributes .
2543
            ($currentValue ? ' checked="checked"' : '') .
2544
            ($tagParams ? ' ' . $tagParams : '') .
2545
            ' />';
2546
    }
2547
2548
    /**
2549
     * Input field function menu
2550
     * Works like ->getFuncMenu() / ->getFuncCheck() but displays an input field instead which updates the script "onchange"
2551
     *
2552
     * @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=...
2553
     * @param string $elementName The form elements name, probably something like "SET[...]
2554
     * @param string $currentValue The value to be selected currently.
2555
     * @param int $size Relative size of input field, max is 48
2556
     * @param string $script The script to send the &id to, if empty it's automatically found
2557
     * @param string $addParams Additional parameters to pass to the script.
2558
     * @return string HTML code for input text field.
2559
     * @see getFuncMenu()
2560
     */
2561
    public static function getFuncInput(
2562
        $mainParams,
2563
        $elementName,
2564
        $currentValue,
2565
        $size = 10,
2566
        $script = '',
2567
        $addParams = ''
2568
    ) {
2569
        $scriptUrl = self::buildScriptUrl($mainParams, $addParams, $script);
2570
        $onChange = 'window.location.href = ' . GeneralUtility::quoteJSvalue($scriptUrl . '&' . $elementName . '=') . '+escape(this.value);';
2571
        return '<input type="text" class="form-control" name="' . $elementName . '" value="' . htmlspecialchars($currentValue) . '" onchange="' . htmlspecialchars($onChange) . '" />';
2572
    }
2573
2574
    /**
2575
     * Builds the URL to the current script with given arguments
2576
     *
2577
     * @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=...
2578
     * @param string $addParams Additional parameters to pass to the script.
2579
     * @param string $script The script to send the &id to, if empty it's automatically found
2580
     * @return string The complete script URL
2581
     */
2582
    protected static function buildScriptUrl($mainParams, $addParams, $script = '')
2583
    {
2584
        if (!is_array($mainParams)) {
2585
            $mainParams = ['id' => $mainParams];
2586
        }
2587
        if (!$script) {
2588
            $script = PathUtility::basename(Environment::getCurrentScript());
2589
        }
2590
2591
        if ($routePath = GeneralUtility::_GP('route')) {
2592
            $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
2593
            $scriptUrl = (string)$uriBuilder->buildUriFromRoutePath($routePath, $mainParams);
2594
            $scriptUrl .= $addParams;
2595
        } else {
2596
            $scriptUrl = $script . HttpUtility::buildQueryString($mainParams, '?') . $addParams;
2597
        }
2598
2599
        return $scriptUrl;
2600
    }
2601
2602
    /**
2603
     * Call to update the page tree frame (or something else..?) after
2604
     * use 'updatePageTree' as a first parameter will set the page tree to be updated.
2605
     *
2606
     * @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.
2607
     * @param mixed $params Additional information for the update signal, used to only refresh a branch of the tree
2608
     * @see BackendUtility::getUpdateSignalCode()
2609
     */
2610
    public static function setUpdateSignal($set = '', $params = '')
2611
    {
2612
        $beUser = static::getBackendUserAuthentication();
2613
        $modData = $beUser->getModuleData(
2614
            \TYPO3\CMS\Backend\Utility\BackendUtility::class . '::getUpdateSignal',
2615
            'ses'
2616
        );
2617
        if ($set) {
2618
            $modData[$set] = [
2619
                'set' => $set,
2620
                'parameter' => $params
2621
            ];
2622
        } else {
2623
            // clear the module data
2624
            $modData = [];
2625
        }
2626
        $beUser->pushModuleData(\TYPO3\CMS\Backend\Utility\BackendUtility::class . '::getUpdateSignal', $modData);
2627
    }
2628
2629
    /**
2630
     * Call to update the page tree frame (or something else..?) if this is set by the function
2631
     * setUpdateSignal(). It will return some JavaScript that does the update
2632
     *
2633
     * @return string HTML javascript code
2634
     * @see BackendUtility::setUpdateSignal()
2635
     */
2636
    public static function getUpdateSignalCode()
2637
    {
2638
        $signals = [];
2639
        $modData = static::getBackendUserAuthentication()->getModuleData(
2640
            \TYPO3\CMS\Backend\Utility\BackendUtility::class . '::getUpdateSignal',
2641
            'ses'
2642
        );
2643
        if (empty($modData)) {
2644
            return '';
2645
        }
2646
        // Hook: Allows to let TYPO3 execute your JS code
2647
        $updateSignals = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['updateSignalHook'] ?? [];
2648
        // Loop through all setUpdateSignals and get the JS code
2649
        foreach ($modData as $set => $val) {
2650
            if (isset($updateSignals[$set])) {
2651
                $params = ['set' => $set, 'parameter' => $val['parameter'], 'JScode' => ''];
2652
                $ref = null;
2653
                GeneralUtility::callUserFunction($updateSignals[$set], $params, $ref);
2654
                $signals[] = $params['JScode'];
2655
            } else {
2656
                switch ($set) {
2657
                    case 'updatePageTree':
2658
                        $signals[] = '
2659
								if (top && top.TYPO3.Backend && top.TYPO3.Backend.NavigationContainer.PageTree) {
2660
									top.TYPO3.Backend.NavigationContainer.PageTree.refreshTree();
2661
								}
2662
							';
2663
                        break;
2664
                    case 'updateFolderTree':
2665
                        $signals[] = '
2666
								if (top && top.nav_frame && top.nav_frame.location) {
2667
									top.nav_frame.location.reload(true);
2668
								}';
2669
                        break;
2670
                    case 'updateModuleMenu':
2671
                        $signals[] = '
2672
								if (top && top.TYPO3.ModuleMenu && top.TYPO3.ModuleMenu.App) {
2673
									top.TYPO3.ModuleMenu.App.refreshMenu();
2674
								}';
2675
                        break;
2676
                    case 'updateTopbar':
2677
                        $signals[] = '
2678
								if (top && top.TYPO3.Backend && top.TYPO3.Backend.Topbar) {
2679
									top.TYPO3.Backend.Topbar.refresh();
2680
								}';
2681
                        break;
2682
                }
2683
            }
2684
        }
2685
        $content = implode(LF, $signals);
2686
        // For backwards compatibility, should be replaced
2687
        self::setUpdateSignal();
2688
        return $content;
2689
    }
2690
2691
    /**
2692
     * Returns an array which is most backend modules becomes MOD_SETTINGS containing values from function menus etc. determining the function of the module.
2693
     * This is kind of session variable management framework for the backend users.
2694
     * 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
2695
     * Ultimately, see Inside TYPO3 for how to use this function in relation to your modules.
2696
     *
2697
     * @param array $MOD_MENU MOD_MENU is an array that defines the options in menus.
2698
     * @param array $CHANGED_SETTINGS CHANGED_SETTINGS represents the array used when passing values to the script from the menus.
2699
     * @param string $modName modName is the name of this module. Used to get the correct module data.
2700
     * @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.
2701
     * @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.
2702
     * @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)
2703
     * @throws \RuntimeException
2704
     * @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
2705
     */
2706
    public static function getModuleData(
2707
        $MOD_MENU,
2708
        $CHANGED_SETTINGS,
2709
        $modName,
2710
        $type = '',
2711
        $dontValidateList = '',
2712
        $setDefaultList = ''
2713
    ) {
2714
        if ($modName && is_string($modName)) {
2715
            // Getting stored user-data from this module:
2716
            $beUser = static::getBackendUserAuthentication();
2717
            $settings = $beUser->getModuleData($modName, $type);
2718
            $changed = 0;
2719
            if (!is_array($settings)) {
2720
                $changed = 1;
2721
                $settings = [];
2722
            }
2723
            if (is_array($MOD_MENU)) {
0 ignored issues
show
introduced by
The condition is_array($MOD_MENU) is always true.
Loading history...
2724
                foreach ($MOD_MENU as $key => $var) {
2725
                    // If a global var is set before entering here. eg if submitted, then it's substituting the current value the array.
2726
                    if (is_array($CHANGED_SETTINGS) && isset($CHANGED_SETTINGS[$key])) {
2727
                        if (is_array($CHANGED_SETTINGS[$key])) {
2728
                            $serializedSettings = serialize($CHANGED_SETTINGS[$key]);
2729
                            if ((string)$settings[$key] !== $serializedSettings) {
2730
                                $settings[$key] = $serializedSettings;
2731
                                $changed = 1;
2732
                            }
2733
                        } else {
2734
                            if ((string)$settings[$key] !== (string)$CHANGED_SETTINGS[$key]) {
2735
                                $settings[$key] = $CHANGED_SETTINGS[$key];
2736
                                $changed = 1;
2737
                            }
2738
                        }
2739
                    }
2740
                    // If the $var is an array, which denotes the existence of a menu, we check if the value is permitted
2741
                    if (is_array($var) && (!$dontValidateList || !GeneralUtility::inList($dontValidateList, $key))) {
2742
                        // If the setting is an array or not present in the menu-array, MOD_MENU, then the default value is inserted.
2743
                        if (is_array($settings[$key]) || !isset($MOD_MENU[$key][$settings[$key]])) {
2744
                            $settings[$key] = (string)key($var);
2745
                            $changed = 1;
2746
                        }
2747
                    }
2748
                    // Sets default values (only strings/checkboxes, not menus)
2749
                    if ($setDefaultList && !is_array($var)) {
2750
                        if (GeneralUtility::inList($setDefaultList, $key) && !isset($settings[$key])) {
2751
                            $settings[$key] = (string)$var;
2752
                        }
2753
                    }
2754
                }
2755
            } else {
2756
                throw new \RuntimeException('No menu', 1568119229);
2757
            }
2758
            if ($changed) {
2759
                $beUser->pushModuleData($modName, $settings);
2760
            }
2761
            return $settings;
2762
        }
2763
        throw new \RuntimeException('Wrong module name "' . $modName . '"', 1568119221);
2764
    }
2765
2766
    /*******************************************
2767
     *
2768
     * Core
2769
     *
2770
     *******************************************/
2771
    /**
2772
     * Unlock or Lock a record from $table with $uid
2773
     * If $table and $uid is not set, then all locking for the current BE_USER is removed!
2774
     *
2775
     * @param string $table Table name
2776
     * @param int $uid Record uid
2777
     * @param int $pid Record pid
2778
     * @internal
2779
     */
2780
    public static function lockRecords($table = '', $uid = 0, $pid = 0)
2781
    {
2782
        $beUser = static::getBackendUserAuthentication();
2783
        if (isset($beUser->user['uid'])) {
2784
            $userId = (int)$beUser->user['uid'];
2785
            if ($table && $uid) {
2786
                $fieldsValues = [
2787
                    'userid' => $userId,
2788
                    'feuserid' => 0,
2789
                    'tstamp' => $GLOBALS['EXEC_TIME'],
2790
                    'record_table' => $table,
2791
                    'record_uid' => $uid,
2792
                    'username' => $beUser->user['username'],
2793
                    'record_pid' => $pid
2794
                ];
2795
                GeneralUtility::makeInstance(ConnectionPool::class)
2796
                    ->getConnectionForTable('sys_lockedrecords')
2797
                    ->insert(
2798
                        'sys_lockedrecords',
2799
                        $fieldsValues
2800
                    );
2801
            } else {
2802
                GeneralUtility::makeInstance(ConnectionPool::class)
2803
                    ->getConnectionForTable('sys_lockedrecords')
2804
                    ->delete(
2805
                        'sys_lockedrecords',
2806
                        ['userid' => (int)$userId]
2807
                    );
2808
            }
2809
        }
2810
    }
2811
2812
    /**
2813
     * Returns information about whether the record from table, $table, with uid, $uid is currently locked
2814
     * (edited by another user - which should issue a warning).
2815
     * Notice: Locking is not strictly carried out since locking is abandoned when other backend scripts
2816
     * are activated - which means that a user CAN have a record "open" without having it locked.
2817
     * So this just serves as a warning that counts well in 90% of the cases, which should be sufficient.
2818
     *
2819
     * @param string $table Table name
2820
     * @param int $uid Record uid
2821
     * @return array|bool
2822
     * @internal
2823
     */
2824
    public static function isRecordLocked($table, $uid)
2825
    {
2826
        $runtimeCache = self::getRuntimeCache();
2827
        $cacheId = 'backend-recordLocked';
2828
        $recordLockedCache = $runtimeCache->get($cacheId);
2829
        if ($recordLockedCache !== false) {
2830
            $lockedRecords = $recordLockedCache;
2831
        } else {
2832
            $lockedRecords = [];
2833
2834
            $queryBuilder = static::getQueryBuilderForTable('sys_lockedrecords');
2835
            $result = $queryBuilder
2836
                ->select('*')
2837
                ->from('sys_lockedrecords')
2838
                ->where(
2839
                    $queryBuilder->expr()->neq(
2840
                        'sys_lockedrecords.userid',
2841
                        $queryBuilder->createNamedParameter(
2842
                            static::getBackendUserAuthentication()->user['uid'],
2843
                            \PDO::PARAM_INT
2844
                        )
2845
                    ),
2846
                    $queryBuilder->expr()->gt(
2847
                        'sys_lockedrecords.tstamp',
2848
                        $queryBuilder->createNamedParameter(
2849
                            $GLOBALS['EXEC_TIME'] - 2 * 3600,
2850
                            \PDO::PARAM_INT
2851
                        )
2852
                    )
2853
                )
2854
                ->execute();
2855
2856
            $lang = static::getLanguageService();
2857
            while ($row = $result->fetch()) {
2858
                // Get the type of the user that locked this record:
2859
                if ($row['userid']) {
2860
                    $userTypeLabel = 'beUser';
2861
                } elseif ($row['feuserid']) {
2862
                    $userTypeLabel = 'feUser';
2863
                } else {
2864
                    $userTypeLabel = 'user';
2865
                }
2866
                $userType = $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.' . $userTypeLabel);
2867
                // Get the username (if available):
2868
                if ($row['username']) {
2869
                    $userName = $row['username'];
2870
                } else {
2871
                    $userName = $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.unknownUser');
2872
                }
2873
                $lockedRecords[$row['record_table'] . ':' . $row['record_uid']] = $row;
2874
                $lockedRecords[$row['record_table'] . ':' . $row['record_uid']]['msg'] = sprintf(
2875
                    $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.lockedRecordUser'),
2876
                    $userType,
2877
                    $userName,
2878
                    self::calcAge(
2879
                        $GLOBALS['EXEC_TIME'] - $row['tstamp'],
2880
                        $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.minutesHoursDaysYears')
2881
                    )
2882
                );
2883
                if ($row['record_pid'] && !isset($lockedRecords[$row['record_table'] . ':' . $row['record_pid']])) {
2884
                    $lockedRecords['pages:' . $row['record_pid']]['msg'] = sprintf(
2885
                        $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.lockedRecordUser_content'),
2886
                        $userType,
2887
                        $userName,
2888
                        self::calcAge(
2889
                            $GLOBALS['EXEC_TIME'] - $row['tstamp'],
2890
                            $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.minutesHoursDaysYears')
2891
                        )
2892
                    );
2893
                }
2894
            }
2895
            $runtimeCache->set($cacheId, $lockedRecords);
2896
        }
2897
2898
        return $lockedRecords[$table . ':' . $uid] ?? false;
2899
    }
2900
2901
    /**
2902
     * Returns TSConfig for the TCEFORM object in Page TSconfig.
2903
     * Used in TCEFORMs
2904
     *
2905
     * @param string $table Table name present in TCA
2906
     * @param array $row Row from table
2907
     * @return array
2908
     */
2909
    public static function getTCEFORM_TSconfig($table, $row)
2910
    {
2911
        $res = [];
2912
        // Get main config for the table
2913
        [$TScID, $cPid] = self::getTSCpid($table, $row['uid'], $row['pid']);
2914
        if ($TScID >= 0) {
2915
            $tsConfig = static::getPagesTSconfig($TScID)['TCEFORM.'][$table . '.'] ?? [];
2916
            $typeVal = self::getTCAtypeValue($table, $row);
2917
            foreach ($tsConfig as $key => $val) {
2918
                if (is_array($val)) {
2919
                    $fieldN = substr($key, 0, -1);
2920
                    $res[$fieldN] = $val;
2921
                    unset($res[$fieldN]['types.']);
2922
                    if ((string)$typeVal !== '' && is_array($val['types.'][$typeVal . '.'])) {
2923
                        ArrayUtility::mergeRecursiveWithOverrule($res[$fieldN], $val['types.'][$typeVal . '.']);
2924
                    }
2925
                }
2926
            }
2927
        }
2928
        $res['_CURRENT_PID'] = $cPid;
2929
        $res['_THIS_UID'] = $row['uid'];
2930
        // So the row will be passed to foreign_table_where_query()
2931
        $res['_THIS_ROW'] = $row;
2932
        return $res;
2933
    }
2934
2935
    /**
2936
     * Find the real PID of the record (with $uid from $table).
2937
     * 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).
2938
     *
2939
     * @param string $table Table name
2940
     * @param int $uid Record uid
2941
     * @param int|string $pid Record pid, could be negative then pointing to a record from same table whose pid to find and return
2942
     * @return int
2943
     * @internal
2944
     * @see \TYPO3\CMS\Core\DataHandling\DataHandler::copyRecord()
2945
     * @see \TYPO3\CMS\Backend\Utility\BackendUtility::getTSCpid()
2946
     */
2947
    public static function getTSconfig_pidValue($table, $uid, $pid)
2948
    {
2949
        // If pid is an integer this takes precedence in our lookup.
2950
        if (MathUtility::canBeInterpretedAsInteger($pid)) {
2951
            $thePidValue = (int)$pid;
2952
            // If ref to another record, look that record up.
2953
            if ($thePidValue < 0) {
2954
                $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

2954
                $pidRec = self::getRecord($table, /** @scrutinizer ignore-type */ abs($thePidValue), 'pid');
Loading history...
2955
                $thePidValue = is_array($pidRec) ? $pidRec['pid'] : -2;
2956
            }
2957
        } else {
2958
            // Try to fetch the record pid from uid. If the uid is 'NEW...' then this will of course return nothing
2959
            $rr = self::getRecord($table, $uid);
2960
            $thePidValue = null;
2961
            if (is_array($rr)) {
2962
                // First check if the pid is -1 which means it is a workspaced element. Get the "real" record:
2963
                if ($rr['pid'] == '-1') {
2964
                    $rr = self::getRecord($table, $rr['t3ver_oid'], 'pid');
2965
                    if (is_array($rr)) {
2966
                        $thePidValue = $rr['pid'];
2967
                    }
2968
                } else {
2969
                    // Returning the "pid" of the record
2970
                    $thePidValue = $rr['pid'];
2971
                }
2972
            }
2973
            if (!$thePidValue) {
2974
                // Returns -1 if the record with this pid was not found.
2975
                $thePidValue = -1;
2976
            }
2977
        }
2978
        return $thePidValue;
2979
    }
2980
2981
    /**
2982
     * Return the real pid of a record and caches the result.
2983
     * The non-cached method needs database queries to do the job, so this method
2984
     * can be used if code sometimes calls the same record multiple times to save
2985
     * some queries. This should not be done if the calling code may change the
2986
     * same record meanwhile.
2987
     *
2988
     * @param string $table Tablename
2989
     * @param string $uid UID value
2990
     * @param string $pid PID value
2991
     * @return array Array of two integers; first is the real PID of a record, second is the PID value for TSconfig.
2992
     */
2993
    public static function getTSCpidCached($table, $uid, $pid)
2994
    {
2995
        $runtimeCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('runtime');
2996
        $firstLevelCache = $runtimeCache->get('backendUtilityTscPidCached') ?: [];
2997
        $key = $table . ':' . $uid . ':' . $pid;
2998
        if (!isset($firstLevelCache[$key])) {
2999
            $firstLevelCache[$key] = static::getTSCpid($table, (int)$uid, (int)$pid);
3000
            $runtimeCache->set('backendUtilityTscPidCached', $firstLevelCache);
3001
        }
3002
        return $firstLevelCache[$key];
3003
    }
3004
3005
    /**
3006
     * 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.
3007
     *
3008
     * @param string $table Table name
3009
     * @param int $uid Record uid
3010
     * @param int|string $pid Record pid
3011
     * @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,
3012
     * second value is the PID value for TSconfig (uid if table is pages, otherwise the pid)
3013
     * @internal
3014
     * @see \TYPO3\CMS\Core\DataHandling\DataHandler::setHistory()
3015
     * @see \TYPO3\CMS\Core\DataHandling\DataHandler::process_datamap()
3016
     */
3017
    public static function getTSCpid($table, $uid, $pid)
3018
    {
3019
        // If pid is negative (referring to another record) the pid of the other record is fetched and returned.
3020
        $cPid = self::getTSconfig_pidValue($table, $uid, $pid);
3021
        // $TScID is the id of $table = pages, else it's the pid of the record.
3022
        $TScID = $table === 'pages' && MathUtility::canBeInterpretedAsInteger($uid) ? $uid : $cPid;
3023
        return [$TScID, $cPid];
3024
    }
3025
3026
    /**
3027
     * Returns soft-reference parser for the softRef processing type
3028
     * Usage: $softRefObj = BackendUtility::softRefParserObj('[parser key]');
3029
     *
3030
     * @param string $spKey softRef parser key
3031
     * @return mixed If available, returns Soft link parser object, otherwise false.
3032
     * @internal should only be used from within TYPO3 Core
3033
     */
3034
    public static function softRefParserObj($spKey)
3035
    {
3036
        $className = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['softRefParser'][$spKey] ?? false;
3037
        if ($className) {
3038
            return GeneralUtility::makeInstance($className);
3039
        }
3040
        return false;
3041
    }
3042
3043
    /**
3044
     * Gets an instance of the runtime cache.
3045
     *
3046
     * @return FrontendInterface
3047
     */
3048
    protected static function getRuntimeCache()
3049
    {
3050
        return GeneralUtility::makeInstance(CacheManager::class)->getCache('runtime');
3051
    }
3052
3053
    /**
3054
     * Returns array of soft parser references
3055
     *
3056
     * @param string $parserList softRef parser list
3057
     * @return array|bool Array where the parser key is the key and the value is the parameter string, FALSE if no parsers were found
3058
     * @throws \InvalidArgumentException
3059
     * @internal should only be used from within TYPO3 Core
3060
     */
3061
    public static function explodeSoftRefParserList($parserList)
3062
    {
3063
        // Return immediately if list is blank:
3064
        if ((string)$parserList === '') {
3065
            return false;
3066
        }
3067
3068
        $runtimeCache = self::getRuntimeCache();
3069
        $cacheId = 'backend-softRefList-' . md5($parserList);
3070
        $parserListCache = $runtimeCache->get($cacheId);
3071
        if ($parserListCache !== false) {
3072
            return $parserListCache;
3073
        }
3074
3075
        // Otherwise parse the list:
3076
        $keyList = GeneralUtility::trimExplode(',', $parserList, true);
3077
        $output = [];
3078
        foreach ($keyList as $val) {
3079
            $reg = [];
3080
            if (preg_match('/^([[:alnum:]_-]+)\\[(.*)\\]$/', $val, $reg)) {
3081
                $output[$reg[1]] = GeneralUtility::trimExplode(';', $reg[2], true);
3082
            } else {
3083
                $output[$val] = '';
3084
            }
3085
        }
3086
        $runtimeCache->set($cacheId, $output);
3087
        return $output;
3088
    }
3089
3090
    /**
3091
     * Returns TRUE if $modName is set and is found as a main- or submodule in $TBE_MODULES array
3092
     *
3093
     * @param string $modName Module name
3094
     * @return bool
3095
     */
3096
    public static function isModuleSetInTBE_MODULES($modName)
3097
    {
3098
        $loaded = [];
3099
        foreach ($GLOBALS['TBE_MODULES'] as $mkey => $list) {
3100
            $loaded[$mkey] = 1;
3101
            if (!is_array($list) && trim($list)) {
3102
                $subList = GeneralUtility::trimExplode(',', $list, true);
3103
                foreach ($subList as $skey) {
3104
                    $loaded[$mkey . '_' . $skey] = 1;
3105
                }
3106
            }
3107
        }
3108
        return $modName && isset($loaded[$modName]);
3109
    }
3110
3111
    /**
3112
     * Counting references to a record/file
3113
     *
3114
     * @param string $table Table name (or "_FILE" if its a file)
3115
     * @param string $ref Reference: If table, then int-uid, if _FILE, then file reference (relative to Environment::getPublicPath())
3116
     * @param string $msg Message with %s, eg. "There were %s records pointing to this file!
3117
     * @param string|int|null $count Reference count
3118
     * @return string|int Output string (or int count value if no msg string specified)
3119
     */
3120
    public static function referenceCount($table, $ref, $msg = '', $count = null)
3121
    {
3122
        if ($count === null) {
3123
3124
            // Build base query
3125
            $queryBuilder = static::getQueryBuilderForTable('sys_refindex');
3126
            $queryBuilder
3127
                ->count('*')
3128
                ->from('sys_refindex')
3129
                ->where(
3130
                    $queryBuilder->expr()->eq('ref_table', $queryBuilder->createNamedParameter($table, \PDO::PARAM_STR)),
3131
                    $queryBuilder->expr()->eq('deleted', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
3132
                );
3133
3134
            // Look up the path:
3135
            if ($table === '_FILE') {
3136
                if (!GeneralUtility::isFirstPartOfStr($ref, Environment::getPublicPath())) {
3137
                    return '';
3138
                }
3139
3140
                $ref = PathUtility::stripPathSitePrefix($ref);
3141
                $queryBuilder->andWhere(
3142
                    $queryBuilder->expr()->eq('ref_string', $queryBuilder->createNamedParameter($ref, \PDO::PARAM_STR))
3143
                );
3144
            } else {
3145
                $queryBuilder->andWhere(
3146
                    $queryBuilder->expr()->eq('ref_uid', $queryBuilder->createNamedParameter($ref, \PDO::PARAM_INT))
3147
                );
3148
                if ($table === 'sys_file') {
3149
                    $queryBuilder->andWhere($queryBuilder->expr()->neq('tablename', $queryBuilder->quote('sys_file_metadata')));
3150
                }
3151
            }
3152
3153
            $count = $queryBuilder->execute()->fetchColumn(0);
3154
        }
3155
3156
        if ($count) {
3157
            return $msg ? sprintf($msg, $count) : $count;
3158
        }
3159
        return $msg ? '' : 0;
3160
    }
3161
3162
    /**
3163
     * Counting translations of records
3164
     *
3165
     * @param string $table Table name
3166
     * @param string $ref Reference: the record's uid
3167
     * @param string $msg Message with %s, eg. "This record has %s translation(s) which will be deleted, too!
3168
     * @return string Output string (or int count value if no msg string specified)
3169
     */
3170
    public static function translationCount($table, $ref, $msg = '')
3171
    {
3172
        $count = null;
3173
        if ($GLOBALS['TCA'][$table]['ctrl']['languageField']
3174
            && $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']
3175
        ) {
3176
            $queryBuilder = static::getQueryBuilderForTable($table);
3177
            $queryBuilder->getRestrictions()
3178
                ->removeAll()
3179
                ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
3180
3181
            $count = (int)$queryBuilder
3182
                ->count('*')
3183
                ->from($table)
3184
                ->where(
3185
                    $queryBuilder->expr()->eq(
3186
                        $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
3187
                        $queryBuilder->createNamedParameter($ref, \PDO::PARAM_INT)
3188
                    ),
3189
                    $queryBuilder->expr()->neq(
3190
                        $GLOBALS['TCA'][$table]['ctrl']['languageField'],
3191
                        $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
3192
                    )
3193
                )
3194
                ->execute()
3195
                ->fetchColumn(0);
3196
        }
3197
3198
        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...
3199
            return sprintf($msg, $count);
3200
        }
3201
3202
        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...
3203
            return $msg ? sprintf($msg, $count) : $count;
3204
        }
3205
        return $msg ? '' : 0;
3206
    }
3207
3208
    /*******************************************
3209
     *
3210
     * Workspaces / Versioning
3211
     *
3212
     *******************************************/
3213
    /**
3214
     * Select all versions of a record, ordered by latest created version (uid DESC)
3215
     *
3216
     * @param string $table Table name to select from
3217
     * @param int $uid Record uid for which to find versions.
3218
     * @param string $fields Field list to select
3219
     * @param int|null $workspace Search in workspace ID and Live WS, if 0 search only in LiveWS, if NULL search in all WS.
3220
     * @param bool $includeDeletedRecords If set, deleted-flagged versions are included! (Only for clean-up script!)
3221
     * @param array $row The current record
3222
     * @return array|null Array of versions of table/uid
3223
     * @internal should only be used from within TYPO3 Core
3224
     */
3225
    public static function selectVersionsOfRecord(
3226
        $table,
3227
        $uid,
3228
        $fields = '*',
3229
        $workspace = 0,
3230
        $includeDeletedRecords = false,
3231
        $row = null
3232
    ) {
3233
        $outputRows = [];
3234
        if (static::isTableWorkspaceEnabled($table)) {
3235
            if (is_array($row) && !$includeDeletedRecords) {
3236
                $row['_CURRENT_VERSION'] = true;
3237
                $outputRows[] = $row;
3238
            } else {
3239
                // Select UID version:
3240
                $row = self::getRecord($table, $uid, $fields, '', !$includeDeletedRecords);
3241
                // Add rows to output array:
3242
                if ($row) {
3243
                    $row['_CURRENT_VERSION'] = true;
3244
                    $outputRows[] = $row;
3245
                }
3246
            }
3247
3248
            $queryBuilder = static::getQueryBuilderForTable($table);
3249
            $queryBuilder->getRestrictions()->removeAll();
3250
3251
            // build fields to select
3252
            $queryBuilder->select(...GeneralUtility::trimExplode(',', $fields));
3253
3254
            $queryBuilder
3255
                ->from($table)
3256
                ->where(
3257
                    $queryBuilder->expr()->neq('uid', $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)),
3258
                    $queryBuilder->expr()->eq('t3ver_oid', $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT))
3259
                )
3260
                ->orderBy('uid', 'DESC');
3261
3262
            if (!$includeDeletedRecords) {
3263
                $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
3264
            }
3265
3266
            if ($workspace === 0) {
3267
                // Only in Live WS
3268
                $queryBuilder->andWhere(
3269
                    $queryBuilder->expr()->eq(
3270
                        't3ver_wsid',
3271
                        $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
3272
                    )
3273
                );
3274
            } elseif ($workspace !== null) {
3275
                // In Live WS and Workspace with given ID
3276
                $queryBuilder->andWhere(
3277
                    $queryBuilder->expr()->in(
3278
                        't3ver_wsid',
3279
                        $queryBuilder->createNamedParameter([0, (int)$workspace], Connection::PARAM_INT_ARRAY)
3280
                    )
3281
                );
3282
            }
3283
3284
            $rows = $queryBuilder->execute()->fetchAll();
3285
3286
            // Add rows to output array:
3287
            if (is_array($rows)) {
0 ignored issues
show
introduced by
The condition is_array($rows) is always true.
Loading history...
3288
                $outputRows = array_merge($outputRows, $rows);
3289
            }
3290
            return $outputRows;
3291
        }
3292
        return null;
3293
    }
3294
3295
    /**
3296
     * Find page-tree PID for versionized record
3297
     * Used whenever you are tracking something back, like making the root line.
3298
     * Will only translate if the workspace of the input record matches that of the current user (unless flag set)
3299
     * Principle; Record offline! => Find online?
3300
     *
3301
     * If the record had its pid corrected to the online versions pid,
3302
     * then "_ORIG_pid" is set for moved records to the PID of the moved location.
3303
     *
3304
     * @param string $table Table name
3305
     * @param array $rr Record array passed by reference. As minimum, "pid" and "uid" fields must exist! "t3ver_oid", "t3ver_state" and "t3ver_wsid" is nice and will save you a DB query.
3306
     * @param bool $ignoreWorkspaceMatch Ignore workspace match
3307
     * @see PageRepository::fixVersioningPid()
3308
     * @internal should only be used from within TYPO3 Core
3309
     * @deprecated will be removed in TYPO3 v12, use workspaceOL() or getRecordWSOL() directly to achieve the same result.
3310
     */
3311
    public static function fixVersioningPid($table, &$rr, $ignoreWorkspaceMatch = false)
3312
    {
3313
        trigger_error('BackendUtility::fixVersioningPid() will be removed in TYPO3 v12, use BackendUtility::workspaceOL() or BackendUtility::getRecordWSOL() directly.', E_USER_DEPRECATED);
3314
        if (!ExtensionManagementUtility::isLoaded('workspaces')) {
3315
            return;
3316
        }
3317
        if (!static::isTableWorkspaceEnabled($table)) {
3318
            return;
3319
        }
3320
        // Check that the input record is an offline version from a table that supports versioning
3321
        if (!is_array($rr)) {
0 ignored issues
show
introduced by
The condition is_array($rr) is always true.
Loading history...
3322
            return;
3323
        }
3324
        $incomingPid = $rr['pid'] ?? null;
3325
        // Check values for t3ver_oid and t3ver_wsid:
3326
        if (isset($rr['t3ver_oid']) && isset($rr['t3ver_wsid']) && isset($rr['t3ver_state'])) {
3327
            // If "t3ver_oid" is already a field, just set this:
3328
            $oid = $rr['t3ver_oid'];
3329
            $workspaceId = (int)$rr['t3ver_wsid'];
3330
            $versionState = (int)$rr['t3ver_state'];
3331
        } else {
3332
            $oid = 0;
3333
            $workspaceId = 0;
3334
            $versionState = 0;
3335
            // Otherwise we have to expect "uid" to be in the record and look up based on this:
3336
            $newPidRec = self::getRecord($table, $rr['uid'], 'pid,t3ver_oid,t3ver_wsid,t3ver_state');
3337
            if (is_array($newPidRec)) {
3338
                $incomingPid = $newPidRec['pid'];
3339
                $oid = $newPidRec['t3ver_oid'];
3340
                $workspaceId = $newPidRec['t3ver_wsid'];
3341
                $versionState = $newPidRec['t3ver_state'];
3342
            }
3343
        }
3344
        if ($oid && ($ignoreWorkspaceMatch || (static::getBackendUserAuthentication() instanceof BackendUserAuthentication && $workspaceId === (int)static::getBackendUserAuthentication()->workspace))) {
3345
            // Use moved PID in case of move pointer
3346
            if ($versionState === VersionState::MOVE_POINTER) {
3347
                if ($incomingPid !== null) {
3348
                    $movedPageIdInWorkspace = $incomingPid;
3349
                } else {
3350
                    $versionedMovePointer = self::getRecord($table, $rr['uid'], 'pid');
3351
                    $movedPageIdInWorkspace = $versionedMovePointer['pid'];
3352
                }
3353
                $rr['_ORIG_pid'] = $incomingPid;
3354
                $rr['pid'] = $movedPageIdInWorkspace;
3355
            }
3356
        }
3357
    }
3358
3359
    /**
3360
     * Workspace Preview Overlay.
3361
     *
3362
     * Generally ALWAYS used when records are selected based on uid or pid.
3363
     * Principle; Record online! => Find offline?
3364
     * The function MAY set $row to FALSE. This happens if a moved record is given and
3365
     * $unsetMovePointers is set to true. In other words, you should check if the input record
3366
     * is still an array afterwards when using this function.
3367
     *
3368
     * If the versioned record is a moved record the "pid" value will then contain the newly moved location
3369
     * and "ORIG_pid" will contain the live pid.
3370
     *
3371
     * @param string $table Table name
3372
     * @param array $row Record by reference. At least "uid", "pid", "t3ver_oid" and "t3ver_state" must be set. Keys not prefixed with '_' are used as field names in SQL.
3373
     * @param int $wsid Workspace ID, if not specified will use static::getBackendUserAuthentication()->workspace
3374
     * @param bool $unsetMovePointers If TRUE the function does not return a "pointer" row for moved records in a workspace
3375
     * @see fixVersioningPid()
3376
     */
3377
    public static function workspaceOL($table, &$row, $wsid = -99, $unsetMovePointers = false)
3378
    {
3379
        if (!ExtensionManagementUtility::isLoaded('workspaces') || !is_array($row) || !static::isTableWorkspaceEnabled($table)) {
3380
            return;
3381
        }
3382
3383
        // Initialize workspace ID
3384
        $wsid = (int)$wsid;
3385
        if ($wsid === -99 && static::getBackendUserAuthentication() instanceof BackendUserAuthentication) {
3386
            $wsid = (int)static::getBackendUserAuthentication()->workspace;
3387
        }
3388
        if ($wsid === 0) {
3389
            // Return early if in live workspace
3390
            return;
3391
        }
3392
3393
        // Check if input record is a moved record
3394
        $incomingRecordIsAMoveVersion = false;
3395
        if (isset($row['t3ver_oid'], $row['t3ver_state'])
3396
            && $row['t3ver_oid'] > 0
3397
            && (int)$row['t3ver_state'] === VersionState::MOVE_POINTER
3398
        ) {
3399
            // @todo: This handling needs a review, together with the 4th param $unsetMovePointers
3400
            $incomingRecordIsAMoveVersion = true;
3401
        }
3402
3403
        $wsAlt = self::getWorkspaceVersionOfRecord(
3404
            $wsid,
3405
            $table,
3406
            $row['uid'],
3407
            implode(',', static::purgeComputedPropertyNames(array_keys($row)))
3408
        );
3409
3410
        // If version was found, swap the default record with that one.
3411
        if (is_array($wsAlt)) {
3412
            // If t3ver_state is not found, then find it... (but we like best if it is here...)
3413
            if (!isset($wsAlt['t3ver_state'])) {
3414
                $stateRec = self::getRecord($table, $wsAlt['uid'], 't3ver_state');
3415
                $versionState = VersionState::cast($stateRec['t3ver_state']);
3416
            } else {
3417
                $versionState = VersionState::cast($wsAlt['t3ver_state']);
3418
            }
3419
            // Check if this is in move-state
3420
            if ($versionState->equals(VersionState::MOVE_POINTER)) {
3421
                // @todo Same problem as frontend in versionOL(). See TODO point there and todo above.
3422
                if (!$incomingRecordIsAMoveVersion && $unsetMovePointers) {
3423
                    $row = false;
3424
                    return;
3425
                }
3426
                // When a moved record is found the "PID" value contains the newly moved location
3427
                // Whereas the _ORIG_pid field contains the PID of the live version
3428
                $wsAlt['_ORIG_pid'] = $row['pid'];
3429
            }
3430
            // Swap UID
3431
            if (!$versionState->equals(VersionState::NEW_PLACEHOLDER)) {
3432
                $wsAlt['_ORIG_uid'] = $wsAlt['uid'];
3433
                $wsAlt['uid'] = $row['uid'];
3434
            }
3435
            // Backend css class:
3436
            $wsAlt['_CSSCLASS'] = 'ver-element';
3437
            // Changing input record to the workspace version alternative:
3438
            $row = $wsAlt;
3439
        }
3440
    }
3441
3442
    /**
3443
     * Select the workspace version of a record, if exists
3444
     *
3445
     * @param int $workspace Workspace ID
3446
     * @param string $table Table name to select from
3447
     * @param int $uid Record uid for which to find workspace version.
3448
     * @param string $fields Field list to select
3449
     * @return array|bool If found, return record, otherwise false
3450
     */
3451
    public static function getWorkspaceVersionOfRecord($workspace, $table, $uid, $fields = '*')
3452
    {
3453
        if (ExtensionManagementUtility::isLoaded('workspaces')) {
3454
            if ($workspace !== 0 && self::isTableWorkspaceEnabled($table)) {
3455
3456
                // Select workspace version of record:
3457
                $queryBuilder = static::getQueryBuilderForTable($table);
3458
                $queryBuilder->getRestrictions()
3459
                    ->removeAll()
3460
                    ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
3461
3462
                // build fields to select
3463
                $queryBuilder->select(...GeneralUtility::trimExplode(',', $fields));
3464
3465
                $row = $queryBuilder
3466
                    ->from($table)
3467
                    ->where(
3468
                        $queryBuilder->expr()->eq(
3469
                            't3ver_wsid',
3470
                            $queryBuilder->createNamedParameter($workspace, \PDO::PARAM_INT)
3471
                        ),
3472
                        $queryBuilder->expr()->orX(
3473
                            // t3ver_state=1 does not contain a t3ver_oid, and returns itself
3474
                            $queryBuilder->expr()->andX(
3475
                                $queryBuilder->expr()->eq(
3476
                                    'uid',
3477
                                    $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
3478
                                ),
3479
                                $queryBuilder->expr()->eq(
3480
                                    't3ver_state',
3481
                                    $queryBuilder->createNamedParameter(VersionState::NEW_PLACEHOLDER, \PDO::PARAM_INT)
3482
                                )
3483
                            ),
3484
                            $queryBuilder->expr()->eq(
3485
                                't3ver_oid',
3486
                                $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
3487
                            )
3488
                        )
3489
                    )
3490
                    ->execute()
3491
                    ->fetch();
3492
3493
                return $row;
3494
            }
3495
        }
3496
        return false;
3497
    }
3498
3499
    /**
3500
     * Returns live version of record
3501
     *
3502
     * @param string $table Table name
3503
     * @param int $uid Record UID of draft, offline version
3504
     * @param string $fields Field list, default is *
3505
     * @return array|null If found, the record, otherwise NULL
3506
     */
3507
    public static function getLiveVersionOfRecord($table, $uid, $fields = '*')
3508
    {
3509
        $liveVersionId = self::getLiveVersionIdOfRecord($table, $uid);
3510
        if ($liveVersionId !== null) {
3511
            return self::getRecord($table, $liveVersionId, $fields);
3512
        }
3513
        return null;
3514
    }
3515
3516
    /**
3517
     * Gets the id of the live version of a record.
3518
     *
3519
     * @param string $table Name of the table
3520
     * @param int $uid Uid of the offline/draft record
3521
     * @return int|null The id of the live version of the record (or NULL if nothing was found)
3522
     * @internal should only be used from within TYPO3 Core
3523
     */
3524
    public static function getLiveVersionIdOfRecord($table, $uid)
3525
    {
3526
        if (!ExtensionManagementUtility::isLoaded('workspaces')) {
3527
            return null;
3528
        }
3529
        $liveVersionId = null;
3530
        if (self::isTableWorkspaceEnabled($table)) {
3531
            $currentRecord = self::getRecord($table, $uid, 'pid,t3ver_oid,t3ver_state');
3532
            if (is_array($currentRecord)) {
3533
                if ((int)$currentRecord['t3ver_oid'] > 0) {
3534
                    $liveVersionId = $currentRecord['t3ver_oid'];
3535
                } elseif ((int)($currentRecord['t3ver_state']) === VersionState::NEW_PLACEHOLDER) {
3536
                    // New versions do not have a live counterpart
3537
                    $liveVersionId = (int)$uid;
3538
                }
3539
            }
3540
        }
3541
        return $liveVersionId;
3542
    }
3543
3544
    /**
3545
     * Performs mapping of new uids to new versions UID in case of import inside a workspace.
3546
     *
3547
     * @param string $table Table name
3548
     * @param int $uid Record uid (of live record placeholder)
3549
     * @return int Uid of offline version if any, otherwise live uid.
3550
     * @internal should only be used from within TYPO3 Core
3551
     */
3552
    public static function wsMapId($table, $uid)
3553
    {
3554
        $wsRec = null;
3555
        if (static::getBackendUserAuthentication() instanceof BackendUserAuthentication) {
3556
            $wsRec = self::getWorkspaceVersionOfRecord(
3557
                static::getBackendUserAuthentication()->workspace,
3558
                $table,
3559
                $uid,
3560
                'uid'
3561
            );
3562
        }
3563
        return is_array($wsRec) ? $wsRec['uid'] : $uid;
3564
    }
3565
3566
    /*******************************************
3567
     *
3568
     * Miscellaneous
3569
     *
3570
     *******************************************/
3571
3572
    /**
3573
     * Creates ADMCMD parameters for the "viewpage" extension / frontend
3574
     *
3575
     * @param array $pageInfo Page record
3576
     * @param \TYPO3\CMS\Core\Context\Context $context
3577
     * @return string Query-parameters
3578
     * @internal
3579
     */
3580
    public static function ADMCMD_previewCmds($pageInfo, Context $context)
3581
    {
3582
        $simUser = '';
3583
        $simTime = '';
3584
        if ($pageInfo['fe_group'] > 0) {
3585
            $simUser = '&ADMCMD_simUser=' . $pageInfo['fe_group'];
3586
        } elseif ((int)$pageInfo['fe_group'] === -2) {
3587
            // -2 means "show at any login". We simulate first available fe_group.
3588
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
3589
                ->getQueryBuilderForTable('fe_groups');
3590
            $queryBuilder->getRestrictions()
3591
                ->removeAll()
3592
                ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
3593
                ->add(GeneralUtility::makeInstance(HiddenRestriction::class));
3594
3595
            $activeFeGroupRow = $queryBuilder->select('uid')
3596
                ->from('fe_groups')
3597
                ->execute()
3598
                ->fetch();
3599
3600
            if (!empty($activeFeGroupRow)) {
3601
                $simUser = '&ADMCMD_simUser=' . $activeFeGroupRow['uid'];
3602
            }
3603
        }
3604
        $startTime = (int)$pageInfo['starttime'];
3605
        $endTime = (int)$pageInfo['endtime'];
3606
        if ($startTime > $GLOBALS['EXEC_TIME']) {
3607
            // simulate access time to ensure PageRepository will find the page and in turn PageRouter will generate
3608
            // an URL for it
3609
            $dateAspect = GeneralUtility::makeInstance(DateTimeAspect::class, new \DateTimeImmutable('@' . $startTime));
3610
            $context->setAspect('date', $dateAspect);
3611
            $simTime = '&ADMCMD_simTime=' . $startTime;
3612
        }
3613
        if ($endTime < $GLOBALS['EXEC_TIME'] && $endTime !== 0) {
3614
            // Set access time to page's endtime subtracted one second to ensure PageRepository will find the page and
3615
            // in turn PageRouter will generate an URL for it
3616
            $dateAspect = GeneralUtility::makeInstance(
3617
                DateTimeAspect::class,
3618
                new \DateTimeImmutable('@' . ($endTime - 1))
3619
            );
3620
            $context->setAspect('date', $dateAspect);
3621
            $simTime = '&ADMCMD_simTime=' . ($endTime - 1);
3622
        }
3623
        return $simUser . $simTime;
3624
    }
3625
3626
    /**
3627
     * Determines whether a table is enabled for workspaces.
3628
     *
3629
     * @param string $table Name of the table to be checked
3630
     * @return bool
3631
     */
3632
    public static function isTableWorkspaceEnabled($table)
3633
    {
3634
        return !empty($GLOBALS['TCA'][$table]['ctrl']['versioningWS']);
3635
    }
3636
3637
    /**
3638
     * Gets the TCA configuration of a field.
3639
     *
3640
     * @param string $table Name of the table
3641
     * @param string $field Name of the field
3642
     * @return array
3643
     */
3644
    public static function getTcaFieldConfiguration($table, $field)
3645
    {
3646
        $configuration = [];
3647
        if (isset($GLOBALS['TCA'][$table]['columns'][$field]['config'])) {
3648
            $configuration = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
3649
        }
3650
        return $configuration;
3651
    }
3652
3653
    /**
3654
     * Whether to ignore restrictions on a web-mount of a table.
3655
     * The regular behaviour is that records to be accessed need to be
3656
     * in a valid user's web-mount.
3657
     *
3658
     * @param string $table Name of the table
3659
     * @return bool
3660
     */
3661
    public static function isWebMountRestrictionIgnored($table)
3662
    {
3663
        return !empty($GLOBALS['TCA'][$table]['ctrl']['security']['ignoreWebMountRestriction']);
3664
    }
3665
3666
    /**
3667
     * Whether to ignore restrictions on root-level records.
3668
     * The regular behaviour is that records on the root-level (page-id 0)
3669
     * only can be accessed by admin users.
3670
     *
3671
     * @param string $table Name of the table
3672
     * @return bool
3673
     */
3674
    public static function isRootLevelRestrictionIgnored($table)
3675
    {
3676
        return !empty($GLOBALS['TCA'][$table]['ctrl']['security']['ignoreRootLevelRestriction']);
3677
    }
3678
3679
    /**
3680
     * @param string $table
3681
     * @return Connection
3682
     */
3683
    protected static function getConnectionForTable($table)
3684
    {
3685
        return GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
3686
    }
3687
3688
    /**
3689
     * @param string $table
3690
     * @return QueryBuilder
3691
     */
3692
    protected static function getQueryBuilderForTable($table)
3693
    {
3694
        return GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
3695
    }
3696
3697
    /**
3698
     * @return LoggerInterface
3699
     */
3700
    protected static function getLogger()
3701
    {
3702
        return GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__);
3703
    }
3704
3705
    /**
3706
     * @return LanguageService
3707
     */
3708
    protected static function getLanguageService()
3709
    {
3710
        return $GLOBALS['LANG'];
3711
    }
3712
3713
    /**
3714
     * @return BackendUserAuthentication|null
3715
     */
3716
    protected static function getBackendUserAuthentication()
3717
    {
3718
        return $GLOBALS['BE_USER'] ?? null;
3719
    }
3720
}
3721