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 / PermissionResolver.php

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

220 lines 6.2 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\Repository\Permissions\AccessRulesRepository;
15 use IvyForms\Services\Permissions\IvyFormsAccess;
16
17 /**
18 * Resolves IvyForms capability keys from stored access rules.
19 *
20 * v1: union of grants from all rules matching the user (role or user row) and form scope.
21 * User-targeted rules are processed after role-targeted so overlapping keys behave as overrides.
22 */
23 class PermissionResolver
24 {
25 private AccessRulesRepository $rulesRepository;
26
27 /** @var list<array<string,mixed>>|null */
28 private ?array $cachedRules = null;
29
30 /** @var array<string, list<string>> */
31 private array $effectiveGrantsCache = [];
32
33 public function __construct(
34 AccessRulesRepository $rulesRepository
35 ) {
36 $this->rulesRepository = $rulesRepository;
37 }
38
39 /**
40 * @param \WP_User $user
41 */
42 public function userCan(string $permissionKey, $user, ?int $formId = null): bool
43 {
44 if (!in_array($permissionKey, PermissionCatalog::keys(), true)) {
45 return false;
46 }
47
48 if (!$user instanceof \WP_User || (int) $user->ID <= 0) {
49 return false;
50 }
51
52 if (user_can($user, IvyFormsAccess::CAP_MANAGE_ALL)) {
53 return true;
54 }
55 if (user_can($user, PermissionCatalog::wpCapability($permissionKey))) {
56 return true;
57 }
58
59 $grants = $this->effectiveGrants($user, $formId);
60
61 return in_array($permissionKey, $grants, true);
62 }
63
64 /**
65 * True when the user has at least one key via a global rule or any form-scoped rule.
66 *
67 * Used for list routes (forms/entries search) where no single form id applies.
68 *
69 * @param \WP_User $user
70 * @param list<string> $permissionKeys
71 */
72 public function userCanAnyOf($user, array $permissionKeys): bool
73 {
74 if (!$user instanceof \WP_User || (int) $user->ID <= 0) {
75 return false;
76 }
77
78 if (user_can($user, IvyFormsAccess::CAP_MANAGE_ALL)) {
79 return true;
80 }
81
82 foreach ($permissionKeys as $permissionKey) {
83 if (!in_array($permissionKey, PermissionCatalog::keys(), true)) {
84 continue;
85 }
86 if (user_can($user, PermissionCatalog::wpCapability($permissionKey))) {
87 return true;
88 }
89 if ($this->userCan($permissionKey, $user, null)) {
90 return true;
91 }
92 }
93
94 return FormScopedGrantChecker::userHasAnyGrant($this->getRules(), $user, $permissionKeys);
95 }
96
97 /**
98 * Like userCan(), but when $formId is null also allows form-scoped rules (list/single-action gates).
99 *
100 * @param \WP_User $user
101 */
102 public function userCanIncludingFormScoped(string $permissionKey, $user, ?int $formId = null): bool
103 {
104 if ($this->userCan($permissionKey, $user, $formId)) {
105 return true;
106 }
107
108 if ($formId !== null) {
109 return false;
110 }
111
112 return FormScopedGrantChecker::userHasAnyGrant($this->getRules(), $user, [$permissionKey]);
113 }
114
115 /**
116 * @param \WP_User $user
117 * @return list<string>
118 */
119 public function effectiveGrants($user, ?int $formId = null): array
120 {
121 if (!$user instanceof \WP_User || (int) $user->ID <= 0) {
122 return [];
123 }
124
125 $cacheKey = (int) $user->ID . ':' . ($formId === null ? 'null' : (string) $formId);
126 if (isset($this->effectiveGrantsCache[ $cacheKey ])) {
127 return $this->effectiveGrantsCache[ $cacheKey ];
128 }
129
130 $rules = $this->getRules();
131 if ($rules === []) {
132 $this->effectiveGrantsCache[ $cacheKey ] = [];
133
134 return [];
135 }
136
137 $orderedRules = $this->getRulesOrderedForMerge($rules, $user, $formId);
138 if ($orderedRules === []) {
139 $this->effectiveGrantsCache[ $cacheKey ] = [];
140
141 return [];
142 }
143
144 $grants = $this->extractPermissionSet($orderedRules);
145 $this->effectiveGrantsCache[ $cacheKey ] = $grants;
146
147 return $grants;
148 }
149
150 public function invalidateCache(): void
151 {
152 $this->cachedRules = null;
153 $this->effectiveGrantsCache = [];
154 }
155
156 /**
157 * @return list<array<string,mixed>>
158 */
159 private function getRules(): array
160 {
161 if ($this->cachedRules !== null) {
162 return $this->cachedRules;
163 }
164
165 $payload = $this->rulesRepository->getPayload();
166 $this->cachedRules = $payload['rules'];
167
168 return $this->cachedRules;
169 }
170
171 /**
172 * @param list<array<string,mixed>> $rules
173 * @param \WP_User $user
174 * @return list<array<string,mixed>>
175 */
176 private function getRulesOrderedForMerge(array $rules, $user, ?int $formId): array
177 {
178 $roleMatches = [];
179 $userMatches = [];
180 foreach ($rules as $rule) {
181 if (!AccessRuleMatcher::appliesToForm($rule, $formId)) {
182 continue;
183 }
184 $ruleType = (string) ($rule['type'] ?? '');
185 if ($ruleType === 'user' && AccessRuleMatcher::matchesUser($rule, $user)) {
186 $userMatches[] = $rule;
187 continue;
188 }
189 if ($ruleType === 'role' && AccessRuleMatcher::matchesRole($rule, $user)) {
190 $roleMatches[] = $rule;
191 }
192 }
193
194 return array_merge(
195 AccessRuleMatcher::sortForMerge($roleMatches),
196 AccessRuleMatcher::sortForMerge($userMatches)
197 );
198 }
199
200 /**
201 * @param list<array<string,mixed>> $rules
202 * @return list<string>
203 */
204 private function extractPermissionSet(array $rules): array
205 {
206 $permissionSet = [];
207 foreach ($rules as $rule) {
208 $permissions = $rule['permissions'] ?? [];
209 if (!is_array($permissions)) {
210 continue;
211 }
212 foreach (PermissionCatalog::sanitize($permissions) as $permissionKey) {
213 $permissionSet[$permissionKey] = true;
214 }
215 }
216
217 return array_keys($permissionSet);
218 }
219 }
220