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/MCP/Support/AbilityGuard.php +36 -22 2.4.0 → 2.4.5 View file →
@@ -12,14 +12,10 @@
12 12 'fst_manage_other_tickets',
13 13 ];
14 14
15 15 /**
16 - * Per-ability permission requirements.
17 - *
18 - * any_of — passes if the user holds at least one of the listed caps.
19 - * all_of — every listed cap must be held individually.
20 - *
21 - * Both keys may be present; both must pass.
16 + * Per-ability caps. any_of needs one of the caps, all_of needs every cap.
17 + * When both are set, both must pass.
22 18 */
23 19 private static function capabilities()
24 20 {
25 21 return [
@@ -25,42 +21,44 @@
25 21 return [
26 22 'fluent-support/list-tickets' => ['any_of' => ['fst_view_tickets']],
27 23 'fluent-support/get-ticket' => ['any_of' => ['fst_view_tickets']],
28 24 'fluent-support/create-ticket' => ['any_of' => self::MANAGE_TICKET_CAPS],
29 - 'fluent-support/reply-to-ticket' => ['any_of' => self::MANAGE_TICKET_CAPS],
25 + // Draft-only agents may call it; ResponseTools forces draft mode for them.
26 + 'fluent-support/reply-to-ticket' => ['any_of' => array_merge(self::MANAGE_TICKET_CAPS, ['fst_draft_reply'])],
30 27 'fluent-support/close-ticket' => ['any_of' => self::MANAGE_TICKET_CAPS],
31 - 'fluent-support/reopen-ticket' => ['any_of' => self::MANAGE_TICKET_CAPS],
32 28 'fluent-support/update-ticket' => ['any_of' => self::MANAGE_TICKET_CAPS],
33 29 'fluent-support/delete-ticket' => ['any_of' => self::MANAGE_TICKET_CAPS, 'all_of' => ['fst_delete_tickets']],
34 30 'fluent-support/get-ticket-activity' => ['any_of' => ['fst_view_tickets']],
35 31 'fluent-support/merge-tickets' => ['any_of' => ['fst_merge_tickets']],
36 32 'fluent-support/add-internal-note' => ['any_of' => self::MANAGE_TICKET_CAPS],
37 - 'fluent-support/assign-ticket' => ['any_of' => self::MANAGE_TICKET_CAPS, 'all_of' => ['fst_assign_agents']],
33 + // Own drafts and notes only (row gate in ResponseTools).
34 + 'fluent-support/update-response' => ['any_of' => array_merge(self::MANAGE_TICKET_CAPS, ['fst_draft_reply'])],
35 + 'fluent-support/delete-response' => ['any_of' => array_merge(self::MANAGE_TICKET_CAPS, ['fst_draft_reply'])],
36 + // Approvers publish any draft; managers their own (checked in the tool).
37 + 'fluent-support/publish-draft-reply' => ['any_of' => array_merge(self::MANAGE_TICKET_CAPS, ['fst_approve_draft_reply'])],
38 38 'fluent-support/tag-ticket' => ['any_of' => self::MANAGE_TICKET_CAPS],
39 - 'fluent-support/create-tag' => ['any_of' => self::MANAGE_TICKET_CAPS],
40 39 'fluent-support/get-customer-tickets' => ['any_of' => ['fst_sensitive_data']],
41 40 'fluent-support/get-support-context' => ['any_of' => ['fst_view_tickets']],
42 41 'fluent-support/list-saved-replies' => ['any_of' => ['fst_manage_saved_replies']],
43 - 'fluent-support/create-saved-reply' => ['any_of' => ['fst_manage_saved_replies']],
44 - 'fluent-support/update-saved-reply' => ['any_of' => ['fst_manage_saved_replies']],
45 - 'fluent-support/delete-saved-reply' => ['any_of' => ['fst_manage_saved_replies']],
46 42 'fluent-support/get-support-insights' => ['any_of' => ['fst_view_all_reports']],
47 43 'fluent-support/search-customers' => ['any_of' => ['fst_sensitive_data']],
48 44 'fluent-support/get-mentions' => ['any_of' => ['fst_view_tickets']],
49 - 'fluent-support/list-workflows' => ['any_of' => ['fst_manage_workflows']],
45 + 'fluent-support/update-customer' => ['any_of' => ['fst_sensitive_data']],
46 + // Same as the admin route: a ticket manager who also holds Split Ticket.
47 + 'fluent-support/split-ticket' => ['any_of' => self::MANAGE_TICKET_CAPS, 'all_of' => ['fst_split_ticket']],
48 + // Prompts: they only return instructions; the tools they name check their own caps.
49 + 'fluent-support/triage-queue' => ['any_of' => ['fst_view_tickets']],
50 + 'fluent-support/draft-reply' => ['any_of' => ['fst_view_tickets']],
51 + 'fluent-support/summarize-ticket' => ['any_of' => ['fst_view_tickets']],
52 + 'fluent-support/support-report' => ['any_of' => ['fst_view_all_reports']],
50 53 'fluent-support/bulk-action' => ['any_of' => self::MANAGE_TICKET_CAPS],
51 54 ];
52 55 }
53 56
54 57 /**
55 - * Returns a permission_callback closure for the given ability name.
56 - *
57 - * Only checks that the user is authenticated. Capability enforcement
58 - * is deferred to wrapExecuteCallback() in AbilitiesRegistrar so that
59 - * all denials — both auth and cap — return structured JSON via
60 - * MCPHelper::error() rather than the adapter's plain "Permission denied".
61 - *
62 - * Fails closed for unknown abilities.
58 + * permission_callback for an ability. Only checks login and fails closed for
59 + * unknown abilities. Caps are checked in AbilitiesRegistrar::wrapExecuteCallback()
60 + * so denials come back as structured errors, not the adapter's "Permission denied".
63 61 */
64 62 public static function callbackFor($abilityName)
65 63 {
66 64 return function () use ($abilityName) {
@@ -75,8 +73,24 @@
75 73 {
76 74 $caps = self::capabilities()[$abilityName] ?? null;
77 75
78 76 if ($caps === null) {
77 + return false;
78 + }
79 +
80 + return self::checkCaps($caps);
81 + }
82 +
83 + /**
84 + * Whether the current user has these caps: any_of needs one, all_of needs every
85 + * one. Pro's tools pass their own caps here so both plugins check the same way.
86 + *
87 + * @param array{any_of?: string[], all_of?: string[]} $caps
88 + * @return bool
89 + */
90 + public static function checkCaps(array $caps)
91 + {
92 + if (empty($caps['any_of']) && empty($caps['all_of'])) {
79 93 return false;
80 94 }
81 95
82 96 if (!empty($caps['any_of']) && !PermissionManager::userCan($caps['any_of'])) {