| 1 |
<?php |
| 2 |
/** |
| 3 |
* LegacyAutomationTools — the free plugin's "Automation" CRUD |
| 4 |
* (includes/core/rest-api/Controllers/class-automation-controller.php, |
| 5 |
* REST namespace `wpfnl/v1`, `WpfnlAutomationController`). |
| 6 |
* |
| 7 |
* This is a DIFFERENT feature from the Mail Mint automation *canvas* already |
| 8 |
* covered by WPFunnels Pro's own `AutomationTools.php` (funnel-scoped CRM |
| 9 |
* triggers like Zapier/Pabbly/ActiveCampaign wired up per-funnel, backed by |
| 10 |
* wpfunnels-pro/includes/core/rest-api/controllers/AutomationController.php |
| 11 |
* and driven by admin/src/components/Automation/*.vue). This class does not |
| 12 |
* touch that feature or duplicate its abilities. |
| 13 |
* |
| 14 |
* `WpfnlAutomationController` itself is a thin CRUD proxy in front of Mail |
| 15 |
* Mint's own automation engine |
| 16 |
* (`MintMail\App\Internal\Automation\AutomationModel`) — every method here |
| 17 |
* requires Mail Mint active, exactly like the REST controller does, and every |
| 18 |
* write goes through the same controller methods the REST routes call so |
| 19 |
* this never re-implements Mail Mint's validation. |
| 20 |
* |
| 21 |
* A "Test Zapier" ability was considered (per Drawer.vue's `testZapier()`, |
| 22 |
* which POSTs to `wpfunnels/v1/automation/wpfnl-test-zapier/{funnelId}`) and |
| 23 |
* deliberately dropped: that endpoint, and the Zapier/Pabbly CRM automation |
| 24 |
* UI around it (Automation.vue, Drawer.vue, Trigger.vue — all of which read |
| 25 |
* `get_data_by_index`/`delete` from `wpfunnels/v1/automation`), belong to |
| 26 |
* WPFunnels Pro's `AutomationController.php`, not to anything in this free |
| 27 |
* plugin. A grep for "zapier" (case-insensitive) across this plugin's |
| 28 |
* `includes/` turned up no route registration and no handler — only UI |
| 29 |
* copy/labels and the marketing comparison table. There is no free-plugin |
| 30 |
* backend to wrap, so none is invented here. |
| 31 |
* |
| 32 |
* @package WPFunnels\MCP |
| 33 |
* @since 3.14.0 |
| 34 |
*/ |
| 35 |
|
| 36 |
namespace WPFunnels\MCP\Tools; |
| 37 |
|
| 38 |
defined( 'ABSPATH' ) || exit; |
| 39 |
|
| 40 |
use WPFunnels\MCP\Helpers\MCPHelper; |
| 41 |
use WPFunnels\Rest\Controllers\WpfnlAutomationController; |
| 42 |
|
| 43 |
/** |
| 44 |
* Class LegacyAutomationTools |
| 45 |
*/ |
| 46 |
class LegacyAutomationTools { |
| 47 |
|
| 48 |
/** |
| 49 |
* Ability definitions for this domain. |
| 50 |
* |
| 51 |
* @return array |
| 52 |
*/ |
| 53 |
public static function definitions() { |
| 54 |
return [ |
| 55 |
'wpfunnels/list-legacy-automations' => [ |
| 56 |
'label' => __( 'List Legacy Automations', 'wpfnl' ), |
| 57 |
'description' => 'List automations created through WPFunnels\' legacy Automation CRUD (a Mail Mint-backed feature, separate from the Automation Canvas). Requires Mail Mint active. Note: the underlying endpoint only returns automations tagged as Mail Mint-native — see the response\'s "note" field.', |
| 58 |
'input_schema' => [ |
| 59 |
'type' => 'object', |
| 60 |
'properties' => array_merge( |
| 61 |
MCPHelper::paginationSchema(), |
| 62 |
[] |
| 63 |
), |
| 64 |
], |
| 65 |
'execute_callback' => [ __CLASS__, 'listLegacyAutomations' ], |
| 66 |
'permission_callback' => MCPHelper::currentUserCan( 'manage_options' ), |
| 67 |
'annotations' => [ 'readonly' ], |
| 68 |
], |
| 69 |
'wpfunnels/create-legacy-automation' => [ |
| 70 |
'label' => __( 'Create Legacy Automation', 'wpfnl' ), |
| 71 |
'description' => 'Create a legacy (Mail Mint-backed) automation. Requires Mail Mint active. `data` must include `name` and `trigger_name`; `status` defaults to "draft" ("active" to enable immediately) and `author` defaults to the current user.', |
| 72 |
'input_schema' => [ |
| 73 |
'type' => 'object', |
| 74 |
'properties' => [ |
| 75 |
'data' => [ |
| 76 |
'type' => 'object', |
| 77 |
'description' => 'Automation payload. At minimum: {name, trigger_name}. Optional: {status: "draft"|"active", author, steps: [...]} — see wpfunnels/list-legacy-automations output for the shape of an existing automation.', |
| 78 |
], |
| 79 |
], |
| 80 |
'required' => [ 'data' ], |
| 81 |
], |
| 82 |
'execute_callback' => [ __CLASS__, 'createLegacyAutomation' ], |
| 83 |
'permission_callback' => MCPHelper::currentUserCan( 'manage_options' ), |
| 84 |
'annotations' => [], |
| 85 |
], |
| 86 |
'wpfunnels/update-legacy-automation' => [ |
| 87 |
'label' => __( 'Update Legacy Automation', 'wpfnl' ), |
| 88 |
'description' => 'Update a legacy (Mail Mint-backed) automation. Requires Mail Mint active. The underlying engine only accepts the write when `data` includes `name`, `trigger_name` and `status` together with any changed fields — a partial patch missing one of those three will fail. Read wpfunnels/list-legacy-automations first and send back the full record with your changes merged in.', |
| 89 |
'input_schema' => [ |
| 90 |
'type' => 'object', |
| 91 |
'properties' => [ |
| 92 |
'automation_id' => [ |
| 93 |
'type' => 'integer', |
| 94 |
'description' => 'Automation ID.', |
| 95 |
], |
| 96 |
'data' => [ |
| 97 |
'type' => 'object', |
| 98 |
'description' => 'Full automation payload including name, trigger_name and status (see this ability\'s description).', |
| 99 |
], |
| 100 |
], |
| 101 |
'required' => [ 'automation_id', 'data' ], |
| 102 |
], |
| 103 |
'execute_callback' => [ __CLASS__, 'updateLegacyAutomation' ], |
| 104 |
'permission_callback' => MCPHelper::currentUserCan( 'manage_options' ), |
| 105 |
'annotations' => [ 'destructive' ], |
| 106 |
], |
| 107 |
'wpfunnels/bulk-delete-legacy-automations' => [ |
| 108 |
'label' => __( 'Bulk Delete Legacy Automations', 'wpfnl' ), |
| 109 |
'description' => 'Permanently delete one or more legacy automations, including their steps, logs and scheduled emails. Requires Mail Mint active. This cannot be undone.', |
| 110 |
'input_schema' => [ |
| 111 |
'type' => 'object', |
| 112 |
'properties' => [ |
| 113 |
'automation_ids' => [ |
| 114 |
'type' => 'array', |
| 115 |
'description' => 'Automation IDs to delete.', |
| 116 |
'items' => [ 'type' => 'integer' ], |
| 117 |
], |
| 118 |
], |
| 119 |
'required' => [ 'automation_ids' ], |
| 120 |
], |
| 121 |
'execute_callback' => [ __CLASS__, 'bulkDeleteLegacyAutomations' ], |
| 122 |
'permission_callback' => MCPHelper::currentUserCan( 'manage_options' ), |
| 123 |
'annotations' => [ 'destructive' ], |
| 124 |
], |
| 125 |
'wpfunnels/toggle-legacy-automation' => [ |
| 126 |
'label' => __( 'Toggle Legacy Automation', 'wpfnl' ), |
| 127 |
'description' => 'Activate or deactivate a legacy automation by flipping its `status` between "active" and "draft" (the real active/inactive flag Mail Mint stores for it). Requires Mail Mint active.', |
| 128 |
'input_schema' => [ |
| 129 |
'type' => 'object', |
| 130 |
'properties' => [ |
| 131 |
'automation_id' => [ |
| 132 |
'type' => 'integer', |
| 133 |
'description' => 'Automation ID.', |
| 134 |
], |
| 135 |
'active' => [ |
| 136 |
'type' => 'boolean', |
| 137 |
'description' => 'true to set status "active", false to set status "draft".', |
| 138 |
], |
| 139 |
], |
| 140 |
'required' => [ 'automation_id', 'active' ], |
| 141 |
], |
| 142 |
'execute_callback' => [ __CLASS__, 'toggleLegacyAutomation' ], |
| 143 |
'permission_callback' => MCPHelper::currentUserCan( 'manage_options' ), |
| 144 |
'annotations' => [ 'destructive' ], |
| 145 |
], |
| 146 |
]; |
| 147 |
} |
| 148 |
|
| 149 |
/** |
| 150 |
* Whether Mail Mint's automation engine and this plugin's automation |
| 151 |
* REST controller are both available. |
| 152 |
* |
| 153 |
* @return bool |
| 154 |
*/ |
| 155 |
private static function mailMintAvailable() { |
| 156 |
return class_exists( '\MintMail\App\Internal\Automation\AutomationModel' ) |
| 157 |
&& class_exists( '\WPFunnels\Rest\Controllers\WpfnlAutomationController' ); |
| 158 |
} |
| 159 |
|
| 160 |
/** |
| 161 |
* Standard error for when Mail Mint isn't available. |
| 162 |
* |
| 163 |
* @return \WP_Error |
| 164 |
*/ |
| 165 |
private static function mailMintUnavailableError() { |
| 166 |
return MCPHelper::error( |
| 167 |
'mail_mint_required', |
| 168 |
'Legacy automations require Mail Mint. Install and activate it first — see wpfunnels/activate-mail-mint.' |
| 169 |
); |
| 170 |
} |
| 171 |
|
| 172 |
/** |
| 173 |
* List legacy automations, reusing the real REST controller method. |
| 174 |
* |
| 175 |
* @param array $input Tool input. |
| 176 |
* @return array|\WP_Error |
| 177 |
*/ |
| 178 |
public static function listLegacyAutomations( $input = [] ) { |
| 179 |
if ( ! self::mailMintAvailable() ) { |
| 180 |
return self::mailMintUnavailableError(); |
| 181 |
} |
| 182 |
|
| 183 |
$page = isset( $input['page'] ) ? max( 1, (int) $input['page'] ) : 1; |
| 184 |
$per_page = MCPHelper::perPage( isset( $input['per_page'] ) ? $input['per_page'] : MCPHelper::DEFAULT_PER_PAGE ); |
| 185 |
|
| 186 |
$controller = new WpfnlAutomationController(); |
| 187 |
$request = new \WP_REST_Request( 'GET', '/wpfnl/v1/automation' ); |
| 188 |
$request->set_param( 'page', $page ); |
| 189 |
$request->set_param( 'per_page', $per_page ); |
| 190 |
|
| 191 |
$response = $controller->get_items( $request ); |
| 192 |
if ( is_wp_error( $response ) ) { |
| 193 |
return $response; |
| 194 |
} |
| 195 |
|
| 196 |
$data = $response->get_data(); |
| 197 |
|
| 198 |
return [ |
| 199 |
'automations' => isset( $data['data'] ) ? $data['data'] : [], |
| 200 |
'pagination' => [ |
| 201 |
'page' => $page, |
| 202 |
'per_page' => $per_page, |
| 203 |
'total' => isset( $data['count'] ) ? (int) $data['count'] : 0, |
| 204 |
'total_pages' => isset( $data['total_pages'] ) ? (int) $data['total_pages'] : 0, |
| 205 |
], |
| 206 |
'note' => 'The underlying endpoint only returns automations whose "source" meta is "mint". Automations created via wpfunnels/create-legacy-automation are tagged source="wpf" and may not appear in this list — this mirrors an existing mismatch in the underlying REST endpoint, not something this tool introduces.', |
| 207 |
]; |
| 208 |
} |
| 209 |
|
| 210 |
/** |
| 211 |
* Create a legacy automation, reusing the real REST controller method. |
| 212 |
* |
| 213 |
* @param array $input Tool input. |
| 214 |
* @return array|\WP_Error |
| 215 |
*/ |
| 216 |
public static function createLegacyAutomation( $input = [] ) { |
| 217 |
if ( ! self::mailMintAvailable() ) { |
| 218 |
return self::mailMintUnavailableError(); |
| 219 |
} |
| 220 |
|
| 221 |
$data = isset( $input['data'] ) && is_array( $input['data'] ) ? $input['data'] : []; |
| 222 |
|
| 223 |
if ( empty( $data['name'] ) || empty( $data['trigger_name'] ) ) { |
| 224 |
return MCPHelper::error( 'missing_fields', 'Provide at least data.name and data.trigger_name.' ); |
| 225 |
} |
| 226 |
|
| 227 |
if ( empty( $data['status'] ) ) { |
| 228 |
$data['status'] = 'draft'; |
| 229 |
} |
| 230 |
|
| 231 |
$controller = new WpfnlAutomationController(); |
| 232 |
$request = new \WP_REST_Request( 'POST', '/wpfnl/v1/automation' ); |
| 233 |
$request->set_body( wp_json_encode( $data ) ); |
| 234 |
|
| 235 |
$response = $controller->create_item( $request ); |
| 236 |
if ( is_wp_error( $response ) ) { |
| 237 |
return $response; |
| 238 |
} |
| 239 |
|
| 240 |
$result = $response->get_data(); |
| 241 |
|
| 242 |
return [ |
| 243 |
'success' => true, |
| 244 |
'automation_id' => isset( $result['id'] ) ? (int) $result['id'] : null, |
| 245 |
]; |
| 246 |
} |
| 247 |
|
| 248 |
/** |
| 249 |
* Update a legacy automation, reusing the real REST controller method. |
| 250 |
* |
| 251 |
* @param array $input Tool input. |
| 252 |
* @return array|\WP_Error |
| 253 |
*/ |
| 254 |
public static function updateLegacyAutomation( $input = [] ) { |
| 255 |
if ( ! self::mailMintAvailable() ) { |
| 256 |
return self::mailMintUnavailableError(); |
| 257 |
} |
| 258 |
|
| 259 |
$id = isset( $input['automation_id'] ) ? (int) $input['automation_id'] : 0; |
| 260 |
if ( ! $id ) { |
| 261 |
return MCPHelper::error( 'missing_automation_id', 'Provide automation_id.' ); |
| 262 |
} |
| 263 |
|
| 264 |
$data = isset( $input['data'] ) && is_array( $input['data'] ) ? $input['data'] : []; |
| 265 |
if ( empty( $data ) ) { |
| 266 |
return MCPHelper::error( 'missing_data', 'Provide the automation fields to write in data.' ); |
| 267 |
} |
| 268 |
|
| 269 |
$controller = new WpfnlAutomationController(); |
| 270 |
$request = new \WP_REST_Request( 'PUT', '/wpfnl/v1/automation/' . $id ); |
| 271 |
$request->set_param( 'id', $id ); |
| 272 |
$request->set_body( wp_json_encode( $data ) ); |
| 273 |
|
| 274 |
$response = $controller->update_item( $request ); |
| 275 |
if ( is_wp_error( $response ) ) { |
| 276 |
return $response; |
| 277 |
} |
| 278 |
|
| 279 |
return [ |
| 280 |
'success' => true, |
| 281 |
'automation_id' => $id, |
| 282 |
]; |
| 283 |
} |
| 284 |
|
| 285 |
/** |
| 286 |
* Permanently delete one or more legacy automations. |
| 287 |
* |
| 288 |
* `WpfnlAutomationController` only exposes a single-item delete route, so |
| 289 |
* this calls Mail Mint's own `AutomationModel::destroy_all()` directly — |
| 290 |
* the same bulk-delete method Mail Mint's own admin list table uses, |
| 291 |
* rather than looping single deletes through the REST controller. |
| 292 |
* |
| 293 |
* @param array $input Tool input. |
| 294 |
* @return array|\WP_Error |
| 295 |
*/ |
| 296 |
public static function bulkDeleteLegacyAutomations( $input = [] ) { |
| 297 |
if ( ! self::mailMintAvailable() ) { |
| 298 |
return self::mailMintUnavailableError(); |
| 299 |
} |
| 300 |
|
| 301 |
$ids = isset( $input['automation_ids'] ) && is_array( $input['automation_ids'] ) |
| 302 |
? array_values( array_filter( array_map( 'intval', $input['automation_ids'] ) ) ) |
| 303 |
: []; |
| 304 |
|
| 305 |
if ( empty( $ids ) ) { |
| 306 |
return MCPHelper::error( 'missing_automation_ids', 'Provide at least one automation_id.' ); |
| 307 |
} |
| 308 |
|
| 309 |
$result = \MintMail\App\Internal\Automation\AutomationModel::destroy_all( $ids ); |
| 310 |
|
| 311 |
return [ |
| 312 |
'success' => (bool) $result, |
| 313 |
'automation_ids' => $ids, |
| 314 |
]; |
| 315 |
} |
| 316 |
|
| 317 |
/** |
| 318 |
* Activate/deactivate a legacy automation by flipping its `status`. |
| 319 |
* |
| 320 |
* Mail Mint's `create_or_update()` requires `name`, `author`, |
| 321 |
* `trigger_name` and `status` together on every write, so this reads the |
| 322 |
* existing record first (via the real `get_item()` controller method), |
| 323 |
* merges in the new status, and writes the full record back through |
| 324 |
* `update_item()` — never inventing a lighter-weight "just flip a flag" |
| 325 |
* path the underlying engine doesn't actually support. |
| 326 |
* |
| 327 |
* @param array $input Tool input. |
| 328 |
* @return array|\WP_Error |
| 329 |
*/ |
| 330 |
public static function toggleLegacyAutomation( $input = [] ) { |
| 331 |
if ( ! self::mailMintAvailable() ) { |
| 332 |
return self::mailMintUnavailableError(); |
| 333 |
} |
| 334 |
|
| 335 |
$id = isset( $input['automation_id'] ) ? (int) $input['automation_id'] : 0; |
| 336 |
if ( ! $id ) { |
| 337 |
return MCPHelper::error( 'missing_automation_id', 'Provide automation_id.' ); |
| 338 |
} |
| 339 |
|
| 340 |
if ( ! isset( $input['active'] ) ) { |
| 341 |
return MCPHelper::error( 'missing_active', 'Provide active: true or false.' ); |
| 342 |
} |
| 343 |
$active = (bool) $input['active']; |
| 344 |
|
| 345 |
$controller = new WpfnlAutomationController(); |
| 346 |
|
| 347 |
$get_request = new \WP_REST_Request( 'GET', '/wpfnl/v1/automation/' . $id ); |
| 348 |
$get_request->set_param( 'id', $id ); |
| 349 |
$current = $controller->get_item( $get_request ); |
| 350 |
if ( is_wp_error( $current ) ) { |
| 351 |
return $current; |
| 352 |
} |
| 353 |
|
| 354 |
$current_data = $current->get_data(); |
| 355 |
$record = isset( $current_data['data'][0] ) ? $current_data['data'][0] : null; |
| 356 |
if ( ! $record ) { |
| 357 |
return MCPHelper::error( 'automation_not_found', sprintf( 'No automation found with ID %d.', $id ) ); |
| 358 |
} |
| 359 |
|
| 360 |
$status = $active ? 'active' : 'draft'; |
| 361 |
|
| 362 |
$payload = [ |
| 363 |
'id' => $id, |
| 364 |
'name' => isset( $record['name'] ) ? $record['name'] : '', |
| 365 |
'author' => isset( $record['author'] ) ? $record['author'] : get_current_user_id(), |
| 366 |
'trigger_name' => isset( $record['trigger_name'] ) ? $record['trigger_name'] : '', |
| 367 |
'status' => $status, |
| 368 |
]; |
| 369 |
|
| 370 |
$update_request = new \WP_REST_Request( 'PUT', '/wpfnl/v1/automation/' . $id ); |
| 371 |
$update_request->set_param( 'id', $id ); |
| 372 |
$update_request->set_body( wp_json_encode( $payload ) ); |
| 373 |
|
| 374 |
$response = $controller->update_item( $update_request ); |
| 375 |
if ( is_wp_error( $response ) ) { |
| 376 |
return $response; |
| 377 |
} |
| 378 |
|
| 379 |
return [ |
| 380 |
'success' => true, |
| 381 |
'automation_id' => $id, |
| 382 |
'active' => $active, |
| 383 |
'status' => $status, |
| 384 |
]; |
| 385 |
} |
| 386 |
} |
| 387 |
|