PluginProbe
The Innovative Form Builder – IvyForms / 1.4
The Innovative Form Builder – IvyForms v1.4
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 / Common / Helpers / McpHelpers / McpAccessRulesMerger.php

McpAccessRulesMerger.php in The Innovative Form Builder – IvyForms 1.4, at backend/src/Common/Helpers/McpHelpers/McpAccessRulesMerger.php

166 lines 4.7 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 * @licence See COPYING.md for license details.
6 */
7
8 namespace IvyForms\Common\Helpers\McpHelpers;
9
10 /**
11 * Pure access-rule merge helpers for MCP permission grant/revoke.
12 */
13 final class McpAccessRulesMerger
14 {
15 /**
16 * Add/merge a grant onto a rule that matches the principal and form scope, or append a new rule.
17 *
18 * @param list<array<string, mixed>> $rules
19 * @param 'role'|'user' $type
20 * @param string|int $principal
21 * @param list<string> $permissions
22 * @param list<int>|null $formIds
23 * @return list<array<string, mixed>>
24 */
25 public static function upsertGrant(
26 array $rules,
27 string $type,
28 $principal,
29 array $permissions,
30 ?array $formIds
31 ): array {
32 $scopeKey = self::scopeKey($formIds);
33 foreach ($rules as &$rule) {
34 if (!self::ruleMatches($rule, $type, $principal, $scopeKey)) {
35 continue;
36 }
37 $existing = is_array($rule['permissions'] ?? null) ? $rule['permissions'] : [];
38 $rule['permissions'] = array_values(array_unique(array_merge($existing, $permissions)));
39 $rule['updated_at'] = time();
40
41 unset($rule);
42
43 return $rules;
44 }
45 unset($rule);
46
47 $rules[] = self::newRule($type, $principal, $permissions, $formIds);
48
49 return $rules;
50 }
51
52 /**
53 * Remove permission keys from matching rules, dropping rules that become empty.
54 *
55 * Only rules for the same principal and form scope as {@see upsertGrant()} are modified:
56 * omit formIds to revoke from global rules only; pass formIds to revoke from that scope.
57 *
58 * @param list<array<string, mixed>> $rules
59 * @param 'role'|'user' $type
60 * @param string|int $principal
61 * @param list<string> $permissions
62 * @param list<int>|null $formIds
63 * @return list<array<string, mixed>>
64 */
65 public static function applyRevoke(
66 array $rules,
67 string $type,
68 $principal,
69 array $permissions,
70 ?array $formIds
71 ): array {
72 $scopeKey = self::scopeKey($formIds);
73 $out = [];
74 foreach ($rules as $rule) {
75 if (!self::ruleMatches($rule, $type, $principal, $scopeKey)) {
76 $out[] = $rule;
77 continue;
78 }
79 $existing = is_array($rule['permissions'] ?? null) ? $rule['permissions'] : [];
80 $remaining = array_values(array_diff($existing, $permissions));
81 if ($remaining === []) {
82 continue;
83 }
84 $rule['permissions'] = $remaining;
85 $rule['updated_at'] = time();
86 $out[] = $rule;
87 }
88
89 return $out;
90 }
91
92 /**
93 * @param array<string, mixed> $rule
94 * @param 'role'|'user' $type
95 * @param string|int $principal
96 */
97 private static function ruleMatches(array $rule, string $type, $principal, string $scopeKey): bool
98 {
99 if (($rule['type'] ?? '') !== $type) {
100 return false;
101 }
102
103 if (self::scopeKey($rule['form_ids'] ?? null) !== $scopeKey) {
104 return false;
105 }
106
107 if ($type === 'role') {
108 return strtolower(trim((string) ($rule['role_slug'] ?? ''))) === (string) $principal;
109 }
110
111 return (int) ($rule['user_id'] ?? 0) === (int) $principal;
112 }
113
114 /**
115 * @param 'role'|'user' $type
116 * @param string|int $principal
117 * @param list<string> $permissions
118 * @param list<int>|null $formIds
119 * @return array<string, mixed>
120 */
121 private static function newRule(string $type, $principal, array $permissions, ?array $formIds): array
122 {
123 $now = time();
124 $rule = [
125 'id' => self::ruleId(),
126 'type' => $type,
127 'permissions' => $permissions,
128 'form_ids' => $formIds,
129 'created_at' => $now,
130 'updated_at' => $now,
131 ];
132 if ($type === 'role') {
133 $rule['role_slug'] = (string) $principal;
134
135 return $rule;
136 }
137
138 $rule['user_id'] = (int) $principal;
139
140 return $rule;
141 }
142
143 /**
144 * @param list<int>|null $formIds
145 */
146 private static function scopeKey(?array $formIds): string
147 {
148 if ($formIds === null) {
149 return 'global';
150 }
151 $ids = array_values(array_unique(array_map('intval', $formIds)));
152 sort($ids);
153
154 return 'forms:' . implode(',', $ids);
155 }
156
157 private static function ruleId(): string
158 {
159 if (function_exists('wp_generate_uuid4')) {
160 return 'mcp-' . wp_generate_uuid4();
161 }
162
163 return uniqid('mcp-', true);
164 }
165 }
166