PluginProbe ʕ •ᴥ•ʔ
Atarim – AI Agency for WordPress: Edit Pages, Fix Code, Update Plugins, SEO & Client Feedback / trunk
Atarim – AI Agency for WordPress: Edit Pages, Fix Code, Update Plugins, SEO & Client Feedback vtrunk
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 / fluent-forms / class-avcf-abilities-fluent-pro.php
atarim-visual-collaboration / third-party / forms / fluent-forms Last commit date
class-avcf-abilities-fluent-pro.php 3 weeks ago class-avcf-abilities-fluent.php 3 weeks ago class-avcf-fluent-detector.php 1 month ago class-avcf-fluent-helpers.php 3 weeks ago
class-avcf-abilities-fluent-pro.php
412 lines
1 <?php
2 /**
3 * Fluent Forms — power MCP abilities (standalone cluster, -pro file).
4 *
5 * Fluent-specific depth: form CRUD, submission status/favourite/delete workflow,
6 * a generic form_meta read/write (Fluent keeps notifications, confirmations and
7 * integration feeds as JSON rows in fluentform_form_meta), typed notification
8 * helpers, confirmation read, and an integration-feed listing. Writers use
9 * Fluent's capabilities with a manage_options fallback; deletes are
10 * confirm-gated. create-form is experimental (raw insert of Fluent's JSON
11 * schema). Direct $wpdb; not runtime-tested here.
12 *
13 * @package atarim-visual-collaboration
14 */
15
16 if ( ! defined('ABSPATH') ) {
17 exit;
18 }
19
20 class AVCF_Abilities_Fluent_Pro extends AVCF_Abilities_Base {
21
22 /** @var AVCF_Fluent_Detector */
23 private $detector;
24
25 public function __construct() {
26 $this->detector = new AVCF_Fluent_Detector();
27 }
28
29 public function register() {
30 if ( ! $this->detector->avcf_fluent_is_available() ) {
31 return;
32 }
33 $this->register_create_form();
34 $this->register_update_form();
35 $this->register_delete_form();
36 $this->register_delete_entry();
37 $this->register_set_entry_status();
38 $this->register_favorite_entry();
39 $this->register_list_form_meta();
40 $this->register_get_form_meta();
41 $this->register_save_form_meta();
42 $this->register_list_notifications();
43 $this->register_save_notification();
44 $this->register_get_confirmation();
45 $this->register_list_integration_feeds();
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_edit_forms() { return current_user_can( 'fluentform_forms_manager' ) || current_user_can( 'manage_options' ); }
55 public function can_edit_entries() { return current_user_can( 'fluentform_entries_viewer' ) || current_user_can( 'manage_options' ); }
56 public function can_settings() { return current_user_can( 'fluentform_settings_manager' ) || current_user_can( 'fluentform_forms_manager' ) || current_user_can( 'manage_options' ); }
57
58 public function form_exists( $form_id ) {
59 global $wpdb; $t = AVCF_Fluent_Helpers::forms_table();
60 return (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM `{$t}` WHERE id = %d", (int) $form_id ) ) > 0;
61 }
62 /** Decoded JSON for a meta_key, or null. */
63 public function get_meta( $form_id, $key ) {
64 global $wpdb; $t = AVCF_Fluent_Helpers::meta_table();
65 $val = $wpdb->get_var( $wpdb->prepare( "SELECT value FROM `{$t}` WHERE form_id = %d AND meta_key = %s LIMIT 1", (int) $form_id, (string) $key ) );
66 if ( $val === null ) { return null; }
67 $decoded = json_decode( $val, true );
68 return $decoded === null ? $val : $decoded;
69 }
70 public function set_meta( $form_id, $key, $value ) {
71 global $wpdb; $t = AVCF_Fluent_Helpers::meta_table();
72 $json = is_string( $value ) ? $value : wp_json_encode( $value );
73 $id = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM `{$t}` WHERE form_id = %d AND meta_key = %s LIMIT 1", (int) $form_id, (string) $key ) );
74 if ( $id ) { return $wpdb->update( $t, [ 'value' => $json ], [ 'id' => (int) $id ], [ '%s' ], [ '%d' ] ); }
75 return $wpdb->insert( $t, [ 'form_id' => (int) $form_id, 'meta_key' => (string) $key, 'value' => $json ] );
76 }
77
78 /* ----------------------------- create-form ------------------------- */
79
80 private function register_create_form() {
81 $self = $this;
82 wp_register_ability( 'atarim/fluent-create-form', [
83 'label' => 'Create Fluent Form', 'category' => 'atarim',
84 'description' => 'Create a new Fluent form. EXPERIMENTAL: builds a minimal valid form_fields JSON from a simple fields array (each: { type (e.g. input_text, input_email, textarea, select), label, name?, required?, options?[] }) plus a submit button. Returns the new form_id. Complex field types should be refined in the Fluent editor afterwards.',
85 'input_schema' => [ 'type' => 'object', 'properties' => [ 'title' => [ 'type' => 'string', 'minLength' => 1 ], 'fields' => [ 'type' => 'array', 'items' => [ 'type' => 'object' ] ], 'status' => [ 'type' => 'string', 'enum' => [ 'published', 'unpublished' ], 'default' => 'published' ] ], 'required' => [ 'title' ], 'additionalProperties' => false ],
86 'output_schema'=> [ 'type' => 'object', 'properties' => [ 'success' => [ 'type' => 'boolean' ], 'form_id' => [ 'type' => 'integer' ], 'message' => [ 'type' => 'string' ] ], 'required' => [ 'success', 'message' ] ],
87 'execute_callback' => function( $input = [] ) use ( $self ) {
88 global $wpdb;
89 $title = sanitize_text_field( $input['title'] );
90 if ( $title === '' ) { return [ 'success' => false, 'message' => 'A title is required.' ]; }
91 $fields = [];
92 $defs = isset( $input['fields'] ) && is_array( $input['fields'] ) ? $input['fields'] : [];
93 $i = 1;
94 foreach ( $defs as $f ) {
95 if ( ! is_array( $f ) ) { continue; }
96 $element = isset( $f['type'] ) ? (string) $f['type'] : 'input_text';
97 $name = isset( $f['name'] ) && $f['name'] !== '' ? sanitize_key( $f['name'] ) : ( 'field_' . $i );
98 $label = isset( $f['label'] ) ? (string) $f['label'] : ( 'Field ' . $i );
99 $settings = [ 'label' => $label, 'validation_rules' => [ 'required' => [ 'value' => ! empty( $f['required'] ), 'message' => 'This field is required.' ] ] ];
100 if ( isset( $f['options'] ) && is_array( $f['options'] ) ) {
101 $settings['advanced_options'] = [];
102 foreach ( $f['options'] as $opt ) { $txt = is_array( $opt ) ? ( isset( $opt['label'] ) ? $opt['label'] : '' ) : (string) $opt; $settings['advanced_options'][] = [ 'label' => (string) $txt, 'value' => sanitize_title( (string) $txt ) ]; }
103 }
104 $fields[] = [ 'index' => $i - 1, 'element' => $element, 'attributes' => [ 'type' => 'text', 'name' => $name ], 'settings' => $settings, 'editor_options' => [ 'title' => $label ] ];
105 $i++;
106 }
107 $form_fields = wp_json_encode( [ 'fields' => $fields, 'submitButton' => [ 'uniqElKey' => 'el_' . time(), 'element' => 'button', 'attributes' => [ 'type' => 'submit', 'class' => '' ], 'settings' => [ 'button_style' => 'default', 'align' => 'left', 'button_size' => 'md', 'color' => '#ffffff', 'background_color' => '#409EFF', 'button_ui' => [ 'type' => 'default', 'text' => 'Submit' ] ] ] ] );
108 $now = current_time( 'mysql' );
109 $ok = $wpdb->insert( AVCF_Fluent_Helpers::forms_table(), [ 'title' => $title, 'form_fields' => $form_fields, 'status' => isset( $input['status'] ) ? (string) $input['status'] : 'published', 'has_payment' => 0, 'type' => 'form', 'created_by' => get_current_user_id(), 'created_at' => $now, 'updated_at' => $now ] );
110 if ( ! $ok ) { return [ 'success' => false, 'message' => 'Create failed (schema mismatch possible).' ]; }
111 return [ 'success' => true, 'form_id' => (int) $wpdb->insert_id, 'message' => sprintf( 'Form %d created (experimental — verify in the Fluent editor).', (int) $wpdb->insert_id ) ];
112 },
113 'permission_callback' => function() use ( $self ) { return $self->can_edit_forms(); },
114 'meta' => $this->write_meta( false ),
115 ] );
116 }
117
118 /* ----------------------------- update-form ------------------------- */
119
120 private function register_update_form() {
121 $self = $this;
122 wp_register_ability( 'atarim/fluent-update-form', [
123 'label' => 'Update Fluent Form', 'category' => 'atarim',
124 'description' => 'Update a form\'s title and/or status, and/or replace its form_fields with a full structure object (must contain "fields"). Only provided keys change.',
125 'input_schema' => [ 'type' => 'object', 'properties' => [ 'form_id' => [ 'type' => 'integer', 'minimum' => 1 ], 'title' => [ 'type' => 'string' ], 'status' => [ 'type' => 'string', 'enum' => [ 'published', 'unpublished' ] ], 'form_fields' => [ 'type' => 'object' ] ], 'required' => [ 'form_id' ], 'additionalProperties' => false ],
126 'output_schema'=> $this->std(),
127 'execute_callback' => function( $input = [] ) use ( $self ) {
128 global $wpdb;
129 $form_id = (int) $input['form_id'];
130 if ( ! $self->form_exists( $form_id ) ) { return [ 'success' => false, 'message' => 'Form not found.' ]; }
131 $data = [ 'updated_at' => current_time( 'mysql' ) ]; $fmt = [ '%s' ];
132 if ( isset( $input['title'] ) ) { $data['title'] = sanitize_text_field( $input['title'] ); $fmt[] = '%s'; }
133 if ( isset( $input['status'] ) ) { $data['status'] = (string) $input['status']; $fmt[] = '%s'; }
134 if ( isset( $input['form_fields'] ) && is_array( $input['form_fields'] ) ) {
135 if ( ! isset( $input['form_fields']['fields'] ) ) { return [ 'success' => false, 'message' => 'form_fields must contain a "fields" array.' ]; }
136 $data['form_fields'] = wp_json_encode( $input['form_fields'] ); $fmt[] = '%s';
137 }
138 $r = $wpdb->update( AVCF_Fluent_Helpers::forms_table(), $data, [ 'id' => $form_id ], $fmt, [ '%d' ] );
139 if ( $r === false ) { return [ 'success' => false, 'message' => 'Update failed.' ]; }
140 return [ 'success' => true, 'message' => 'Form updated.' ];
141 },
142 'permission_callback' => function() use ( $self ) { return $self->can_edit_forms(); },
143 'meta' => $this->write_meta( false ),
144 ] );
145 }
146
147 /* ----------------------------- delete-form ------------------------- */
148
149 private function register_delete_form() {
150 $self = $this;
151 wp_register_ability( 'atarim/fluent-delete-form', [
152 'label' => 'Delete Fluent Form', 'category' => 'atarim',
153 'description' => 'Permanently delete a form, its form_meta, and all its submissions (+ submission_meta). DESTRUCTIVE — dry run unless confirm:true.',
154 'input_schema' => [ 'type' => 'object', 'properties' => [ 'form_id' => [ 'type' => 'integer', 'minimum' => 1 ], 'confirm' => [ 'type' => 'boolean', 'default' => false ] ], 'required' => [ 'form_id' ], 'additionalProperties' => false ],
155 'output_schema'=> $this->std(),
156 'execute_callback' => function( $input = [] ) use ( $self ) {
157 global $wpdb;
158 $form_id = (int) $input['form_id'];
159 if ( ! $self->form_exists( $form_id ) ) { return [ 'success' => false, 'message' => 'Form not found.' ]; }
160 $sub_count = AVCF_Fluent_Helpers::count_entries( $form_id, true );
161 if ( empty( $input['confirm'] ) ) { return [ 'success' => true, 'message' => sprintf( 'Dry run: re-call with confirm:true to delete this form, its settings, and %d submission(s).', $sub_count ) ]; }
162 $subs = AVCF_Fluent_Helpers::subs_table();
163 $sub_ids = $wpdb->get_col( $wpdb->prepare( "SELECT id FROM `{$subs}` WHERE form_id = %d", $form_id ) );
164 if ( $sub_ids ) {
165 $smeta = $wpdb->prefix . 'fluentform_submission_meta';
166 $in = implode( ',', array_map( 'intval', $sub_ids ) );
167 $wpdb->query( "DELETE FROM `{$smeta}` WHERE response_id IN ({$in})" );
168 }
169 $wpdb->delete( $subs, [ 'form_id' => $form_id ], [ '%d' ] );
170 $wpdb->delete( AVCF_Fluent_Helpers::meta_table(), [ 'form_id' => $form_id ], [ '%d' ] );
171 $wpdb->delete( AVCF_Fluent_Helpers::forms_table(), [ 'id' => $form_id ], [ '%d' ] );
172 return [ 'success' => true, 'message' => sprintf( 'Form %d and %d submission(s) deleted.', $form_id, count( (array) $sub_ids ) ) ];
173 },
174 'permission_callback' => function() use ( $self ) { return $self->can_edit_forms(); },
175 'meta' => $this->write_meta( true ),
176 ] );
177 }
178
179 /* ----------------------------- delete-entry ------------------------ */
180
181 private function register_delete_entry() {
182 $self = $this;
183 wp_register_ability( 'atarim/fluent-delete-entry', [
184 'label' => 'Delete Fluent Entry', 'category' => 'atarim',
185 'description' => 'Permanently delete a submission and its meta. DESTRUCTIVE — dry run unless confirm:true.',
186 'input_schema' => [ 'type' => 'object', 'properties' => [ 'entry_id' => [ 'type' => 'integer', 'minimum' => 1 ], 'confirm' => [ 'type' => 'boolean', 'default' => false ] ], 'required' => [ 'entry_id' ], 'additionalProperties' => false ],
187 'output_schema'=> $this->std(),
188 'execute_callback' => function( $input = [] ) {
189 global $wpdb;
190 $entry_id = (int) $input['entry_id'];
191 $subs = AVCF_Fluent_Helpers::subs_table();
192 if ( (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM `{$subs}` WHERE id = %d", $entry_id ) ) === 0 ) { return [ 'success' => false, 'message' => 'Entry not found.' ]; }
193 if ( empty( $input['confirm'] ) ) { return [ 'success' => true, 'message' => 'Dry run: re-call with confirm:true to permanently delete this submission.' ]; }
194 $smeta = $wpdb->prefix . 'fluentform_submission_meta';
195 $wpdb->delete( $smeta, [ 'response_id' => $entry_id ], [ '%d' ] );
196 $wpdb->delete( $subs, [ 'id' => $entry_id ], [ '%d' ] );
197 return [ 'success' => true, 'message' => sprintf( 'Entry %d deleted.', $entry_id ) ];
198 },
199 'permission_callback' => function() use ( $self ) { return $self->can_edit_entries(); },
200 'meta' => $this->write_meta( true ),
201 ] );
202 }
203
204 /* --------------------------- set-entry-status ---------------------- */
205
206 private function register_set_entry_status() {
207 $self = $this;
208 wp_register_ability( 'atarim/fluent-set-entry-status', [
209 'label' => 'Set Fluent Entry Status', 'category' => 'atarim',
210 'description' => 'Set a submission\'s status: unread, read, spam, or trashed.',
211 'input_schema' => [ 'type' => 'object', 'properties' => [ 'entry_id' => [ 'type' => 'integer', 'minimum' => 1 ], 'status' => [ 'type' => 'string', 'enum' => [ 'unread', 'read', 'spam', 'trashed' ] ] ], 'required' => [ 'entry_id', 'status' ], 'additionalProperties' => false ],
212 'output_schema'=> $this->std(),
213 'execute_callback' => function( $input = [] ) {
214 global $wpdb;
215 $r = $wpdb->update( AVCF_Fluent_Helpers::subs_table(), [ 'status' => (string) $input['status'] ], [ 'id' => (int) $input['entry_id'] ], [ '%s' ], [ '%d' ] );
216 if ( $r === false ) { return [ 'success' => false, 'message' => 'Update failed.' ]; }
217 return [ 'success' => true, 'message' => sprintf( 'Entry status set to %s.', (string) $input['status'] ) ];
218 },
219 'permission_callback' => function() use ( $self ) { return $self->can_edit_entries(); },
220 'meta' => $this->write_meta( false ),
221 ] );
222 }
223
224 /* ---------------------------- favorite-entry ----------------------- */
225
226 private function register_favorite_entry() {
227 $self = $this;
228 wp_register_ability( 'atarim/fluent-favorite-entry', [
229 'label' => 'Favorite Fluent Entry', 'category' => 'atarim',
230 'description' => 'Mark a submission as favourite (or clear with favorite:false).',
231 'input_schema' => [ 'type' => 'object', 'properties' => [ 'entry_id' => [ 'type' => 'integer', 'minimum' => 1 ], 'favorite' => [ 'type' => 'boolean', 'default' => true ] ], 'required' => [ 'entry_id' ], 'additionalProperties' => false ],
232 'output_schema'=> $this->std(),
233 'execute_callback' => function( $input = [] ) {
234 global $wpdb;
235 $val = ! isset( $input['favorite'] ) || ! empty( $input['favorite'] ) ? 1 : 0;
236 $r = $wpdb->update( AVCF_Fluent_Helpers::subs_table(), [ 'is_favourite' => $val ], [ 'id' => (int) $input['entry_id'] ], [ '%d' ], [ '%d' ] );
237 if ( $r === false ) { return [ 'success' => false, 'message' => 'Update failed.' ]; }
238 return [ 'success' => true, 'message' => $val ? 'Entry favourited.' : 'Entry unfavourited.' ];
239 },
240 'permission_callback' => function() use ( $self ) { return $self->can_edit_entries(); },
241 'meta' => $this->write_meta( false ),
242 ] );
243 }
244
245 /* ---------------------------- list-form-meta ----------------------- */
246
247 private function register_list_form_meta() {
248 $self = $this;
249 wp_register_ability( 'atarim/fluent-list-form-meta', [
250 'label' => 'List Fluent Form Meta Keys', 'category' => 'atarim',
251 'description' => 'List the form_meta keys stored on a form (e.g. notifications, formSettings, *_feeds), with value sizes. Use fluent-get-form-meta to read one.',
252 'input_schema' => [ 'type' => 'object', 'properties' => [ 'form_id' => [ 'type' => 'integer', 'minimum' => 1 ] ], 'required' => [ 'form_id' ], 'additionalProperties' => false ],
253 'output_schema'=> [ 'type' => 'object', 'properties' => [ 'success' => [ 'type' => 'boolean' ], 'meta_keys' => [ 'type' => 'array' ], 'message' => [ 'type' => 'string' ] ], 'required' => [ 'success', 'message' ] ],
254 'execute_callback' => function( $input = [] ) {
255 global $wpdb; $t = AVCF_Fluent_Helpers::meta_table();
256 $rows = $wpdb->get_results( $wpdb->prepare( "SELECT id, meta_key, LENGTH(value) AS len FROM `{$t}` WHERE form_id = %d ORDER BY meta_key ASC", (int) $input['form_id'] ) );
257 $out = [];
258 foreach ( (array) $rows as $r ) { $out[] = [ 'id' => (int) $r->id, 'meta_key' => (string) $r->meta_key, 'value_length' => (int) $r->len ]; }
259 return [ 'success' => true, 'meta_keys' => $out, 'message' => sprintf( '%d meta key(s).', count( $out ) ) ];
260 },
261 'permission_callback' => function() use ( $self ) { return $self->can_settings(); },
262 'meta' => $this->ro_meta(),
263 ] );
264 }
265
266 /* ----------------------------- get-form-meta ----------------------- */
267
268 private function register_get_form_meta() {
269 $self = $this;
270 wp_register_ability( 'atarim/fluent-get-form-meta', [
271 'label' => 'Get Fluent Form Meta', 'category' => 'atarim',
272 'description' => 'Read and JSON-decode a single form_meta value by meta_key (e.g. notifications, formSettings).',
273 'input_schema' => [ 'type' => 'object', 'properties' => [ 'form_id' => [ 'type' => 'integer', 'minimum' => 1 ], 'meta_key' => [ 'type' => 'string', 'minLength' => 1 ] ], 'required' => [ 'form_id', 'meta_key' ], 'additionalProperties' => false ],
274 'output_schema'=> [ 'type' => 'object', 'properties' => [ 'success' => [ 'type' => 'boolean' ], 'value' => [], 'message' => [ 'type' => 'string' ] ], 'required' => [ 'success', 'message' ] ],
275 'execute_callback' => function( $input = [] ) use ( $self ) {
276 $val = $self->get_meta( (int) $input['form_id'], (string) $input['meta_key'] );
277 if ( $val === null ) { return [ 'success' => false, 'message' => 'Meta key not found on this form.' ]; }
278 return [ 'success' => true, 'value' => $val, 'message' => 'OK.' ];
279 },
280 'permission_callback' => function() use ( $self ) { return $self->can_settings(); },
281 'meta' => $this->ro_meta(),
282 ] );
283 }
284
285 /* ---------------------------- save-form-meta ----------------------- */
286
287 private function register_save_form_meta() {
288 $self = $this;
289 wp_register_ability( 'atarim/fluent-save-form-meta', [
290 'label' => 'Save Fluent Form Meta', 'category' => 'atarim',
291 'description' => 'Create or update a form_meta key with a JSON value (object/array). Powerful generic settings writer — use deliberately. Returns nothing beyond success.',
292 'input_schema' => [ 'type' => 'object', 'properties' => [ 'form_id' => [ 'type' => 'integer', 'minimum' => 1 ], 'meta_key' => [ 'type' => 'string', 'minLength' => 1 ], 'value' => [] ], 'required' => [ 'form_id', 'meta_key', 'value' ], 'additionalProperties' => false ],
293 'output_schema'=> $this->std(),
294 'execute_callback' => function( $input = [] ) use ( $self ) {
295 if ( ! $self->form_exists( (int) $input['form_id'] ) ) { return [ 'success' => false, 'message' => 'Form not found.' ]; }
296 $r = $self->set_meta( (int) $input['form_id'], (string) $input['meta_key'], $input['value'] );
297 if ( $r === false ) { return [ 'success' => false, 'message' => 'Save failed.' ]; }
298 return [ 'success' => true, 'message' => sprintf( 'Meta "%s" saved.', (string) $input['meta_key'] ) ];
299 },
300 'permission_callback' => function() use ( $self ) { return $self->can_settings(); },
301 'meta' => $this->write_meta( false ),
302 ] );
303 }
304
305 /* ------------------------- list-notifications ---------------------- */
306
307 private function register_list_notifications() {
308 $self = $this;
309 wp_register_ability( 'atarim/fluent-list-notifications', [
310 'label' => 'List Fluent Notifications', 'category' => 'atarim',
311 'description' => 'List a form\'s email notifications in full: { id, name, sendTo, subject, message, enabled }.',
312 'input_schema' => [ 'type' => 'object', 'properties' => [ 'form_id' => [ 'type' => 'integer', 'minimum' => 1 ] ], 'required' => [ 'form_id' ], 'additionalProperties' => false ],
313 'output_schema'=> [ 'type' => 'object', 'properties' => [ 'success' => [ 'type' => 'boolean' ], 'notifications' => [ 'type' => 'array' ], 'message' => [ 'type' => 'string' ] ], 'required' => [ 'success', 'message' ] ],
314 'execute_callback' => function( $input = [] ) {
315 $raw = AVCF_Fluent_Helpers::read_notifications_raw( (int) $input['form_id'] );
316 $out = [];
317 foreach ( (array) $raw as $nid => $n ) {
318 if ( ! is_array( $n ) ) { continue; }
319 $out[] = [ 'id' => (string) $nid, 'name' => isset( $n['name'] ) ? (string) $n['name'] : '', 'sendTo' => isset( $n['sendTo'] ) ? $n['sendTo'] : ( isset( $n['email'] ) ? [ 'email' => (string) $n['email'] ] : null ), 'subject' => isset( $n['subject'] ) ? (string) $n['subject'] : '', 'message' => isset( $n['message'] ) ? (string) $n['message'] : '', 'enabled' => ! isset( $n['enabled'] ) || ! empty( $n['enabled'] ) ];
320 }
321 return [ 'success' => true, 'notifications' => $out, 'message' => sprintf( '%d notification(s).', count( $out ) ) ];
322 },
323 'permission_callback' => function() use ( $self ) { return $self->can_settings(); },
324 'meta' => $this->ro_meta(),
325 ] );
326 }
327
328 /* -------------------------- save-notification ---------------------- */
329
330 private function register_save_notification() {
331 $self = $this;
332 wp_register_ability( 'atarim/fluent-save-notification', [
333 'label' => 'Create/Update Fluent Notification', 'category' => 'atarim',
334 'description' => 'Create or update an email notification. Omit notification_id to create (a numeric id is generated). Settable: name, email (recipient), subject, message, enabled. Returns the notification_id.',
335 'input_schema' => [ 'type' => 'object', 'properties' => [ 'form_id' => [ 'type' => 'integer', 'minimum' => 1 ], 'notification_id' => [ 'type' => 'string' ], 'name' => [ 'type' => 'string' ], 'email' => [ 'type' => 'string' ], 'subject' => [ 'type' => 'string' ], 'message' => [ 'type' => 'string' ], 'enabled' => [ 'type' => 'boolean' ] ], 'required' => [ 'form_id' ], 'additionalProperties' => false ],
336 'output_schema'=> [ 'type' => 'object', 'properties' => [ 'success' => [ 'type' => 'boolean' ], 'notification_id' => [ 'type' => 'string' ], 'message' => [ 'type' => 'string' ] ], 'required' => [ 'success', 'message' ] ],
337 'execute_callback' => function( $input = [] ) use ( $self ) {
338 if ( ! $self->form_exists( (int) $input['form_id'] ) ) { return [ 'success' => false, 'message' => 'Form not found.' ]; }
339 $notifs = AVCF_Fluent_Helpers::read_notifications_raw( (int) $input['form_id'] );
340 if ( ! is_array( $notifs ) ) { $notifs = []; }
341 $nid = isset( $input['notification_id'] ) && $input['notification_id'] !== '' ? (string) $input['notification_id'] : (string) ( count( $notifs ) + 1 );
342 $n = isset( $notifs[ $nid ] ) && is_array( $notifs[ $nid ] ) ? $notifs[ $nid ] : [];
343 if ( isset( $input['name'] ) ) { $n['name'] = sanitize_text_field( $input['name'] ); }
344 if ( isset( $input['subject'] ) ) { $n['subject'] = (string) $input['subject']; }
345 if ( isset( $input['message'] ) ) { $n['message'] = (string) $input['message']; }
346 if ( isset( $input['enabled'] ) ) { $n['enabled'] = ! empty( $input['enabled'] ); }
347 if ( isset( $input['email'] ) ) {
348 if ( isset( $n['sendTo'] ) && is_array( $n['sendTo'] ) ) { $n['sendTo']['email'] = (string) $input['email']; }
349 else { $n['sendTo'] = [ 'type' => 'email', 'email' => (string) $input['email'] ]; }
350 }
351 $notifs[ $nid ] = $n;
352 $r = $self->set_meta( (int) $input['form_id'], 'notifications', $notifs );
353 if ( $r === false ) { return [ 'success' => false, 'message' => 'Save failed.' ]; }
354 return [ 'success' => true, 'notification_id' => $nid, 'message' => 'Notification saved.' ];
355 },
356 'permission_callback' => function() use ( $self ) { return $self->can_settings(); },
357 'meta' => $this->write_meta( false ),
358 ] );
359 }
360
361 /* --------------------------- get-confirmation ---------------------- */
362
363 private function register_get_confirmation() {
364 $self = $this;
365 wp_register_ability( 'atarim/fluent-get-confirmation', [
366 'label' => 'Get Fluent Confirmation', 'category' => 'atarim',
367 'description' => 'Read a form\'s confirmation/redirect settings from the formSettings meta (messageToShow, redirectTo, customPage/customUrl when set).',
368 'input_schema' => [ 'type' => 'object', 'properties' => [ 'form_id' => [ 'type' => 'integer', 'minimum' => 1 ] ], 'required' => [ 'form_id' ], 'additionalProperties' => false ],
369 'output_schema'=> [ 'type' => 'object', 'properties' => [ 'success' => [ 'type' => 'boolean' ], 'confirmation' => [], 'message' => [ 'type' => 'string' ] ], 'required' => [ 'success', 'message' ] ],
370 'execute_callback' => function( $input = [] ) use ( $self ) {
371 $settings = $self->get_meta( (int) $input['form_id'], 'formSettings' );
372 if ( ! is_array( $settings ) ) { return [ 'success' => false, 'message' => 'No formSettings meta on this form.' ]; }
373 $conf = isset( $settings['confirmation'] ) ? $settings['confirmation'] : null;
374 return [ 'success' => true, 'confirmation' => $conf, 'message' => $conf === null ? 'No confirmation block found in formSettings.' : 'OK.' ];
375 },
376 'permission_callback' => function() use ( $self ) { return $self->can_settings(); },
377 'meta' => $this->ro_meta(),
378 ] );
379 }
380
381 /* ----------------------- list-integration-feeds -------------------- */
382
383 private function register_list_integration_feeds() {
384 $self = $this;
385 wp_register_ability( 'atarim/fluent-list-integration-feeds', [
386 'label' => 'List Fluent Integration Feeds', 'category' => 'atarim',
387 'description' => 'Best-effort listing of configured CRM/marketing/payment integration feeds on a form (e.g. mailchimp, slack, webhook), read from form_meta keys that look like feeds. Read-only; returns { meta_key, name, enabled } per feed where determinable.',
388 'input_schema' => [ 'type' => 'object', 'properties' => [ 'form_id' => [ 'type' => 'integer', 'minimum' => 1 ] ], 'required' => [ 'form_id' ], 'additionalProperties' => false ],
389 'output_schema'=> [ 'type' => 'object', 'properties' => [ 'success' => [ 'type' => 'boolean' ], 'feeds' => [ 'type' => 'array' ], 'message' => [ 'type' => 'string' ] ], 'required' => [ 'success', 'message' ] ],
390 'execute_callback' => function( $input = [] ) {
391 global $wpdb; $t = AVCF_Fluent_Helpers::meta_table();
392 $rows = $wpdb->get_results( $wpdb->prepare( "SELECT meta_key, value FROM `{$t}` WHERE form_id = %d", (int) $input['form_id'] ) );
393 $skip = [ 'notifications', 'formSettings', '_total_views', 'template_name', 'advancedValidationSettings', '_primary_email_field' ];
394 $feeds = [];
395 foreach ( (array) $rows as $r ) {
396 $key = (string) $r->meta_key;
397 if ( in_array( $key, $skip, true ) ) { continue; }
398 $decoded = json_decode( $r->value, true );
399 $looks_feed = ( strpos( $key, 'feed' ) !== false ) || ( is_array( $decoded ) && ( isset( $decoded['integration_name'] ) || isset( $decoded['list_id'] ) || isset( $decoded['feedStatus'] ) || isset( $decoded['enabled'] ) ) );
400 if ( ! $looks_feed ) { continue; }
401 $name = is_array( $decoded ) && isset( $decoded['name'] ) ? (string) $decoded['name'] : $key;
402 $enabled = is_array( $decoded ) ? ( isset( $decoded['enabled'] ) ? (bool) $decoded['enabled'] : ( isset( $decoded['feedStatus'] ) ? (bool) $decoded['feedStatus'] : true ) ) : true;
403 $feeds[] = [ 'meta_key' => $key, 'name' => $name, 'enabled' => $enabled ];
404 }
405 return [ 'success' => true, 'feeds' => $feeds, 'message' => sprintf( '%d integration feed(s) detected (best-effort).', count( $feeds ) ) ];
406 },
407 'permission_callback' => function() use ( $self ) { return $self->can_settings(); },
408 'meta' => $this->ro_meta(),
409 ] );
410 }
411 }
412