__( 'Workflows', 'wpbot-automator' ),
'email-templates' => __( 'Email Templates', 'wpbot-automator' ),
'logs' => __( 'Activity Log', 'wpbot-automator' ),
'tables' => __( 'Tables', 'wpbot-automator' ),
'support' => __( 'Support', 'wpbot-automator' ),
'help' => __( 'Help', 'wpbot-automator' ),
);
?>
$tab_name ) : ?>
';
break;
}
?>
';
}
public static function render_logs_page() {
echo '';
}
public static function render_tables_page() {
echo '';
}
public static function wpbotauto_valid_license() {
$wpbot_automator_license_valid = get_option('wpbot_automator_license_valid');
if( $wpbot_automator_license_valid === 'automator_master_license' ) {
return true;
}
return false;
}
/**
* Enqueue admin assets
*
* @param string $hook Current admin page hook.
*/
public static function enqueue_admin_assets( $hook ) {
// Only enqueue on our specific automator page
if ( ! isset( $_GET['page'] ) || 'wpbot-automator' !== $_GET['page'] ) {
return;
}
$asset_file = WPBOT_AUTOMATOR_PLUGIN_DIR . 'build/index.asset.php';
if ( file_exists( $asset_file ) ) {
$asset = require $asset_file;
wp_enqueue_script(
'wpbot-automator-admin',
WPBOT_AUTOMATOR_PLUGIN_URL . 'build/index.js',
$asset['dependencies'],
$asset['version'],
true
);
wp_enqueue_style(
'wpbot-automator-admin',
WPBOT_AUTOMATOR_PLUGIN_URL . 'build/index.css',
array( 'wp-components' ),
$asset['version']
);
$is_pro_active = self::wpbotauto_valid_license();
// Pass data to JavaScript.
wp_localize_script(
'wpbot-automator-admin',
'wpbotAutomator',
array(
'apiUrl' => rest_url( 'wpbot-automator/v1' ),
'nonce' => wp_create_nonce( 'wp_rest' ),
'pluginUrl' => WPBOT_AUTOMATOR_PLUGIN_URL,
'userEmail' => get_option( 'admin_email' ),
'isProActive' => $is_pro_active,
)
);
}
}
/**
* Register REST API routes
*/
public static function register_rest_routes() {
// Workflows endpoint.
register_rest_route(
'wpbot-automator/v1',
'/workflows',
array(
'methods' => 'GET',
'callback' => array( __CLASS__, 'get_workflows' ),
'permission_callback' => array( __CLASS__, 'check_permission' ),
)
);
register_rest_route(
'wpbot-automator/v1',
'/workflows',
array(
'methods' => 'POST',
'callback' => array( __CLASS__, 'create_workflow' ),
'permission_callback' => array( __CLASS__, 'check_permission' ),
)
);
register_rest_route(
'wpbot-automator/v1',
'/workflows/(?P\d+)',
array(
'methods' => 'PUT',
'callback' => array( __CLASS__, 'update_workflow' ),
'permission_callback' => array( __CLASS__, 'check_permission' ),
)
);
register_rest_route(
'wpbot-automator/v1',
'/workflows/(?P\d+)',
array(
'methods' => 'DELETE',
'callback' => array( __CLASS__, 'delete_workflow' ),
'permission_callback' => array( __CLASS__, 'check_permission' ),
)
);
// Products endpoint for WooCommerce.
register_rest_route(
'wpbot-automator/v1',
'/products',
array(
'methods' => 'GET',
'callback' => array( __CLASS__, 'get_products' ),
'permission_callback' => array( __CLASS__, 'check_permission' ),
)
);
// WPForms forms list.
register_rest_route(
'wpbot-automator/v1',
'/wpforms-forms',
array(
'methods' => 'GET',
'callback' => array( __CLASS__, 'get_wpforms_forms' ),
'permission_callback' => array( __CLASS__, 'check_permission' ),
)
);
// Conversational forms list.
register_rest_route(
'wpbot-automator/v1',
'/conversational-forms',
array(
'methods' => 'GET',
'callback' => array( __CLASS__, 'get_conversational_forms' ),
'permission_callback' => array( __CLASS__, 'check_permission' ),
)
);
// Contact Form 7 forms list.
register_rest_route(
'wpbot-automator/v1',
'/cf7-forms',
array(
'methods' => 'GET',
'callback' => array( __CLASS__, 'get_cf7_forms' ),
'permission_callback' => array( __CLASS__, 'check_permission' ),
)
);
// Test action endpoint.
register_rest_route(
'wpbot-automator/v1',
'/test-action',
array(
'methods' => 'POST',
'callback' => array( __CLASS__, 'test_action' ),
'permission_callback' => array( __CLASS__, 'check_permission' ),
)
);
// Logs endpoint.
register_rest_route(
'wpbot-automator/v1',
'/logs',
array(
array(
'methods' => 'GET',
'callback' => array( __CLASS__, 'get_logs' ),
'permission_callback' => array( __CLASS__, 'check_permission' ),
),
array(
'methods' => 'DELETE',
'callback' => array( __CLASS__, 'clear_logs' ),
'permission_callback' => array( __CLASS__, 'check_permission' ),
),
)
);
register_rest_route(
'wpbot-automator/v1',
'/logs/summary',
array(
'methods' => 'GET',
'callback' => array( __CLASS__, 'get_logs_summary' ),
'permission_callback' => array( __CLASS__, 'check_permission' ),
)
);
// Export workflows endpoint.
register_rest_route(
'wpbot-automator/v1',
'/workflows/export',
array(
'methods' => 'GET',
'callback' => array( __CLASS__, 'export_workflows' ),
'permission_callback' => array( __CLASS__, 'check_permission' ),
)
);
// Import workflows endpoint.
register_rest_route(
'wpbot-automator/v1',
'/workflows/import',
array(
'methods' => 'POST',
'callback' => array( __CLASS__, 'import_workflows' ),
'permission_callback' => array( __CLASS__, 'check_permission' ),
)
);
// AI Workflow Generator endpoint.
register_rest_route(
'wpbot-automator/v1',
'/generate-workflow',
array(
'methods' => 'POST',
'callback' => array( __CLASS__, 'generate_workflow' ),
'permission_callback' => array( __CLASS__, 'check_permission' ),
)
);
// Support request endpoint.
register_rest_route(
'wpbot-automator/v1',
'/support/send',
array(
'methods' => 'POST',
'callback' => array( __CLASS__, 'send_support_email' ),
'permission_callback' => array( __CLASS__, 'check_permission' ),
)
);
// Credentials endpoint (GET / POST).
register_rest_route(
'wpbot-automator/v1',
'/credentials',
array(
array(
'methods' => 'GET',
'callback' => array( __CLASS__, 'get_credentials' ),
'permission_callback' => array( __CLASS__, 'check_permission' ),
),
array(
'methods' => 'POST',
'callback' => array( __CLASS__, 'save_credential' ),
'permission_callback' => array( __CLASS__, 'check_permission' ),
)
)
);
// Credentials delete endpoint.
register_rest_route(
'wpbot-automator/v1',
'/credentials/(?P[a-zA-Z0-9_\-]+)',
array(
'methods' => 'DELETE',
'callback' => array( __CLASS__, 'delete_credential' ),
'permission_callback' => array( __CLASS__, 'check_permission' ),
)
);
// Email Templates endpoint.
register_rest_route(
'wpbot-automator/v1',
'/email-templates',
array(
array(
'methods' => 'GET',
'callback' => array( __CLASS__, 'get_email_templates' ),
'permission_callback' => array( __CLASS__, 'check_permission' ),
),
array(
'methods' => 'POST',
'callback' => array( __CLASS__, 'save_email_template' ),
'permission_callback' => array( __CLASS__, 'check_permission' ),
)
)
);
register_rest_route(
'wpbot-automator/v1',
'/email-templates/(?P\d+)',
array(
array(
'methods' => 'GET',
'callback' => array( __CLASS__, 'get_email_template' ),
'permission_callback' => array( __CLASS__, 'check_permission' ),
),
array(
'methods' => 'PUT',
'callback' => array( __CLASS__, 'save_email_template' ),
'permission_callback' => array( __CLASS__, 'check_permission' ),
),
array(
'methods' => 'DELETE',
'callback' => array( __CLASS__, 'delete_email_template' ),
'permission_callback' => array( __CLASS__, 'check_permission' ),
)
)
);
// Webhook trigger endpoint — registered here (in addition to Webhook class)
// to guarantee the route is always available regardless of hook timing.
register_rest_route(
'wpbot-automator/v1',
'/webhook/(?P[a-zA-Z0-9\-_]+)',
array(
'methods' => array( 'GET', 'POST' ),
'callback' => array( __CLASS__, 'handle_webhook_trigger' ),
'permission_callback' => '__return_true',
)
);
}
/**
* Handle incoming webhook — public entry point registered in register_rest_routes().
*
* Accepts any GET/POST request, extracts the token from the URL, merges all
* request params as trigger data, and fires the matching workflow.
*
* @param \WP_REST_Request $request Request object.
* @return \WP_REST_Response
*/
public static function handle_webhook_trigger( \WP_REST_Request $request ) {
$token = $request->get_param( 'token' );
$data = $request->get_params();
// target_node_id tells the workflow runner which trigger node to start from.
$data['target_node_id'] = 'node-' . $token;
\WPbot_Automator\Engine\Workflow_Runner::run_workflows_for_trigger(
'webhook:incoming_webhook',
$data
);
return rest_ensure_response( array(
'success' => true,
'message' => 'Webhook received and workflow triggered.',
'token' => $token,
) );
}
/**
* Get logs
*
* @param \WP_REST_Request $request Request object.
* @return \WP_REST_Response
*/
public static function get_logs( $request ) {
global $wpdb;
$table_logs = Database::get_logs_table();
$table_workflows = Database::get_workflows_table();
$workflow_id = absint( $request->get_param( 'workflow_id' ) );
$status = sanitize_text_field( $request->get_param( 'status' ) );
$per_page = min( max( absint( $request->get_param( 'per_page' ) ?: 25 ), 1 ), 200 );
$page = max( absint( $request->get_param( 'page' ) ?: 1 ), 1 );
$offset = ( $page - 1 ) * $per_page;
$where_parts = array();
$query_params = array();
if ( $workflow_id ) {
$where_parts[] = 'l.workflow_id = %d';
$query_params[] = $workflow_id;
}
if ( $status && in_array( $status, array( 'success', 'failed' ), true ) ) {
$where_parts[] = 'l.status = %s';
$query_params[] = $status;
}
$where = $where_parts ? 'WHERE ' . implode( ' AND ', $where_parts ) : '';
$count_sql = "SELECT COUNT(*) FROM {$table_logs} l {$where}";
$total = (int) ( $query_params
? $wpdb->get_var( $wpdb->prepare( $count_sql, ...$query_params ) )
: $wpdb->get_var( $count_sql ) );
$list_params = array_merge( $query_params, array( $per_page, $offset ) );
$sql = "SELECT l.*, w.name as workflow_name
FROM {$table_logs} l
LEFT JOIN {$table_workflows} w ON l.workflow_id = w.id
{$where}
ORDER BY l.created_at DESC
LIMIT %d OFFSET %d";
$logs = $wpdb->get_results( $wpdb->prepare( $sql, ...$list_params ), ARRAY_A );
return rest_ensure_response( array(
'logs' => $logs ?: array(),
'total' => $total,
'page' => $page,
'per_page' => $per_page,
'total_pages' => $total > 0 ? (int) ceil( $total / $per_page ) : 0,
) );
}
public static function get_logs_summary() {
global $wpdb;
$table = Database::get_logs_table();
$total = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$table}" );
$success = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$table} WHERE status = 'success'" );
$failed = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$table} WHERE status = 'failed'" );
$last = $wpdb->get_var( "SELECT created_at FROM {$table} ORDER BY created_at DESC LIMIT 1" );
return rest_ensure_response( array(
'total' => $total,
'success' => $success,
'failed' => $failed,
'last_run' => $last ?: null,
) );
}
public static function clear_logs() {
global $wpdb;
$wpdb->query( 'TRUNCATE TABLE ' . Database::get_logs_table() );
return rest_ensure_response( array( 'success' => true ) );
}
/**
* Check permission for REST API
*
* @return bool
*/
public static function check_permission() {
return current_user_can( 'manage_options' );
}
/**
* Get workflows
*
* @param \WP_REST_Request $request Request object.
* @return \WP_REST_Response
*/
public static function get_workflows( $request ) {
global $wpdb;
$table = Database::get_workflows_table();
$workflows = $wpdb->get_results(
"SELECT * FROM {$table} ORDER BY created_at DESC",
ARRAY_A
);
// Decode workflow_data JSON.
foreach ( $workflows as &$workflow ) {
$workflow['workflow_data'] = json_decode( $workflow['workflow_data'], true );
}
return rest_ensure_response( $workflows );
}
/**
* Create workflow
*
* @param \WP_REST_Request $request Request object.
* @return \WP_REST_Response
*/
public static function create_workflow( $request ) {
global $wpdb;
$table = Database::get_workflows_table();
$name = sanitize_text_field( $request->get_param( 'name' ) );
$description = sanitize_textarea_field( $request->get_param( 'description' ) );
$workflow_data = wp_json_encode( $request->get_param( 'workflow_data' ) );
$status = sanitize_text_field( $request->get_param( 'status' ) ) ?: 'active';
$result = $wpdb->insert(
$table,
array(
'name' => $name,
'description' => $description,
'workflow_data' => $workflow_data,
'status' => $status,
),
array( '%s', '%s', '%s', '%s' )
);
if ( $result ) {
return rest_ensure_response(
array(
'success' => true,
'id' => $wpdb->insert_id,
)
);
}
return new \WP_Error( 'create_failed', __( 'Failed to create workflow', 'wpbot-automator' ), array( 'status' => 500 ) );
}
/**
* Update workflow
*
* @param \WP_REST_Request $request Request object.
* @return \WP_REST_Response
*/
public static function update_workflow( $request ) {
global $wpdb;
$table = Database::get_workflows_table();
$id = absint( $request->get_param( 'id' ) );
$name = sanitize_text_field( $request->get_param( 'name' ) );
$description = sanitize_textarea_field( $request->get_param( 'description' ) );
$workflow_data = wp_json_encode( $request->get_param( 'workflow_data' ) );
$status = sanitize_text_field( $request->get_param( 'status' ) );
$result = $wpdb->update(
$table,
array(
'name' => $name,
'description' => $description,
'workflow_data' => $workflow_data,
'status' => $status,
),
array( 'id' => $id ),
array( '%s', '%s', '%s', '%s' ),
array( '%d' )
);
if ( false !== $result ) {
return rest_ensure_response( array( 'success' => true ) );
}
return new \WP_Error( 'update_failed', __( 'Failed to update workflow', 'wpbot-automator' ), array( 'status' => 500 ) );
}
/**
* Delete workflow
*
* @param \WP_REST_Request $request Request object.
* @return \WP_REST_Response
*/
public static function delete_workflow( $request ) {
global $wpdb;
$table = Database::get_workflows_table();
$id = absint( $request->get_param( 'id' ) );
$result = $wpdb->delete(
$table,
array( 'id' => $id ),
array( '%d' )
);
if ( $result ) {
return rest_ensure_response( array( 'success' => true ) );
}
return new \WP_Error( 'delete_failed', __( 'Failed to delete workflow', 'wpbot-automator' ), array( 'status' => 500 ) );
}
/**
* Get WooCommerce products
*
* @param \WP_REST_Request $request Request object.
* @return \WP_REST_Response
*/
public static function get_products( $request ) {
if ( ! class_exists( 'WooCommerce' ) ) {
return rest_ensure_response( array() );
}
$args = array(
'limit' => -1,
'status' => 'publish',
);
$products = wc_get_products( $args );
$response = array();
foreach ( $products as $product ) {
$response[] = array(
'id' => $product->get_id(),
'name' => $product->get_name(),
);
}
return rest_ensure_response( $response );
}
/**
* Get list of WPForms forms.
*/
public static function get_wpforms_forms() {
if ( ! function_exists( 'wpforms' ) ) {
return rest_ensure_response( array() );
}
$forms = wpforms()->form->get( '', array( 'fields' => 'id,post_title', 'posts_per_page' => -1 ) );
$response = array();
if ( is_array( $forms ) ) {
foreach ( $forms as $form ) {
$response[] = array(
'id' => (string) $form->ID,
'name' => $form->post_title,
);
}
}
return rest_ensure_response( $response );
}
/**
* Get list of Conversational forms.
*/
public static function get_conversational_forms() {
if ( ! class_exists( 'Qcformbuilder_Forms' ) ) {
return rest_ensure_response( array() );
}
$forms = \Qcformbuilder_Forms::get_forms( true, false );
$response = array();
if ( is_array( $forms ) ) {
foreach ( $forms as $form_id => $form ) {
$response[] = array(
'id' => (string) $form_id,
'name' => isset( $form['name'] ) ? $form['name'] : $form_id,
);
}
}
return rest_ensure_response( $response );
}
/**
* Get list of Contact Form 7 forms.
*/
public static function get_cf7_forms() {
if ( ! class_exists( 'WPCF7_ContactForm' ) ) {
return rest_ensure_response( array() );
}
$forms = \WPCF7_ContactForm::find( array( 'posts_per_page' => -1 ) );
$response = array();
foreach ( $forms as $form ) {
$response[] = array(
'id' => (string) $form->id(),
'name' => $form->title(),
);
}
return rest_ensure_response( $response );
}
/**
* Test an action
*
* @param \WP_REST_Request $request Request object.
* @return \WP_REST_Response
*/
public static function test_action( $request ) {
$app_id = sanitize_text_field( $request->get_param( 'appId' ) );
$action_id = sanitize_text_field( $request->get_param( 'actionId' ) );
$config = $request->get_param( 'config' );
$action_obj = Registry::get_action( $app_id );
if ( ! $action_obj ) {
return new \WP_Error( 'action_not_found', __( 'Action not found', 'wpbot-automator' ), array( 'status' => 404 ) );
}
// Prepare dummy trigger data for token parsing (optional).
$trigger_data = array(
'token' => 'sample_token_value',
'id' => 123,
'name' => 'Sample Name',
);
$action_data = array(
'actionId' => $action_id,
'config' => $config,
);
$result = $action_obj->execute( $action_data, $trigger_data );
return rest_ensure_response( $result );
}
/**
* Export workflows as a JSON download
*
* @param \WP_REST_Request $request Request object.
* @return void — sends headers and exits.
*/
public static function export_workflows( $request ) {
global $wpdb;
$table = Database::get_workflows_table();
$id = absint( $request->get_param( 'id' ) );
if ( $id ) {
$rows = $wpdb->get_results(
$wpdb->prepare( "SELECT * FROM {$table} WHERE id = %d", $id ),
ARRAY_A
);
$filename = 'wpbot-workflow-' . $id . '.json';
} else {
$rows = $wpdb->get_results( "SELECT * FROM {$table} ORDER BY created_at DESC", ARRAY_A );
$filename = 'wpbot-workflows-all.json';
}
if ( empty( $rows ) ) {
wp_send_json_error( array( 'message' => 'No workflows found.' ), 404 );
return;
}
// Decode workflow_data so the exported JSON is human-readable.
foreach ( $rows as &$row ) {
$row['workflow_data'] = json_decode( $row['workflow_data'], true );
}
unset( $row );
$json = wp_json_encode( array( 'workflows' => $rows ), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE );
header( 'Content-Type: application/json' );
header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
header( 'Cache-Control: no-cache, no-store, must-revalidate' );
echo $json; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
exit;
}
/**
* Import workflows from a JSON payload
*
* @param \WP_REST_Request $request Request object.
* @return \WP_REST_Response|\WP_Error
*/
public static function import_workflows( $request ) {
global $wpdb;
$table = Database::get_workflows_table();
$workflows = $request->get_param( 'workflows' );
if ( empty( $workflows ) || ! is_array( $workflows ) ) {
return new \WP_Error(
'invalid_data',
__( 'Invalid import data. Expected a "workflows" array.', 'wpbot-automator' ),
array( 'status' => 400 )
);
}
$imported = 0;
$errors = array();
foreach ( $workflows as $index => $workflow ) {
$name = isset( $workflow['name'] ) ? sanitize_text_field( $workflow['name'] ) : '';
$description = isset( $workflow['description'] ) ? sanitize_textarea_field( $workflow['description'] ) : '';
$status = isset( $workflow['status'] ) ? sanitize_text_field( $workflow['status'] ) : 'inactive';
$workflow_data = isset( $workflow['workflow_data'] ) ? wp_json_encode( $workflow['workflow_data'] ) : wp_json_encode( array() );
if ( empty( $name ) ) {
$errors[] = "Workflow at index {$index} is missing a name — skipped.";
continue;
}
$result = $wpdb->insert(
$table,
array(
'name' => $name,
'description' => $description,
'status' => $status,
'workflow_data' => $workflow_data,
),
array( '%s', '%s', '%s', '%s' )
);
if ( $result ) {
++$imported;
} else {
$errors[] = "Failed to insert workflow \"{$name}\" (index {$index}).";
}
}
return rest_ensure_response(
array(
'success' => $imported > 0,
'imported' => $imported,
'errors' => $errors,
)
);
}
/**
* Send support email
*
* @param \WP_REST_Request $request Request object.
* @return \WP_REST_Response|\WP_Error
*/
public static function send_support_email( $request ) {
$from_email = sanitize_email( $request->get_param( 'from_email' ) );
$message = sanitize_textarea_field( $request->get_param( 'message' ) );
if ( ! is_email( $from_email ) ) {
return new \WP_Error( 'invalid_email', __( 'Invalid email address', 'wpbot-automator' ), array( 'status' => 400 ) );
}
if ( empty( $message ) ) {
return new \WP_Error( 'empty_message', __( 'Message cannot be empty', 'wpbot-automator' ), array( 'status' => 400 ) );
}
$to = 'hello@wpbot.pro';
$subject = 'WPBot Automator Support Request';
$headers = array(
'Content-Type: text/html; charset=UTF-8',
'Reply-To: ' . $from_email,
'From: ' . $from_email,
);
$body = wpautop( $message );
$sent = wp_mail( $to, $subject, $body, $headers );
if ( $sent ) {
return rest_ensure_response( array( 'success' => true ) );
}
return new \WP_Error( 'email_failed', __( 'Failed to send support email', 'wpbot-automator' ), array( 'status' => 500 ) );
}
/**
* Get credentials
*
* @param \WP_REST_Request $request Request object.
* @return \WP_REST_Response
*/
public static function get_credentials( $request ) {
$app_id = sanitize_text_field( $request->get_param( 'app_id' ) );
$credentials = get_option( 'wpbot_automator_credentials', array() );
if ( $app_id ) {
$filtered = array();
foreach ( $credentials as $cred ) {
if ( isset($cred['app_id']) && $cred['app_id'] === $app_id ) {
// We only send back public safe info to frontend (name, id, app_id).
$filtered[] = array(
'id' => $cred['id'],
'name' => $cred['name'],
'app_id' => $cred['app_id']
);
}
}
return rest_ensure_response( $filtered );
}
// If no app_id provided, return all credentials (names and IDs only)
$filtered = array();
foreach ( $credentials as $cred ) {
$filtered[] = array(
'id' => $cred['id'],
'name' => $cred['name'],
'app_id' => $cred['app_id']
);
}
return rest_ensure_response( $filtered );
}
/**
* Save a credential
*
* @param \WP_REST_Request $request Request object.
* @return \WP_REST_Response|\WP_Error
*/
public static function save_credential( $request ) {
$name = sanitize_text_field( $request->get_param( 'name' ) );
$app_id = sanitize_text_field( $request->get_param( 'app_id' ) );
$data = $request->get_param( 'data' );
error_log('save_credential params: name=' . $name . ', app_id=' . $app_id . ', data=' . print_r($data, true));
if ( empty( $name ) || empty( $app_id ) || empty( $data ) ) {
error_log('save_credential missing data error');
return new \WP_Error( 'missing_data', __( 'Name, app_id, and data are required.', 'wpbot-automator' ), array( 'status' => 400 ) );
}
$credentials = get_option( 'wpbot_automator_credentials', array() );
if ( ! is_array( $credentials ) ) {
$credentials = array();
}
$new_id = uniqid( 'cred_' );
$credentials[$new_id] = array(
'id' => $new_id,
'name' => $name,
'app_id' => $app_id,
'data' => $data,
'created_at' => current_time( 'mysql' ),
);
update_option( 'wpbot_automator_credentials', $credentials );
error_log('save_credential saved: ' . $new_id);
return rest_ensure_response( array(
'success' => true,
'credential' => array(
'id' => $new_id,
'name' => $name,
'app_id' => $app_id
)
) );
}
/**
* Delete a credential
*
* @param \WP_REST_Request $request Request object.
* @return \WP_REST_Response|\WP_Error
*/
public static function delete_credential( $request ) {
$id = sanitize_text_field( $request->get_param( 'id' ) );
$credentials = get_option( 'wpbot_automator_credentials', array() );
if ( isset( $credentials[$id] ) ) {
unset( $credentials[$id] );
update_option( 'wpbot_automator_credentials', $credentials );
return rest_ensure_response( array( 'success' => true ) );
}
return new \WP_Error( 'not_found', __( 'Credential not found.', 'wpbot-automator' ), array( 'status' => 404 ) );
}
/**
* Get all email templates
*/
public static function get_email_templates() {
global $wpdb;
$table = Database::get_email_templates_table();
if ( ! $wpdb->get_var( "SHOW TABLES LIKE '$table'" ) ) {
return rest_ensure_response( array() );
}
$templates = $wpdb->get_results(
"SELECT id, name FROM {$table} ORDER BY name ASC",
ARRAY_A
);
error_log( 'WPbot Automator - GET Templates - Table: ' . $table );
error_log( 'WPbot Automator - GET Templates - Count: ' . count( $templates ) );
error_log( 'WPbot Automator - GET Templates - JSON: ' . wp_json_encode( $templates ) );
return rest_ensure_response( is_array( $templates ) ? $templates : array() );
}
/**
* Get a single email template
*/
public static function get_email_template( $request ) {
global $wpdb;
$id = absint( $request->get_param( 'id' ) );
$table = Database::get_email_templates_table();
$template = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$table} WHERE id = %d", $id ), ARRAY_A );
if ( ! $template ) {
return new \WP_Error( 'not_found', __( 'Template not found', 'wpbot-automator' ), array( 'status' => 404 ) );
}
return rest_ensure_response( $template );
}
/**
* Save an email template (Create or Update)
*/
public static function save_email_template( $request ) {
global $wpdb;
$table = Database::get_email_templates_table();
$id = absint( $request->get_param( 'id' ) );
$name = sanitize_text_field( $request->get_param( 'name' ) );
$subject = sanitize_text_field( $request->get_param( 'subject' ) );
$body_html = $request->get_param( 'body_html' ); // Allow HTML here.
$body_json = $request->get_param( 'body_json' );
if ( empty( $name ) ) {
return new \WP_Error( 'missing_name', __( 'Template name is required', 'wpbot-automator' ), array( 'status' => 400 ) );
}
// Free plan: limit to 3 templates on creation.
if ( ! $id ) {
if ( ! function_exists( 'wpbot_automator_pro_init' ) ) {
$count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$table}" );
if ( $count >= 3 ) {
return new \WP_Error(
'template_limit_reached',
__( 'Free plan is limited to 3 email templates. Upgrade to Pro for unlimited templates.', 'wpbot-automator' ),
array( 'status' => 403 )
);
}
}
}
$data = array(
'name' => $name,
'subject' => $subject,
'body_html' => $body_html,
'body_json' => is_scalar( $body_json ) ? $body_json : wp_json_encode( $body_json ),
);
error_log( 'WPbot Automator - Saving Template - ID: ' . $id );
error_log( 'WPbot Automator - Saving Template - Data: ' . wp_json_encode( array_keys( $data ) ) );
if ( $id ) {
$result = $wpdb->update( $table, $data, array( 'id' => $id ) );
} else {
$result = $wpdb->insert( $table, $data );
$id = $wpdb->insert_id;
}
if ( false === $result ) {
error_log( 'WPbot Automator - Save Template FAILED. Error: ' . $wpdb->last_error );
return new \WP_Error( 'save_failed', __( 'Failed to save template', 'wpbot-automator' ) . ': ' . $wpdb->last_error, array( 'status' => 500 ) );
}
error_log( 'WPbot Automator - Save Template SUCCESS. ID: ' . $id );
return rest_ensure_response( array( 'success' => true, 'id' => $id ) );
}
/**
* Delete an email template
*/
public static function delete_email_template( $request ) {
global $wpdb;
$table = Database::get_email_templates_table();
$id = absint( $request->get_param( 'id' ) );
$result = $wpdb->delete( $table, array( 'id' => $id ) );
if ( ! $result ) {
return new \WP_Error( 'delete_failed', __( 'Failed to delete template', 'wpbot-automator' ), array( 'status' => 500 ) );
}
return rest_ensure_response( array( 'success' => true ) );
}
/**
* AI Workflow Generator — generate a workflow from a natural-language prompt.
*
* @param \WP_REST_Request $request
*/
public static function generate_workflow( \WP_REST_Request $request ) {
$provider = sanitize_text_field( $request->get_param( 'provider' ) );
$api_key = sanitize_text_field( $request->get_param( 'api_key' ) );
$model = sanitize_text_field( $request->get_param( 'model' ) );
$user_prompt = sanitize_textarea_field( $request->get_param( 'prompt' ) );
if ( empty( $api_key ) || empty( $user_prompt ) ) {
return new \WP_Error( 'missing_params', 'API key and prompt are required.', array( 'status' => 400 ) );
}
$system_prompt = self::get_workflow_generator_system_prompt();
switch ( $provider ) {
case 'openai':
$raw = self::call_openai_for_workflow( $api_key, $model ?: 'gpt-4o-mini', $system_prompt, $user_prompt );
break;
case 'claude':
$raw = self::call_claude_for_workflow( $api_key, $model ?: 'claude-3-5-sonnet-20240620', $system_prompt, $user_prompt );
break;
case 'gemini':
$raw = self::call_gemini_for_workflow( $api_key, $model ?: 'gemini-1.5-flash', $system_prompt, $user_prompt );
break;
default:
return new \WP_Error( 'invalid_provider', 'Invalid AI provider. Use openai, claude, or gemini.', array( 'status' => 400 ) );
}
if ( is_wp_error( $raw ) ) {
return $raw;
}
// Strip markdown code fences if the model wrapped the JSON.
$json_text = trim( $raw );
$json_text = preg_replace( '/^```(?:json)?\s*/i', '', $json_text );
$json_text = preg_replace( '/\s*```\s*$/', '', $json_text );
$workflow = json_decode( $json_text, true );
if ( JSON_ERROR_NONE !== json_last_error() || empty( $workflow['nodes'] ) || empty( $workflow['connections'] ) ) {
return new \WP_Error( 'parse_error', 'AI returned invalid workflow data. Please try again with a clearer description.', array( 'status' => 422 ) );
}
return rest_ensure_response( array(
'success' => true,
'workflow' => $workflow,
) );
}
/**
* System prompt sent to every AI provider.
*/
private static function get_workflow_generator_system_prompt() {
return 'You are a workflow automation assistant for WPBot Automator, a WordPress plugin similar to Zapier.
Convert the user\'s plain-English description into a JSON workflow using ONLY the apps and action IDs listed below.
TRIGGER APPS (first node only, type="trigger"):
- wordpress: wp_post_publish, wp_user_register, wp_user_login, wp_comment_added
- woocommerce: wc_order_created, wc_order_status_change, wc_payment_completed
- wpforms: wpforms_submit
- cf7: cf7_submit
- fluent_forms: ff_form_submit
- webhook: incoming_webhook
- facebook: facebook_lead_ads
ACTION APPS (all subsequent nodes, type="action"):
- google_sheets: gs_add_row, gs_update_row
- mail: send_email
- telegram: tg_send_message
- whatsapp: wa_send_message
- facebook: fb_create_post
- instagram: ig_publish_photo
- linkedin: li_create_post
- openai: chat_completion
- claude: chat_completion
- gemini: chat_completion
- fluentcrm: create_contact
- filters: filter_condition
- iterator: iterator_loop
- iterator_end: iterator_end
- webhook: outgoing_webhook
- api: api_request
- mailrefine: mr_verify_email
Return ONLY valid JSON with no explanation, no markdown fences:
{"name":"Short workflow name","description":"One sentence description","nodes":[{"type":"trigger","appId":"wpforms","actionId":"wpforms_submit"},{"type":"action","appId":"google_sheets","actionId":"gs_add_row"}],"connections":[{"from":0,"to":1}]}
Rules:
- Exactly one trigger node at index 0
- All other nodes are actions
- "from" and "to" are zero-based node indices
- Use ONLY the appId/actionId values from the lists above — never invent new ones
- For parallel branches, multiple connection objects may share the same "from" value
- Return ONLY the raw JSON object, nothing else';
}
/**
* Call OpenAI chat completions API.
*
* @return string|\WP_Error Raw text response or error.
*/
private static function call_openai_for_workflow( $api_key, $model, $system_prompt, $user_prompt ) {
$body = array(
'model' => $model,
'messages' => array(
array( 'role' => 'system', 'content' => $system_prompt ),
array( 'role' => 'user', 'content' => $user_prompt ),
),
'temperature' => 0.2,
'response_format' => array( 'type' => 'json_object' ),
);
$response = wp_remote_post(
'https://api.openai.com/v1/chat/completions',
array(
'timeout' => 60,
'headers' => array(
'Authorization' => 'Bearer ' . $api_key,
'Content-Type' => 'application/json',
),
'body' => wp_json_encode( $body ),
)
);
if ( is_wp_error( $response ) ) {
return $response;
}
$data = json_decode( wp_remote_retrieve_body( $response ), true );
if ( ! empty( $data['error'] ) ) {
return new \WP_Error( 'openai_error', $data['error']['message'] ?? 'OpenAI error', array( 'status' => 400 ) );
}
return $data['choices'][0]['message']['content'] ?? '';
}
/**
* Call Anthropic Claude messages API.
*
* @return string|\WP_Error
*/
private static function call_claude_for_workflow( $api_key, $model, $system_prompt, $user_prompt ) {
$body = array(
'model' => $model,
'max_tokens' => 1024,
'system' => $system_prompt,
'messages' => array(
array( 'role' => 'user', 'content' => $user_prompt ),
),
);
$response = wp_remote_post(
'https://api.anthropic.com/v1/messages',
array(
'timeout' => 60,
'headers' => array(
'x-api-key' => $api_key,
'anthropic-version' => '2023-06-01',
'Content-Type' => 'application/json',
),
'body' => wp_json_encode( $body ),
)
);
if ( is_wp_error( $response ) ) {
return $response;
}
$data = json_decode( wp_remote_retrieve_body( $response ), true );
if ( ! empty( $data['error'] ) ) {
return new \WP_Error( 'claude_error', $data['error']['message'] ?? 'Claude error', array( 'status' => 400 ) );
}
return $data['content'][0]['text'] ?? '';
}
/**
* Call Google Gemini generateContent API.
*
* @return string|\WP_Error
*/
private static function call_gemini_for_workflow( $api_key, $model, $system_prompt, $user_prompt ) {
$url = 'https://generativelanguage.googleapis.com/v1beta/models/' . rawurlencode( $model ) . ':generateContent?key=' . rawurlencode( $api_key );
$body = array(
'contents' => array(
array(
'role' => 'user',
'parts' => array( array( 'text' => $system_prompt . "\n\nUser request: " . $user_prompt ) ),
),
),
'generationConfig' => array(
'temperature' => 0.2,
'responseMimeType' => 'application/json',
),
);
$response = wp_remote_post(
$url,
array(
'timeout' => 60,
'headers' => array( 'Content-Type' => 'application/json' ),
'body' => wp_json_encode( $body ),
)
);
if ( is_wp_error( $response ) ) {
return $response;
}
$data = json_decode( wp_remote_retrieve_body( $response ), true );
if ( ! empty( $data['error'] ) ) {
return new \WP_Error( 'gemini_error', $data['error']['message'] ?? 'Gemini error', array( 'status' => 400 ) );
}
return $data['candidates'][0]['content']['parts'][0]['text'] ?? '';
}
}