PluginProbe
The Innovative Form Builder – IvyForms / 1.5.1
The Innovative Form Builder – IvyForms v1.5.1
1.5.1 1.5 1.4.1 1.4 trunk 0.1.2 0.2 0.2.1 0.3 0.3.1 0.4 0.5 0.6 0.6.1 0.6.1-backup 0.6.1.1 0.7 0.8 0.8.1 0.8.2 0.9 0.9.1 1.0 1.1 1.1.1 All 27 releases
ivyforms / backend / src / Services / Permissions / AccessRulesValidator.php

AccessRulesValidator.php in The Innovative Form Builder – IvyForms 1.5.1, at backend/src/Services/Permissions/AccessRulesValidator.php

274 lines 8.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * @copyright © Melograno Venture Studio. All rights reserved.
5 */
6
7 namespace IvyForms\Services\Permissions;
8
9 // phpcs:disable PSR1.Files.SideEffects
10 if (!defined('ABSPATH')) {
11 exit;
12 }
13
14 use IvyForms\Common\Exceptions\ValidationException;
15 use IvyForms\Repository\Form\FormRepositoryInterface;
16 use IvyForms\Services\Translations\BackendStrings;
17
18 /**
19 * Validates payloads for /permissions/access-rules updates.
20 */
21 class AccessRulesValidator
22 {
23 /** @var array<string, string> */
24 private array $exceptionStrings;
25
26 private FormRepositoryInterface $formRepository;
27
28 public function __construct(
29 FormRepositoryInterface $formRepository
30 ) {
31 $this->formRepository = $formRepository;
32 $this->exceptionStrings = BackendStrings::getExceptionStrings();
33 }
34
35 /**
36 * @param mixed[] $rules
37 * @return list<array<string,mixed>>
38 *
39 * @throws ValidationException
40 */
41 public function validateAndNormalize(array $rules): array
42 {
43 if (!array_is_list($rules)) {
44 throw new ValidationException($this->exceptionStrings['rules_payload_required']);
45 }
46
47 $normalized = [];
48 $seenIds = [];
49 $ruleDataList = [];
50 $allFormIds = [];
51
52 foreach ($rules as $index => $rule) {
53 $ruleData = $this->validateRuleBase($rule, (int) $index, $seenIds);
54 $ruleDataList[] = $ruleData;
55
56 if (!array_key_exists('form_ids', $ruleData) || $ruleData['form_ids'] === null) {
57 continue;
58 }
59 if (!is_array($ruleData['form_ids'])) {
60 continue;
61 }
62 foreach ($ruleData['form_ids'] as $formId) {
63 $allFormIds[] = (int) $formId;
64 }
65 }
66
67 $existingFormIds = $this->buildExistingFormIdSet($allFormIds);
68
69 foreach ($ruleDataList as $ruleData) {
70 $permissions = $this->normalizePermissions($ruleData);
71 $formIds = $this->normalizeFormIds($ruleData, $existingFormIds);
72 $timestamps = $this->resolveTimestamps($ruleData);
73
74 if ($ruleData['type'] === 'role') {
75 $normalized[] = $this->buildRoleRule($ruleData, $permissions, $formIds, $timestamps);
76 continue;
77 }
78 $normalized[] = $this->buildUserRule($ruleData, $permissions, $formIds, $timestamps);
79 }
80
81 return $normalized;
82 }
83
84 /**
85 * @param list<int> $formIds
86 * @return array<int, true>
87 */
88 private function buildExistingFormIdSet(array $formIds): array
89 {
90 $existing = $this->formRepository->filterExistingIds($formIds);
91 $set = [];
92 foreach ($existing as $id) {
93 $set[ $id ] = true;
94 }
95
96 return $set;
97 }
98
99 /**
100 * @param mixed $rule
101 * @param array<string,bool> $seenIds
102 * @return array<string,mixed>
103 *
104 * @throws ValidationException
105 */
106 private function validateRuleBase($rule, int $index, array &$seenIds): array
107 {
108 if (!is_array($rule)) {
109 throw new ValidationException(sprintf(
110 $this->exceptionStrings['permissions_rule_invalid_index'],
111 $index
112 ));
113 }
114
115 $type = isset($rule['type']) ? (string) $rule['type'] : '';
116 if (!in_array($type, ['role', 'user'], true)) {
117 throw new ValidationException($this->exceptionStrings['permissions_rule_type_required']);
118 }
119
120 $id = isset($rule['id']) ? (string) $rule['id'] : '';
121 if ($id === '') {
122 throw new ValidationException($this->exceptionStrings['permissions_rule_id_required']);
123 }
124 if (isset($seenIds[$id])) {
125 throw new ValidationException($this->exceptionStrings['permissions_rule_duplicate_id']);
126 }
127 $seenIds[$id] = true;
128
129 $rule['type'] = $type;
130 $rule['id'] = $id;
131
132 return $rule;
133 }
134
135 /**
136 * @param array<string,mixed> $rule
137 * @return list<string>
138 *
139 * @throws ValidationException
140 */
141 private function normalizePermissions(array $rule): array
142 {
143 $raw = isset($rule['permissions']) && is_array($rule['permissions'])
144 ? $rule['permissions']
145 : [];
146 $permissions = PermissionCatalog::sanitizeWithDependencies($raw);
147 if ($permissions === []) {
148 throw new ValidationException($this->exceptionStrings['permissions_rule_grant_required']);
149 }
150
151 return $permissions;
152 }
153
154 /**
155 * @param array<string,mixed> $rule
156 * @param array<int, true> $existingFormIds
157 * @return list<int>|null
158 *
159 * @throws ValidationException
160 */
161 private function normalizeFormIds(array $rule, array $existingFormIds): ?array
162 {
163 if (!array_key_exists('form_ids', $rule) || $rule['form_ids'] === null) {
164 return null;
165 }
166 if (!is_array($rule['form_ids'])) {
167 throw new ValidationException($this->exceptionStrings['permissions_form_ids_invalid']);
168 }
169
170 $requestedIds = [];
171 $formIds = [];
172 foreach ($rule['form_ids'] as $formId) {
173 $normalizedFormId = (int) $formId;
174 if ($normalizedFormId <= 0) {
175 throw new ValidationException($this->exceptionStrings['permissions_form_id_invalid']);
176 }
177 $requestedIds[] = $normalizedFormId;
178 if (!isset($existingFormIds[ $normalizedFormId ])) {
179 continue;
180 }
181 $formIds[] = $normalizedFormId;
182 }
183
184 $formIds = array_values(array_unique($formIds));
185 if ($requestedIds !== [] && $formIds === []) {
186 $missing = array_values(array_unique($requestedIds));
187 throw new ValidationException(
188 sprintf(
189 $this->exceptionStrings['permissions_deleted_forms'],
190 implode(', ', array_map('strval', $missing))
191 )
192 );
193 }
194
195 return $formIds;
196 }
197
198 /**
199 * @param array<string,mixed> $rule
200 * @return array{created_at:int,updated_at:int}
201 */
202 private function resolveTimestamps(array $rule): array
203 {
204 $createdAt = isset($rule['created_at']) ? (int) $rule['created_at'] : 0;
205 $updatedAt = isset($rule['updated_at']) ? (int) $rule['updated_at'] : 0;
206 $now = time();
207 if ($createdAt <= 0) {
208 $createdAt = $now;
209 }
210 if ($updatedAt <= 0) {
211 $updatedAt = $now;
212 }
213
214 return [
215 'created_at' => $createdAt,
216 'updated_at' => $updatedAt,
217 ];
218 }
219
220 /**
221 * @param array<string,mixed> $rule
222 * @param list<string> $permissions
223 * @param list<int>|null $formIds
224 * @param array{created_at:int,updated_at:int} $timestamps
225 * @return array<string,mixed>
226 *
227 * @throws ValidationException
228 */
229 private function buildRoleRule(array $rule, array $permissions, ?array $formIds, array $timestamps): array
230 {
231 $slug = isset($rule['role_slug']) ? (string) $rule['role_slug'] : '';
232 if ($slug === '' || !wp_roles()->is_role($slug)) {
233 throw new ValidationException($this->exceptionStrings['permissions_role_slug_invalid']);
234 }
235
236 return [
237 'id' => $rule['id'],
238 'type' => 'role',
239 'role_slug' => $slug,
240 'permissions' => $permissions,
241 'form_ids' => $formIds,
242 'created_at' => $timestamps['created_at'],
243 'updated_at' => $timestamps['updated_at'],
244 ];
245 }
246
247 /**
248 * @param array<string,mixed> $rule
249 * @param list<string> $permissions
250 * @param list<int>|null $formIds
251 * @param array{created_at:int,updated_at:int} $timestamps
252 * @return array<string,mixed>
253 *
254 * @throws ValidationException
255 */
256 private function buildUserRule(array $rule, array $permissions, ?array $formIds, array $timestamps): array
257 {
258 $userId = isset($rule['user_id']) ? (int) $rule['user_id'] : 0;
259 if ($userId <= 0 || !get_user_by('id', $userId)) {
260 throw new ValidationException($this->exceptionStrings['permissions_user_id_invalid']);
261 }
262
263 return [
264 'id' => $rule['id'],
265 'type' => 'user',
266 'user_id' => $userId,
267 'permissions' => $permissions,
268 'form_ids' => $formIds,
269 'created_at' => $timestamps['created_at'],
270 'updated_at' => $timestamps['updated_at'],
271 ];
272 }
273 }
274