Completed
Push — master ( 64542d...27c7de )
by
unknown
12:47
created

WorkspaceService::getMoveToPlaceHolderFromPages()   B

Complexity

Conditions 5
Paths 12

Size

Total Lines 103
Code Lines 68

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 68
nc 12
nop 4
dl 0
loc 103
rs 8.387
c 0
b 0
f 0

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
 * It is free software; you can redistribute it and/or modify it under
7
 * the terms of the GNU General Public License, either version 2
8
 * of the License, or any later version.
9
 *
10
 * For the full copyright and license information, please read the
11
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
 * The TYPO3 project - inspiring people to share!
14
 */
15
16
namespace TYPO3\CMS\Workspaces\Service;
17
18
use TYPO3\CMS\Backend\Utility\BackendUtility;
19
use TYPO3\CMS\Core\Database\Connection;
20
use TYPO3\CMS\Core\Database\ConnectionPool;
21
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
22
use TYPO3\CMS\Core\Database\Query\QueryHelper;
23
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
24
use TYPO3\CMS\Core\Database\Query\Restriction\RootLevelRestriction;
25
use TYPO3\CMS\Core\Localization\LanguageService;
26
use TYPO3\CMS\Core\SingletonInterface;
27
use TYPO3\CMS\Core\Type\Bitmask\Permission;
28
use TYPO3\CMS\Core\Utility\GeneralUtility;
29
use TYPO3\CMS\Core\Utility\MathUtility;
30
use TYPO3\CMS\Core\Versioning\VersionState;
31
32
/**
33
 * Workspace service
34
 */
35
class WorkspaceService implements SingletonInterface
36
{
37
    /**
38
     * @var array
39
     */
40
    protected $versionsOnPageCache = [];
41
42
    /**
43
     * @var array
44
     */
45
    protected $pagesWithVersionsInTable = [];
46
47
    const TABLE_WORKSPACE = 'sys_workspace';
48
    const LIVE_WORKSPACE_ID = 0;
49
50
    /**
51
     * retrieves the available workspaces from the database and checks whether
52
     * they're available to the current BE user
53
     *
54
     * @return array array of workspaces available to the current user
55
     */
56
    public function getAvailableWorkspaces()
57
    {
58
        $availableWorkspaces = [];
59
        // add default workspaces
60
        if ($GLOBALS['BE_USER']->checkWorkspace(['uid' => (string)self::LIVE_WORKSPACE_ID])) {
61
            $availableWorkspaces[self::LIVE_WORKSPACE_ID] = self::getWorkspaceTitle(self::LIVE_WORKSPACE_ID);
62
        }
63
        // add custom workspaces (selecting all, filtering by BE_USER check):
64
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_workspace');
65
        $queryBuilder->getRestrictions()
66
            ->add(GeneralUtility::makeInstance(RootLevelRestriction::class));
67
68
        $result = $queryBuilder
69
            ->select('uid', 'title', 'adminusers', 'members')
70
            ->from('sys_workspace')
71
            ->orderBy('title')
72
            ->execute();
73
74
        while ($workspace = $result->fetch()) {
75
            if ($GLOBALS['BE_USER']->checkWorkspace($workspace)) {
76
                $availableWorkspaces[$workspace['uid']] = $workspace['title'];
77
            }
78
        }
79
        return $availableWorkspaces;
80
    }
81
82
    /**
83
     * Gets the current workspace ID.
84
     *
85
     * @return int The current workspace ID
86
     */
87
    public function getCurrentWorkspace()
88
    {
89
        return $GLOBALS['BE_USER']->workspace;
90
    }
91
92
    /**
93
     * easy function to just return the number of hours.
94
     *
95
     * a preview link is valid, based on the workspaces' custom value (default to 48 hours)
96
     * or falls back to the users' TSconfig value "options.workspaces.previewLinkTTLHours".
97
     *
98
     * by default, it's 48hs.
99
     *
100
     * @return int The hours as a number
101
     */
102
    public function getPreviewLinkLifetime(): int
103
    {
104
        $workspaceId = $GLOBALS['BE_USER']->workspace;
105
        if ($workspaceId > 0) {
106
            $wsRecord = BackendUtility::getRecord('sys_workspace', $workspaceId, '*');
107
            if (($wsRecord['previewlink_lifetime'] ?? 0) > 0) {
108
                return (int)$wsRecord['previewlink_lifetime'];
109
            }
110
        }
111
        $ttlHours = (int)($GLOBALS['BE_USER']->getTSConfig()['options.']['workspaces.']['previewLinkTTLHours'] ?? 0);
112
        return $ttlHours ?: 24 * 2;
113
    }
114
115
    /**
116
     * Find the title for the requested workspace.
117
     *
118
     * @param int $wsId
119
     * @return string
120
     * @throws \InvalidArgumentException
121
     */
122
    public static function getWorkspaceTitle($wsId)
123
    {
124
        $title = false;
125
        switch ($wsId) {
126
            case self::LIVE_WORKSPACE_ID:
127
                $title = static::getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:shortcut_onlineWS');
128
                break;
129
            default:
130
                $labelField = $GLOBALS['TCA']['sys_workspace']['ctrl']['label'];
131
                $wsRecord = BackendUtility::getRecord('sys_workspace', $wsId, 'uid,' . $labelField);
132
                if (is_array($wsRecord)) {
133
                    $title = $wsRecord[$labelField];
134
                }
135
        }
136
        if ($title === false) {
137
            throw new \InvalidArgumentException('No such workspace defined', 1476045469);
138
        }
139
        return $title;
140
    }
141
142
    /**
143
     * Building DataHandler CMD-array for publishing all versions in a workspace.
144
     *
145
     * @param int $wsid Real workspace ID, cannot be ONLINE (zero).
146
     * @param bool $_ Unused, previously used to choose between swapping and publishing
147
     * @param int $pageId The page id
148
     * @param int|null $language Select specific language only
149
     * @return array Command array for DataHandler
150
     */
151
    public function getCmdArrayForPublishWS($wsid, $_ = false, $pageId = 0, $language = null)
152
    {
153
        $wsid = (int)$wsid;
154
        $cmd = [];
155
        if ($wsid > 0) {
156
            // Define stage to select:
157
            $stage = -99;
158
            $workspaceRec = BackendUtility::getRecord('sys_workspace', $wsid);
159
            if ($workspaceRec['publish_access'] & 1) {
160
                $stage = StagesService::STAGE_PUBLISH_ID;
161
            }
162
            // Select all versions to publishing
163
            $versions = $this->selectVersionsInWorkspace(
164
                $wsid,
165
                $stage,
166
                $pageId ?: -1,
167
                999,
168
                'tables_modify',
169
                $language
170
            );
171
            // Traverse the selection to build CMD array:
172
            foreach ($versions as $table => $records) {
173
                foreach ($records as $rec) {
174
                    // Build the cmd Array:
175
                    $cmd[$table][$rec['t3ver_oid']]['version'] = ['action' => 'swap', 'swapWith' => $rec['uid']];
176
                }
177
            }
178
        }
179
        return $cmd;
180
    }
181
182
    /**
183
     * Building DataHandler CMD-array for releasing all versions in a workspace.
184
     *
185
     * @param int $wsid Real workspace ID, cannot be ONLINE (zero).
186
     * @param bool $flush Run Flush (TRUE) or ClearWSID (FALSE) command
187
     * @param int $pageId The page id
188
     * @param int $language Select specific language only
189
     * @return array Command array for DataHandler
190
     */
191
    public function getCmdArrayForFlushWS($wsid, $flush = true, $pageId = 0, $language = null)
192
    {
193
        $wsid = (int)$wsid;
194
        $cmd = [];
195
        if ($wsid > 0) {
196
            // Define stage to select:
197
            $stage = -99;
198
            // Select all versions to publish
199
            $versions = $this->selectVersionsInWorkspace(
200
                $wsid,
201
                $stage,
202
                $pageId ?: -1,
203
                999,
204
                'tables_modify',
205
                $language
206
            );
207
            // Traverse the selection to build CMD array:
208
            foreach ($versions as $table => $records) {
209
                foreach ($records as $rec) {
210
                    // Build the cmd Array:
211
                    $cmd[$table][$rec['uid']]['version'] = ['action' => $flush ? 'flush' : 'clearWSID'];
212
                }
213
            }
214
        }
215
        return $cmd;
216
    }
217
218
    /**
219
     * Select all records from workspace pending for publishing
220
     * Used from backend to display workspace overview
221
     * User for auto-publishing for selecting versions for publication
222
     *
223
     * @param int $wsid Workspace ID. If -99, will select ALL versions from ANY workspace. If -98 will select all but ONLINE. >=-1 will select from the actual workspace
224
     * @param int $stage Stage filter: -99 means no filtering, otherwise it will be used to select only elements with that stage. For publishing, that would be "10
225
     * @param int $pageId Page id: Live page for which to find versions in workspace!
226
     * @param int $recursionLevel Recursion Level - select versions recursive - parameter is only relevant if $pageId != -1
227
     * @param string $selectionType How to collect records for "listing" or "modify" these tables. Support the permissions of each type of record, see \TYPO3\CMS\Core\Authentication\BackendUserAuthentication::check.
228
     * @param int $language Select specific language only
229
     * @return array Array of all records uids etc. First key is table name, second key incremental integer. Records are associative arrays with uid and t3ver_oidfields. The pid of the online record is found as "livepid" the pid of the offline record is found in "wspid
230
     */
231
    public function selectVersionsInWorkspace($wsid, $stage = -99, $pageId = -1, $recursionLevel = 0, $selectionType = 'tables_select', $language = null)
232
    {
233
        $wsid = (int)$wsid;
234
        $output = [];
235
        // Contains either nothing or a list with live-uids
236
        if ($pageId != -1 && $recursionLevel > 0) {
237
            $pageList = $this->getTreeUids($pageId, $wsid, $recursionLevel);
238
        } elseif ($pageId != -1) {
239
            $pageList = (string)$pageId;
240
        } else {
241
            $pageList = '';
242
            // check if person may only see a "virtual" page-root
243
            $mountPoints = array_map('intval', $GLOBALS['BE_USER']->returnWebmounts());
244
            $mountPoints = array_unique($mountPoints);
245
            if (!in_array(0, $mountPoints)) {
246
                $tempPageIds = [];
247
                foreach ($mountPoints as $mountPoint) {
248
                    $tempPageIds[] = $this->getTreeUids($mountPoint, $wsid, $recursionLevel);
249
                }
250
                $pageList = implode(',', $tempPageIds);
251
                $pageList = implode(',', array_unique(explode(',', $pageList)));
252
            }
253
        }
254
        // Traversing all tables supporting versioning:
255
        foreach ($GLOBALS['TCA'] as $table => $cfg) {
256
            // we do not collect records from tables without permissions on them.
257
            if (!$GLOBALS['BE_USER']->check($selectionType, $table)) {
258
                continue;
259
            }
260
            if (BackendUtility::isTableWorkspaceEnabled($table)) {
261
                $recs = $this->selectAllVersionsFromPages($table, $pageList, $wsid, $stage, $language);
262
                $moveRecs = $this->getMovedRecordsFromPages($table, $pageList, $wsid, $stage);
263
                $recs = array_merge($recs, $moveRecs);
264
                $recs = $this->filterPermittedElements($recs, $table);
265
                if (!empty($recs)) {
266
                    $output[$table] = $recs;
267
                }
268
            }
269
        }
270
        return $output;
271
    }
272
273
    /**
274
     * Find all versionized elements except moved records.
275
     *
276
     * @param string $table
277
     * @param string $pageList
278
     * @param int $wsid
279
     * @param int $stage
280
     * @param int $language
281
     * @return array
282
     */
283
    protected function selectAllVersionsFromPages($table, $pageList, $wsid, $stage, $language = null)
284
    {
285
        // Include root level page as there might be some records with where root level
286
        // restriction is ignored (e.g. FAL records)
287
        if ($pageList !== '' && BackendUtility::isRootLevelRestrictionIgnored($table)) {
288
            $pageList .= ',0';
289
        }
290
        $isTableLocalizable = BackendUtility::isTableLocalizable($table);
291
        $languageParentField = '';
292
        // If table is not localizable, but localized records shall
293
        // be collected, an empty result array needs to be returned:
294
        if ($isTableLocalizable === false && $language > 0) {
295
            return [];
296
        }
297
        if ($isTableLocalizable) {
298
            $languageParentField = 'A.' . $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'];
299
        }
300
301
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
302
        $queryBuilder->getRestrictions()->removeAll()
303
            ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
304
305
        $fields = ['A.uid', 'A.pid', 'A.t3ver_oid', 'A.t3ver_stage', 'B.pid', 'B.pid AS wspid', 'B.pid AS livepid'];
306
        if ($isTableLocalizable) {
307
            $fields[] = $languageParentField;
308
            $fields[] = 'A.' . $GLOBALS['TCA'][$table]['ctrl']['languageField'];
309
        }
310
        // Table A is the offline version and t3ver_oid>0 defines offline
311
        // Table B (online) must have t3ver_oid=0 to signify being online.
312
        $constraints = [
313
            $queryBuilder->expr()->gt(
314
                'A.t3ver_oid',
315
                $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
316
            ),
317
            $queryBuilder->expr()->eq(
318
                'B.t3ver_oid',
319
                $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
320
            ),
321
            $queryBuilder->expr()->neq(
322
                'A.t3ver_state',
323
                $queryBuilder->createNamedParameter(
324
                    (string)new VersionState(VersionState::MOVE_POINTER),
325
                    \PDO::PARAM_INT
326
                )
327
            )
328
        ];
329
330
        if ($pageList) {
331
            $pageIdRestriction = GeneralUtility::intExplode(',', $pageList, true);
332
            if ($table === 'pages') {
333
                $constraints[] = $queryBuilder->expr()->orX(
334
                    $queryBuilder->expr()->in(
335
                        'B.uid',
336
                        $queryBuilder->createNamedParameter(
337
                            $pageIdRestriction,
338
                            Connection::PARAM_INT_ARRAY
339
                        )
340
                    ),
341
                    $queryBuilder->expr()->in(
342
                        'B.' . $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
343
                        $queryBuilder->createNamedParameter(
344
                            $pageIdRestriction,
345
                            Connection::PARAM_INT_ARRAY
346
                        )
347
                    )
348
                );
349
            } else {
350
                $constraints[] = $queryBuilder->expr()->in(
351
                    'B.pid',
352
                    $queryBuilder->createNamedParameter(
353
                        $pageIdRestriction,
354
                        Connection::PARAM_INT_ARRAY
355
                    )
356
                );
357
            }
358
        }
359
360
        if ($isTableLocalizable && MathUtility::canBeInterpretedAsInteger($language)) {
361
            $constraints[] = $queryBuilder->expr()->eq(
362
                'A.' . $GLOBALS['TCA'][$table]['ctrl']['languageField'],
363
                $queryBuilder->createNamedParameter($language, \PDO::PARAM_INT)
364
            );
365
        }
366
367
        if ($wsid >= 0) {
368
            $constraints[] = $queryBuilder->expr()->eq(
369
                'A.t3ver_wsid',
370
                $queryBuilder->createNamedParameter($wsid, \PDO::PARAM_INT)
371
            );
372
        }
373
374
        if ((int)$stage !== -99) {
375
            $constraints[] = $queryBuilder->expr()->eq(
376
                'A.t3ver_stage',
377
                $queryBuilder->createNamedParameter($stage, \PDO::PARAM_INT)
378
            );
379
        }
380
381
        // ... and finally the join between the two tables.
382
        $constraints[] = $queryBuilder->expr()->eq('A.t3ver_oid', $queryBuilder->quoteIdentifier('B.uid'));
383
384
        // Select all records from this table in the database from the workspace
385
        // This joins the online version with the offline version as tables A and B
386
        // Order by UID, mostly to have a sorting in the backend overview module which
387
        // doesn't "jump around" when publishing.
388
        $rows = $queryBuilder->select(...$fields)
389
            ->from($table, 'A')
390
            ->from($table, 'B')
391
            ->where(...$constraints)
392
            ->orderBy('B.uid')
393
            ->execute()
394
            ->fetchAll();
395
396
        return $rows;
397
    }
398
399
    /**
400
     * Find all moved records at their new position.
401
     *
402
     * @param string $table
403
     * @param string $pageList
404
     * @param int $wsid
405
     * @param int $stage
406
     * @return array
407
     */
408
    protected function getMovedRecordsFromPages($table, $pageList, $wsid, $stage)
409
    {
410
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
411
        $queryBuilder->getRestrictions()->removeAll()
412
            ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
413
414
        // Aliases:
415
        // B - online record
416
        // C - move pointer (t3ver_state = 4)
417
        $constraints = [
418
            $queryBuilder->expr()->eq(
419
                'B.t3ver_state',
420
                $queryBuilder->createNamedParameter(
421
                    (string)new VersionState(VersionState::DEFAULT_STATE),
422
                    \PDO::PARAM_INT
423
                )
424
            ),
425
            $queryBuilder->expr()->eq(
426
                'B.t3ver_wsid',
427
                $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
428
            ),
429
            $queryBuilder->expr()->eq(
430
                'C.t3ver_state',
431
                $queryBuilder->createNamedParameter(
432
                    (string)new VersionState(VersionState::MOVE_POINTER),
433
                    \PDO::PARAM_INT
434
                )
435
            ),
436
            $queryBuilder->expr()->eq('B.uid', $queryBuilder->quoteIdentifier('C.t3ver_oid'))
437
        ];
438
439
        if ($wsid >= 0) {
440
            $constraints[] = $queryBuilder->expr()->eq(
441
                'C.t3ver_wsid',
442
                $queryBuilder->createNamedParameter($wsid, \PDO::PARAM_INT)
443
            );
444
        }
445
446
        if ((int)$stage !== -99) {
447
            $constraints[] = $queryBuilder->expr()->eq(
448
                'C.t3ver_stage',
449
                $queryBuilder->createNamedParameter($stage, \PDO::PARAM_INT)
450
            );
451
        }
452
453
        if ($pageList) {
454
            $pageIdRestriction = GeneralUtility::intExplode(',', $pageList, true);
455
            if ($table === 'pages') {
456
                $constraints[] = $queryBuilder->expr()->orX(
457
                    $queryBuilder->expr()->in(
458
                        'B.uid',
459
                        $queryBuilder->createNamedParameter(
460
                            $pageIdRestriction,
461
                            Connection::PARAM_INT_ARRAY
462
                        )
463
                    ),
464
                    $queryBuilder->expr()->in(
465
                        'C.pid',
466
                        $queryBuilder->createNamedParameter(
467
                            $pageIdRestriction,
468
                            Connection::PARAM_INT_ARRAY
469
                        )
470
                    ),
471
                    $queryBuilder->expr()->in(
472
                        'B.' . $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
473
                        $queryBuilder->createNamedParameter(
474
                            $pageIdRestriction,
475
                            Connection::PARAM_INT_ARRAY
476
                        )
477
                    )
478
                );
479
            } else {
480
                $constraints[] = $queryBuilder->expr()->in(
481
                    'C.pid',
482
                    $queryBuilder->createNamedParameter(
483
                        $pageIdRestriction,
484
                        Connection::PARAM_INT_ARRAY
485
                    )
486
                );
487
            }
488
        }
489
490
        $rows = $queryBuilder
491
            ->select('C.pid AS wspid', 'B.uid AS t3ver_oid', 'C.uid AS uid', 'B.pid AS livepid')
492
            ->from($table, 'B')
493
            ->from($table, 'C')
494
            ->where(...$constraints)
495
            ->orderBy('C.uid')
496
            ->execute()
497
            ->fetchAll();
498
499
        return $rows;
500
    }
501
502
    /**
503
     * Find all page uids recursive starting from a specific page
504
     *
505
     * @param int $pageId
506
     * @param int $wsid
507
     * @param int $recursionLevel
508
     * @return string Comma sep. uid list
509
     */
510
    protected function getTreeUids($pageId, $wsid, $recursionLevel)
511
    {
512
        // Reusing existing functionality with the drawback that
513
        // mount points are not covered yet
514
        $permsClause = QueryHelper::stripLogicalOperatorPrefix(
515
            $GLOBALS['BE_USER']->getPagePermsClause(Permission::PAGE_SHOW)
516
        );
517
        if ($pageId > 0) {
518
            $pageList = array_merge(
519
                [ (int)$pageId ],
520
                $this->getPageChildrenRecursive((int)$pageId, (int)$recursionLevel, 0, $permsClause)
521
            );
522
        } else {
523
            $mountPoints = $GLOBALS['BE_USER']->uc['pageTree_temporaryMountPoint'];
524
            if (!is_array($mountPoints) || empty($mountPoints)) {
525
                $mountPoints = array_map('intval', $GLOBALS['BE_USER']->returnWebmounts());
526
                $mountPoints = array_unique($mountPoints);
527
            }
528
            $pageList = [];
529
            foreach ($mountPoints as $mountPoint) {
530
                $pageList = array_merge(
531
                    $pageList
532
                    [ (int)$mountPoint ],
533
                    $this->getPageChildrenRecursive((int)$mountPoint, (int)$recursionLevel, 0, $permsClause)
534
                );
535
            }
536
        }
537
        $pageList = array_unique($pageList);
538
539
        if (BackendUtility::isTableWorkspaceEnabled('pages') && !empty($pageList)) {
540
            // Remove the "subbranch" if a page was moved away
541
            $pageIds = $pageList;
542
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
543
            $queryBuilder->getRestrictions()
544
                ->removeAll()
545
                ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
546
            $result = $queryBuilder
547
                ->select('uid', 'pid', 't3ver_oid')
548
                ->from('pages')
549
                ->where(
550
                    $queryBuilder->expr()->in(
551
                        't3ver_oid',
552
                        $queryBuilder->createNamedParameter($pageIds, Connection::PARAM_INT_ARRAY)
553
                    ),
554
                    $queryBuilder->expr()->eq(
555
                        't3ver_wsid',
556
                        $queryBuilder->createNamedParameter($wsid, \PDO::PARAM_INT)
557
                    ),
558
                    $queryBuilder->expr()->eq(
559
                        't3ver_state',
560
                        $queryBuilder->createNamedParameter(VersionState::MOVE_POINTER, \PDO::PARAM_INT)
561
                    )
562
                )
563
                ->orderBy('uid')
564
                ->execute();
565
566
            $movedAwayPages = [];
567
            while ($row = $result->fetch()) {
568
                $movedAwayPages[$row['t3ver_oid']] = $row;
569
            }
570
571
            // move all pages away
572
            $newList = array_diff($pageIds, array_keys($movedAwayPages));
573
            // keep current page in the list
574
            $newList[] = $pageId;
575
            // move back in if still connected to the "remaining" pages
576
            do {
577
                $changed = false;
578
                foreach ($movedAwayPages as $uid => $rec) {
579
                    if (in_array($rec['pid'], $newList) && !in_array($uid, $newList)) {
580
                        $newList[] = $uid;
581
                        $changed = true;
582
                    }
583
                }
584
            } while ($changed);
585
586
            // In case moving pages is enabled we need to replace all move-to pointer with their origin
587
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
588
            $queryBuilder->getRestrictions()
589
                ->removeAll()
590
                ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
591
            $result = $queryBuilder->select('uid', 't3ver_oid')
592
                ->from('pages')
593
                ->where(
594
                    $queryBuilder->expr()->in(
595
                        'uid',
596
                        $queryBuilder->createNamedParameter($newList, Connection::PARAM_INT_ARRAY)
597
                    )
598
                )
599
                ->orderBy('uid')
600
                ->execute();
601
602
            $pages = [];
603
            while ($row = $result->fetch()) {
604
                $pages[$row['uid']] = $row;
605
            }
606
607
            $pageIds = $newList;
608
            if (!in_array($pageId, $pageIds)) {
609
                $pageIds[] = $pageId;
610
            }
611
612
            $newList = [];
613
            foreach ($pageIds as $pageId) {
0 ignored issues
show
introduced by
$pageId is overwriting one of the parameters of this function.
Loading history...
614
                if ((int)$pages[$pageId]['t3ver_oid'] > 0) {
615
                    $newList[] = (int)$pages[$pageId]['t3ver_oid'];
616
                } else {
617
                    $newList[] = $pageId;
618
                }
619
            }
620
            $pageList = $newList;
621
        }
622
623
        return implode(',', $pageList);
624
    }
625
626
    /**
627
     * Recursively fetch all children of a given page
628
     *
629
     * @param int $pid uid of the page
630
     * @param int $depth
631
     * @param int $begin
632
     * @param string $permsClause
633
     * @return int[] List of child row $uid's
634
     */
635
    protected function getPageChildrenRecursive(int $pid, int $depth, int $begin, string $permsClause): array
636
    {
637
        $children = [];
638
        if ($pid && $depth > 0) {
639
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
640
            $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
641
            $statement = $queryBuilder->select('uid')
642
                ->from('pages')
643
                ->where(
644
                    $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT)),
645
                    $queryBuilder->expr()->eq('sys_language_uid', 0),
646
                    $permsClause
647
                )
648
                ->execute();
649
            while ($row = $statement->fetch()) {
650
                if ($begin <= 0) {
651
                    $children[] = (int)$row['uid'];
652
                }
653
                if ($depth > 1) {
654
                    $theSubList = $this->getPageChildrenRecursive((int)$row['uid'], $depth - 1, $begin - 1, $permsClause);
655
                    $children = array_merge($children, $theSubList);
656
                }
657
            }
658
        }
659
        return $children;
660
    }
661
662
    /**
663
     * Remove all records which are not permitted for the user
664
     *
665
     * @param array $recs
666
     * @param string $table
667
     * @return array
668
     */
669
    protected function filterPermittedElements($recs, $table)
670
    {
671
        $permittedElements = [];
672
        if (is_array($recs)) {
0 ignored issues
show
introduced by
The condition is_array($recs) is always true.
Loading history...
673
            foreach ($recs as $rec) {
674
                if ($this->isPageAccessibleForCurrentUser($table, $rec) && $this->isLanguageAccessibleForCurrentUser($table, $rec)) {
675
                    $permittedElements[] = $rec;
676
                }
677
            }
678
        }
679
        return $permittedElements;
680
    }
681
682
    /**
683
     * Checking access to the page the record is on, respecting ignored root level restrictions
684
     *
685
     * @param string $table Name of the table
686
     * @param array $record Record row to be checked
687
     * @return bool
688
     */
689
    protected function isPageAccessibleForCurrentUser($table, array $record)
690
    {
691
        $pageIdField = $table === 'pages' ? 'uid' : 'wspid';
692
        $pageId = isset($record[$pageIdField]) ? (int)$record[$pageIdField] : null;
693
        if ($pageId === null) {
694
            return false;
695
        }
696
        if ($pageId === 0 && BackendUtility::isRootLevelRestrictionIgnored($table)) {
697
            return true;
698
        }
699
        $page = BackendUtility::getRecord('pages', $pageId, 'uid,pid,perms_userid,perms_user,perms_groupid,perms_group,perms_everybody');
700
701
        return $GLOBALS['BE_USER']->doesUserHaveAccess($page, Permission::PAGE_SHOW);
702
    }
703
704
    /**
705
     * Check current be users language access on given record.
706
     *
707
     * @param string $table Name of the table
708
     * @param array $record Record row to be checked
709
     * @return bool
710
     */
711
    protected function isLanguageAccessibleForCurrentUser($table, array $record)
712
    {
713
        if (BackendUtility::isTableLocalizable($table)) {
714
            $languageUid = $record[$GLOBALS['TCA'][$table]['ctrl']['languageField']];
715
        } else {
716
            return true;
717
        }
718
        return $GLOBALS['BE_USER']->checkLanguageAccess($languageUid);
719
    }
720
721
    /**
722
     * Determine whether a specific page is new and not yet available in the LIVE workspace
723
     *
724
     * @param int $id Primary key of the page to check
725
     * @param int $language Language for which to check the page
726
     * @return bool
727
     */
728
    public static function isNewPage($id, $language = 0)
729
    {
730
        $isNewPage = false;
731
        // If the language is not default, check state of overlay
732
        if ($language > 0) {
733
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
734
                ->getQueryBuilderForTable('pages');
735
            $queryBuilder->getRestrictions()
736
                ->removeAll()
737
                ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
738
            $row = $queryBuilder->select('t3ver_state')
739
                ->from('pages')
740
                ->where(
741
                    $queryBuilder->expr()->eq(
742
                        $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'],
743
                        $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)
744
                    ),
745
                    $queryBuilder->expr()->eq(
746
                        $GLOBALS['TCA']['pages']['ctrl']['languageField'],
747
                        $queryBuilder->createNamedParameter($language, \PDO::PARAM_INT)
748
                    ),
749
                    $queryBuilder->expr()->eq(
750
                        't3ver_wsid',
751
                        $queryBuilder->createNamedParameter($GLOBALS['BE_USER']->workspace, \PDO::PARAM_INT)
752
                    )
753
                )
754
                ->setMaxResults(1)
755
                ->execute()
756
                ->fetch();
757
758
            if ($row !== false) {
759
                $isNewPage = VersionState::cast($row['t3ver_state'])->equals(VersionState::NEW_PLACEHOLDER);
760
            }
761
        } else {
762
            $rec = BackendUtility::getRecord('pages', $id, 't3ver_state');
763
            if (is_array($rec)) {
764
                $isNewPage = VersionState::cast($rec['t3ver_state'])->equals(VersionState::NEW_PLACEHOLDER);
765
            }
766
        }
767
        return $isNewPage;
768
    }
769
770
    /**
771
     * Determines whether a page has workspace versions.
772
     *
773
     * @param int $workspaceId
774
     * @param int $pageId
775
     * @return bool
776
     */
777
    public function hasPageRecordVersions($workspaceId, $pageId)
778
    {
779
        if ((int)$workspaceId === 0 || (int)$pageId === 0) {
780
            return false;
781
        }
782
783
        if (isset($this->versionsOnPageCache[$workspaceId][$pageId])) {
784
            return $this->versionsOnPageCache[$workspaceId][$pageId];
785
        }
786
787
        $this->versionsOnPageCache[$workspaceId][$pageId] = false;
788
789
        foreach ($GLOBALS['TCA'] as $tableName => $tableConfiguration) {
790
            if ($tableName === 'pages' || !BackendUtility::isTableWorkspaceEnabled($tableName)) {
791
                continue;
792
            }
793
794
            $pages = $this->fetchPagesWithVersionsInTable($workspaceId, $tableName);
795
            // Early break on first match
796
            if (!empty($pages[(string)$pageId])) {
797
                $this->versionsOnPageCache[$workspaceId][$pageId] = true;
798
                break;
799
            }
800
        }
801
802
        $parameters = [
803
            'workspaceId' => $workspaceId,
804
            'pageId' => $pageId,
805
            'versionsOnPageCache' => &$this->versionsOnPageCache,
806
        ];
807
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][\TYPO3\CMS\Workspaces\Service\WorkspaceService::class]['hasPageRecordVersions'] ?? [] as $hookFunction) {
808
            GeneralUtility::callUserFunction($hookFunction, $parameters, $this);
809
        }
810
811
        return $this->versionsOnPageCache[$workspaceId][$pageId];
812
    }
813
814
    /**
815
     * Gets all pages that have workspace versions per table.
816
     *
817
     * Result:
818
     * [
819
     *   'sys_template' => [],
820
     *   'tt_content' => [
821
     *     1 => true,
822
     *     11 => true,
823
     *     13 => true,
824
     *     15 => true
825
     *   ],
826
     *   'tx_something => [
827
     *     15 => true,
828
     *     11 => true,
829
     *     21 => true
830
     *   ],
831
     * ]
832
     *
833
     * @param int $workspaceId
834
     *
835
     * @return array
836
     */
837
    public function getPagesWithVersionsInTable($workspaceId)
838
    {
839
        foreach ($GLOBALS['TCA'] as $tableName => $tableConfiguration) {
840
            if ($tableName === 'pages' || !BackendUtility::isTableWorkspaceEnabled($tableName)) {
841
                continue;
842
            }
843
844
            $this->fetchPagesWithVersionsInTable($workspaceId, $tableName);
845
        }
846
847
        return $this->pagesWithVersionsInTable[$workspaceId];
848
    }
849
850
    /**
851
     * Gets all pages that have workspace versions in a particular table.
852
     *
853
     * Result:
854
     * [
855
     *   1 => true,
856
     *   11 => true,
857
     *   13 => true,
858
     *   15 => true
859
     * ],
860
     *
861
     * @param int $workspaceId
862
     * @param string $tableName
863
     * @return array
864
     */
865
    protected function fetchPagesWithVersionsInTable($workspaceId, $tableName)
866
    {
867
        if ((int)$workspaceId === 0) {
868
            return [];
869
        }
870
871
        if (!isset($this->pagesWithVersionsInTable[$workspaceId])) {
872
            $this->pagesWithVersionsInTable[$workspaceId] = [];
873
        }
874
875
        if (!isset($this->pagesWithVersionsInTable[$workspaceId][$tableName])) {
876
            $this->pagesWithVersionsInTable[$workspaceId][$tableName] = [];
877
878
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($tableName);
879
            $queryBuilder->getRestrictions()
880
                ->removeAll()
881
                ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
882
883
            // Fetch all versioned record within a workspace
884
            $result = $queryBuilder
885
                ->select('pid')
886
                ->from($tableName)
887
                ->where(
888
                    $queryBuilder->expr()->gt(
889
                        't3ver_oid',
890
                        $queryBuilder->createNamedParameter(
891
                            0,
892
                            \PDO::PARAM_INT
893
                        )
894
                    ),
895
                    $queryBuilder->expr()->eq(
896
                        't3ver_wsid',
897
                        $queryBuilder->createNamedParameter(
898
                            $workspaceId,
899
                            \PDO::PARAM_INT
900
                        )
901
                    )
902
                )
903
                ->groupBy('pid')
904
                ->execute();
905
906
            $pageIds = [];
907
            while ($row = $result->fetch()) {
908
                $pageIds[$row['pid']] = true;
909
            }
910
911
            $this->pagesWithVersionsInTable[$workspaceId][$tableName] = $pageIds;
912
913
            $parameters = [
914
                'workspaceId' => $workspaceId,
915
                'tableName' => $tableName,
916
                'pagesWithVersionsInTable' => &$this->pagesWithVersionsInTable,
917
            ];
918
            foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][\TYPO3\CMS\Workspaces\Service\WorkspaceService::class]['fetchPagesWithVersionsInTable'] ?? [] as $hookFunction) {
919
                GeneralUtility::callUserFunction($hookFunction, $parameters, $this);
920
            }
921
        }
922
923
        return $this->pagesWithVersionsInTable[$workspaceId][$tableName];
924
    }
925
926
    /**
927
     * @param string $tableName
928
     * @return QueryBuilder
929
     */
930
    protected function createQueryBuilderForTable(string $tableName)
931
    {
932
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
933
            ->getQueryBuilderForTable($tableName);
934
        $queryBuilder->getRestrictions()
935
            ->removeAll()
936
            ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
937
        return $queryBuilder;
938
    }
939
940
    /**
941
     * @return LanguageService|null
942
     */
943
    protected static function getLanguageService(): ?LanguageService
944
    {
945
        return $GLOBALS['LANG'] ?? null;
946
    }
947
}
948