PluginProbe ʕ •ᴥ•ʔ
Atarim – AI Agency for WordPress: Edit Pages, Fix Code, Update Plugins, SEO & Client Feedback / 5.1.1
Atarim – AI Agency for WordPress: Edit Pages, Fix Code, Update Plugins, SEO & Client Feedback v5.1.1
5.1.3 5.1.2 5.1.1 5.1 5.0 trunk 3.10 3.11 3.12 3.13 3.14 3.15 3.16 3.17 3.18 3.19 3.2.0 3.2.1 3.22 3.22.1 3.22.2 3.22.3 3.22.4 3.22.5 3.22.6 3.3.0 3.3.1 3.3.2 3.3.2.1 3.3.2.2 3.3.3 3.30 3.31 3.32 3.4 3.4.1 3.4.3 3.4.4 3.5 3.5.1 3.6 3.6.1 3.7 3.8 3.9 3.9.1 3.9.2 3.9.3 3.9.4 3.9.6 3.9.6.1 4.0 4.0.1 4.0.2 4.0.3 4.0.4 4.0.5 4.0.6 4.0.7 4.0.8 4.0.9 4.1.0 4.1.1 4.1.2 4.1.3 4.2 4.2.1 4.2.2 4.3 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.4
atarim-visual-collaboration / third-party / forms / forminator / class-avcf-abilities-forminator-pro.php
atarim-visual-collaboration / third-party / forms / forminator Last commit date
class-avcf-abilities-forminator-pro.php 3 weeks ago class-avcf-abilities-forminator.php 3 weeks ago class-avcf-forminator-detector.php 3 weeks ago class-avcf-forminator-helpers.php 3 weeks ago
class-avcf-abilities-forminator-pro.php
386 lines
1 <?php
2 /**
3 * Forminator — power MCP abilities (standalone cluster, -pro file).
4 *
5 * Forminator-specific depth via Forminator_API and the form model: form CRUD,
6 * entry create/delete/bulk-delete, notification management (saved through the
7 * model), and read access to the sibling Poll and Quiz modules. create-form and
8 * create-entry are experimental (Forminator's field/data shapes are intricate);
9 * deletes are confirm-gated; poll/quiz abilities degrade gracefully when those
10 * API methods are absent. Permission baseline is manage_options (Forminator's
11 * default admin gate). Built on Forminator's API; not runtime-tested here.
12 *
13 * @package atarim-visual-collaboration
14 */
15
16 if ( ! defined('ABSPATH') ) {
17 exit;
18 }
19
20 class AVCF_Abilities_Forminator_Pro extends AVCF_Abilities_Base {
21
22 /** @var AVCF_Forminator_Detector */
23 private $detector;
24
25 public function __construct() {
26 $this->detector = new AVCF_Forminator_Detector();
27 }
28
29 public function register() {
30 if ( ! $this->detector->avcf_forminator_is_available() ) {
31 return;
32 }
33 $this->register_create_form();
34 $this->register_update_form();
35 $this->register_delete_form();
36 $this->register_create_entry();
37 $this->register_delete_entry();
38 $this->register_bulk_delete_entries();
39 $this->register_list_notifications();
40 $this->register_save_notification();
41 $this->register_list_polls();
42 $this->register_get_poll();
43 $this->register_list_poll_entries();
44 $this->register_list_quizzes();
45 $this->register_get_quiz();
46 }
47
48 /* ------------------------------ shared ----------------------------- */
49
50 private function ro_meta() { return [ 'mcp' => [ 'public' => true, 'type' => 'tool' ], 'annotations' => [ 'readonly' => true, 'destructive' => false, 'idempotent' => true ] ]; }
51 private function write_meta( $d = false ) { return [ 'mcp' => [ 'public' => true, 'type' => 'tool' ], 'annotations' => [ 'readonly' => false, 'destructive' => (bool) $d, 'idempotent' => false ] ]; }
52 private function std() { return [ 'type' => 'object', 'properties' => [ 'success' => [ 'type' => 'boolean' ], 'message' => [ 'type' => 'string' ] ], 'required' => [ 'success', 'message' ] ]; }
53
54 public function can_manage() { return current_user_can( 'manage_options' ); }
55
56 /* ----------------------------- create-form ------------------------- */
57
58 private function register_create_form() {
59 $self = $this;
60 wp_register_ability( 'atarim/forminator-create-form', [
61 'label' => 'Create Forminator Form', 'category' => 'atarim',
62 'description' => 'Create a new Forminator form via Forminator_API::add_form. EXPERIMENTAL. Provide name; fields/settings are optional raw structures. Returns the new form_id. Refine in the Forminator editor afterwards.',
63 'input_schema' => [ 'type' => 'object', 'properties' => [ 'name' => [ 'type' => 'string', 'minLength' => 1 ], 'fields' => [ 'type' => 'array' ], 'settings' => [ 'type' => 'object' ] ], 'required' => [ 'name' ], 'additionalProperties' => false ],
64 'output_schema'=> [ 'type' => 'object', 'properties' => [ 'success' => [ 'type' => 'boolean' ], 'form_id' => [ 'type' => 'integer' ], 'message' => [ 'type' => 'string' ] ], 'required' => [ 'success', 'message' ] ],
65 'execute_callback' => function( $input = [] ) {
66 if ( ! method_exists( 'Forminator_API', 'add_form' ) ) { return [ 'success' => false, 'message' => 'Forminator_API::add_form unavailable.' ]; }
67 $name = sanitize_text_field( $input['name'] );
68 if ( $name === '' ) { return [ 'success' => false, 'message' => 'A name is required.' ]; }
69 $fields = isset( $input['fields'] ) && is_array( $input['fields'] ) ? $input['fields'] : [];
70 $settings = isset( $input['settings'] ) && is_array( $input['settings'] ) ? $input['settings'] : [];
71 $settings['formName'] = $name;
72 $new_id = Forminator_API::add_form( $name, 'custom-forms', $fields, $settings );
73 if ( is_wp_error( $new_id ) || ! $new_id ) { return [ 'success' => false, 'message' => is_wp_error( $new_id ) ? $new_id->get_error_message() : 'Create failed.' ]; }
74 return [ 'success' => true, 'form_id' => (int) $new_id, 'message' => sprintf( 'Form %d created (experimental — verify in the Forminator editor).', (int) $new_id ) ];
75 },
76 'permission_callback' => function() use ( $self ) { return $self->can_manage(); },
77 'meta' => $this->write_meta( false ),
78 ] );
79 }
80
81 /* ----------------------------- update-form ------------------------- */
82
83 private function register_update_form() {
84 $self = $this;
85 wp_register_ability( 'atarim/forminator-update-form', [
86 'label' => 'Update Forminator Form', 'category' => 'atarim',
87 'description' => 'Update a form by mutating its model and saving: name (settings.formName), a settings patch (merged), and/or a full fields replacement. Only provided keys change.',
88 'input_schema' => [ 'type' => 'object', 'properties' => [ 'form_id' => [ 'type' => 'integer', 'minimum' => 1 ], 'name' => [ 'type' => 'string' ], 'settings' => [ 'type' => 'object' ], 'fields' => [ 'type' => 'array' ] ], 'required' => [ 'form_id' ], 'additionalProperties' => false ],
89 'output_schema'=> $this->std(),
90 'execute_callback' => function( $input = [] ) {
91 $form = Forminator_API::get_form( (int) $input['form_id'] );
92 if ( ! $form || is_wp_error( $form ) ) { return [ 'success' => false, 'message' => 'Form not found.' ]; }
93 if ( ! is_object( $form ) || ! property_exists( $form, 'settings' ) || ! method_exists( $form, 'save' ) ) { return [ 'success' => false, 'message' => 'Form model is not editable in this version.' ]; }
94 $settings = is_array( $form->settings ) ? $form->settings : [];
95 if ( isset( $input['settings'] ) && is_array( $input['settings'] ) ) { $settings = array_merge( $settings, $input['settings'] ); }
96 if ( isset( $input['name'] ) ) { $settings['formName'] = sanitize_text_field( $input['name'] ); }
97 $form->settings = $settings;
98 if ( isset( $input['fields'] ) && is_array( $input['fields'] ) && property_exists( $form, 'fields' ) ) { $form->fields = $input['fields']; }
99 $form->save();
100 return [ 'success' => true, 'message' => 'Form updated.' ];
101 },
102 'permission_callback' => function() use ( $self ) { return $self->can_manage(); },
103 'meta' => $this->write_meta( false ),
104 ] );
105 }
106
107 /* ----------------------------- delete-form ------------------------- */
108
109 private function register_delete_form() {
110 $self = $this;
111 wp_register_ability( 'atarim/forminator-delete-form', [
112 'label' => 'Delete Forminator Form', 'category' => 'atarim',
113 'description' => 'Permanently delete a form and its entries via Forminator_API::delete_form. DESTRUCTIVE — dry run unless confirm:true.',
114 'input_schema' => [ 'type' => 'object', 'properties' => [ 'form_id' => [ 'type' => 'integer', 'minimum' => 1 ], 'confirm' => [ 'type' => 'boolean', 'default' => false ] ], 'required' => [ 'form_id' ], 'additionalProperties' => false ],
115 'output_schema'=> $this->std(),
116 'execute_callback' => function( $input = [] ) {
117 if ( ! method_exists( 'Forminator_API', 'delete_form' ) ) { return [ 'success' => false, 'message' => 'Forminator_API::delete_form unavailable.' ]; }
118 $form = Forminator_API::get_form( (int) $input['form_id'] );
119 if ( ! $form || is_wp_error( $form ) ) { return [ 'success' => false, 'message' => 'Form not found.' ]; }
120 $count = AVCF_Forminator_Helpers::count_entries( (int) $input['form_id'], true );
121 if ( empty( $input['confirm'] ) ) { return [ 'success' => true, 'message' => sprintf( 'Dry run: re-call with confirm:true to delete this form and %d entr(y/ies).', $count ) ]; }
122 $r = Forminator_API::delete_form( (int) $input['form_id'] );
123 if ( is_wp_error( $r ) ) { return [ 'success' => false, 'message' => $r->get_error_message() ]; }
124 return [ 'success' => true, 'message' => sprintf( 'Form %d deleted.', (int) $input['form_id'] ) ];
125 },
126 'permission_callback' => function() use ( $self ) { return $self->can_manage(); },
127 'meta' => $this->write_meta( true ),
128 ] );
129 }
130
131 /* ----------------------------- create-entry ------------------------ */
132
133 private function register_create_entry() {
134 $self = $this;
135 wp_register_ability( 'atarim/forminator-create-entry', [
136 'label' => 'Create Forminator Entry', 'category' => 'atarim',
137 'description' => 'Create an entry via Forminator_API::add_form_entry. EXPERIMENTAL. values is a map of field_slug => value. Returns the new entry_id.',
138 'input_schema' => [ 'type' => 'object', 'properties' => [ 'form_id' => [ 'type' => 'integer', 'minimum' => 1 ], 'values' => [ 'type' => 'object' ] ], 'required' => [ 'form_id', 'values' ], 'additionalProperties' => false ],
139 'output_schema'=> [ 'type' => 'object', 'properties' => [ 'success' => [ 'type' => 'boolean' ], 'entry_id' => [ 'type' => 'integer' ], 'message' => [ 'type' => 'string' ] ], 'required' => [ 'success', 'message' ] ],
140 'execute_callback' => function( $input = [] ) {
141 if ( ! method_exists( 'Forminator_API', 'add_form_entry' ) ) { return [ 'success' => false, 'message' => 'Forminator_API::add_form_entry unavailable.' ]; }
142 $entry_id = Forminator_API::add_form_entry( (int) $input['form_id'], (array) $input['values'] );
143 if ( is_wp_error( $entry_id ) || ! $entry_id ) { return [ 'success' => false, 'message' => is_wp_error( $entry_id ) ? $entry_id->get_error_message() : 'Create failed (check field slugs/values).' ]; }
144 return [ 'success' => true, 'entry_id' => (int) $entry_id, 'message' => sprintf( 'Entry %d created.', (int) $entry_id ) ];
145 },
146 'permission_callback' => function() use ( $self ) { return $self->can_manage(); },
147 'meta' => $this->write_meta( false ),
148 ] );
149 }
150
151 /* ----------------------------- delete-entry ------------------------ */
152
153 private function register_delete_entry() {
154 $self = $this;
155 wp_register_ability( 'atarim/forminator-delete-entry', [
156 'label' => 'Delete Forminator Entry', 'category' => 'atarim',
157 'description' => 'Permanently delete a single entry via Forminator_API::delete_form_entry. DESTRUCTIVE — dry run unless confirm:true.',
158 'input_schema' => [ 'type' => 'object', 'properties' => [ 'form_id' => [ 'type' => 'integer', 'minimum' => 1 ], 'entry_id' => [ 'type' => 'integer', 'minimum' => 1 ], 'confirm' => [ 'type' => 'boolean', 'default' => false ] ], 'required' => [ 'form_id', 'entry_id' ], 'additionalProperties' => false ],
159 'output_schema'=> $this->std(),
160 'execute_callback' => function( $input = [] ) {
161 if ( ! method_exists( 'Forminator_API', 'delete_form_entry' ) ) { return [ 'success' => false, 'message' => 'Forminator_API::delete_form_entry unavailable.' ]; }
162 if ( empty( $input['confirm'] ) ) { return [ 'success' => true, 'message' => 'Dry run: re-call with confirm:true to permanently delete this entry.' ]; }
163 $r = Forminator_API::delete_form_entry( (int) $input['form_id'], (int) $input['entry_id'] );
164 if ( is_wp_error( $r ) ) { return [ 'success' => false, 'message' => $r->get_error_message() ]; }
165 return [ 'success' => true, 'message' => sprintf( 'Entry %d deleted.', (int) $input['entry_id'] ) ];
166 },
167 'permission_callback' => function() use ( $self ) { return $self->can_manage(); },
168 'meta' => $this->write_meta( true ),
169 ] );
170 }
171
172 /* -------------------------- bulk-delete-entries -------------------- */
173
174 private function register_bulk_delete_entries() {
175 $self = $this;
176 wp_register_ability( 'atarim/forminator-bulk-delete-entries', [
177 'label' => 'Bulk Delete Forminator Entries', 'category' => 'atarim',
178 'description' => 'Permanently delete multiple entries via Forminator_API::delete_form_entries. Provide entry_ids[]. DESTRUCTIVE — dry run unless confirm:true.',
179 'input_schema' => [ 'type' => 'object', 'properties' => [ 'form_id' => [ 'type' => 'integer', 'minimum' => 1 ], 'entry_ids' => [ 'type' => 'array', 'items' => [ 'type' => 'integer' ], 'minItems' => 1 ], 'confirm' => [ 'type' => 'boolean', 'default' => false ] ], 'required' => [ 'form_id', 'entry_ids' ], 'additionalProperties' => false ],
180 'output_schema'=> $this->std(),
181 'execute_callback' => function( $input = [] ) {
182 if ( ! method_exists( 'Forminator_API', 'delete_form_entries' ) ) { return [ 'success' => false, 'message' => 'Forminator_API::delete_form_entries unavailable.' ]; }
183 $ids = array_values( array_filter( array_map( 'intval', (array) $input['entry_ids'] ) ) );
184 if ( empty( $ids ) ) { return [ 'success' => false, 'message' => 'No valid entry ids.' ]; }
185 if ( empty( $input['confirm'] ) ) { return [ 'success' => true, 'message' => sprintf( 'Dry run: re-call with confirm:true to permanently delete %d entr(y/ies).', count( $ids ) ) ]; }
186 $r = Forminator_API::delete_form_entries( (int) $input['form_id'], $ids );
187 if ( is_wp_error( $r ) ) { return [ 'success' => false, 'message' => $r->get_error_message() ]; }
188 return [ 'success' => true, 'message' => sprintf( '%d entr(y/ies) deleted.', count( $ids ) ) ];
189 },
190 'permission_callback' => function() use ( $self ) { return $self->can_manage(); },
191 'meta' => $this->write_meta( true ),
192 ] );
193 }
194
195 /* ------------------------- list-notifications ---------------------- */
196
197 private function register_list_notifications() {
198 $self = $this;
199 wp_register_ability( 'atarim/forminator-list-notifications', [
200 'label' => 'List Forminator Notifications', 'category' => 'atarim',
201 'description' => 'List a form\'s email notifications in full: { id, label, recipients, subject }.',
202 'input_schema' => [ 'type' => 'object', 'properties' => [ 'form_id' => [ 'type' => 'integer', 'minimum' => 1 ] ], 'required' => [ 'form_id' ], 'additionalProperties' => false ],
203 'output_schema'=> [ 'type' => 'object', 'properties' => [ 'success' => [ 'type' => 'boolean' ], 'notifications' => [ 'type' => 'array' ], 'message' => [ 'type' => 'string' ] ], 'required' => [ 'success', 'message' ] ],
204 'execute_callback' => function( $input = [] ) {
205 $form = Forminator_API::get_form( (int) $input['form_id'] );
206 if ( ! $form || is_wp_error( $form ) ) { return [ 'success' => false, 'message' => 'Form not found.' ]; }
207 return [ 'success' => true, 'notifications' => AVCF_Forminator_Helpers::map_notifications( AVCF_Forminator_Helpers::settings_array( $form ) ), 'message' => 'OK.' ];
208 },
209 'permission_callback' => function() use ( $self ) { return $self->can_manage(); },
210 'meta' => $this->ro_meta(),
211 ] );
212 }
213
214 /* -------------------------- save-notification ---------------------- */
215
216 private function register_save_notification() {
217 $self = $this;
218 wp_register_ability( 'atarim/forminator-save-notification', [
219 'label' => 'Create/Update Forminator Notification', 'category' => 'atarim',
220 'description' => 'Create or update an email notification (saved via the form model). Omit notification_id to append a new one. Settable: label, recipients (email), subject, message. Returns the notification_id. Best-effort.',
221 'input_schema' => [ 'type' => 'object', 'properties' => [ 'form_id' => [ 'type' => 'integer', 'minimum' => 1 ], 'notification_id' => [ 'type' => 'string' ], 'label' => [ 'type' => 'string' ], 'recipients' => [ 'type' => 'string' ], 'subject' => [ 'type' => 'string' ], 'message' => [ 'type' => 'string' ] ], 'required' => [ 'form_id' ], 'additionalProperties' => false ],
222 'output_schema'=> [ 'type' => 'object', 'properties' => [ 'success' => [ 'type' => 'boolean' ], 'notification_id' => [ 'type' => 'string' ], 'message' => [ 'type' => 'string' ] ], 'required' => [ 'success', 'message' ] ],
223 'execute_callback' => function( $input = [] ) {
224 $form = Forminator_API::get_form( (int) $input['form_id'] );
225 if ( ! $form || is_wp_error( $form ) ) { return [ 'success' => false, 'message' => 'Form not found.' ]; }
226 if ( ! is_object( $form ) || ! property_exists( $form, 'settings' ) || ! method_exists( $form, 'save' ) ) { return [ 'success' => false, 'message' => 'Form model is not editable in this version.' ]; }
227 $settings = is_array( $form->settings ) ? $form->settings : [];
228 $key = isset( $settings['notifications'] ) && is_array( $settings['notifications'] ) ? 'notifications' : 'notifications';
229 if ( ! isset( $settings[ $key ] ) || ! is_array( $settings[ $key ] ) ) { $settings[ $key ] = []; }
230 $nid = isset( $input['notification_id'] ) && $input['notification_id'] !== '' ? (string) $input['notification_id'] : '';
231 $target_idx = null;
232 if ( $nid !== '' ) {
233 foreach ( $settings[ $key ] as $idx => $n ) { $cid = isset( $n['slug'] ) ? (string) $n['slug'] : (string) $idx; if ( $cid === $nid ) { $target_idx = $idx; break; } }
234 }
235 if ( $target_idx === null ) {
236 $nid = $nid !== '' ? $nid : ( 'notification-' . ( count( $settings[ $key ] ) + 1 ) );
237 $settings[ $key ][] = [ 'slug' => $nid ];
238 $target_idx = count( $settings[ $key ] ) - 1;
239 }
240 if ( isset( $input['label'] ) ) { $settings[ $key ][ $target_idx ]['label'] = sanitize_text_field( $input['label'] ); }
241 if ( isset( $input['recipients'] ) ) { $settings[ $key ][ $target_idx ]['recipients'] = (string) $input['recipients']; }
242 if ( isset( $input['subject'] ) ) { $settings[ $key ][ $target_idx ]['email-subject'] = (string) $input['subject']; }
243 if ( isset( $input['message'] ) ) { $settings[ $key ][ $target_idx ]['email-editor'] = (string) $input['message']; }
244 $form->settings = $settings;
245 $form->save();
246 return [ 'success' => true, 'notification_id' => (string) $nid, 'message' => 'Notification saved (verify in the Forminator editor).' ];
247 },
248 'permission_callback' => function() use ( $self ) { return $self->can_manage(); },
249 'meta' => $this->write_meta( false ),
250 ] );
251 }
252
253 /* ------------------------------- polls ----------------------------- */
254
255 private function register_list_polls() {
256 $self = $this;
257 wp_register_ability( 'atarim/forminator-list-polls', [
258 'label' => 'List Forminator Polls', 'category' => 'atarim',
259 'description' => 'List Forminator polls: { id, title, total_votes }.',
260 'input_schema' => [ 'type' => 'object', 'properties' => [ 'limit' => [ 'type' => 'integer', 'minimum' => 1, 'maximum' => 200, 'default' => 100 ] ], 'additionalProperties' => false ],
261 'output_schema'=> [ 'type' => 'object', 'properties' => [ 'success' => [ 'type' => 'boolean' ], 'polls' => [ 'type' => 'array' ], 'message' => [ 'type' => 'string' ] ], 'required' => [ 'success', 'message' ] ],
262 'execute_callback' => function( $input = [] ) {
263 if ( ! method_exists( 'Forminator_API', 'get_polls' ) ) { return [ 'success' => false, 'message' => 'Forminator polls API unavailable.' ]; }
264 $all = Forminator_API::get_polls( null, 1, -1 );
265 if ( ! is_array( $all ) ) { $all = []; }
266 $out = [];
267 foreach ( $all as $p ) {
268 $pid = AVCF_Forminator_Helpers::extract_id( $p );
269 if ( $pid <= 0 ) { continue; }
270 $out[] = [ 'id' => $pid, 'title' => AVCF_Forminator_Helpers::extract_title( $p ), 'total_votes' => AVCF_Forminator_Helpers::count_entries( $pid, true ) ];
271 }
272 return [ 'success' => true, 'polls' => $out, 'message' => sprintf( '%d poll(s).', count( $out ) ) ];
273 },
274 'permission_callback' => function() use ( $self ) { return $self->can_manage(); },
275 'meta' => $this->ro_meta(),
276 ] );
277 }
278
279 private function register_get_poll() {
280 $self = $this;
281 wp_register_ability( 'atarim/forminator-get-poll', [
282 'label' => 'Get Forminator Poll', 'category' => 'atarim',
283 'description' => 'A poll\'s answer options plus total votes and a best-effort per-answer breakdown.',
284 'input_schema' => [ 'type' => 'object', 'properties' => [ 'poll_id' => [ 'type' => 'integer', 'minimum' => 1 ] ], 'required' => [ 'poll_id' ], 'additionalProperties' => false ],
285 'output_schema'=> [ 'type' => 'object', 'properties' => [ 'success' => [ 'type' => 'boolean' ], 'poll' => [ 'type' => 'object' ], 'message' => [ 'type' => 'string' ] ], 'required' => [ 'success', 'message' ] ],
286 'execute_callback' => function( $input = [] ) {
287 if ( ! method_exists( 'Forminator_API', 'get_poll' ) ) { return [ 'success' => false, 'message' => 'Forminator polls API unavailable.' ]; }
288 $poll = Forminator_API::get_poll( (int) $input['poll_id'] );
289 if ( ! $poll || is_wp_error( $poll ) ) { return [ 'success' => false, 'message' => 'Poll not found.' ]; }
290 $answers = [];
291 $fields = is_object( $poll ) && property_exists( $poll, 'fields' ) ? $poll->fields : [];
292 foreach ( (array) $fields as $f ) {
293 $fs = ( is_object( $f ) && property_exists( $f, 'raw' ) && is_array( $f->raw ) ) ? $f->raw : ( is_array( $f ) ? $f : [] );
294 $answers[] = [ 'slug' => is_object( $f ) && property_exists( $f, 'slug' ) ? (string) $f->slug : '', 'title' => isset( $fs['title'] ) ? (string) $fs['title'] : ( isset( $fs['field_label'] ) ? (string) $fs['field_label'] : '' ) ];
295 }
296 $breakdown = [];
297 global $wpdb;
298 $emeta = $wpdb->prefix . 'frmt_form_entry_meta';
299 $etab = AVCF_Forminator_Helpers::entry_table();
300 $rows = $wpdb->get_results( $wpdb->prepare( "SELECT m.meta_value AS val, COUNT(*) AS cnt FROM `{$emeta}` m INNER JOIN `{$etab}` e ON e.entry_id = m.entry_id WHERE e.form_id = %d GROUP BY m.meta_value", (int) $input['poll_id'] ), ARRAY_A );
301 foreach ( (array) $rows as $r ) { $breakdown[] = [ 'answer' => (string) $r['val'], 'votes' => (int) $r['cnt'] ]; }
302 return [ 'success' => true, 'poll' => [ 'id' => (int) $input['poll_id'], 'title' => AVCF_Forminator_Helpers::extract_title( $poll ), 'answers' => $answers, 'total_votes' => AVCF_Forminator_Helpers::count_entries( (int) $input['poll_id'], true ), 'breakdown' => $breakdown ], 'message' => 'OK.' ];
303 },
304 'permission_callback' => function() use ( $self ) { return $self->can_manage(); },
305 'meta' => $this->ro_meta(),
306 ] );
307 }
308
309 private function register_list_poll_entries() {
310 $self = $this;
311 wp_register_ability( 'atarim/forminator-list-poll-entries', [
312 'label' => 'List Forminator Poll Votes', 'category' => 'atarim',
313 'description' => 'List individual votes for a poll (newest first), normalized like form entries. Paged via limit/offset.',
314 'input_schema' => [ 'type' => 'object', 'properties' => [ 'poll_id' => [ 'type' => 'integer', 'minimum' => 1 ], 'limit' => [ 'type' => 'integer', 'minimum' => 1, 'maximum' => 200, 'default' => 50 ], 'offset' => [ 'type' => 'integer', 'minimum' => 0, 'default' => 0 ] ], 'required' => [ 'poll_id' ], 'additionalProperties' => false ],
315 'output_schema'=> [ 'type' => 'object', 'properties' => [ 'success' => [ 'type' => 'boolean' ], 'votes' => [ 'type' => 'array' ], 'total' => [ 'type' => 'integer' ], 'message' => [ 'type' => 'string' ] ], 'required' => [ 'success', 'message' ] ],
316 'execute_callback' => function( $input = [] ) {
317 if ( ! method_exists( 'Forminator_API', 'get_entries' ) ) { return [ 'success' => false, 'message' => 'Forminator entries API unavailable.' ]; }
318 $poll_id = (int) $input['poll_id'];
319 $limit = isset( $input['limit'] ) ? max( 1, (int) $input['limit'] ) : 50;
320 $offset = isset( $input['offset'] ) ? max( 0, (int) $input['offset'] ) : 0;
321 $raw = Forminator_API::get_entries( $poll_id, [ 'limit' => $limit, 'offset' => $offset ] );
322 if ( is_wp_error( $raw ) ) { return [ 'success' => false, 'message' => $raw->get_error_message() ]; }
323 $out = [];
324 foreach ( (array) $raw as $e ) { $out[] = AVCF_Forminator_Helpers::normalize_entry( $e, $poll_id, [], false ); }
325 return [ 'success' => true, 'votes' => $out, 'total' => AVCF_Forminator_Helpers::count_entries( $poll_id, true ), 'message' => sprintf( '%d vote(s).', count( $out ) ) ];
326 },
327 'permission_callback' => function() use ( $self ) { return $self->can_manage(); },
328 'meta' => $this->ro_meta(),
329 ] );
330 }
331
332 /* ------------------------------ quizzes ---------------------------- */
333
334 private function register_list_quizzes() {
335 $self = $this;
336 wp_register_ability( 'atarim/forminator-list-quizzes', [
337 'label' => 'List Forminator Quizzes', 'category' => 'atarim',
338 'description' => 'List Forminator quizzes: { id, title, total_submissions }.',
339 'input_schema' => [ 'type' => 'object', 'properties' => [ 'limit' => [ 'type' => 'integer', 'minimum' => 1, 'maximum' => 200, 'default' => 100 ] ], 'additionalProperties' => false ],
340 'output_schema'=> [ 'type' => 'object', 'properties' => [ 'success' => [ 'type' => 'boolean' ], 'quizzes' => [ 'type' => 'array' ], 'message' => [ 'type' => 'string' ] ], 'required' => [ 'success', 'message' ] ],
341 'execute_callback' => function( $input = [] ) {
342 if ( ! method_exists( 'Forminator_API', 'get_quizzes' ) ) { return [ 'success' => false, 'message' => 'Forminator quizzes API unavailable.' ]; }
343 $all = Forminator_API::get_quizzes( null, 1, -1 );
344 if ( ! is_array( $all ) ) { $all = []; }
345 $out = [];
346 foreach ( $all as $q ) {
347 $qid = AVCF_Forminator_Helpers::extract_id( $q );
348 if ( $qid <= 0 ) { continue; }
349 $out[] = [ 'id' => $qid, 'title' => AVCF_Forminator_Helpers::extract_title( $q ), 'total_submissions' => AVCF_Forminator_Helpers::count_entries( $qid, true ) ];
350 }
351 return [ 'success' => true, 'quizzes' => $out, 'message' => sprintf( '%d quiz(zes).', count( $out ) ) ];
352 },
353 'permission_callback' => function() use ( $self ) { return $self->can_manage(); },
354 'meta' => $this->ro_meta(),
355 ] );
356 }
357
358 private function register_get_quiz() {
359 $self = $this;
360 wp_register_ability( 'atarim/forminator-get-quiz', [
361 'label' => 'Get Forminator Quiz', 'category' => 'atarim',
362 'description' => 'A quiz\'s questions and answer options (best-effort from the quiz model fields), plus total submissions.',
363 'input_schema' => [ 'type' => 'object', 'properties' => [ 'quiz_id' => [ 'type' => 'integer', 'minimum' => 1 ] ], 'required' => [ 'quiz_id' ], 'additionalProperties' => false ],
364 'output_schema'=> [ 'type' => 'object', 'properties' => [ 'success' => [ 'type' => 'boolean' ], 'quiz' => [ 'type' => 'object' ], 'message' => [ 'type' => 'string' ] ], 'required' => [ 'success', 'message' ] ],
365 'execute_callback' => function( $input = [] ) {
366 if ( ! method_exists( 'Forminator_API', 'get_quiz' ) ) { return [ 'success' => false, 'message' => 'Forminator quizzes API unavailable.' ]; }
367 $quiz = Forminator_API::get_quiz( (int) $input['quiz_id'] );
368 if ( ! $quiz || is_wp_error( $quiz ) ) { return [ 'success' => false, 'message' => 'Quiz not found.' ]; }
369 $questions = [];
370 $fields = is_object( $quiz ) && property_exists( $quiz, 'questions' ) ? $quiz->questions : ( is_object( $quiz ) && property_exists( $quiz, 'fields' ) ? $quiz->fields : [] );
371 foreach ( (array) $fields as $f ) {
372 $arr = is_array( $f ) ? $f : ( is_object( $f ) && property_exists( $f, 'raw' ) && is_array( $f->raw ) ? $f->raw : [] );
373 $answers = [];
374 if ( isset( $arr['answers'] ) && is_array( $arr['answers'] ) ) {
375 foreach ( $arr['answers'] as $a ) { $answers[] = is_array( $a ) && isset( $a['title'] ) ? (string) $a['title'] : ( is_string( $a ) ? $a : '' ); }
376 }
377 $questions[] = [ 'title' => isset( $arr['title'] ) ? (string) $arr['title'] : '', 'answers' => $answers ];
378 }
379 return [ 'success' => true, 'quiz' => [ 'id' => (int) $input['quiz_id'], 'title' => AVCF_Forminator_Helpers::extract_title( $quiz ), 'questions' => $questions, 'total_submissions' => AVCF_Forminator_Helpers::count_entries( (int) $input['quiz_id'], true ) ], 'message' => 'OK.' ];
380 },
381 'permission_callback' => function() use ( $self ) { return $self->can_manage(); },
382 'meta' => $this->ro_meta(),
383 ] );
384 }
385 }
386