PluginProbe
Fluent Support – Helpdesk & Customer Support Ticket System / trunk
Fluent Support – Helpdesk & Customer Support Ticket System vtrunk
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 1.5.5 All 68 releases
fluent-support / app / Modules / MCP / Support / PermissionGate.php

PermissionGate.php in Fluent Support – Helpdesk & Customer Support Ticket System trunk, at app/Modules/MCP/Support/PermissionGate.php

134 lines 4.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentSupport\App\Modules\MCP\Support;
4
5 use FluentSupport\App\Modules\PermissionManager;
6 use FluentSupport\App\Services\Helper;
7
8 /**
9 * Maps MCP abilities to Fluent Support's existing capability model. The MCP
10 * user IS a WordPress user with Fluent Support agent permissions (fst_*),
11 * stored in _fluent_support_permissions user meta. We never invent a parallel
12 * permission system — we reuse PermissionManager, the same check the admin
13 * REST routes use (AgentTicketPolicy calls the same methods for every ticket
14 * API request).
15 *
16 * Two layers:
17 * - transport(): can this user reach the Fluent Support MCP endpoint at all?
18 * - per-ability permission_callback: gating inside AbilitiesRegistrar.
19 *
20 * Annotations are UX hints only; this class is the transport enforcement boundary.
21 */
22 class PermissionGate
23 {
24 const OPTION_KEY = '_mcp_settings';
25
26 /**
27 * Transport gate for the fluent-support MCP server. Runs on every request.
28 * Checks (a) MCP is enabled and (b) user holds at least a Fluent Support
29 * agent permission. Per-ability permission_callback still runs on top.
30 *
31 * @param mixed $request
32 * @return true|\WP_Error
33 */
34 public static function transport($request = null)
35 {
36 if (!self::isEnabled()) {
37 return new \WP_Error(
38 'fluent_support_mcp_disabled',
39 __('Fluent Support MCP is disabled. Enable it in Settings → MCP.', 'fluent-support')
40 );
41 }
42
43 // Explicit login check for a clear error code; canAccessTicketRoutes()
44 // already returns false for unauthenticated users (user_id 0 → empty
45 // permissions), but the distinct code helps the agent distinguish
46 // "not logged in" from "logged in but no Fluent Support role".
47 if (!is_user_logged_in()) {
48 return new \WP_Error(
49 'fluent_support_mcp_unauthorized',
50 __('Authentication required to access the Fluent Support MCP server.', 'fluent-support')
51 );
52 }
53
54 // Reuse the same check AgentTicketPolicy uses for all GET ticket routes.
55 if (!PermissionManager::canAccessTicketRoutes()) {
56 return new \WP_Error(
57 'fluent_support_mcp_forbidden',
58 __('Your account does not have Fluent Support access.', 'fluent-support')
59 );
60 }
61
62 return true;
63 }
64
65 /**
66 * Whether the MCP server is enabled. Ships OFF; enabled via the
67 * fluent_kit/mcp_toggle_handlers filter (FluentHub) or a future settings UI.
68 */
69 public static function isEnabled()
70 {
71 $settings = Helper::getOption(self::OPTION_KEY, []);
72
73 return is_array($settings) && isset($settings['active']) && $settings['active'] === 'yes';
74 }
75
76 /**
77 * Persist the master on/off switch. Called from the FluentHub toggle handler
78 * and any future settings controller.
79 *
80 * Defense-in-depth: re-check manage_options here because the toolkit toggle
81 * path delegates auth to an external plugin.
82 *
83 * @param bool $enabled
84 * @return bool
85 */
86 public static function setEnabled($enabled)
87 {
88 if (!current_user_can('manage_options')) {
89 return false;
90 }
91
92 $settings = Helper::getOption(self::OPTION_KEY, []);
93 if (!is_array($settings)) {
94 $settings = [];
95 }
96
97 $settings['active'] = $enabled ? 'yes' : 'no';
98 Helper::updateOption(self::OPTION_KEY, $settings);
99
100 return (bool) $enabled;
101 }
102
103 /**
104 * SLA thresholds used by getSupportContext and getSupportInsights.
105 * Written by McpSettingsController::saveSettings().
106 *
107 * @return array{first_response_hours: int, resolution_hours: int}
108 */
109 public static function getSlaSettings()
110 {
111 $settings = Helper::getOption(self::OPTION_KEY, []);
112 $sla = isset($settings['sla']) && is_array($settings['sla']) ? $settings['sla'] : [];
113
114 return [
115 'first_response_hours' => min(max((int) ($sla['first_response_hours'] ?? 4), 1), 720),
116 'resolution_hours' => min(max((int) ($sla['resolution_hours'] ?? 24), 1), 8760),
117 ];
118 }
119
120 /**
121 * AI guidelines injected into getSupportContext. Sanitized at read time so
122 * anything stored raw before Phase 3 is cleaned on the way out.
123 *
124 * @return string Up to 2000 characters, no HTML.
125 */
126 public static function getAiGuidelines()
127 {
128 $settings = Helper::getOption(self::OPTION_KEY, []);
129 $raw = isset($settings['ai_guidelines']) ? (string) $settings['ai_guidelines'] : '';
130
131 return mb_substr(wp_strip_all_tags($raw), 0, 2000);
132 }
133 }
134