wizard_service = $wizard_service;
$this->quality_checker = $quality_checker;
$this->prerequisites = $prerequisites;
$this->init_hooks();
}
/**
* Initialize hooks
*
* @return void
*/
private function init_hooks(): void {
add_action( 'admin_post_carticy_ai_checkout_wizard_save_step', array( $this, 'handle_save_step' ) );
add_action( 'admin_post_carticy_ai_checkout_wizard_navigate', array( $this, 'handle_navigation' ) );
add_action( 'admin_post_carticy_ai_checkout_wizard_reset', array( $this, 'handle_reset' ) );
// New test setup handlers.
add_action( 'admin_post_carticy_ai_checkout_wizard_toggle_test_mode', array( $this, 'handle_toggle_test_mode' ) );
add_action( 'admin_post_carticy_ai_checkout_wizard_save_test_webhook', array( $this, 'handle_save_test_webhook' ) );
add_action( 'admin_post_carticy_ai_checkout_wizard_save_security', array( $this, 'handle_save_security' ) );
// AJAX handler for running tests.
add_action( 'wp_ajax_carticy_ai_checkout_run_conformance_tests', array( $this, 'ajax_run_conformance_tests' ) );
// AJAX handler for quick fixes.
add_action( 'wp_ajax_carticy_ai_checkout_enable_robots_filter', array( $this, 'ajax_enable_robots_filter' ) );
// AJAX handler for saving test results.
add_action( 'wp_ajax_carticy_ai_checkout_save_test_results', array( $this, 'ajax_save_test_results' ) );
}
/**
* Render wizard page
*
* @return void
*/
public function render_page(): void {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
$current_step = $this->wizard_service->get_current_step();
$wizard_data = $this->wizard_service->get_wizard_data();
$total_steps = $this->wizard_service->get_total_steps();
$completion = $this->wizard_service->get_completion_percentage();
?>
wizard_service->is_step_completed( $i ) ) {
$step_class .= ' completed';
}
?>
get_step_title( $i ) ); ?>
render_step( $current_step ); ?>
__( 'Business Info', 'carticy-ai-checkout-for-woocommerce' ),
2 => __( 'Product Feed', 'carticy-ai-checkout-for-woocommerce' ),
3 => __( 'Policies', 'carticy-ai-checkout-for-woocommerce' ),
4 => __( 'Data Quality', 'carticy-ai-checkout-for-woocommerce' ),
5 => __( 'Prerequisites', 'carticy-ai-checkout-for-woocommerce' ),
6 => __( 'Test Setup', 'carticy-ai-checkout-for-woocommerce' ),
7 => __( 'Integration', 'carticy-ai-checkout-for-woocommerce' ),
8 => __( 'Run Tests', 'carticy-ai-checkout-for-woocommerce' ),
9 => __( 'Submit', 'carticy-ai-checkout-for-woocommerce' ),
default => '',
};
}
/**
* Render specific step
*
* @param int $step Step number.
* @return void
*/
private function render_step( int $step ): void {
$wizard_data = $this->wizard_service->get_wizard_data();
$template_map = array(
1 => 'step-1-business-info.php',
2 => 'step-2-product-feed.php',
3 => 'step-3-policies.php',
4 => 'step-4-data-quality.php',
5 => 'step-5-prerequisites.php',
6 => 'step-6-test-setup.php',
7 => 'step-7-integration.php',
8 => 'step-8-conformance.php',
9 => 'step-9-submit.php',
);
$template = $template_map[ $step ] ?? '';
if ( empty( $template ) ) {
return;
}
$template_path = dirname( dirname( __DIR__ ) ) . '/templates/admin/wizard/' . $template;
if ( file_exists( $template_path ) ) {
// Pass data to template.
$data = $wizard_data;
$wizard_service = $this->wizard_service;
$quality_checker = $this->quality_checker;
$prerequisites = $this->prerequisites;
include $template_path;
}
}
/**
* Handle step save
*
* @return void
*/
public function handle_save_step(): void {
// Verify nonce.
if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), 'carticy_wizard_step' ) ) {
wp_die( esc_html__( 'Security check failed', 'carticy-ai-checkout-for-woocommerce' ) );
}
// Verify permissions.
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'Unauthorized', 'carticy-ai-checkout-for-woocommerce' ) );
}
$step = isset( $_POST['step'] ) ? (int) $_POST['step'] : 0;
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Array sanitized per-field below.
$step_data = isset( $_POST['step_data'] ) ? (array) wp_unslash( $_POST['step_data'] ) : array();
$errors = array();
// Validate based on step.
switch ( $step ) {
case 1:
$sanitized = array(
'business_name' => sanitize_text_field( $step_data['business_name'] ?? '' ),
'business_entity' => sanitize_text_field( $step_data['business_entity'] ?? '' ),
'contact_name' => sanitize_text_field( $step_data['contact_name'] ?? '' ),
'contact_email' => sanitize_email( $step_data['contact_email'] ?? '' ),
'store_url' => esc_url_raw( $step_data['store_url'] ?? '' ),
);
$validation = $this->wizard_service->validate_business_info( $sanitized );
if ( is_array( $validation ) ) {
$errors = $validation;
} else {
$this->wizard_service->save_step_data( $step, $sanitized );
}
break;
case 2:
// Product feed configuration (saved via AJAX in step template).
$this->wizard_service->save_step_data( $step, array() );
break;
case 3:
$sanitized = array(
'terms_of_service' => esc_url_raw( $step_data['terms_of_service'] ?? '' ),
'privacy_policy' => esc_url_raw( $step_data['privacy_policy'] ?? '' ),
'return_policy' => esc_url_raw( $step_data['return_policy'] ?? '' ),
);
$validation = $this->wizard_service->validate_policies( $sanitized );
if ( is_array( $validation ) ) {
$errors = $validation;
} else {
$this->wizard_service->save_step_data( $step, $sanitized );
}
break;
case 4:
case 5:
case 6:
case 7:
case 8:
case 9:
// These steps are informational, auto-generated, or configuration-based.
$this->wizard_service->save_step_data( $step, array() );
break;
}
if ( ! empty( $errors ) ) {
// Store errors in transient for field-level validation.
set_transient( 'carticy_ai_checkout_wizard_errors_' . get_current_user_id(), $errors, 30 );
// Add aggregated error message using WordPress settings errors system.
$error_list = '- ' . implode( '
- ', array_values( $errors ) ) . '
';
add_settings_error(
'carticy_ai_checkout_wizard',
'validation_failed',
'' . __( 'Please correct the following errors:', 'carticy-ai-checkout-for-woocommerce' ) . '' . $error_list,
'error'
);
// Persist settings errors across redirect.
set_transient( 'settings_errors', get_settings_errors(), 30 );
$redirect = add_query_arg(
array(
'page' => 'carticy-ai-checkout-application-wizard',
'settings-updated' => 'error',
),
admin_url( 'admin.php' )
);
} else {
// Move to next step.
$next_step = min( $step + 1, $this->wizard_service->get_total_steps() );
$this->wizard_service->set_current_step( $next_step );
$redirect = add_query_arg(
array(
'page' => 'carticy-ai-checkout-application-wizard',
'saved' => '1',
),
admin_url( 'admin.php' )
);
}
wp_safe_redirect( $redirect );
exit;
}
/**
* Handle wizard navigation
*
* @return void
*/
public function handle_navigation(): void {
// Verify nonce.
if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), 'carticy_wizard_navigate' ) ) {
wp_die( esc_html__( 'Security check failed', 'carticy-ai-checkout-for-woocommerce' ) );
}
// Verify permissions.
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'Unauthorized', 'carticy-ai-checkout-for-woocommerce' ) );
}
$direction = isset( $_POST['direction'] ) ? sanitize_text_field( wp_unslash( $_POST['direction'] ) ) : '';
$current_step = $this->wizard_service->get_current_step();
if ( 'next' === $direction ) {
$new_step = min( $current_step + 1, $this->wizard_service->get_total_steps() );
} elseif ( 'prev' === $direction ) {
$new_step = max( $current_step - 1, 1 );
// When going backwards, remove current step and all future steps from completed_steps.
$wizard_data = $this->wizard_service->get_wizard_data();
$completed_steps = $wizard_data['completed_steps'] ?? array();
$completed_steps = array_filter( $completed_steps, fn( $step ) => $step < $new_step );
$wizard_data['completed_steps'] = array_values( $completed_steps );
$this->wizard_service->save_wizard_data( $wizard_data );
} else {
$new_step = $current_step;
}
$this->wizard_service->set_current_step( $new_step );
wp_safe_redirect(
add_query_arg(
array( 'page' => 'carticy-ai-checkout-application-wizard' ),
admin_url( 'admin.php' )
)
);
exit;
}
/**
* Handle wizard reset
*
* @return void
*/
public function handle_reset(): void {
// Verify nonce.
if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), 'carticy_wizard_reset' ) ) {
wp_die( esc_html__( 'Security check failed', 'carticy-ai-checkout-for-woocommerce' ) );
}
// Verify permissions.
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'Unauthorized', 'carticy-ai-checkout-for-woocommerce' ) );
}
$this->wizard_service->clear_wizard_data();
wp_safe_redirect(
add_query_arg(
array( 'page' => 'carticy-ai-checkout-application-wizard' ),
admin_url( 'admin.php' )
)
);
exit;
}
/**
* Handle toggle test mode
*
* @return void
*/
public function handle_toggle_test_mode(): void {
// Verify nonce.
if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), 'carticy_wizard_test_setup' ) ) {
wp_die( esc_html__( 'Security check failed', 'carticy-ai-checkout-for-woocommerce' ) );
}
// Verify permissions.
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'Unauthorized', 'carticy-ai-checkout-for-woocommerce' ) );
}
$current_mode = get_option( 'carticy_ai_checkout_test_mode', 'yes' );
$new_mode = ( 'yes' === $current_mode ) ? 'no' : 'yes';
update_option( 'carticy_ai_checkout_test_mode', $new_mode );
wp_safe_redirect(
add_query_arg(
array(
'page' => 'carticy-ai-checkout-application-wizard',
'updated' => '1',
),
admin_url( 'admin.php' )
)
);
exit;
}
/**
* Handle save test webhook URL
*
* @return void
*/
public function handle_save_test_webhook(): void {
// Verify nonce.
if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), 'carticy_wizard_test_setup' ) ) {
wp_die( esc_html__( 'Security check failed', 'carticy-ai-checkout-for-woocommerce' ) );
}
// Verify permissions.
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'Unauthorized', 'carticy-ai-checkout-for-woocommerce' ) );
}
$test_webhook_url = isset( $_POST['test_webhook_url'] ) ? esc_url_raw( wp_unslash( $_POST['test_webhook_url'] ) ) : '';
update_option( 'carticy_ai_checkout_test_webhook_url', $test_webhook_url );
wp_safe_redirect(
add_query_arg(
array(
'page' => 'carticy-ai-checkout-application-wizard',
'updated' => '1',
),
admin_url( 'admin.php' )
)
);
exit;
}
/**
* Handle save security settings
*
* @return void
*/
public function handle_save_security(): void {
// Verify nonce.
if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), 'carticy_wizard_integration' ) ) {
wp_die( esc_html__( 'Security check failed', 'carticy-ai-checkout-for-woocommerce' ) );
}
// Verify permissions.
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'Unauthorized', 'carticy-ai-checkout-for-woocommerce' ) );
}
$enable_ip_allowlist = isset( $_POST['enable_ip_allowlist'] ) && 'yes' === $_POST['enable_ip_allowlist'] ? 'yes' : 'no';
update_option( 'carticy_ai_checkout_enable_ip_allowlist', $enable_ip_allowlist );
wp_safe_redirect(
add_query_arg(
array(
'page' => 'carticy-ai-checkout-application-wizard',
'updated' => '1',
),
admin_url( 'admin.php' )
)
);
exit;
}
/**
* AJAX handler for running conformance tests
*
* @return void
*/
public function ajax_run_conformance_tests(): void {
// Verify nonce.
check_ajax_referer( 'carticy_wizard_tests', 'nonce' );
// Verify permissions.
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error(
array( 'message' => __( 'Unauthorized', 'carticy-ai-checkout-for-woocommerce' ) ),
403
);
}
// Get conformance test service.
$init = \Carticy\AiCheckout\Init::get_instance();
if ( ! $init ) {
wp_send_json_error( array( 'message' => 'Plugin not initialized' ), 500 );
}
$conformance_service = $init->get_service( 'conformance_test' );
if ( ! $conformance_service ) {
wp_send_json_error( array( 'message' => 'Conformance test service not available' ), 500 );
}
// Check if running single test or getting test list.
$action = isset( $_POST['test_action'] ) ? sanitize_text_field( wp_unslash( $_POST['test_action'] ) ) : 'list';
// Handle different actions.
switch ( $action ) {
case 'list':
// Return list of all tests.
wp_send_json_success(
array(
'tests' => $conformance_service->get_test_list(),
)
);
break;
case 'run':
// Run single test.
$test_id = isset( $_POST['test_id'] ) ? sanitize_text_field( wp_unslash( $_POST['test_id'] ) ) : '';
if ( empty( $test_id ) ) {
wp_send_json_error( array( 'message' => 'Test ID required' ), 400 );
}
try {
$result = $conformance_service->run_single_test( $test_id );
if ( ! $result['success'] ) {
wp_send_json_error(
array(
'message' => $result['error'] ?? 'Test failed',
'test_id' => $test_id,
),
500
);
}
wp_send_json_success(
array(
'test_id' => $test_id,
'result' => $result['result'],
)
);
} catch ( \Exception $e ) {
wp_send_json_error(
array(
'message' => $e->getMessage(),
'test_id' => $test_id,
),
500
);
}
break;
case 'cleanup':
// Cleanup test artifacts.
try {
$deleted = $conformance_service->cleanup_test_artifacts();
wp_send_json_success(
array(
'message' => sprintf( 'Cleaned up %d test products', $deleted ),
'deleted' => $deleted,
)
);
} catch ( \Exception $e ) {
wp_send_json_error( array( 'message' => $e->getMessage() ), 500 );
}
break;
default:
wp_send_json_error( array( 'message' => 'Invalid action' ), 400 );
}
}
/**
* AJAX handler for enabling robots.txt filter
*
* @return void
*/
public function ajax_enable_robots_filter(): void {
// Verify nonce.
check_ajax_referer( 'carticy_wizard_tests', 'nonce' );
// Verify permissions.
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error(
array( 'message' => __( 'Unauthorized', 'carticy-ai-checkout-for-woocommerce' ) ),
403
);
}
// Enable the robots.txt filter.
update_option( 'carticy_ai_checkout_enable_openai_robots', 'yes' );
wp_send_json_success(
array(
'message' => __( 'Robots.txt filter enabled successfully', 'carticy-ai-checkout-for-woocommerce' ),
)
);
}
/**
* AJAX handler for saving test results
*
* @return void
*/
public function ajax_save_test_results(): void {
// Verify nonce.
check_ajax_referer( 'carticy_wizard_tests', 'nonce' );
// Verify permissions.
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error(
array( 'message' => __( 'Unauthorized', 'carticy-ai-checkout-for-woocommerce' ) ),
403
);
}
// Get results from POST data.
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- JSON string decoded and validated below.
$results_json = isset( $_POST['results'] ) ? sanitize_text_field( wp_unslash( $_POST['results'] ) ) : '';
$results = json_decode( $results_json, true );
if ( ! $results || ! is_array( $results ) ) {
wp_send_json_error( array( 'message' => __( 'Invalid results data', 'carticy-ai-checkout-for-woocommerce' ) ), 400 );
}
// Validate required structure.
if ( ! isset( $results['summary'] ) || ! is_array( $results['summary'] ) ) {
wp_send_json_error( array( 'message' => __( 'Invalid results structure: missing summary', 'carticy-ai-checkout-for-woocommerce' ) ), 400 );
}
if ( ! isset( $results['tests'] ) || ! is_array( $results['tests'] ) ) {
wp_send_json_error( array( 'message' => __( 'Invalid results structure: missing tests', 'carticy-ai-checkout-for-woocommerce' ) ), 400 );
}
// Save to transient (1 hour expiry).
set_transient( 'carticy_ai_checkout_wizard_test_results', $results, HOUR_IN_SECONDS );
wp_send_json_success(
array(
'message' => __( 'Test results saved successfully', 'carticy-ai-checkout-for-woocommerce' ),
)
);
}
}