CategoryPermissionsAspect.php 6.11 KB
Newer Older
1
<?php
2

3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
7
8
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
9
 *
10
11
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
14
 * The TYPO3 project - inspiring people to share!
 */
15

16
17
namespace TYPO3\CMS\Backend\Security;

18
use Psr\Http\Message\ServerRequestInterface;
19
20
21
use TYPO3\CMS\Backend\Tree\TreeNode;
use TYPO3\CMS\Backend\Tree\TreeNodeCollection;
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
29
 * This event listener deals with tree data security which reacts on a PSR-14 event
 * 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
39
{
    /**
     * @var string
     */
40
    private $categoryTableName = 'sys_category';
41
42
43
44

    /**
     * @var BackendUserAuthentication
     */
45
    private $backendUserAuthentication;
46
47
48
49
50
51
52
53
54
55

    /**
     * @param BackendUserAuthentication|null $backendUserAuthentication
     */
    public function __construct($backendUserAuthentication = null)
    {
        $this->backendUserAuthentication = $backendUserAuthentication ?: $GLOBALS['BE_USER'];
    }

    /**
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
67
68
            return;
        }

69
70
71
        $dataProvider = $event->getProvider();
        $treeData = $event->getTreeData();

72
        if (!$this->backendUserAuthentication->isAdmin() && $dataProvider->getTableName() === $this->categoryTableName) {
73
74
75
76
77
78
79
80
81

            // Get User permissions related to category
            $categoryMountPoints = $this->backendUserAuthentication->getCategoryMountPoints();

            // Backup child nodes to be processed.
            $treeNodeCollection = $treeData->getChildNodes();

            if (!empty($categoryMountPoints) && !empty($treeNodeCollection)) {

82
83
                // Check the rootline against categoryMountPoints when tree was filtered
                if ($dataProvider->getRootUid() !== null) {
84
85
86
                    if (in_array($dataProvider->getRootUid(), $categoryMountPoints)) {
                        return;
                    }
87
88
89
90
91
92
93
                    $uidsInRootline = $this->findUidsInRootline($dataProvider->getRootUid());
                    if (!empty(array_intersect($categoryMountPoints, $uidsInRootline))) {
                        // One of the parents was found in categoryMountPoints so all children are secure
                        return;
                    }
                }

94
                // First, remove all child nodes which must be analyzed to be considered as "secure".
95
96
97
98
99
                // The nodes were backed up in variable $treeNodeCollection beforehand.
                $treeData->removeChildNodes();

                // Create an empty tree node collection to receive the secured nodes.
                /** @var TreeNodeCollection $securedTreeNodeCollection */
100
                $securedTreeNodeCollection = GeneralUtility::makeInstance(TreeNodeCollection::class);
101
102
103

                foreach ($categoryMountPoints as $categoryMountPoint) {
                    $treeNode = $this->lookUpCategoryMountPointInTreeNodes((int)$categoryMountPoint, $treeNodeCollection);
104
                    if ($treeNode !== null) {
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
                        $securedTreeNodeCollection->append($treeNode);
                    }
                }

                // Reset child nodes.
                $treeData->setChildNodes($securedTreeNodeCollection);
            }
        }
    }

    /**
     * Recursively look up for a category mount point within a tree.
     *
     * @param int $categoryMountPoint
     * @param TreeNodeCollection $treeNodeCollection
120
     * @return TreeNode|null
121
     */
122
    private function lookUpCategoryMountPointInTreeNodes($categoryMountPoint, TreeNodeCollection $treeNodeCollection)
123
124
125
126
127
128
    {
        $result = null;

        // If any User permission, recursively traverse the tree and set tree part as mount point
        foreach ($treeNodeCollection as $treeNode) {

129
            /** @var TreeNode $treeNode */
130
131
132
133
134
135
136
137
138
            if ((int)$treeNode->getId() === $categoryMountPoint) {
                $result = $treeNode;
                break;
            }

            if ($treeNode->hasChildNodes()) {

                /** @var TreeNode $node */
                $node = $this->lookUpCategoryMountPointInTreeNodes($categoryMountPoint, $treeNode->getChildNodes());
139
                if ($node !== null) {
140
141
142
143
144
145
146
                    $result = $node;
                    break;
                }
            }
        }
        return $result;
    }
147
148
149
150
151
152
153

    /**
     * Find parent uids in rootline
     *
     * @param int $uid
     * @return array
     */
154
    private function findUidsInRootline($uid)
155
    {
156
157
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
            ->getQueryBuilderForTable($this->categoryTableName);
158
159
160
161
162
163
164
        $row = $queryBuilder
            ->select('parent')
            ->from($this->categoryTableName)
            ->where(
                $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT))
            )
            ->execute()
165
            ->fetchAssociative();
166
167
168
169
170
171
172
173

        $parentUids = [];
        if ($row['parent'] > 0) {
            $parentUids = $this->findUidsInRootline($row['parent']);
            $parentUids[] = $row['parent'];
        }
        return $parentUids;
    }
174
}