PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / 6.2.12
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder v6.2.12
6.2.14 6.2.13 6.2.12 6.2.10 6.2.11 6.2.9 6.2.8 6.2.7 6.2.6 6.2.5 6.2.4 6.2.3 6.2.2 3.6.22 3.6.31 3.6.40 3.6.41 3.6.42 3.6.50 3.6.51 3.6.60 3.6.61 3.6.62 3.6.64 3.6.65 All 196 releases
fluentform / app / Modules / MCP / Support / FormAccess.php

FormAccess.php in Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder 6.2.12, at app/Modules/MCP/Support/FormAccess.php

161 lines 5.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentForm\App\Modules\MCP\Support;
4
5 defined('ABSPATH') || exit;
6
7 use FluentForm\App\Models\Form;
8 use FluentForm\App\Models\Submission;
9
10 /**
11 * Shared form/entry access + scoping for the MCP tools.
12 *
13 * Centralizes the three things every form-touching tool was repeating:
14 * - resolve a form_id / entry_id to a model with the right not_found /
15 * forbidden / missing_identifier errors,
16 * - apply the current user's form scope to a query,
17 * - the "real form field?" test that keeps internal response keys
18 * (nonces, referers, embed ids) out of agent-facing output.
19 */
20 class FormAccess
21 {
22 // Non-underscore response keys that are never real form fields.
23 const SYSTEM_KEYS = [
24 'g-recaptcha-response',
25 'h-captcha-response',
26 'cf-turnstile-response',
27 ];
28
29 /**
30 * Validate + access-check + load a form. Accepts the raw tool params
31 * (form_id is read from them) or a bare id, so every tool shares one
32 * resolve line instead of repeating the extraction.
33 *
34 * @param array|int|string $params
35 *
36 * @return Form|\WP_Error
37 */
38 public static function resolveForm($params)
39 {
40 $formId = is_array($params) ? (isset($params['form_id']) ? $params['form_id'] : 0) : $params;
41 $formId = (int) $formId;
42 if (!$formId) {
43 return MCPHelper::error(ErrorCodes::MISSING_IDENTIFIER, __('form_id is required.', 'fluentform'), ['fields' => ['form_id']]);
44 }
45 if (!PermissionGate::canAccessForm($formId)) {
46 return MCPHelper::error(ErrorCodes::FORBIDDEN, __('You do not have access to this form.', 'fluentform'));
47 }
48 $form = Form::query()->find($formId);
49 if (!$form) {
50 return MCPHelper::error(ErrorCodes::NOT_FOUND, __('No form found for the given form_id.', 'fluentform'));
51 }
52
53 return $form;
54 }
55
56 /**
57 * Validate + load an entry, then check its (DB-resolved) form against the
58 * user's form scope — never trusting a caller-supplied form_id (IDOR-safe).
59 * Accepts the raw tool params (entry_id is read from them) or a bare id.
60 *
61 * @param array|int|string $params
62 *
63 * @return Submission|\WP_Error
64 */
65 public static function resolveSubmission($params)
66 {
67 $entryId = is_array($params) ? (isset($params['entry_id']) ? $params['entry_id'] : 0) : $params;
68 $entryId = (int) $entryId;
69 if (!$entryId) {
70 return MCPHelper::error(ErrorCodes::MISSING_IDENTIFIER, __('entry_id is required.', 'fluentform'), ['fields' => ['entry_id']]);
71 }
72 $submission = Submission::query()->find($entryId);
73 if (!$submission) {
74 return MCPHelper::error(ErrorCodes::NOT_FOUND, __('No entry found for the given entry_id.', 'fluentform'));
75 }
76 if (!PermissionGate::canAccessForm($submission->form_id)) {
77 return MCPHelper::error(ErrorCodes::FORBIDDEN, __('You do not have access to this entry\'s form.', 'fluentform'));
78 }
79
80 return $submission;
81 }
82
83 /**
84 * Apply the current user's form scope to a query. $column is the form-id
85 * column on the queried table ('id' for forms, 'form_id' for submissions,
86 * or a table-qualified name when the query joins).
87 */
88 public static function applyScope($query, $column = 'form_id')
89 {
90 $scope = PermissionGate::formScope();
91 if (false !== $scope) {
92 $query->whereIn($column, $scope ? $scope : [0]);
93 }
94
95 return $query;
96 }
97
98 /** Count non-trashed entries for a form, or null on failure. */
99 public static function entryCount($formId)
100 {
101 try {
102 return (int) Submission::query()
103 ->where('form_id', $formId)
104 ->where('status', '!=', 'trashed')
105 ->count();
106 } catch (\Throwable $e) {
107 return null;
108 }
109 }
110
111 /**
112 * Non-trashed entry counts for many forms in one grouped query, so list
113 * tools never run a COUNT per row. Returns [formId => int] covering every
114 * requested id (0 when absent), or nulls on failure.
115 *
116 * @return array<int, int|null>
117 */
118 public static function entryCounts(array $formIds)
119 {
120 $formIds = array_values(array_unique(array_map('intval', $formIds)));
121 if (!$formIds) {
122 return [];
123 }
124
125 try {
126 $rows = Submission::query()
127 ->whereIn('form_id', $formIds)
128 ->where('status', '!=', 'trashed')
129 ->selectRaw('form_id, COUNT(*) as total')
130 ->groupBy('form_id')
131 ->get();
132
133 $out = array_fill_keys($formIds, 0);
134 foreach ($rows as $row) {
135 $out[(int) $row->form_id] = (int) $row->total;
136 }
137
138 return $out;
139 } catch (\Throwable $e) {
140 return array_fill_keys($formIds, null);
141 }
142 }
143
144 /**
145 * True for response keys that are framework internals, not user-entered
146 * fields: anything prefixed with "_" (nonces, _wp_http_referer,
147 * __fluent_form_embded_post_id) plus a few named captcha tokens.
148 */
149 public static function isInternalKey($key)
150 {
151 if (!is_string($key) || '' === $key) {
152 return false;
153 }
154 if ('_' === $key[0]) {
155 return true;
156 }
157
158 return in_array($key, self::SYSTEM_KEYS, true);
159 }
160 }
161