Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
99.01% covered (success)
99.01%
100 / 101
90.91% covered (success)
90.91%
10 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
BasicPermissionRepository
99.01% covered (success)
99.01%
100 / 101
90.91% covered (success)
90.91%
10 / 11
28
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 grantUserRight
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 getRightData
93.33% covered (success)
93.33%
14 / 15
0.00% covered (danger)
0.00%
0 / 1
4.00
 getRightId
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
3
 checkUserRight
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
2
 getUserRights
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
5
 addRight
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
1
 renameRight
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 getAllRightsData
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
8
 refuseAllUserRights
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 nextRightId
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3/**
4 * BasicPermission Repository.
5 *
6 * This Source Code Form is subject to the terms of the Mozilla Public License,
7 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
8 * obtain one at https://mozilla.org/MPL/2.0/.
9 *
10 * @package   phpMyFAQ
11 * @author    Thorsten Rinne <thorsten@phpmyfaq.de>
12 * @copyright 2025 phpMyFAQ Team
13 * @license   https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
14 * @link      https://www.phpmyfaq.de
15 * @since     2025-11-09
16 */
17
18declare(strict_types=1);
19
20namespace phpMyFAQ\Permission;
21
22use phpMyFAQ\Configuration;
23use phpMyFAQ\Database;
24
25readonly class BasicPermissionRepository
26{
27    public function __construct(
28        private Configuration $configuration,
29    ) {
30    }
31
32    /**
33     * Grants a user right by inserting into the faquser_right table.
34     *
35     * @param int $userId  User ID
36     * @param int $rightId Right ID
37     */
38    public function grantUserRight(int $userId, int $rightId): bool
39    {
40        $insert = sprintf(
41            'INSERT INTO %sfaquser_right (user_id, right_id) VALUES (%d, %d)',
42            Database::getTablePrefix(),
43            $userId,
44            $rightId,
45        );
46
47        return (bool) $this->configuration->getDb()->query($insert);
48    }
49
50    /**
51     * Returns an associative array with all data stored in the
52     * database for the specified right.
53     *
54     * @param int $rightId Right ID
55     * @return array<string, mixed>
56     */
57    public function getRightData(int $rightId): array
58    {
59        $select = sprintf('
60            SELECT
61                right_id,
62                name,
63                description,
64                for_users,
65                for_groups,
66                for_sections
67            FROM
68                %sfaqright
69            WHERE
70                right_id = %d', Database::getTablePrefix(), $rightId);
71
72        $res = $this->configuration->getDb()->query($select);
73        if ($this->configuration->getDb()->numRows($res) !== 1) {
74            return [];
75        }
76
77        $rightData = $this->configuration->getDb()->fetchArray($res);
78        if (!is_array($rightData)) {
79            return [];
80        }
81
82        $rightData['for_users'] = (bool) $rightData['for_users'];
83        $rightData['for_groups'] = (bool) $rightData['for_groups'];
84        $rightData['for_sections'] = (bool) $rightData['for_sections'];
85
86        $normalizedRightData = [];
87        foreach ($rightData as $fieldName => $fieldValue) {
88            $normalizedRightData[(string) $fieldName] = $fieldValue;
89        }
90
91        return $normalizedRightData;
92    }
93
94    /**
95     * Returns the right-ID of the right with the name $name.
96     *
97     * @param string $name Right name
98     */
99    public function getRightId(string $name): int
100    {
101        $select = sprintf(
102            "
103            SELECT
104                right_id
105            FROM
106                %sfaqright
107            WHERE
108                name = '%s'",
109            Database::getTablePrefix(),
110            $this->configuration->getDb()->escape($name),
111        );
112
113        $res = $this->configuration->getDb()->query($select);
114        if ($this->configuration->getDb()->numRows($res) !== 1) {
115            return 0;
116        }
117
118        $row = $this->configuration->getDb()->fetchArray($res);
119
120        return is_array($row) ? (int) ($row['right_id'] ?? 0) : 0;
121    }
122
123    /**
124     * Returns true if the user given by user_id has the right
125     * specified by right_id, otherwise false.
126     *
127     * @param int $userId  User ID
128     * @param int $rightId Right ID
129     */
130    public function checkUserRight(int $userId, int $rightId): bool
131    {
132        if ($rightId <= 0) {
133            return false;
134        }
135
136        $select = sprintf(
137            '
138            SELECT
139                fr.right_id AS right_id
140            FROM
141                %sfaqright fr,
142                %sfaquser_right fur,
143                %sfaquser fu
144            WHERE
145                fr.right_id = %d AND
146                fr.right_id = fur.right_id AND
147                fu.user_id  = %d AND
148                fu.user_id  = fur.user_id',
149            Database::getTablePrefix(),
150            Database::getTablePrefix(),
151            Database::getTablePrefix(),
152            $rightId,
153            $userId,
154        );
155
156        $res = $this->configuration->getDb()->query($select);
157
158        return $this->configuration->getDb()->numRows($res) === 1;
159    }
160
161    /**
162     * Returns an array with the IDs of all user-rights the user
163     * specified by user_id owns. Group rights are not taken into
164     * account.
165     *
166     * @param int $userId User ID
167     * @return array<int>
168     */
169    public function getUserRights(int $userId): array
170    {
171        $select = sprintf(
172            '
173            SELECT
174                fr.right_id AS right_id
175            FROM
176                %sfaqright fr,
177                %sfaquser_right fur,
178                %sfaquser fu
179            WHERE
180                fr.right_id = fur.right_id AND
181                fu.user_id  = %d AND
182                fu.user_id  = fur.user_id',
183            Database::getTablePrefix(),
184            Database::getTablePrefix(),
185            Database::getTablePrefix(),
186            $userId,
187        );
188
189        $res = $this->configuration->getDb()->query($select);
190        $result = [];
191        while (true) {
192            $row = $this->configuration->getDb()->fetchArray($res);
193            if ($row === false || $row === null || $row === []) {
194                break;
195            }
196
197            $result[] = (int) $row['right_id'];
198        }
199
200        return $result;
201    }
202
203    /**
204     * Adds a new right into the database. Returns the ID of the new right.
205     *
206     * @param array<string, string|int> $rightData Array of rights
207     */
208    public function addRight(array $rightData, int $nextId): bool
209    {
210        $db = $this->configuration->getDb();
211        $insert = sprintf(
212            "
213            INSERT INTO
214                %sfaqright
215            (right_id, name, description, for_users, for_groups, for_sections)
216                VALUES
217            (%d, '%s', '%s', %d, %d, %d)",
218            Database::getTablePrefix(),
219            $nextId,
220            $db->escape((string) $rightData['name']),
221            $db->escape((string) $rightData['description']),
222            $rightData['for_users'] ?? 1,
223            $rightData['for_groups'] ?? 1,
224            $rightData['for_sections'] ?? 1,
225        );
226
227        return (bool) $db->query($insert);
228    }
229
230    /**
231     * Renames a right, only used for updates.
232     *
233     * @param int    $rightId Right ID
234     * @param string $newName New name
235     */
236    public function renameRight(int $rightId, string $newName): bool
237    {
238        $db = $this->configuration->getDb();
239        $update = sprintf('
240            UPDATE
241                %sfaqright
242            SET
243                name = \'%s\'
244            WHERE
245                right_id = %d', Database::getTablePrefix(), $db->escape($newName), $rightId);
246
247        return (bool) $db->query($update);
248    }
249
250    /**
251     * Returns an array that contains all rights stored in the database.
252     *
253     * @param string $order Ordering (ASC or DESC)
254     * @return array<int, array<string, mixed>>
255     */
256    public function getAllRightsData(string $order = 'ASC'): array
257    {
258        $order = strtoupper($order) === 'DESC' ? 'DESC' : 'ASC';
259        $select = sprintf('
260            SELECT
261                right_id,
262                name,
263                description,
264                for_users,
265                for_groups,
266                for_sections
267            FROM
268                %sfaqright
269            ORDER BY
270                right_id %s', Database::getTablePrefix(), $order);
271
272        $res = $this->configuration->getDb()->query($select);
273        $result = [];
274        $i = 0;
275
276        if ($res) {
277            while (true) {
278                $row = $this->configuration->getDb()->fetchArray($res);
279                if ($row === false || $row === null || $row === []) {
280                    break;
281                }
282
283                $normalizedRow = [];
284                foreach ($row as $fieldName => $fieldValue) {
285                    $normalizedRow[(string) $fieldName] = $fieldValue;
286                }
287
288                $result[$i] = $normalizedRow;
289                ++$i;
290            }
291        }
292
293        return $result;
294    }
295
296    /**
297     * Refuses all user rights.
298     *
299     * @param int $userId User ID
300     */
301    public function refuseAllUserRights(int $userId): bool
302    {
303        $delete = sprintf('
304            DELETE FROM
305                %sfaquser_right
306            WHERE
307                user_id  = %d', Database::getTablePrefix(), $userId);
308
309        return (bool) $this->configuration->getDb()->query($delete);
310    }
311
312    /**
313     * Generates the next ID for the faqright table.
314     */
315    public function nextRightId(): int
316    {
317        return $this->configuration->getDb()->nextId(Database::getTablePrefix() . 'faqright', 'right_id');
318    }
319}