class-avcf-abilities-cf7-pro.php
3 weeks ago
class-avcf-abilities-cf7.php
3 weeks ago
class-avcf-cf7-detector.php
1 month ago
class-avcf-cf7-helpers.php
1 month ago
class-avcf-abilities-cf7.php
233 lines
| 1 | <?php |
| 2 | /** |
| 3 | * Contact Form 7 — core MCP abilities (standalone cluster). |
| 4 | * |
| 5 | * CF7's native surface is config-only (it stores no submissions), so the core |
| 6 | * is shaped to CF7's reality: form list/get, the [form] template, the mail / |
| 7 | * mail_2 notification templates, the messages, additional settings, plus the |
| 8 | * two writes from the common surface (notification recipients, duplicate). |
| 9 | * Config mutation and a Flamingo-backed entry bridge live in the -pro file. |
| 10 | * Permissions use CF7's capabilities with a manage_options fallback. Built on |
| 11 | * CF7's model; not runtime-tested here. |
| 12 | * |
| 13 | * @package atarim-visual-collaboration |
| 14 | */ |
| 15 | |
| 16 | if ( ! defined('ABSPATH') ) { |
| 17 | exit; |
| 18 | } |
| 19 | |
| 20 | class AVCF_Abilities_CF7 extends AVCF_Abilities_Base { |
| 21 | |
| 22 | /** @var AVCF_CF7_Detector */ |
| 23 | private $detector; |
| 24 | |
| 25 | public function __construct() { |
| 26 | $this->detector = new AVCF_CF7_Detector(); |
| 27 | } |
| 28 | |
| 29 | public function register() { |
| 30 | if ( ! $this->detector->avcf_cf7_is_available() ) { |
| 31 | return; |
| 32 | } |
| 33 | $this->register_list_forms(); |
| 34 | $this->register_get_form(); |
| 35 | $this->register_get_form_template(); |
| 36 | $this->register_list_mail_templates(); |
| 37 | $this->register_get_messages(); |
| 38 | $this->register_get_additional_settings(); |
| 39 | $this->register_update_notifications(); |
| 40 | $this->register_duplicate_form(); |
| 41 | } |
| 42 | |
| 43 | /* ------------------------------ shared ----------------------------- */ |
| 44 | |
| 45 | private function ro_meta() { return [ 'mcp' => [ 'public' => true, 'type' => 'tool' ], 'annotations' => [ 'readonly' => true, 'destructive' => false, 'idempotent' => true ] ]; } |
| 46 | private function write_meta( $d = false ) { return [ 'mcp' => [ 'public' => true, 'type' => 'tool' ], 'annotations' => [ 'readonly' => false, 'destructive' => (bool) $d, 'idempotent' => false ] ]; } |
| 47 | |
| 48 | public function can_read() { return current_user_can( 'wpcf7_read_contact_forms' ) || current_user_can( 'wpcf7_edit_contact_forms' ) || current_user_can( 'manage_options' ); } |
| 49 | public function can_edit() { return current_user_can( 'wpcf7_edit_contact_forms' ) || current_user_can( 'manage_options' ); } |
| 50 | |
| 51 | /* ---------------------------- list-forms --------------------------- */ |
| 52 | |
| 53 | private function register_list_forms() { |
| 54 | $self = $this; |
| 55 | wp_register_ability( 'atarim/cf7-list-forms', [ |
| 56 | 'label' => 'List Contact Form 7 Forms', 'category' => 'atarim', |
| 57 | 'description' => 'List CF7 forms: { id, title, status, entries_total, last_submission, created_at }. CF7 stores no entries itself, so entries_total/last_submission are always null here — use the Flamingo abilities (atarim/flamingo-*) for submission data, passing this form_id. Optional search, limit, offset.', |
| 58 | 'input_schema' => [ 'type' => 'object', 'properties' => [ 'search' => [ 'type' => 'string' ], 'limit' => [ 'type' => 'integer', 'minimum' => 1, 'maximum' => 200, 'default' => 50 ], 'offset' => [ 'type' => 'integer', 'minimum' => 0, 'default' => 0 ] ], 'additionalProperties' => false ], |
| 59 | 'output_schema'=> [ 'type' => 'object', 'properties' => [ 'success' => [ 'type' => 'boolean' ], 'forms' => [ 'type' => 'array' ], 'total' => [ 'type' => 'integer' ], 'message' => [ 'type' => 'string' ] ], 'required' => [ 'success', 'message' ] ], |
| 60 | 'execute_callback' => function( $input = [] ) { |
| 61 | $qa = [ 'post_type' => 'wpcf7_contact_form', 'post_status' => 'any', 'posts_per_page' => isset( $input['limit'] ) ? max( 1, (int) $input['limit'] ) : 50, 'offset' => isset( $input['offset'] ) ? max( 0, (int) $input['offset'] ) : 0, 'orderby' => 'date', 'order' => 'DESC' ]; |
| 62 | if ( isset( $input['search'] ) && $input['search'] !== '' ) { $qa['s'] = (string) $input['search']; } |
| 63 | $q = new WP_Query( $qa ); |
| 64 | $out = []; |
| 65 | foreach ( $q->posts as $post ) { $out[] = AVCF_CF7_Helpers::map_form_summary( $post ); } |
| 66 | return [ 'success' => true, 'forms' => $out, 'total' => (int) $q->found_posts, 'message' => sprintf( '%d form(s). For submission data, use the atarim/flamingo-* abilities.', count( $out ) ) ]; |
| 67 | }, |
| 68 | 'permission_callback' => function() use ( $self ) { return $self->can_read(); }, |
| 69 | 'meta' => $this->ro_meta(), |
| 70 | ] ); |
| 71 | } |
| 72 | |
| 73 | /* ----------------------------- get-form ---------------------------- */ |
| 74 | |
| 75 | private function register_get_form() { |
| 76 | $self = $this; |
| 77 | wp_register_ability( 'atarim/cf7-get-form', [ |
| 78 | 'label' => 'Get Contact Form 7 Form', 'category' => 'atarim', |
| 79 | 'description' => 'CF7 form overview: fields parsed from the form template (id, type, required), mail / mail_2 notification summaries, and shortcode.', |
| 80 | 'input_schema' => [ 'type' => 'object', 'properties' => [ 'form_id' => [ 'type' => 'integer', 'minimum' => 1 ] ], 'required' => [ 'form_id' ], 'additionalProperties' => false ], |
| 81 | 'output_schema'=> [ 'type' => 'object', 'properties' => [ 'success' => [ 'type' => 'boolean' ], 'form' => [ 'type' => 'object' ], 'message' => [ 'type' => 'string' ] ], 'required' => [ 'success', 'message' ] ], |
| 82 | 'execute_callback' => function( $input = [] ) { |
| 83 | $post = get_post( (int) $input['form_id'] ); |
| 84 | if ( ! $post || $post->post_type !== 'wpcf7_contact_form' ) { return [ 'success' => false, 'message' => 'Form not found.' ]; } |
| 85 | return [ 'success' => true, 'form' => AVCF_CF7_Helpers::map_form_full( $post, AVCF_CF7_Helpers::get_cf7( (int) $input['form_id'] ) ), 'message' => 'OK.' ]; |
| 86 | }, |
| 87 | 'permission_callback' => function() use ( $self ) { return $self->can_read(); }, |
| 88 | 'meta' => $this->ro_meta(), |
| 89 | ] ); |
| 90 | } |
| 91 | |
| 92 | /* ------------------------- get-form-template ----------------------- */ |
| 93 | |
| 94 | private function register_get_form_template() { |
| 95 | $self = $this; |
| 96 | wp_register_ability( 'atarim/cf7-get-form-template', [ |
| 97 | 'label' => 'Get CF7 Form Template', 'category' => 'atarim', |
| 98 | 'description' => 'The raw [form] template markup (CF7 form-tags) for a form.', |
| 99 | 'input_schema' => [ 'type' => 'object', 'properties' => [ 'form_id' => [ 'type' => 'integer', 'minimum' => 1 ] ], 'required' => [ 'form_id' ], 'additionalProperties' => false ], |
| 100 | 'output_schema'=> [ 'type' => 'object', 'properties' => [ 'success' => [ 'type' => 'boolean' ], 'template' => [ 'type' => 'string' ], 'message' => [ 'type' => 'string' ] ], 'required' => [ 'success', 'message' ] ], |
| 101 | 'execute_callback' => function( $input = [] ) { |
| 102 | $cf7 = AVCF_CF7_Helpers::get_cf7( (int) $input['form_id'] ); |
| 103 | if ( ! $cf7 || ! method_exists( $cf7, 'prop' ) ) { return [ 'success' => false, 'message' => 'Form not found.' ]; } |
| 104 | return [ 'success' => true, 'template' => (string) $cf7->prop( 'form' ), 'message' => 'OK.' ]; |
| 105 | }, |
| 106 | 'permission_callback' => function() use ( $self ) { return $self->can_read(); }, |
| 107 | 'meta' => $this->ro_meta(), |
| 108 | ] ); |
| 109 | } |
| 110 | |
| 111 | /* ------------------------ list-mail-templates ---------------------- */ |
| 112 | |
| 113 | private function register_list_mail_templates() { |
| 114 | $self = $this; |
| 115 | wp_register_ability( 'atarim/cf7-list-mail-templates', [ |
| 116 | 'label' => 'List CF7 Mail Templates', 'category' => 'atarim', |
| 117 | 'description' => 'The full mail and mail_2 templates: recipient, sender, subject, body, additional_headers, use_html, active.', |
| 118 | 'input_schema' => [ 'type' => 'object', 'properties' => [ 'form_id' => [ 'type' => 'integer', 'minimum' => 1 ] ], 'required' => [ 'form_id' ], 'additionalProperties' => false ], |
| 119 | 'output_schema'=> [ 'type' => 'object', 'properties' => [ 'success' => [ 'type' => 'boolean' ], 'mail_templates' => [ 'type' => 'array' ], 'message' => [ 'type' => 'string' ] ], 'required' => [ 'success', 'message' ] ], |
| 120 | 'execute_callback' => function( $input = [] ) { |
| 121 | $cf7 = AVCF_CF7_Helpers::get_cf7( (int) $input['form_id'] ); |
| 122 | if ( ! $cf7 || ! method_exists( $cf7, 'prop' ) ) { return [ 'success' => false, 'message' => 'Form not found.' ]; } |
| 123 | $out = []; |
| 124 | foreach ( [ 'mail', 'mail_2' ] as $key ) { |
| 125 | $m = AVCF_CF7_Helpers::map_mail( $cf7->prop( $key ), $key ); |
| 126 | if ( $m ) { $out[] = $m; } |
| 127 | } |
| 128 | return [ 'success' => true, 'mail_templates' => $out, 'message' => sprintf( '%d mail template(s).', count( $out ) ) ]; |
| 129 | }, |
| 130 | 'permission_callback' => function() use ( $self ) { return $self->can_read(); }, |
| 131 | 'meta' => $this->ro_meta(), |
| 132 | ] ); |
| 133 | } |
| 134 | |
| 135 | /* ----------------------------- get-messages ------------------------ */ |
| 136 | |
| 137 | private function register_get_messages() { |
| 138 | $self = $this; |
| 139 | wp_register_ability( 'atarim/cf7-get-messages', [ |
| 140 | 'label' => 'Get CF7 Messages', 'category' => 'atarim', |
| 141 | 'description' => 'The form\'s validation/status messages (mail sent, validation errors, spam, etc.) as a key => text map.', |
| 142 | 'input_schema' => [ 'type' => 'object', 'properties' => [ 'form_id' => [ 'type' => 'integer', 'minimum' => 1 ] ], 'required' => [ 'form_id' ], 'additionalProperties' => false ], |
| 143 | 'output_schema'=> [ 'type' => 'object', 'properties' => [ 'success' => [ 'type' => 'boolean' ], 'messages' => [ 'type' => 'object' ], 'message' => [ 'type' => 'string' ] ], 'required' => [ 'success', 'message' ] ], |
| 144 | 'execute_callback' => function( $input = [] ) { |
| 145 | $cf7 = AVCF_CF7_Helpers::get_cf7( (int) $input['form_id'] ); |
| 146 | if ( ! $cf7 || ! method_exists( $cf7, 'prop' ) ) { return [ 'success' => false, 'message' => 'Form not found.' ]; } |
| 147 | $messages = $cf7->prop( 'messages' ); |
| 148 | return [ 'success' => true, 'messages' => is_array( $messages ) ? $messages : [], 'message' => 'OK.' ]; |
| 149 | }, |
| 150 | 'permission_callback' => function() use ( $self ) { return $self->can_read(); }, |
| 151 | 'meta' => $this->ro_meta(), |
| 152 | ] ); |
| 153 | } |
| 154 | |
| 155 | /* ---------------------- get-additional-settings -------------------- */ |
| 156 | |
| 157 | private function register_get_additional_settings() { |
| 158 | $self = $this; |
| 159 | wp_register_ability( 'atarim/cf7-get-additional-settings', [ |
| 160 | 'label' => 'Get CF7 Additional Settings', 'category' => 'atarim', |
| 161 | 'description' => 'The "Additional Settings" text block of a form (raw lines, e.g. demo_mode, skip_mail, on_sent_ok).', |
| 162 | 'input_schema' => [ 'type' => 'object', 'properties' => [ 'form_id' => [ 'type' => 'integer', 'minimum' => 1 ] ], 'required' => [ 'form_id' ], 'additionalProperties' => false ], |
| 163 | 'output_schema'=> [ 'type' => 'object', 'properties' => [ 'success' => [ 'type' => 'boolean' ], 'additional_settings' => [ 'type' => 'string' ], 'lines' => [ 'type' => 'array' ], 'message' => [ 'type' => 'string' ] ], 'required' => [ 'success', 'message' ] ], |
| 164 | 'execute_callback' => function( $input = [] ) { |
| 165 | $cf7 = AVCF_CF7_Helpers::get_cf7( (int) $input['form_id'] ); |
| 166 | if ( ! $cf7 || ! method_exists( $cf7, 'prop' ) ) { return [ 'success' => false, 'message' => 'Form not found.' ]; } |
| 167 | $text = (string) $cf7->prop( 'additional_settings' ); |
| 168 | $lines = array_values( array_filter( array_map( 'trim', preg_split( '/\r\n|\r|\n/', $text ) ) ) ); |
| 169 | return [ 'success' => true, 'additional_settings' => $text, 'lines' => $lines, 'message' => 'OK.' ]; |
| 170 | }, |
| 171 | 'permission_callback' => function() use ( $self ) { return $self->can_read(); }, |
| 172 | 'meta' => $this->ro_meta(), |
| 173 | ] ); |
| 174 | } |
| 175 | |
| 176 | /* ------------------------ update-notifications --------------------- */ |
| 177 | |
| 178 | private function register_update_notifications() { |
| 179 | $self = $this; |
| 180 | wp_register_ability( 'atarim/cf7-update-notifications', [ |
| 181 | 'label' => 'Update CF7 Notification Recipients', 'category' => 'atarim', |
| 182 | 'description' => 'Update the recipient of the mail and/or mail_2 templates. updates: [{ notification_id: "mail"|"mail_2", recipients: [emails] }]. Emails sanitized; unknown/unconfigured templates skipped.', |
| 183 | 'input_schema' => [ 'type' => 'object', 'properties' => [ 'form_id' => [ 'type' => 'integer', 'minimum' => 1 ], 'updates' => [ 'type' => 'array', 'items' => [ 'type' => 'object' ], 'minItems' => 1 ] ], 'required' => [ 'form_id', 'updates' ], 'additionalProperties' => false ], |
| 184 | 'output_schema'=> [ 'type' => 'object', 'properties' => [ 'success' => [ 'type' => 'boolean' ], 'updated' => [ 'type' => 'array' ], 'skipped' => [ 'type' => 'array' ], 'message' => [ 'type' => 'string' ] ], 'required' => [ 'success', 'message' ] ], |
| 185 | 'execute_callback' => function( $input = [] ) { |
| 186 | $cf7 = AVCF_CF7_Helpers::get_cf7( (int) $input['form_id'] ); |
| 187 | if ( ! $cf7 || ! method_exists( $cf7, 'prop' ) ) { return [ 'success' => false, 'message' => 'Form not found.' ]; } |
| 188 | $updated = []; $skipped = []; |
| 189 | foreach ( (array) $input['updates'] as $u ) { |
| 190 | $nid = isset( $u['notification_id'] ) ? (string) $u['notification_id'] : ''; |
| 191 | if ( ! in_array( $nid, [ 'mail', 'mail_2' ], true ) ) { $skipped[] = [ 'notification_id' => $nid, 'reason' => 'CF7 supports "mail" and "mail_2" only.' ]; continue; } |
| 192 | $mail = $cf7->prop( $nid ); |
| 193 | if ( ! is_array( $mail ) ) { $skipped[] = [ 'notification_id' => $nid, 'reason' => 'Not configured on this form.' ]; continue; } |
| 194 | $clean = array_filter( array_map( 'sanitize_email', isset( $u['recipients'] ) ? (array) $u['recipients'] : [] ) ); |
| 195 | if ( empty( $clean ) ) { $skipped[] = [ 'notification_id' => $nid, 'reason' => 'No valid recipient emails.' ]; continue; } |
| 196 | $mail['recipient'] = implode( ', ', $clean ); |
| 197 | $cf7->set_properties( [ $nid => $mail ] ); |
| 198 | $updated[] = $nid; |
| 199 | } |
| 200 | if ( ! empty( $updated ) && method_exists( $cf7, 'save' ) ) { $cf7->save(); } |
| 201 | return [ 'success' => ! empty( $updated ), 'updated' => $updated, 'skipped' => $skipped, 'message' => sprintf( '%d updated, %d skipped.', count( $updated ), count( $skipped ) ) ]; |
| 202 | }, |
| 203 | 'permission_callback' => function() use ( $self ) { return $self->can_edit(); }, |
| 204 | 'meta' => $this->write_meta( false ), |
| 205 | ] ); |
| 206 | } |
| 207 | |
| 208 | /* -------------------------- duplicate-form ------------------------- */ |
| 209 | |
| 210 | private function register_duplicate_form() { |
| 211 | $self = $this; |
| 212 | wp_register_ability( 'atarim/cf7-duplicate-form', [ |
| 213 | 'label' => 'Duplicate CF7 Form', 'category' => 'atarim', |
| 214 | 'description' => 'Clone a form via the CF7 model copy(). Optional new_title (defaults to "<title> (Copy)").', |
| 215 | 'input_schema' => [ 'type' => 'object', 'properties' => [ 'form_id' => [ 'type' => 'integer', 'minimum' => 1 ], 'new_title' => [ 'type' => 'string' ] ], 'required' => [ 'form_id' ], 'additionalProperties' => false ], |
| 216 | 'output_schema'=> [ 'type' => 'object', 'properties' => [ 'success' => [ 'type' => 'boolean' ], 'new_form_id' => [ 'type' => 'integer' ], 'message' => [ 'type' => 'string' ] ], 'required' => [ 'success', 'message' ] ], |
| 217 | 'execute_callback' => function( $input = [] ) { |
| 218 | $orig = AVCF_CF7_Helpers::get_cf7( (int) $input['form_id'] ); |
| 219 | if ( ! $orig ) { return [ 'success' => false, 'message' => 'Form not found.' ]; } |
| 220 | if ( ! method_exists( $orig, 'copy' ) ) { return [ 'success' => false, 'message' => 'CF7 copy() unavailable in this version.' ]; } |
| 221 | $title = isset( $input['new_title'] ) && $input['new_title'] !== '' ? sanitize_text_field( $input['new_title'] ) : ( (string) $orig->title() . ' (Copy)' ); |
| 222 | $copy = $orig->copy(); |
| 223 | if ( $copy && method_exists( $copy, 'set_title' ) ) { $copy->set_title( $title ); } |
| 224 | if ( ! $copy || ! method_exists( $copy, 'save' ) ) { return [ 'success' => false, 'message' => 'Duplicate failed.' ]; } |
| 225 | $copy->save(); |
| 226 | return [ 'success' => true, 'new_form_id' => (int) $copy->id(), 'message' => sprintf( 'Duplicated as "%s".', $title ) ]; |
| 227 | }, |
| 228 | 'permission_callback' => function() use ( $self ) { return $self->can_edit(); }, |
| 229 | 'meta' => $this->write_meta( false ), |
| 230 | ] ); |
| 231 | } |
| 232 | } |
| 233 |