license_manager = new WP_AI_Workflows_License();
add_action( 'admin_menu', array( $this, 'add_admin_menu' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ) );
add_action( 'wp_ajax_wp_ai_workflows_activate_license', array( $this, 'ajax_activate_license' ) );
add_action( 'wp_ajax_wp_ai_workflows_deactivate_license', array( $this, 'ajax_deactivate_license' ) );
add_action( 'wp_ajax_wp_ai_workflows_update_menu_count', array( $this, 'ajax_update_menu_count' ) );
add_action( 'admin_notices', array( $this, 'maybe_render_legacy_welcome_notice' ) );
add_action( 'admin_notices', array( $this, 'maybe_render_welcome_notice' ) );
add_action( 'admin_notices', array( $this, 'maybe_render_analytics_notice' ) );
add_action( 'admin_init', array( $this, 'handle_welcome_notice_dismiss' ) );
add_action( 'admin_post_wp_ai_workflows_analytics_choice', array( $this, 'handle_analytics_notice_choice' ) );
}
/**
* First-run notice pointing a freshly activated site at the plugin.
*
* @return void
*/
public function maybe_render_welcome_notice() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
if ( ! get_option( self::OPTION_WELCOME_NOTICE, 0 ) ) {
return;
}
$open_url = add_query_arg(
array(
'page' => 'wp-ai-workflows',
self::DISMISS_WELCOME_ACTION => 1,
'_wpnonce' => wp_create_nonce( self::DISMISS_WELCOME_ACTION ),
),
admin_url( 'admin.php' )
);
$dismiss_url = wp_nonce_url(
add_query_arg( self::DISMISS_WELCOME_ACTION, 1 ),
self::DISMISS_WELCOME_ACTION
);
printf(
'
',
esc_html__( 'AI Workflow Automation is ready. Build your first automation, or claim 50 free cloud credits.', 'ai-workflow-automation-lite' ),
esc_url( $open_url ),
esc_html__( 'Open AI Workflows', 'ai-workflow-automation-lite' ),
esc_url( $dismiss_url ),
esc_html__( 'Dismiss', 'ai-workflow-automation-lite' )
);
}
/**
* Persist the welcome-notice dismissal.
*
* @return void
*/
public function handle_welcome_notice_dismiss() {
if ( ! isset( $_GET[ self::DISMISS_WELCOME_ACTION ] ) ) {
return;
}
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
$nonce = isset( $_GET['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) : '';
if ( ! wp_verify_nonce( $nonce, self::DISMISS_WELCOME_ACTION ) ) {
return;
}
update_option( self::OPTION_WELCOME_NOTICE, 0, false );
}
/**
* One-time, dismissible opt-in notice for anonymous usage analytics. Shown
* only to a site that already finished onboarding (new users get the
* checkbox in onboarding instead).
*
* @return void
*/
public function maybe_render_analytics_notice() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
$screen = get_current_screen();
if ( null === $screen || ! in_array( $screen->id, array( 'toplevel_page_wp-ai-workflows', 'toplevel_page_wp-ai-workflows-tasks' ), true ) ) {
return;
}
if ( get_option( self::OPTION_ANALYTICS_NOTICE_DISMISSED, false ) ) {
return;
}
if ( get_option( 'wp_ai_workflows_analytics_opt_in', false ) ) {
return;
}
if ( ! get_option( 'wp_ai_workflows_setup_completed' ) ) {
return;
}
?>
403 ) );
}
check_admin_referer( self::ANALYTICS_NOTICE_ACTION );
update_option( self::OPTION_ANALYTICS_NOTICE_DISMISSED, 1, false );
$dismissed = isset( $_POST['dismiss'] ) ? sanitize_text_field( wp_unslash( $_POST['dismiss'] ) ) : '';
$opted_in = isset( $_POST['analytics_opt_in'] ) ? sanitize_text_field( wp_unslash( $_POST['analytics_opt_in'] ) ) : '';
if ( '' === $dismissed && '' !== $opted_in ) {
update_option( 'wp_ai_workflows_analytics_opt_in', true );
}
$referer = wp_get_referer();
wp_safe_redirect( $referer ? $referer : admin_url( 'admin.php?page=wp-ai-workflows' ) );
exit;
}
/**
* One-time "welcome back" notice for a 1.x customer whose license was
* auto-redeemed during the v2.0 migration. Shown once; stays silent on
* any non-grant outcome so an automatic attempt never surfaces an error.
*
* @return void
*/
public function maybe_render_legacy_welcome_notice() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
if ( get_option( WP_AI_Workflows_Platform_Client::OPTION_LEGACY_NOTICE, '' ) ) {
return; // Already shown once.
}
$record = WP_AI_Workflows_Platform_Client::get_legacy_redeem_result();
if ( ! is_array( $record ) || ! isset( $record['state'] ) || 'done' !== $record['state'] ) {
return; // Only a genuine grant gets a celebratory notice.
}
$mechanism = isset( $record['mechanism'] ) ? (string) $record['mechanism'] : '';
if ( 'monthly' === $mechanism ) {
$monthly = isset( $record['monthlyCredits'] ) ? (int) $record['monthlyCredits'] : 0;
$tier = isset( $record['tier'] ) ? (string) $record['tier'] : '';
$tier_label = ( 'business' === $tier ) ? 'Business' : 'Pro';
$ends_raw = isset( $record['endsAt'] ) ? (string) $record['endsAt'] : '';
$ends_ts = '' !== $ends_raw ? strtotime( $ends_raw ) : false;
$ends_fmt = $ends_ts ? date_i18n( get_option( 'date_format' ), $ends_ts ) : '';
$message = sprintf(
/* translators: 1: monthly credit amount, 2: plan tier (Pro/Business), 3: optional " until ". */
'Welcome back! Your existing license gives you %1$s credits/month (plus %2$s features)%3$s.',
number_format_i18n( $monthly ),
$tier_label,
'' !== $ends_fmt ? ' until ' . $ends_fmt : ''
);
} else {
$granted = isset( $record['granted'] ) ? (int) $record['granted'] : 0;
$message = sprintf(
/* translators: %s: goodwill credit gift amount. */
'Welcome back. Your license expired, so we\'ve added a %s-credit gift to get you started.',
number_format_i18n( $granted > 0 ? $granted : 500 )
);
}
printf(
'',
esc_html( $message )
);
// One-time: never show this notice again.
update_option( WP_AI_Workflows_Platform_Client::OPTION_LEGACY_NOTICE, time(), false );
}
public function add_admin_menu() {
$capability = 'manage_options';
WP_AI_Workflows_Utilities::debug_log(
'Adding admin menu page',
'debug',
array(
'capability' => $capability,
'user_has_capability' => current_user_can( $capability ),
)
);
$icon_url = WP_AI_WORKFLOWS_PLUGIN_URL . 'images/AWAIcon.png';
if ( current_user_can( 'manage_options' ) ) {
add_menu_page(
'AI Workflows',
'AI Workflows',
'manage_options',
'wp-ai-workflows',
array( $this, 'render_app' ),
$icon_url,
30
);
}
if ( current_user_can( 'manage_workflow_tasks' ) ) {
$pending_tasks_count = $this->get_pending_tasks_count();
$menu_title = 'Tasks';
if ( $pending_tasks_count > 0 ) {
$menu_title .= " " .
number_format_i18n( $pending_tasks_count ) . '';
}
add_menu_page(
'My Tasks',
$menu_title,
'manage_workflow_tasks',
'wp-ai-workflows-tasks',
array( $this, 'render_tasks_page' ),
'dashicons-clipboard',
31
);
}
}
public function render_tasks_page() {
echo '';
echo '';
echo '© ' . esc_html( gmdate( 'Y' ) ) . ' AI Workflow Automation. All rights reserved.';
echo '
';
}
public function enqueue_admin_scripts( $hook ) {
if ( ! in_array( $hook, array( 'toplevel_page_wp-ai-workflows', 'toplevel_page_wp-ai-workflows-tasks' ) ) ) {
return;
}
// Enqueue WordPress media library for custom logo upload in ChatNode
wp_enqueue_media();
$js_files = glob( WP_AI_WORKFLOWS_PLUGIN_DIR . 'build/static/js/main.*.js' );
$css_files = glob( WP_AI_WORKFLOWS_PLUGIN_DIR . 'build/static/css/main.*.css' );
if ( ! empty( $js_files ) ) {
wp_enqueue_script(
'wp-ai-workflows-app',
WP_AI_WORKFLOWS_PLUGIN_URL . 'build/static/js/' . basename( $js_files[0] ),
array(),
WP_AI_WORKFLOWS_PRO_VERSION,
true
);
$settings = $this->get_localized_settings(
array(
'page' => $hook === 'toplevel_page_wp-ai-workflows-tasks' ? 'tasks' : 'management',
)
);
wp_localize_script( 'wp-ai-workflows-app', 'wpAiWorkflowsSettings', $settings );
$whitelabel_settings = get_option( 'wp_ai_workflows_whitelabel_settings', array() );
wp_localize_script( 'wp-ai-workflows-app', 'wpAiWorkflowsWhitelabel', $whitelabel_settings );
}
if ( ! empty( $css_files ) ) {
wp_enqueue_style(
'wp-ai-workflows-app',
WP_AI_WORKFLOWS_PLUGIN_URL . 'build/static/css/' . basename( $css_files[0] ),
array(),
WP_AI_WORKFLOWS_PRO_VERSION
);
}
if ( $hook === 'toplevel_page_wp-ai-workflows-tasks' ) {
wp_enqueue_script(
'wp-ai-workflows-menu-update',
WP_AI_WORKFLOWS_PLUGIN_URL . 'assets/js/menu-update.js',
array( 'jquery' ),
WP_AI_WORKFLOWS_PRO_VERSION,
true
);
wp_localize_script(
'wp-ai-workflows-menu-update',
'wpAiWorkflowsMenu',
array(
'ajaxurl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'wp_ai_workflows_menu_nonce' ),
)
);
}
}
public function render_app() {
$page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : 'wp-ai-workflows';
$action = isset( $_GET['action'] ) ? sanitize_text_field( wp_unslash( $_GET['action'] ) ) : 'management';
WP_AI_Workflows_Utilities::debug_log(
'Attempting to render admin app',
'debug',
array(
'page' => $page,
'action' => $action,
'user_id' => get_current_user_id(),
'user_roles' => wp_get_current_user()->roles,
)
);
if ( $page !== 'wp-ai-workflows' ) {
WP_AI_Workflows_Utilities::debug_log(
'Attempted to render admin app on incorrect page',
'warning',
array(
'current_page' => $page,
)
);
return;
}
$current_page = $action;
$workflow_id = isset( $_GET['id'] ) ? sanitize_text_field( wp_unslash( $_GET['id'] ) ) : null;
$workflow_data = null;
if ( ( $current_page === 'edit' || $current_page === 'builder' ) && $workflow_id ) {
try {
$request = new WP_REST_Request( 'GET', '/wp-ai-workflows/v1/workflows/' . $workflow_id );
$request->set_param( 'id', $workflow_id );
$response = rest_do_request( $request );
if ( $response->is_error() ) {
WP_AI_Workflows_Utilities::debug_log(
'Error fetching workflow',
'error',
array(
'workflow_id' => $workflow_id,
'error' => $response->get_error_message(),
)
);
} else {
$workflow_data = $response->get_data();
}
} catch ( Exception $e ) {
WP_AI_Workflows_Utilities::debug_log(
'Exception while fetching workflow',
'error',
array(
'workflow_id' => $workflow_id,
'error' => $e->getMessage(),
)
);
}
}
$settings = $this->get_localized_settings(
array(
'page' => $current_page,
'workflowData' => $workflow_data,
'licenseStatus' => get_option( 'wp_ai_workflows_license_status', 'inactive' ),
)
);
wp_localize_script( 'wp-ai-workflows-app', 'wpAiWorkflowsSettings', $settings );
WP_AI_Workflows_Utilities::debug_log(
'Rendering admin app',
'debug',
array(
'page' => $current_page,
'workflow_id' => $workflow_id,
'has_workflow_data' => ! is_null( $workflow_data ),
)
);
echo '';
echo '';
echo '© ' . esc_html( gmdate( 'Y' ) ) . ' WP AI Workflow Automation. All rights reserved.';
echo '
';
}
/**
* Build the shared `wpAiWorkflowsSettings` object localized to the admin
* app bundle.
*
* Called from both `enqueue_admin_scripts()` and `render_app()`, which each
* call `wp_localize_script()` with the same object name - the later call
* silently overwrites the earlier one, so routing both through this method
* keeps the base keys consistent between them.
*
* @param array $extra Page-specific keys to merge over the shared base.
* @return array
*/
private function get_localized_settings( array $extra = array() ) {
$base = array(
'root' => esc_url_raw( rest_url() ),
'nonce' => wp_create_nonce( 'wp_rest' ),
'adminUrl' => admin_url(),
// Used by the sample cloud workflow's prompt so the first run produces
// something about this site rather than generic filler.
'siteName' => sanitize_text_field( get_bloginfo( 'name' ) ),
'installation_id' => get_option( 'wp_ai_workflows_installation_id', '' ),
// Whether WooCommerce is active - gates the agent's product-search
// capability in the chat node UI (hidden when Woo is absent).
'wooActive' => class_exists( 'WooCommerce' ),
// Whether this site is connected to a platform account - drives the
// chat node's "AI source" default (Credits when connected at creation,
// else BYOK) and its disconnected-Credits warning.
'platformConnected' => class_exists( 'WP_AI_Workflows_Platform_Client' )
&& WP_AI_Workflows_Platform_Client::is_connected(),
// Gates the frontend's diagnostic logger (frontend/src/utils/debug.js).
// FALSE by default; set WP_AI_WORKFLOWS_DEBUG in wp-config.php to enable.
// Kept independent of WP_DEBUG so a debugging dev site doesn't spam the UI.
'debug' => defined( 'WP_AI_WORKFLOWS_DEBUG' ) && WP_AI_WORKFLOWS_DEBUG,
// Per-user new-user onboarding state (welcome walkthrough). Localized
// so can gate the first-run overlay with zero flash
// (no mount-time round-trip). Written via REST POST /onboarding.
'onboarding_completed' => (bool) get_user_meta( get_current_user_id(), 'wpaw_onboarding_completed', true ),
'analytics_opt_in' => (bool) get_option( 'wp_ai_workflows_analytics_opt_in', false ),
);
return array_merge( $base, $extra );
}
public function ajax_activate_license() {
check_ajax_referer( 'wp_ai_workflows_nonce', 'nonce' );
if ( ! isset( $_POST['license_key'] ) ) {
wp_send_json_error( 'License key is missing' );
}
$license_key = sanitize_text_field( wp_unslash( $_POST['license_key'] ) );
$result = $this->license_manager->activate_license( $license_key );
wp_send_json( array( 'success' => $result ) );
}
public function ajax_deactivate_license() {
check_ajax_referer( 'wp_ai_workflows_nonce', 'nonce' );
$result = $this->license_manager->deactivate_license();
wp_send_json( array( 'success' => $result ) );
}
public function ajax_update_menu_count() {
check_ajax_referer( 'wp_ai_workflows_menu_nonce', 'nonce' );
$count = $this->get_pending_tasks_count();
wp_send_json_success( array( 'count' => $count ) );
}
private function get_pending_tasks_count() {
global $wpdb;
$table_name = $wpdb->prefix . 'wp_ai_workflows_human_tasks';
$user_id = get_current_user_id();
$user = get_userdata( $user_id );
$user_roles = $user->roles;
$placeholders = implode( ', ', array_fill( 0, count( $user_roles ), '%s' ) );
$count = (int) $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(*) FROM %i
WHERE (assigned_user_id = %d OR (assigned_role IN ($placeholders) AND assigned_role IS NOT NULL))
AND status = %s",
array_merge( array( $table_name, $user_id ), $user_roles, array( 'pending' ) )
)
);
return $count;
}
}