Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
83.15% covered (success)
83.15%
74 / 89
80.00% covered (success)
80.00%
4 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
Permission
83.15% covered (success)
83.15%
74 / 89
80.00% covered (success)
80.00%
4 / 5
39.53
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
 add
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
1 / 1
7
 delete
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 get
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
7
 createPermissionArray
58.33% covered (warning)
58.33%
21 / 36
0.00% covered (danger)
0.00%
0 / 1
34.52
1<?php
2
3/**
4 * FAQ 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\Faq;
20
21use phpMyFAQ\Configuration;
22use phpMyFAQ\Database;
23use phpMyFAQ\Filter;
24use Symfony\Component\HttpFoundation\Request;
25
26/**
27 * Class FaqPermission
28 *
29 * @package phpMyFAQ\Faq
30 */
31class Permission
32{
33    final public const string USER = 'user';
34
35    final public const string GROUP = 'group';
36
37    /**
38     * FaqPermission constructor.
39     */
40    public function __construct(
41        private readonly Configuration $configuration,
42    ) {
43    }
44
45    /**
46     * Adds the record permissions for users and groups.
47     *
48     * @param string $mode 'group' or 'user'
49     * @param int    $faqId ID of the current record
50     * @param int[]  $ids Array of group or user IDs
51     */
52    public function add(string $mode, int $faqId, array $ids): bool
53    {
54        if (self::USER !== $mode && self::GROUP !== $mode) {
55            return false;
56        }
57
58        foreach ($ids as $id) {
59            // Check if permission already exists to avoid duplicate key errors
60            $checkQuery = sprintf(
61                'SELECT 1 FROM %sfaqdata_%s WHERE record_id = %d AND %s_id = %d',
62                Database::getTablePrefix(),
63                $mode,
64                $faqId,
65                $mode,
66                $id,
67            );
68
69            $result = $this->configuration->getDb()->query($checkQuery);
70            if ($result === false) {
71                // Query failed, skip this permission to avoid further errors
72                continue;
73            }
74
75            if ($this->configuration->getDb()->numRows($result) > 0) {
76                continue; // Permission already exists, skip
77            }
78
79            $query = sprintf(
80                'INSERT INTO %sfaqdata_%s (record_id, %s_id) VALUES (%d, %d)',
81                Database::getTablePrefix(),
82                $mode,
83                $mode,
84                $faqId,
85                $id,
86            );
87
88            $insertResult = $this->configuration->getDb()->query($query);
89            if ($insertResult === false) {
90                // Insert failed, continue with next permission
91                continue;
92            }
93        }
94
95        return true;
96    }
97
98    /**
99     * Deletes the record permissions for users and groups.
100     *
101     * @param string $mode 'group' or 'user'
102     * @param int    $faqId ID of the current record
103     */
104    public function delete(string $mode, int $faqId): bool
105    {
106        if (self::USER !== $mode && self::GROUP !== $mode) {
107            return false;
108        }
109
110        $query = sprintf('DELETE FROM %sfaqdata_%s WHERE record_id = %d', Database::getTablePrefix(), $mode, $faqId);
111
112        return (bool) $this->configuration->getDb()->query($query);
113    }
114
115    /**
116     * Returns the record permissions for users and groups.
117     *
118     * @param string $mode 'group' or 'user'
119     * @return array<int>
120     */
121    public function get(string $mode, int $faqId): array
122    {
123        $permissions = [];
124
125        if (self::USER !== $mode && self::GROUP !== $mode) {
126            return $permissions;
127        }
128
129        if (0 === $faqId) {
130            return [-1];
131        }
132
133        $query = sprintf(
134            'SELECT %s_id AS permission FROM %sfaqdata_%s WHERE record_id = %d',
135            $mode,
136            Database::getTablePrefix(),
137            $mode,
138            $faqId,
139        );
140
141        $result = $this->configuration->getDb()->query($query);
142
143        if ($this->configuration->getDb()->numRows($result) > 0) {
144            while (true) {
145                $row = $this->configuration->getDb()->fetchObject($result);
146                if (!is_object($row)) {
147                    break;
148                }
149
150                $permissions[] = (int) $row->permission;
151            }
152        }
153
154        return $permissions;
155    }
156
157    /**
158     * Creates the permission array.
159     *
160     * @return array<string, array<int>>
161     */
162    public function createPermissionArray(): array
163    {
164        $permissions = [];
165
166        $payload = json_decode(Request::createFromGlobals()->getContent());
167        $data = is_object($payload) && property_exists($payload, 'data') && is_object($payload->data)
168            ? $payload->data
169            : (object) [];
170
171        $restrictedUsers = [];
172        if ('all' === Filter::filterVar($data->userpermission ?? null, FILTER_SANITIZE_SPECIAL_CHARS)) {
173            $restrictedUsers = [-1];
174        }
175
176        if ($restrictedUsers === []) {
177            if (is_string($data->restricted_users ?? null)) {
178                $filteredUser = Filter::filterVar($data->restricted_users, FILTER_VALIDATE_INT);
179                $restrictedUsers = is_int($filteredUser) ? [$filteredUser] : [];
180            }
181
182            if (is_array($data->restricted_users ?? null)) {
183                $filteredUsers = Filter::filterArray($data->restricted_users, FILTER_VALIDATE_INT);
184                $restrictedUsers = is_array($filteredUsers)
185                    ? array_values(array_filter($filteredUsers, is_int(...)))
186                    : [];
187            }
188        }
189
190        $permissions += [
191            'restricted_user' => $restrictedUsers,
192        ];
193
194        $restrictedGroups = [];
195        if ('all' === Filter::filterVar($data->grouppermission ?? null, FILTER_SANITIZE_SPECIAL_CHARS)) {
196            $restrictedGroups = [-1];
197        }
198
199        if ($restrictedGroups === []) {
200            if (is_string($data->restricted_groups ?? null)) {
201                $filteredGroup = Filter::filterVar($data->restricted_groups, FILTER_VALIDATE_INT);
202                $restrictedGroups = is_int($filteredGroup) ? [$filteredGroup] : [];
203            }
204
205            if (is_array($data->restricted_groups ?? null)) {
206                $filteredGroups = Filter::filterArray($data->restricted_groups, FILTER_VALIDATE_INT);
207                $restrictedGroups = is_array($filteredGroups)
208                    ? array_values(array_filter($filteredGroups, is_int(...)))
209                    : [];
210            }
211        }
212
213        $permissions += [
214            'restricted_groups' => $restrictedGroups,
215        ];
216
217        return $permissions;
218    }
219}