Passed
Push — master ( 970d9b...cc8532 )
by
unknown
14:07
created

CategoryPermissionsAspect::__construct()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
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\Security;
17
18
use Psr\Http\Message\ServerRequestInterface;
19
use TYPO3\CMS\Backend\Tree\TreeNode;
20
use TYPO3\CMS\Backend\Tree\TreeNodeCollection;
21
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
22
use TYPO3\CMS\Core\Database\ConnectionPool;
23
use TYPO3\CMS\Core\Http\ApplicationType;
24
use TYPO3\CMS\Core\Tree\Event\ModifyTreeDataEvent;
25
use TYPO3\CMS\Core\Utility\GeneralUtility;
26
27
/**
28
 * This event listener deals with tree data security which reacts on a PSR-14 event
29
 * on data object initialization.
30
 *
31
 * The aspect defines category mount points according to BE User permissions.
32
 *
33
 * @internal This class is TYPO3-internal hook and is not considered part of the Public TYPO3 API.
34
 */
35
final class CategoryPermissionsAspect
36
{
37
    /**
38
     * @var string
39
     */
40
    private $categoryTableName = 'sys_category';
41
42
    /**
43
     * @var BackendUserAuthentication
44
     */
45
    private $backendUserAuthentication;
46
47
    /**
48
     * @param BackendUserAuthentication|null $backendUserAuthentication
49
     */
50
    public function __construct($backendUserAuthentication = null)
51
    {
52
        $this->backendUserAuthentication = $backendUserAuthentication ?: $GLOBALS['BE_USER'];
53
    }
54
55
    /**
56
     * The listener for the event in DatabaseTreeDataProvider, which only affects the TYPO3 Backend
57
     *
58
     * @param ModifyTreeDataEvent $event
59
     */
60
    public function addUserPermissionsToCategoryTreeData(ModifyTreeDataEvent $event): void
61
    {
62
        // Only evaluate this in the backend
63
        if (!($GLOBALS['TYPO3_REQUEST'] ?? null) instanceof ServerRequestInterface
64
            || ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isBackend()
65
        ) {
66
            return;
67
        }
68
69
        $dataProvider = $event->getProvider();
70
        $treeData = $event->getTreeData();
71
72
        if (!$this->backendUserAuthentication->isAdmin() && $dataProvider->getTableName() === $this->categoryTableName) {
0 ignored issues
show
Bug introduced by
The method getTableName() does not exist on TYPO3\CMS\Core\Tree\Tabl...urationTreeDataProvider. Since it exists in all sub-types, consider adding an abstract or default implementation to TYPO3\CMS\Core\Tree\Tabl...urationTreeDataProvider. ( Ignorable by Annotation )

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

72
        if (!$this->backendUserAuthentication->isAdmin() && $dataProvider->/** @scrutinizer ignore-call */ getTableName() === $this->categoryTableName) {
Loading history...
73
74
            // Get User permissions related to category
75
            $categoryMountPoints = $this->backendUserAuthentication->getCategoryMountPoints();
76
77
            // Backup child nodes to be processed.
78
            $treeNodeCollection = $treeData->getChildNodes();
79
80
            if (!empty($categoryMountPoints) && !empty($treeNodeCollection)) {
81
82
                // Check the rootline against categoryMountPoints when tree was filtered
83
                if ($dataProvider->getRootUid() !== null) {
0 ignored issues
show
Bug introduced by
The method getRootUid() does not exist on TYPO3\CMS\Core\Tree\Tabl...urationTreeDataProvider. Did you maybe mean getRoot()? ( Ignorable by Annotation )

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

83
                if ($dataProvider->/** @scrutinizer ignore-call */ getRootUid() !== null) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
84
                    if (in_array($dataProvider->getRootUid(), $categoryMountPoints)) {
85
                        return;
86
                    }
87
                    $uidsInRootline = $this->findUidsInRootline($dataProvider->getRootUid());
88
                    if (!empty(array_intersect($categoryMountPoints, $uidsInRootline))) {
89
                        // One of the parents was found in categoryMountPoints so all children are secure
90
                        return;
91
                    }
92
                }
93
94
                // First, remove all child nodes which must be analyzed to be considered as "secure".
95
                // The nodes were backed up in variable $treeNodeCollection beforehand.
96
                $treeData->removeChildNodes();
97
98
                // Create an empty tree node collection to receive the secured nodes.
99
                /** @var TreeNodeCollection $securedTreeNodeCollection */
100
                $securedTreeNodeCollection = GeneralUtility::makeInstance(TreeNodeCollection::class);
101
102
                foreach ($categoryMountPoints as $categoryMountPoint) {
103
                    $treeNode = $this->lookUpCategoryMountPointInTreeNodes((int)$categoryMountPoint, $treeNodeCollection);
104
                    if ($treeNode !== null) {
105
                        $securedTreeNodeCollection->append($treeNode);
106
                    }
107
                }
108
109
                // Reset child nodes.
110
                $treeData->setChildNodes($securedTreeNodeCollection);
111
            }
112
        }
113
    }
114
115
    /**
116
     * Recursively look up for a category mount point within a tree.
117
     *
118
     * @param int $categoryMountPoint
119
     * @param TreeNodeCollection $treeNodeCollection
120
     * @return TreeNode|null
121
     */
122
    private function lookUpCategoryMountPointInTreeNodes($categoryMountPoint, TreeNodeCollection $treeNodeCollection)
123
    {
124
        $result = null;
125
126
        // If any User permission, recursively traverse the tree and set tree part as mount point
127
        foreach ($treeNodeCollection as $treeNode) {
128
129
            /** @var TreeNode $treeNode */
130
            if ((int)$treeNode->getId() === $categoryMountPoint) {
131
                $result = $treeNode;
132
                break;
133
            }
134
135
            if ($treeNode->hasChildNodes()) {
136
137
                /** @var TreeNode $node */
138
                $node = $this->lookUpCategoryMountPointInTreeNodes($categoryMountPoint, $treeNode->getChildNodes());
139
                if ($node !== null) {
140
                    $result = $node;
141
                    break;
142
                }
143
            }
144
        }
145
        return $result;
146
    }
147
148
    /**
149
     * Find parent uids in rootline
150
     *
151
     * @param int $uid
152
     * @return array
153
     */
154
    private function findUidsInRootline($uid)
155
    {
156
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
157
            ->getQueryBuilderForTable($this->categoryTableName);
158
        $row = $queryBuilder
159
            ->select('parent')
160
            ->from($this->categoryTableName)
161
            ->where(
162
                $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT))
163
            )
164
            ->execute()
165
            ->fetch();
166
167
        $parentUids = [];
168
        if ($row['parent'] > 0) {
169
            $parentUids = $this->findUidsInRootline($row['parent']);
170
            $parentUids[] = $row['parent'];
171
        }
172
        return $parentUids;
173
    }
174
}
175