Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.64% covered (success)
98.64%
218 / 221
86.96% covered (success)
86.96%
20 / 23
CRAP
0.00% covered (danger)
0.00%
0 / 1
MediumPermissionRepository
98.64% covered (success)
98.64%
218 / 221
86.96% covered (success)
86.96%
20 / 23
77
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
 getGroupRights
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
6
 checkUserGroupRight
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
3
 grantGroupRight
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 addGroup
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 getGroupId
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
3
 changeGroup
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
5
 deleteGroup
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 deleteGroupMemberships
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 deleteGroupRights
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getGroupMembers
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
6
 getUserGroups
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
6
 getAllGroups
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
6
 getGroupName
88.89% covered (success)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
4.02
 getUserGroupRights
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
6
 getAutoJoinGroups
90.91% covered (success)
90.91%
10 / 11
0.00% covered (danger)
0.00%
0 / 1
6.03
 addToGroup
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 removeFromGroup
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 getGroupData
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
4.01
 removeFromAllGroups
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 refuseAllGroupRights
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 removeAllUsersFromGroup
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 nextGroupId
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3/**
4 * MediumPermission 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-01-09
16 */
17
18declare(strict_types=1);
19
20namespace phpMyFAQ\Permission;
21
22use phpMyFAQ\Configuration;
23use phpMyFAQ\Database;
24
25readonly class MediumPermissionRepository
26{
27    public function __construct(
28        private Configuration $configuration,
29    ) {
30    }
31
32    /**
33     * Returns an array that contains the right-IDs of all
34     * group-rights the group $groupId owns.
35     *
36     * @param int $groupId Group ID
37     * @return array<int>
38     */
39    public function getGroupRights(int $groupId): array
40    {
41        if ($groupId <= 0) {
42            return [];
43        }
44
45        $select = sprintf(
46            '
47            SELECT
48                fr.right_id AS right_id
49            FROM
50                %sfaqright fr,
51                %sfaqgroup_right fgr,
52                %sfaqgroup fg
53            WHERE
54                fg.group_id = %d AND
55                fg.group_id = fgr.group_id AND
56                fr.right_id = fgr.right_id',
57            Database::getTablePrefix(),
58            Database::getTablePrefix(),
59            Database::getTablePrefix(),
60            $groupId,
61        );
62
63        $res = $this->configuration->getDb()->query($select);
64        $result = [];
65        while (true) {
66            $row = $this->configuration->getDb()->fetchArray($res);
67            if ($row === false || $row === null || $row === []) {
68                break;
69            }
70
71            $result[] = (int) $row['right_id'];
72        }
73
74        return $result;
75    }
76
77    /**
78     * Returns true if the user $userId owns the right $rightId
79     * because of a group-membership, otherwise false.
80     *
81     * @param int $userId  User ID
82     * @param int $rightId Right ID
83     */
84    public function checkUserGroupRight(int $userId, int $rightId): bool
85    {
86        if ($rightId <= 0 || $userId <= 0) {
87            return false;
88        }
89
90        $select = sprintf(
91            '
92            SELECT
93                fr.right_id AS right_id
94            FROM
95                %sfaqright fr,
96                %sfaqgroup_right fgr,
97                %sfaqgroup fg,
98                %sfaquser_group fug,
99                %sfaquser fu
100            WHERE
101                fr.right_id = %d AND
102                fr.right_id = fgr.right_id AND
103                fg.group_id = fgr.group_id AND
104                fg.group_id = fug.group_id AND
105                fu.user_id  = fug.user_id AND
106                fu.user_id  = %d',
107            Database::getTablePrefix(),
108            Database::getTablePrefix(),
109            Database::getTablePrefix(),
110            Database::getTablePrefix(),
111            Database::getTablePrefix(),
112            $rightId,
113            $userId,
114        );
115
116        $res = $this->configuration->getDb()->query($select);
117        return $this->configuration->getDb()->numRows($res) !== 0;
118    }
119
120    /**
121     * Grants the group given by $groupId the right specified by $rightId.
122     *
123     * @param int $groupId Group ID
124     * @param int $rightId Right ID
125     */
126    public function grantGroupRight(int $groupId, int $rightId): bool
127    {
128        if ($rightId <= 0 || $groupId <= 0) {
129            return false;
130        }
131
132        $insert = sprintf(
133            'INSERT INTO %sfaqgroup_right (group_id, right_id) VALUES (%d, %d)',
134            Database::getTablePrefix(),
135            $groupId,
136            $rightId,
137        );
138
139        return (bool) $this->configuration->getDb()->query($insert);
140    }
141
142    /**
143     * Adds a new group to the database and returns the ID of the new group.
144     *
145     * @param array<string, string|int> $groupData Array of group data
146     * @param int $nextId Next group ID
147     */
148    public function addGroup(array $groupData, int $nextId): bool
149    {
150        $insert = sprintf(
151            "INSERT INTO %sfaqgroup (group_id, name, description, auto_join) VALUES (%d, '%s', '%s', '%s')",
152            Database::getTablePrefix(),
153            $nextId,
154            $this->configuration->getDb()->escape((string) $groupData['name']),
155            $this->configuration->getDb()->escape((string) $groupData['description']),
156            (int) $groupData['auto_join'],
157        );
158
159        return (bool) $this->configuration->getDb()->query($insert);
160    }
161
162    /**
163     * Returns the ID of the group that has the name $name.
164     *
165     * @param string $name Group name
166     */
167    public function getGroupId(string $name): int
168    {
169        $select = sprintf(
170            "SELECT group_id FROM %sfaqgroup WHERE name = '%s'",
171            Database::getTablePrefix(),
172            $this->configuration->getDb()->escape($name),
173        );
174
175        $res = $this->configuration->getDb()->query($select);
176        if ($this->configuration->getDb()->numRows($res) !== 1) {
177            return 0;
178        }
179
180        $row = $this->configuration->getDb()->fetchArray($res);
181
182        return is_array($row) ? (int) ($row['group_id'] ?? 0) : 0;
183    }
184
185    /**
186     * Changes the group data of the given group.
187     *
188     * @param int $groupId Group ID
189     * @param array<string, string|int> $groupData Array of group data
190     */
191    public function changeGroup(int $groupId, array $groupData): bool
192    {
193        $db = $this->configuration->getDb();
194        $allowedColumns = ['name', 'description', 'auto_join'];
195        $set = '';
196        $comma = '';
197
198        foreach (array_keys($groupData) as $key) {
199            if (!in_array($key, $allowedColumns, strict: true)) {
200                continue;
201            }
202
203            $value = $key === 'auto_join' ? (string) (int) $groupData[$key] : $db->escape((string) $groupData[$key]);
204            $set .= $comma . $key . " = '" . $value . "'";
205            $comma = ",\n                ";
206        }
207
208        if ($set === '') {
209            return false;
210        }
211
212        $update = sprintf('
213            UPDATE
214                %sfaqgroup
215            SET
216                %s
217            WHERE
218                group_id = %d', Database::getTablePrefix(), $set, $groupId);
219
220        return (bool) $db->query($update);
221    }
222
223    /**
224     * Removes the group given by $groupId from the database.
225     *
226     * @param int $groupId Group ID
227     */
228    public function deleteGroup(int $groupId): bool
229    {
230        if ($groupId <= 0) {
231            return false;
232        }
233
234        $delete = sprintf('DELETE FROM %sfaqgroup WHERE group_id = %d', Database::getTablePrefix(), $groupId);
235
236        return (bool) $this->configuration->getDb()->query($delete);
237    }
238
239    /**
240     * Deletes all group memberships for the given group.
241     *
242     * @param int $groupId Group ID
243     */
244    public function deleteGroupMemberships(int $groupId): bool
245    {
246        $delete = sprintf('DELETE FROM %sfaquser_group WHERE group_id = %d', Database::getTablePrefix(), $groupId);
247
248        return (bool) $this->configuration->getDb()->query($delete);
249    }
250
251    /**
252     * Deletes all group rights for the given group.
253     *
254     * @param int $groupId Group ID
255     */
256    public function deleteGroupRights(int $groupId): bool
257    {
258        $delete = sprintf('DELETE FROM %sfaqgroup_right WHERE group_id = %d', Database::getTablePrefix(), $groupId);
259
260        return (bool) $this->configuration->getDb()->query($delete);
261    }
262
263    /**
264     * Returns an array that contains the user-IDs of all members of the group $groupId.
265     *
266     * @param int $groupId Group ID
267     * @return array<int>
268     */
269    public function getGroupMembers(int $groupId): array
270    {
271        if ($groupId <= 0) {
272            return [];
273        }
274
275        $select = sprintf(
276            '
277            SELECT
278                fu.user_id AS user_id
279            FROM
280                %sfaquser fu,
281                %sfaquser_group fug,
282                %sfaqgroup fg
283            WHERE
284                fg.group_id = %d AND
285                fg.group_id = fug.group_id AND
286                fu.user_id  = fug.user_id',
287            Database::getTablePrefix(),
288            Database::getTablePrefix(),
289            Database::getTablePrefix(),
290            $groupId,
291        );
292
293        $res = $this->configuration->getDb()->query($select);
294        $result = [];
295        while (true) {
296            $row = $this->configuration->getDb()->fetchArray($res);
297            if ($row === false || $row === null || $row === []) {
298                break;
299            }
300
301            $result[] = (int) $row['user_id'];
302        }
303
304        return $result;
305    }
306
307    /**
308     * Returns an array that contains the IDs of all groups in which
309     * the user $userId is a member.
310     *
311     * @param int $userId User ID
312     * @return array<int>
313     */
314    public function getUserGroups(int $userId): array
315    {
316        if ($userId <= 0) {
317            return [-1];
318        }
319
320        $select = sprintf(
321            '
322            SELECT
323                fg.group_id AS group_id
324            FROM
325                %sfaquser fu,
326                %sfaquser_group fug,
327                %sfaqgroup fg
328            WHERE
329                fu.user_id  = %d AND
330                fu.user_id  = fug.user_id AND
331                fg.group_id = fug.group_id',
332            Database::getTablePrefix(),
333            Database::getTablePrefix(),
334            Database::getTablePrefix(),
335            $userId,
336        );
337
338        $res = $this->configuration->getDb()->query($select);
339        $result = [-1];
340        while (true) {
341            $row = $this->configuration->getDb()->fetchArray($res);
342            if ($row === false || $row === null || $row === []) {
343                break;
344            }
345
346            $result[] = (int) $row['group_id'];
347        }
348
349        return $result;
350    }
351
352    /**
353     * Returns an array with the IDs of all groups stored in the database.
354     *
355     * @param int|null $userId Optional user ID to filter groups by user membership
356     * @return array<int>
357     */
358    public function getAllGroups(?int $userId = null): array
359    {
360        $select = sprintf('SELECT group_id FROM %sfaqgroup', Database::getTablePrefix());
361        if ($userId !== null) {
362            $select = sprintf(
363                '
364                SELECT
365                    fg.group_id
366                FROM
367                    %sfaqgroup fg
368                LEFT JOIN
369                    %sfaquser_group fug ON fg.group_id = fug.group_id
370                WHERE
371                    fug.user_id = %d',
372                Database::getTablePrefix(),
373                Database::getTablePrefix(),
374                $userId,
375            );
376        }
377
378        $res = $this->configuration->getDb()->query($select);
379        $result = [];
380        while (true) {
381            $row = $this->configuration->getDb()->fetchArray($res);
382            if ($row === false || $row === null || $row === []) {
383                break;
384            }
385
386            $result[] = (int) $row['group_id'];
387        }
388
389        return $result;
390    }
391
392    /**
393     * Returns the name of the group $groupId.
394     *
395     * @param int $groupId Group ID
396     */
397    public function getGroupName(int $groupId): string
398    {
399        if ($groupId <= 0) {
400            return '-';
401        }
402
403        $select = sprintf('
404            SELECT
405                name
406            FROM
407                %sfaqgroup
408            WHERE
409                group_id = %d', Database::getTablePrefix(), $groupId);
410
411        $res = $this->configuration->getDb()->query($select);
412        if ($this->configuration->getDb()->numRows($res) !== 1) {
413            return '-';
414        }
415
416        $row = $this->configuration->getDb()->fetchArray($res);
417
418        return is_array($row) ? (string) ($row['name'] ?? '-') : '-';
419    }
420
421    /**
422     * Returns an array that contains the IDs of all rights the user
423     * $userId owns because of a group-membership.
424     *
425     * @param int $userId User ID
426     * @return array<int>
427     */
428    public function getUserGroupRights(int $userId): array
429    {
430        if ($userId <= 0) {
431            return [];
432        }
433
434        $select = sprintf(
435            '
436            SELECT
437                fr.right_id AS right_id
438            FROM
439                %sfaqright fr,
440                %sfaqgroup_right fgr,
441                %sfaqgroup fg,
442                %sfaquser_group fug,
443                %sfaquser fu
444            WHERE
445                fu.user_id  = %d AND
446                fu.user_id  = fug.user_id AND
447                fg.group_id = fug.group_id AND
448                fg.group_id = fgr.group_id AND
449                fr.right_id = fgr.right_id',
450            Database::getTablePrefix(),
451            Database::getTablePrefix(),
452            Database::getTablePrefix(),
453            Database::getTablePrefix(),
454            Database::getTablePrefix(),
455            $userId,
456        );
457
458        $res = $this->configuration->getDb()->query($select);
459        $result = [];
460        while (true) {
461            $row = $this->configuration->getDb()->fetchArray($res);
462            if ($row === false || $row === null || $row === []) {
463                break;
464            }
465
466            $result[] = (int) $row['right_id'];
467        }
468
469        return $result;
470    }
471
472    /**
473     * Returns groups with auto_join option enabled.
474     *
475     * @return array<int>
476     */
477    public function getAutoJoinGroups(): array
478    {
479        $select = sprintf('SELECT group_id FROM %sfaqgroup WHERE auto_join = 1', Database::getTablePrefix());
480
481        $res = $this->configuration->getDb()->query($select);
482        if (!$res) {
483            return [];
484        }
485
486        $result = [];
487        while (true) {
488            $row = $this->configuration->getDb()->fetchArray($res);
489            if ($row === false || $row === null || $row === []) {
490                break;
491            }
492
493            $result[] = (int) $row['group_id'];
494        }
495
496        return $result;
497    }
498
499    /**
500     * Adds a new member $userId to the group $groupId.
501     *
502     * @param int $userId  User ID
503     * @param int $groupId Group ID
504     */
505    public function addToGroup(int $userId, int $groupId): bool
506    {
507        if ($userId <= 0 || $groupId <= 0) {
508            return false;
509        }
510
511        $insert = sprintf(
512            'INSERT INTO %sfaquser_group (user_id, group_id) VALUES (%d, %d)',
513            Database::getTablePrefix(),
514            $userId,
515            $groupId,
516        );
517
518        return (bool) $this->configuration->getDb()->query($insert);
519    }
520
521    /**
522     * Removes a user from a single group.
523     *
524     * @param int $userId User ID
525     * @param int $groupId Group ID
526     */
527    public function removeFromGroup(int $userId, int $groupId): bool
528    {
529        if ($userId <= 0 || $groupId <= 0) {
530            return false;
531        }
532
533        $delete = sprintf(
534            'DELETE FROM %sfaquser_group WHERE user_id = %d AND group_id = %d',
535            Database::getTablePrefix(),
536            $userId,
537            $groupId,
538        );
539
540        return (bool) $this->configuration->getDb()->query($delete);
541    }
542
543    /**
544     * Returns an associative array with the group-data of the group $groupId.
545     *
546     * @param int $groupId Group ID
547     * @return array<array-key, mixed>
548     */
549    public function getGroupData(int $groupId): array
550    {
551        if ($groupId <= 0) {
552            return [];
553        }
554
555        $select = sprintf(
556            'SELECT group_id, name, description, auto_join FROM %sfaqgroup WHERE group_id = %d',
557            Database::getTablePrefix(),
558            $groupId,
559        );
560
561        $res = $this->configuration->getDb()->query($select);
562        if ($this->configuration->getDb()->numRows($res) !== 1) {
563            return [];
564        }
565
566        $row = $this->configuration->getDb()->fetchArray($res);
567
568        return is_array($row) ? $row : [];
569    }
570
571    /**
572     * Removes the user $userId from all groups.
573     *
574     * @param int $userId User ID
575     */
576    public function removeFromAllGroups(int $userId): bool
577    {
578        if ($userId <= 0) {
579            return false;
580        }
581
582        $delete = sprintf('DELETE FROM %sfaquser_group WHERE user_id  = %d', Database::getTablePrefix(), $userId);
583
584        return (bool) $this->configuration->getDb()->query($delete);
585    }
586
587    /**
588     * Refuses all group rights.
589     *
590     * @param int $groupId Group ID
591     */
592    public function refuseAllGroupRights(int $groupId): bool
593    {
594        if ($groupId <= 0) {
595            return false;
596        }
597
598        $delete = sprintf('DELETE FROM %sfaqgroup_right WHERE group_id  = %d', Database::getTablePrefix(), $groupId);
599
600        return (bool) $this->configuration->getDb()->query($delete);
601    }
602
603    /**
604     * Removes all users from the group $groupId.
605     *
606     * @param int $groupId Group ID
607     */
608    public function removeAllUsersFromGroup(int $groupId): bool
609    {
610        if ($groupId <= 0) {
611            return false;
612        }
613
614        $delete = sprintf('DELETE FROM %sfaquser_group WHERE group_id = %d', Database::getTablePrefix(), $groupId);
615
616        return (bool) $this->configuration->getDb()->query($delete);
617    }
618
619    /**
620     * Generates the next ID for the faqgroup table.
621     */
622    public function nextGroupId(): int
623    {
624        return $this->configuration->getDb()->nextId(Database::getTablePrefix() . 'faqgroup', 'group_id');
625    }
626}