Completed
Push — master ( ba0ea8...c1bb75 )
by
unknown
25:13 queued 10:52
created

GroupResolver::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
c 1
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the TYPO3 CMS project.
7
 *
8
 * It is free software; you can redistribute it and/or modify it under
9
 * the terms of the GNU General Public License, either version 2
10
 * of the License, or any later version.
11
 *
12
 * For the full copyright and license information, please read the
13
 * LICENSE.txt file that was distributed with this source code.
14
 *
15
 * The TYPO3 project - inspiring people to share!
16
 */
17
18
namespace TYPO3\CMS\Core\Authentication;
19
20
use Psr\EventDispatcher\EventDispatcherInterface;
21
use TYPO3\CMS\Core\Authentication\Event\AfterGroupsResolvedEvent;
22
use TYPO3\CMS\Core\Database\Connection;
23
use TYPO3\CMS\Core\Database\ConnectionPool;
24
use TYPO3\CMS\Core\Utility\GeneralUtility;
25
26
/**
27
 * A provider for resolving fe_groups / be_groups, including nested sub groups.
28
 *
29
 * When fetching subgroups, the current group (parent group) is handed in recursive.
30
 * Duplicates are suppressed: If a sub group is including in multiple parent groups,
31
 * it will be resolved only once.
32
 *
33
 * @internal this is not part of TYPO3 Core API.
34
 */
35
class GroupResolver
36
{
37
    protected EventDispatcherInterface $eventDispatcher;
38
    protected string $sourceTable = '';
39
    protected string $sourceField = 'usergroup';
40
    protected string $recursiveSourceField = 'subgroup';
41
42
    public function __construct(EventDispatcherInterface $eventDispatcher)
43
    {
44
        $this->eventDispatcher = $eventDispatcher;
45
    }
46
47
    /**
48
     * Fetch all group records for a given user recursive.
49
     *
50
     * Note order is important: A user with main groups "1,2", where 1 has sub group 3,
51
     * results in "3,1,2" as record list array - sub groups are listed before the group
52
     * that includes the sub group.
53
     *
54
     * @param array $userRecord Used for context in PSR-14 event
55
     * @param string $sourceTable The database table to look up: be_groups / fe_groups depending on context
56
     * @return array List of group records. Note the ordering note above.
57
     */
58
    public function resolveGroupsForUser(array $userRecord, string $sourceTable): array
59
    {
60
        $this->sourceTable = $sourceTable;
61
        $originalGroupIds = GeneralUtility::intExplode(',', $userRecord[$this->sourceField] ?? '', true);
62
        $resolvedGroups = $this->fetchGroupsRecursive($originalGroupIds);
63
        $event = $this->eventDispatcher->dispatch(new AfterGroupsResolvedEvent($sourceTable, $resolvedGroups, $originalGroupIds, $userRecord));
64
        return $event->getGroups();
65
    }
66
67
    /**
68
     * Load a list of group uids, and take into account if groups have been loaded before.
69
     *
70
     * @param int[] $groupIds
71
     * @param array $processedGroupIds
72
     * @return array
73
     */
74
    protected function fetchGroupsRecursive(array $groupIds, array $processedGroupIds = []): array
75
    {
76
        if (empty($groupIds)) {
77
            return [];
78
        }
79
        $foundGroups = $this->fetchRowsFromDatabase($groupIds);
80
        $validGroups = [];
81
        foreach ($groupIds as $groupId) {
82
            // Database did not find the record
83
            if (!is_array($foundGroups[$groupId])) {
84
                continue;
85
            }
86
            // Record was already processed, continue to avoid adding this group again
87
            if (in_array($groupId, $processedGroupIds, true)) {
88
                continue;
89
            }
90
            // Add sub groups first
91
            $subgroupIds = GeneralUtility::intExplode(',', $foundGroups[$groupId][$this->recursiveSourceField] ?? '', true);
92
            if (!empty($subgroupIds)) {
93
                $subgroups = $this->fetchGroupsRecursive($subgroupIds, array_merge($processedGroupIds, [$groupId]));
94
                $validGroups = array_merge($validGroups, $subgroups);
95
            }
96
            // Add main group after sub groups have been added
97
            $validGroups[] = $foundGroups[$groupId];
98
        }
99
        return $validGroups;
100
    }
101
102
    /**
103
     * Does the database query. Does not care about ordering, this is done by caller.
104
     *
105
     * @param array $groupIds
106
     * @return array Full records with record uid as key
107
     */
108
    protected function fetchRowsFromDatabase(array $groupIds): array
109
    {
110
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->sourceTable);
111
        $result = $queryBuilder
112
            ->select('*')
113
            ->from($this->sourceTable)
114
            ->where(
115
                $queryBuilder->expr()->in(
116
                    'uid',
117
                    $queryBuilder->createNamedParameter(
118
                        $groupIds,
119
                        Connection::PARAM_INT_ARRAY
120
                    )
121
                )
122
            )
123
            ->execute();
124
        $groups = [];
125
        while ($row = $result->fetch()) {
126
            $groups[(int)$row['uid']] = $row;
127
        }
128
        return $groups;
129
    }
130
}
131