PluginProbe
Fluent Support – Helpdesk & Customer Support Ticket System / 2.4.5
Fluent Support – Helpdesk & Customer Support Ticket System v2.4.5
2.4.5 2.4.0 2.3.2 2.3.1 2.3.0 2.2.1 2.2.0 trunk 1.10.0 1.10.1 1.10.2 1.10.3 1.10.4 1.10.5 1.4.0 1.4.1 1.4.2 1.4.5 1.4.6 1.4.7 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 All 69 releases
← All changes | app/Modules/PermissionManager.php +25 -57 2.4.0 → 2.4.5 View file →
@@ -7,12 +7,9 @@
7 7 use FluentSupport\App\Services\Tickets\AgentTicketAccess;
8 8 use FluentSupport\Framework\Support\Arr;
9 9
10 10 /**
11 - * PermissionManager class is responsible for getting/settings data related to permission
12 - * @package FluentSupport\App\Modules
13 - *
14 - * @version 1.0.0
11 + * Fluent Support's own agent permissions, stored in user meta.
15 12 */
16 13
17 14 class PermissionManager
18 15 {
@@ -23,9 +20,8 @@
23 20 const VISIBILITY_ASSIGNED_AND_UNASSIGNED = 'assigned_and_unassigned';
24 21 const VISIBILITY_ASSIGNED_ONLY = 'assigned_only';
25 22
26 23 /**
27 - * pluginPermissions method will return the list of permissions support by Fluent Support Plugin
28 24 * @return string[]
29 25 */
30 26 public static function pluginPermissions()
31 27 {
@@ -85,10 +81,9 @@
85 81 return false;
86 82 }
87 83
88 84 /**
89 - * currentUserCan method will return whether a user has the selected permission or not.
90 - * Backward-compatible alias for userCan().
85 + * Alias of userCan().
91 86 *
92 87 * @param $permission
93 88 * @return bool
94 89 */
@@ -97,10 +92,9 @@
97 92 return self::userCan($permission);
98 93 }
99 94
100 95 /**
101 - * attachPermissions method will save selected permissions to user meta.
102 - * Also cleans up any legacy fst_* WordPress capabilities.
96 + * Save permissions to user meta and drop legacy fst_* capabilities.
103 97 *
104 98 * @param $user
105 99 * @param $permissions
106 100 * @return false|mixed
@@ -120,18 +114,16 @@
120 114 }
121 115
122 116 $allPermissions = self::pluginPermissions();
123 117
124 - // Allowlist (never a denylist): only known plugin permissions may be written.
125 - // The privilege-ceiling invariant (an actor may only grant permissions it holds)
126 - // is enforced upstream by AgentPolicy, which limits agent mutations to
127 - // administrators — the only entry point that reaches this write.
118 + // Only known permissions. There is no "grant only what you hold" check
119 + // here because AgentPolicy limits agent edits to administrators.
128 120 $permissions = array_values(array_intersect($allPermissions, $permissions));
129 121
130 122 $exclusionRules = self::getExclusionRules();
131 123 $permissions = self::applyExclusionRules($permissions, $exclusionRules);
132 124
133 - // Auto-grant fst_view_tickets when any manage, draft, or approve permission is present
125 + // Manage, draft and approve all imply fst_view_tickets
134 126 $manageOrDraftPermissions = [
135 127 'fst_manage_own_tickets',
136 128 'fst_manage_unassigned_tickets',
137 129 'fst_manage_other_tickets',
@@ -143,12 +135,10 @@
143 135 && !in_array('fst_view_tickets', $permissions)) {
144 136 $permissions[] = 'fst_view_tickets';
145 137 }
146 138
147 - // Store permissions in user meta
148 139 update_user_meta($user->ID, self::META_KEY, array_values($permissions));
149 140
150 - // Clean up legacy WordPress capabilities
151 141 foreach ($allPermissions as $cap) {
152 142 $user->remove_cap($cap);
153 143 }
154 144
@@ -155,9 +145,9 @@
155 145 return $user;
156 146 }
157 147
158 148 /**
159 - * Clean removal of all Fluent Support permissions for a user.
149 + * Remove all Fluent Support permissions from a user.
160 150 *
161 151 * @param int $userId
162 152 * @return void
163 153 */
@@ -164,9 +154,9 @@
164 154 public static function detachPermissions($userId)
165 155 {
166 156 delete_user_meta($userId, self::META_KEY);
167 157
168 - // Clean up any legacy WordPress capabilities
158 + // Legacy capabilities
169 159 $user = get_user_by('ID', $userId);
170 160 if ($user && !user_can($user, 'manage_options')) {
171 161 foreach (self::pluginPermissions() as $cap) {
172 162 $user->remove_cap($cap);
@@ -197,13 +187,10 @@
197 187 * @return array Each key => value pair means: if key is present, remove value.
198 188 */
199 189 public static function getExclusionRules()
200 190 {
201 - // Mutual exclusion rules applied when assigning permissions:
202 - // - If agent has any manage_*_tickets permission, remove fst_draft_reply
203 - // (draft-only mode is for agents who CANNOT manage tickets)
204 - // - If agent has fst_draft_reply, remove fst_approve_draft_reply
205 - // (draft-only agents should not approve their own drafts)
191 + // Draft-only mode is for agents who can't manage tickets, and a
192 + // draft-only agent must not approve their own drafts.
206 193 return [
207 194 'fst_manage_unassigned_tickets' => 'fst_draft_reply',
208 195 'fst_manage_other_tickets' => 'fst_draft_reply',
209 196 'fst_manage_own_tickets' => 'fst_draft_reply',
@@ -232,12 +219,11 @@
232 219 return is_array($permissions) ? $permissions : [];
233 220 }
234 221
235 222 /**
236 - * getUserPermissions method will get all permissions for a user.
237 - * Reads from user meta with legacy wp_capabilities fallback.
223 + * Reads user meta, migrating legacy fst_* capabilities on first read.
238 224 *
239 - * @param false $user
225 + * @param int|\WP_User|false $user
240 226 * @return array|string[]
241 227 */
242 228 public static function getUserPermissions($user = false)
243 229 {
@@ -256,9 +242,8 @@
256 242 $pluginPermission = array_values(array_diff($pluginPermission, ['fst_draft_reply']));
257 243 return $pluginPermission;
258 244 }
259 245
260 - // Read from meta first
261 246 $permissions = self::getMetaPermissions($user->ID);
262 247
263 248 if (!empty($permissions)) {
264 249 return array_values(array_intersect($permissions, $pluginPermission));
@@ -263,16 +248,14 @@
263 248 if (!empty($permissions)) {
264 249 return array_values(array_intersect($permissions, $pluginPermission));
265 250 }
266 251
267 - // Legacy fallback: read from wp_capabilities and migrate
252 + // Legacy: permissions stored as WP capabilities. Move them to meta.
268 253 $legacyPermissions = array_values(array_intersect(array_keys($user->allcaps), $pluginPermission));
269 254
270 255 if (!empty($legacyPermissions)) {
271 - // Migrate to meta
272 256 update_user_meta($user->ID, self::META_KEY, $legacyPermissions);
273 257
274 - // Clean up legacy caps
275 258 foreach ($legacyPermissions as $cap) {
276 259 $user->remove_cap($cap);
277 260 }
278 261 }
@@ -280,9 +263,8 @@
280 263 return $legacyPermissions;
281 264 }
282 265
283 266 /**
284 - * currentUserPermissions method will return the permission of logged-in user
285 267 * @param bool $cached
286 268 * @return array|mixed|string[]
287 269 */
288 270 public static function currentUserPermissions($cached = true)
@@ -298,11 +280,10 @@
298 280 return $permissions;
299 281 }
300 282
301 283 /**
302 - * Determine the WordPress capability string for menu registration.
303 - * Returns 'manage_options' for admins, the user's WP role for agents
304 - * with permissions, or empty string to hide the menu.
284 + * Capability for the admin menu: 'manage_options' for admins, the user's
285 + * role for agents, or '' to hide the menu.
305 286 *
306 287 * @return string
307 288 */
308 289 public static function getMenuPermission()
@@ -318,9 +299,9 @@
318 299 }
319 300
320 301 $metaPermissions = self::getMetaPermissions($userId);
321 302
322 - // Legacy fallback: check wp_capabilities for fst_* caps
303 + // Legacy agents may still have fst_* capabilities instead of meta
323 304 if (empty($metaPermissions)) {
324 305 $user = get_user_by('ID', $userId);
325 306 if ($user) {
326 307 $legacyPermissions = array_intersect(array_keys($user->allcaps), self::pluginPermissions());
@@ -349,10 +330,10 @@
349 330
350 331 }
351 332
352 333 /**
353 - * Whether the current user can perform mutating ticket actions (reply, close, reopen, assign, etc.).
354 - * Draft-only agents return false here — they can view tickets and create drafts but cannot publish.
334 + * Whether the current user can reply, close, assign, etc. False for
335 + * draft-only agents.
355 336 *
356 337 * @return bool
357 338 */
358 339 public static function canManageTickets()
@@ -364,10 +345,9 @@
364 345 ]);
365 346 }
366 347
367 348 /**
368 - * Whether the current user can access ticket API routes at all (read or write).
369 - * Includes manage, merge, draft-only, and view-only agents.
349 + * Whether the current user can reach ticket routes at all, read or write.
370 350 *
371 351 * @return bool
372 352 */
373 353 public static function canAccessTicketRoutes()
@@ -382,19 +362,16 @@
382 362 ]);
383 363 }
384 364
385 365 /**
386 - * Determine ticket visibility level from a permission set.
366 + * Manage permissions win. Otherwise view-only and draft agents see all
367 + * tickets, since they can't modify them.
387 368 *
388 - * Business rule: fst_view_tickets and fst_draft_reply get full visibility because
389 - * read-only and draft agents need to view any ticket, even though they cannot publish.
390 - *
391 369 * @param array $permissions
392 370 * @return string One of the VISIBILITY_* constants.
393 371 */
394 372 private static function resolveTicketVisibility(array $permissions)
395 373 {
396 - // Manage-level permissions take priority for visibility
397 374 if (in_array('fst_manage_other_tickets', $permissions)) {
398 375 return self::VISIBILITY_ALL;
399 376 }
400 377
@@ -405,9 +382,8 @@
405 382 if (in_array('fst_manage_own_tickets', $permissions)) {
406 383 return self::VISIBILITY_ASSIGNED_ONLY;
407 384 }
408 385
409 - // Non-manage roles (draft, view-only) can see all tickets but cannot modify
410 386 if (in_array('fst_draft_reply', $permissions)
411 387 || in_array('fst_view_tickets', $permissions)) {
412 388 return self::VISIBILITY_ALL;
413 389 }
@@ -415,9 +391,8 @@
415 391 return self::VISIBILITY_ASSIGNED_ONLY;
416 392 }
417 393
418 394 /**
419 - * currentTicketVisibility method will return the permission level for a user in tickets
420 395 * @return string
421 396 */
422 397 public static function currentTicketVisibility()
423 398 {
@@ -425,10 +400,9 @@
425 400 return self::resolveTicketVisibility($permissions);
426 401 }
427 402
428 403 /**
429 - * getAgentTicketVisibility method will return the access level of an agent in tickets
430 - * @param false $userId
404 + * @param int|false $userId
431 405 * @return string
432 406 */
433 407 public static function getAgentTicketVisibility($userId = false)
434 408 {
@@ -441,9 +415,10 @@
441 415 return self::resolveTicketVisibility($permissions);
442 416 }
443 417
444 418 /**
445 - * canAccessTicket method will return whether the selected user has permission in selected ticket or not
419 + * Whether the current agent can access this ticket.
420 + *
446 421 * @param $ticket
447 422 * @return bool
448 423 */
449 424 public static function canAccessTicket($ticket)
@@ -451,9 +426,8 @@
451 426 return (new AgentTicketAccess())->currentAgentCanAccess($ticket);
452 427 }
453 428
454 429 /**
455 - * getReadablePermissionGroups method will return the permission group as array
456 430 * @return array[]
457 431 */
458 432 public static function getReadablePermissionGroups()
459 433 {
@@ -504,15 +478,9 @@
504 478 {
505 479 return MailBox::select(['id', 'name'])->get();
506 480 }
507 481
508 - /*
509 - |--------------------------------------------------------------------------
510 - | Deprecated Methods
511 - |--------------------------------------------------------------------------
512 - | These methods are kept for backward compatibility with third-party add-ons.
513 - | They delegate to the renamed replacements and will be removed in a future release.
514 - */
482 + // Deprecated aliases, kept for third-party add-ons.
515 483
516 484 /**
517 485 * @deprecated Use currentTicketVisibility() instead.
518 486 */