Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
90 / 90
100.00% covered (success)
100.00%
7 / 7
CRAP
100.00% covered (success)
100.00%
1 / 1
Permission
100.00% covered (success)
100.00%
90 / 90
100.00% covered (success)
100.00%
7 / 7
31
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 add
100.00% covered (success)
100.00%
24 / 24
100.00% covered (success)
100.00%
1 / 1
6
 delete
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 isRestricted
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 get
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
6
 getAll
100.00% covered (success)
100.00%
30 / 30
100.00% covered (success)
100.00%
1 / 1
8
 normalizeCategoryIds
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3/**
4 * Category permissions class for phpMyFAQ.
5 * This Source Code Form is subject to the terms of the Mozilla Public License,
6 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
7 * obtain one at https://mozilla.org/MPL/2.0/.
8 *
9 * @package   phpMyFAQ
10 * @author    Thorsten Rinne <thorsten@phpmyfaq.de>
11 * @copyright 2020-2026 phpMyFAQ Team
12 * @license   https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
13 * @link      https://www.phpmyfaq.de
14 * @since     2020-11-04
15 */
16
17declare(strict_types=1);
18
19namespace phpMyFAQ\Category;
20
21use phpMyFAQ\Configuration;
22use phpMyFAQ\Database;
23
24/**
25 * Class CategoryPermission
26 *
27 * @package phpMyFAQ\Category
28 */
29class Permission
30{
31    final public const string USER = 'user';
32
33    final public const string GROUP = 'group';
34
35    /**
36     * FaqPermission constructor.
37     */
38    public function __construct(
39        private readonly Configuration $configuration,
40    ) {
41    }
42
43    /**
44     * Adds the category permissions for users and groups.
45     *
46     * @param string $mode 'group' or 'user'
47     * @param int[]  $categories ID of the current category
48     * @param int[]  $ids Array of group or user IDs
49     */
50    public function add(string $mode, array $categories, array $ids): bool
51    {
52        if (self::USER !== $mode && self::GROUP !== $mode) {
53            return false;
54        }
55
56        foreach ($categories as $category) {
57            foreach ($ids as $id) {
58                $query = sprintf(
59                    'SELECT * FROM %sfaqcategory_%s WHERE category_id = %d AND %s_id = %d',
60                    Database::getTablePrefix(),
61                    $mode,
62                    $category,
63                    $mode,
64                    $id,
65                );
66
67                if ($this->configuration->getDb()->numRows($this->configuration->getDb()->query($query)) !== 0) {
68                    continue;
69                }
70
71                $query = sprintf(
72                    'INSERT INTO %sfaqcategory_%s (category_id, %s_id) VALUES (%d, %d)',
73                    Database::getTablePrefix(),
74                    $mode,
75                    $mode,
76                    $category,
77                    $id,
78                );
79
80                $this->configuration->getDb()->query($query);
81            }
82        }
83
84        return true;
85    }
86
87    /**
88     * Deletes the category permissions for users and groups.
89     *
90     * @param string $mode 'group' or 'user'
91     * @param int[]  $categories ID of the current category
92     */
93    public function delete(string $mode, array $categories): bool
94    {
95        if (self::USER !== $mode && self::GROUP !== $mode) {
96            return false;
97        }
98
99        foreach ($categories as $category) {
100            $query = sprintf('
101                DELETE FROM
102                    %sfaqcategory_%s
103                WHERE
104                    category_id = %d', Database::getTablePrefix(), $mode, $category);
105            $this->configuration->getDb()->query($query);
106        }
107
108        return true;
109    }
110
111    /**
112     * Returns true, if a given category has user or group permissions.
113     * Otherwise, the methods return false.
114     */
115    public function isRestricted(int $categoryId): bool
116    {
117        $hasUserPermissions = $this->get(self::USER, [$categoryId]);
118        $hasGroupPermissions = $this->get(self::GROUP, [$categoryId]);
119
120        return (
121            array_key_exists(0, $hasUserPermissions)
122            && $hasUserPermissions[0] !== -1
123            || array_key_exists(0, $hasGroupPermissions)
124            && $hasGroupPermissions[0] !== -1
125        );
126    }
127
128    /**
129     * Returns the category permissions for users or groups.
130     *
131     * @param string $mode 'group' or 'user'
132     * @param int[]  $categories Array of category ids
133     * @return int[] Array of user or group IDs that have permissions for the given categories
134     */
135    public function get(string $mode, array $categories): array
136    {
137        $permissions = [];
138
139        if (self::USER !== $mode && self::GROUP !== $mode) {
140            return $permissions;
141        }
142
143        $categoryList = $this->normalizeCategoryIds($categories);
144
145        $query = sprintf(
146            'SELECT %s_id AS permission FROM %sfaqcategory_%s WHERE category_id IN (%s)',
147            $mode,
148            Database::getTablePrefix(),
149            $mode,
150            $categoryList,
151        );
152
153        $result = $this->configuration->getDb()->query($query);
154        while (true) {
155            $row = $this->configuration->getDb()->fetchObject($result);
156            if ($row === false || $row === null) {
157                break;
158            }
159
160            $permissions[] = (int) $row->permission;
161        }
162
163        return $permissions;
164    }
165
166    /**
167     * Returns all category permissions for users and groups.
168     *
169     * @param int[] $categories Array of category ids
170     * @return array<int, array<string, int[]>> Array of permissions with user and group IDs for each category
171     */
172    public function getAll(array $categories): array
173    {
174        $permissions = [];
175        $categoryList = $this->normalizeCategoryIds($categories);
176
177        foreach ($categories as $category) {
178            $permissions[(int) $category] = [
179                self::USER => [],
180                self::GROUP => [],
181            ];
182        }
183
184        $query = sprintf(
185            'SELECT category_id, user_id AS permission FROM %sfaqcategory_user WHERE category_id IN (%s)',
186            Database::getTablePrefix(),
187            $categoryList,
188        );
189
190        $result = $this->configuration->getDb()->query($query);
191        while (true) {
192            $row = $this->configuration->getDb()->fetchObject($result);
193            if ($row === false || $row === null) {
194                break;
195            }
196
197            $permissions[(int) $row->category_id][self::USER][] = (int) $row->permission;
198        }
199
200        $query = sprintf(
201            'SELECT category_id, group_id AS permission FROM %sfaqcategory_group WHERE category_id IN (%s)',
202            Database::getTablePrefix(),
203            $categoryList,
204        );
205
206        $result = $this->configuration->getDb()->query($query);
207        while (true) {
208            $row = $this->configuration->getDb()->fetchObject($result);
209            if ($row === false || $row === null) {
210                break;
211            }
212
213            $permissions[(int) $row->category_id][self::GROUP][] = (int) $row->permission;
214        }
215
216        return $permissions;
217    }
218
219    /**
220     * @param int[] $categories
221     */
222    private function normalizeCategoryIds(array $categories): string
223    {
224        $normalizedCategories = array_map(static fn($category): int => (int) $category, $categories);
225
226        return $normalizedCategories === [] ? '0' : implode(', ', $normalizedCategories);
227    }
228}