prerequisites = $prerequisites; $this->init_hooks(); } /** * Initialize WordPress hooks * * @return void */ private function init_hooks(): void { add_action( 'admin_menu', array( $this, 'add_menu_pages' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) ); add_action( 'admin_init', array( $this, 'register_settings' ) ); add_action( 'admin_post_carticy_ai_checkout_refresh_ip_ranges', array( $this, 'handle_ip_refresh' ) ); // Hide third-party admin notices on plugin pages. add_action( 'in_admin_header', array( $this, 'hide_unrelated_notices' ), 99 ); // AJAX handlers for settings page. add_action( 'wp_ajax_carticy_ai_checkout_regenerate_api_key', array( $this, 'ajax_regenerate_api_key' ) ); add_action( 'wp_ajax_carticy_ai_checkout_regenerate_webhook_secret', array( $this, 'ajax_regenerate_webhook_secret' ) ); add_action( 'wp_ajax_carticy_ai_checkout_test_webhook', array( $this, 'ajax_test_webhook' ) ); // Add ChatGPT order indicator to WooCommerce orders list (support both HPOS and legacy CPT). // HPOS (High-Performance Order Storage) - WC 8.2+. add_filter( 'manage_woocommerce_page_wc-orders_columns', array( $this, 'add_chatgpt_order_column' ) ); add_action( 'manage_woocommerce_page_wc-orders_custom_column', array( $this, 'render_chatgpt_order_column' ), 10, 2 ); // Legacy CPT-based orders - TODO: Remove when WooCommerce drops backward compatibility for CPT orders. add_filter( 'manage_edit-shop_order_columns', array( $this, 'add_chatgpt_order_column' ) ); add_action( 'manage_shop_order_posts_custom_column', array( $this, 'render_chatgpt_order_column_legacy' ), 10, 2 ); } /** * Hide third-party admin notices on plugin pages * * Removes all WordPress core and third-party plugin notices on Carticy admin pages * to maintain clean, branded UI. Only affects plugin pages, not other WordPress areas. * * Uses the in_admin_header hook with priority 99 to ensure it runs after most plugins * have registered their notices but before they are rendered. * * @return void */ public function hide_unrelated_notices(): void { // Check if we're on a plugin page using the page parameter. // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( ! isset( $_GET['page'] ) ) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended $page = sanitize_text_field( wp_unslash( $_GET['page'] ) ); // Only hide notices on Carticy plugin pages. if ( ! str_starts_with( $page, 'carticy-ai-checkout-for-woocommerce' ) ) { return; } // Remove all third-party admin notice hooks. // This suppresses WordPress core, WooCommerce, Stripe, and all other plugin notices. remove_all_actions( 'admin_notices' ); remove_all_actions( 'all_admin_notices' ); // Note: Plugin's own notices using add_settings_error() will still display // via the settings_errors() WordPress core function, which is separate from admin_notices. } /** * Add admin menu pages * * @return void */ public function add_menu_pages(): void { add_menu_page( __( 'Carticy AI Checkout', 'carticy-ai-checkout-for-woocommerce' ), __( 'AI Checkout', 'carticy-ai-checkout-for-woocommerce' ), 'manage_options', 'carticy-ai-checkout-for-woocommerce', array( $this, 'render_settings_page' ), 'dashicons-cart', 58 ); // Add Product Feed Manager submenu. add_submenu_page( 'carticy-ai-checkout-for-woocommerce', __( 'Product Feed Manager', 'carticy-ai-checkout-for-woocommerce' ), __( 'Product Feed Manager', 'carticy-ai-checkout-for-woocommerce' ), 'manage_options', 'carticy-ai-checkout-product-feed', array( $this, 'render_product_feed_page' ) ); // Add Application Wizard submenu. add_submenu_page( 'carticy-ai-checkout-for-woocommerce', __( 'Application Wizard', 'carticy-ai-checkout-for-woocommerce' ), __( 'Application Wizard', 'carticy-ai-checkout-for-woocommerce' ), 'manage_options', 'carticy-ai-checkout-application-wizard', array( $this, 'render_application_wizard_page' ) ); // Add Analytics submenu. add_submenu_page( 'carticy-ai-checkout-for-woocommerce', __( 'Analytics', 'carticy-ai-checkout-for-woocommerce' ), __( 'Analytics', 'carticy-ai-checkout-for-woocommerce' ), 'manage_options', 'carticy-ai-checkout-analytics', array( $this, 'render_analytics_page' ) ); // Add Logs & Monitoring submenu. add_submenu_page( 'carticy-ai-checkout-for-woocommerce', __( 'Logs & Monitoring', 'carticy-ai-checkout-for-woocommerce' ), __( 'Logs & Monitoring', 'carticy-ai-checkout-for-woocommerce' ), 'manage_options', 'carticy-ai-checkout-logs', array( $this, 'render_logs_page' ) ); } /** * Enqueue admin assets * * @param string $hook Current admin page hook. * @return void */ public function enqueue_assets( string $hook ): void { // Only load on plugin pages. if ( ! str_contains( $hook, 'carticy' ) ) { return; } // Enqueue unified layout styles (Carticy branding + design system) from Composer package. wp_enqueue_style( 'carticy-design-system', plugin_dir_url( dirname( __DIR__ ) ) . 'vendor/carticy/design-system/dist/design-system.min.css', array(), CARTICY_AI_CHECKOUT_VERSION ); wp_enqueue_style( 'carticy-ai-checkout-layout', plugin_dir_url( dirname( __DIR__ ) ) . 'vendor/carticy/admin-layout/dist/admin-layout.min.css', array( 'carticy-design-system' ), CARTICY_AI_CHECKOUT_VERSION ); // Enqueue admin styles. wp_enqueue_style( 'carticy-ai-checkout-admin', plugin_dir_url( dirname( __DIR__ ) ) . 'assets/css/dist/admin.min.css', array( 'dashicons', 'carticy-ai-checkout-layout' ), CARTICY_AI_CHECKOUT_VERSION ); // Enqueue admin JavaScript (for tabs, etc.) from Composer package. wp_enqueue_script( 'carticy-ai-checkout-admin', plugin_dir_url( dirname( __DIR__ ) ) . 'vendor/carticy/admin-components/dist/admin-components.min.js', array( 'jquery' ), CARTICY_AI_CHECKOUT_VERSION, true ); // Enqueue settings page JavaScript on main settings page. if ( str_contains( $hook, 'carticy-ai-checkout-for-woocommerce' ) && ! str_contains( $hook, 'logs' ) && ! str_contains( $hook, 'wizard' ) && ! str_contains( $hook, 'product-feed' ) && ! str_contains( $hook, 'analytics' ) ) { wp_enqueue_script( 'carticy-ai-checkout-settings', plugin_dir_url( dirname( __DIR__ ) ) . 'assets/js/dist/admin-settings.min.js', array( 'jquery' ), CARTICY_AI_CHECKOUT_VERSION, true ); wp_localize_script( 'carticy-ai-checkout-settings', 'carticySettings', array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'carticy_settings_nonce' ), 'i18n' => array( 'copied' => __( 'Copied!', 'carticy-ai-checkout-for-woocommerce' ), 'copyFailed' => __( 'Copy failed', 'carticy-ai-checkout-for-woocommerce' ), 'confirmRegenerate' => __( 'Are you sure you want to regenerate? This will invalidate the current key.', 'carticy-ai-checkout-for-woocommerce' ), 'regenerating' => __( 'Regenerating...', 'carticy-ai-checkout-for-woocommerce' ), 'testingWebhook' => __( 'Testing webhook...', 'carticy-ai-checkout-for-woocommerce' ), 'webhookSuccess' => __( 'Webhook test successful!', 'carticy-ai-checkout-for-woocommerce' ), 'webhookFailed' => __( 'Webhook test failed. Check the logs for details.', 'carticy-ai-checkout-for-woocommerce' ), ), ) ); } // Enqueue product feed manager styles on product feed page. if ( str_contains( $hook, 'carticy-ai-checkout-product-feed' ) ) { wp_enqueue_style( 'carticy-ai-checkout-product-manager', plugin_dir_url( dirname( __DIR__ ) ) . 'assets/css/dist/admin-product-manager.min.css', array( 'carticy-ai-checkout-admin' ), CARTICY_AI_CHECKOUT_VERSION ); wp_enqueue_script( 'carticy-ai-checkout-product-manager', plugin_dir_url( dirname( __DIR__ ) ) . 'assets/js/dist/admin-product-manager.min.js', array( 'jquery' ), CARTICY_AI_CHECKOUT_VERSION, true ); } // Enqueue wizard styles and scripts on application wizard page. if ( str_contains( $hook, 'carticy-ai-checkout-application-wizard' ) ) { wp_enqueue_style( 'carticy-ai-checkout-wizard', plugin_dir_url( dirname( __DIR__ ) ) . 'assets/css/dist/admin-wizard.min.css', array( 'carticy-ai-checkout-admin' ), CARTICY_AI_CHECKOUT_VERSION ); // Enqueue inline wizard styles (prerequisites, test setup, conformance). wp_enqueue_style( 'carticy-ai-checkout-wizard-inline', plugin_dir_url( dirname( __DIR__ ) ) . 'assets/css/admin-wizard-inline.css', array( 'carticy-ai-checkout-wizard' ), CARTICY_AI_CHECKOUT_VERSION ); // Enqueue wizard navigation script. wp_enqueue_script( 'carticy-ai-checkout-wizard-navigation', plugin_dir_url( dirname( __DIR__ ) ) . 'assets/js/admin-wizard-navigation.js', array(), CARTICY_AI_CHECKOUT_VERSION, true ); wp_localize_script( 'carticy-ai-checkout-wizard-navigation', 'carticyWizardNav', array( 'adminPostUrl' => admin_url( 'admin-post.php' ), 'nonce' => wp_create_nonce( 'carticy_wizard_navigate' ), ) ); // Enqueue conformance test runner script. wp_enqueue_script( 'carticy-ai-checkout-wizard-conformance', plugin_dir_url( dirname( __DIR__ ) ) . 'assets/js/admin-wizard-conformance.js', array( 'jquery', 'carticy-ai-checkout-admin' ), CARTICY_AI_CHECKOUT_VERSION, true ); wp_localize_script( 'carticy-ai-checkout-wizard-conformance', 'carticyConformance', array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'carticy_wizard_tests' ), 'i18n' => array( 'runningTests' => __( 'Running Tests...', 'carticy-ai-checkout-for-woocommerce' ), 'runningTestsIndividually' => __( 'Running tests individually... (Test 1 of 17)', 'carticy-ai-checkout-for-woocommerce' ), 'runningTest' => __( 'Running test', 'carticy-ai-checkout-for-woocommerce' ), 'of' => __( 'of', 'carticy-ai-checkout-for-woocommerce' ), 'testTimeout' => __( 'Test timed out after 60 seconds', 'carticy-ai-checkout-for-woocommerce' ), 'viewDetails' => __( 'View Details', 'carticy-ai-checkout-for-woocommerce' ), 'enableFilter' => __( 'Enable Filter', 'carticy-ai-checkout-for-woocommerce' ), 'allTestsCompleted' => __( 'All tests completed!', 'carticy-ai-checkout-for-woocommerce' ), 'runAllTestsAgain' => __( 'Run All Tests Again', 'carticy-ai-checkout-for-woocommerce' ), 'testName' => __( 'Test Name:', 'carticy-ai-checkout-for-woocommerce' ), 'errorMessage' => __( 'Error Message:', 'carticy-ai-checkout-for-woocommerce' ), 'requestData' => __( 'Request Data:', 'carticy-ai-checkout-for-woocommerce' ), 'responseData' => __( 'Response Data:', 'carticy-ai-checkout-for-woocommerce' ), 'testDetails' => __( 'Test Details', 'carticy-ai-checkout-for-woocommerce' ), 'fixing' => __( 'Fixing...', 'carticy-ai-checkout-for-woocommerce' ), 'fixed' => __( '✓ Fixed', 'carticy-ai-checkout-for-woocommerce' ), 'robotsFilterEnabled' => __( 'Robots.txt filter enabled. Re-run tests to verify.', 'carticy-ai-checkout-for-woocommerce' ), 'failedToEnableFilter' => __( 'Failed to enable filter. Please try manually.', 'carticy-ai-checkout-for-woocommerce' ), 'errorOccurred' => __( 'An error occurred. Please try again.', 'carticy-ai-checkout-for-woocommerce' ), ), ) ); // Enqueue integration scripts (API key regeneration, JSON copy/download). wp_enqueue_script( 'carticy-ai-checkout-wizard-integration', plugin_dir_url( dirname( __DIR__ ) ) . 'assets/js/admin-wizard-integration.js', array( 'jquery' ), CARTICY_AI_CHECKOUT_VERSION, true ); wp_localize_script( 'carticy-ai-checkout-wizard-integration', 'carticyIntegration', array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'carticy_settings_nonce' ), 'downloadDate' => gmdate( 'Y-m-d' ), 'i18n' => array( 'copied' => __( 'Copied!', 'carticy-ai-checkout-for-woocommerce' ), 'confirmRegenerateApiKey' => __( 'Are you sure you want to regenerate the API key? This will invalidate the current key.', 'carticy-ai-checkout-for-woocommerce' ), 'confirmRegenerateWebhookSecret' => __( 'Are you sure you want to regenerate the webhook secret?', 'carticy-ai-checkout-for-woocommerce' ), 'failedToRegenerate' => __( 'Failed to regenerate. Please try again.', 'carticy-ai-checkout-for-woocommerce' ), 'failedToCopy' => __( 'Failed to copy to clipboard. Please copy manually.', 'carticy-ai-checkout-for-woocommerce' ), 'errorOccurred' => __( 'An error occurred. Please try again.', 'carticy-ai-checkout-for-woocommerce' ), ), ) ); } // Enqueue analytics dashboard styles on analytics page. if ( str_contains( $hook, 'carticy-ai-checkout-analytics' ) ) { wp_enqueue_style( 'carticy-ai-checkout-analytics', plugin_dir_url( dirname( __DIR__ ) ) . 'assets/css/dist/analytics-dashboard.min.css', array( 'carticy-ai-checkout-admin' ), CARTICY_AI_CHECKOUT_VERSION ); } // Enqueue logs viewer styles and scripts on logs page. if ( str_contains( $hook, 'carticy-ai-checkout-logs' ) ) { wp_enqueue_style( 'carticy-ai-checkout-logs', plugin_dir_url( dirname( __DIR__ ) ) . 'assets/css/dist/logs-viewer.min.css', array( 'carticy-ai-checkout-admin' ), CARTICY_AI_CHECKOUT_VERSION ); // Enqueue logs viewer script. wp_enqueue_script( 'carticy-ai-checkout-logs-viewer', plugin_dir_url( dirname( __DIR__ ) ) . 'assets/js/admin-logs-viewer.js', array( 'jquery', 'carticy-ai-checkout-admin' ), CARTICY_AI_CHECKOUT_VERSION, true ); wp_localize_script( 'carticy-ai-checkout-logs-viewer', 'carticyLogsViewer', array( 'i18n' => array( 'request' => __( 'Request:', 'carticy-ai-checkout-for-woocommerce' ), 'response' => __( 'Response:', 'carticy-ai-checkout-for-woocommerce' ), 'apiContext' => __( 'API Context', 'carticy-ai-checkout-for-woocommerce' ), 'errorMessage' => __( 'Error Message:', 'carticy-ai-checkout-for-woocommerce' ), 'contextDetails' => __( 'Context Details:', 'carticy-ai-checkout-for-woocommerce' ), 'errorDetails' => __( 'Error Details', 'carticy-ai-checkout-for-woocommerce' ), ), ) ); } } /** * Register plugin settings * * @return void */ public function register_settings(): void { register_setting( 'carticy_ai_checkout_settings', 'carticy_ai_checkout_test_mode', array( 'type' => 'string', 'sanitize_callback' => array( $this, 'sanitize_checkbox' ), 'default' => 'yes', ) ); register_setting( 'carticy_ai_checkout_settings', 'carticy_ai_checkout_delete_data_on_uninstall', array( 'type' => 'string', 'sanitize_callback' => array( $this, 'sanitize_checkbox' ), 'default' => 'no', ) ); register_setting( 'carticy_ai_checkout_settings', 'carticy_ai_checkout_webhook_url', array( 'type' => 'string', 'sanitize_callback' => 'esc_url_raw', 'default' => '', ) ); register_setting( 'carticy_ai_checkout_settings', 'carticy_ai_checkout_enable_ip_allowlist', array( 'type' => 'string', 'sanitize_callback' => array( $this, 'sanitize_checkbox' ), 'default' => 'no', ) ); } /** * Sanitize checkbox value * * @param mixed $value Input value. * @return string 'yes' or 'no'. */ public function sanitize_checkbox( mixed $value ): string { return ( 'yes' === $value || '1' === $value || 1 === $value ) ? 'yes' : 'no'; } /** * Render admin page with unified Carticy layout * * Wraps page content with branded header and footer for consistency across all admin pages. * * @param string $page_title Page title for header (used by template). * @param callable $content Content callback function (used by template). * @param array $content_data Data to pass to content callback (used by template). * @param array $header_data Additional header data (used by template). * @return void * * @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed -- Parameters used by included template. */ private function render_with_layout( string $page_title, callable $content, array $content_data = array(), array $header_data = array() ): void { // Check if test mode is enabled and set badge variables for header template. $show_test_badge = ( 'yes' === get_option( 'carticy_ai_checkout_test_mode', 'yes' ) ); $test_badge_text = __( 'Test Mode', 'carticy-ai-checkout-for-woocommerce' ); require plugin_dir_path( dirname( __DIR__ ) ) . 'vendor/carticy/admin-layout/templates/layout-wrapper.php'; } /** * Render settings page * * @return void */ public function render_settings_page(): void { if ( ! current_user_can( 'manage_options' ) ) { return; } // Handle IP refresh messages (must be before render_with_layout for settings_errors to work). // Verify nonce for admin message display (set in handle_ip_refresh redirect). if ( isset( $_GET['message'] ) && isset( $_GET['_wpnonce'] ) ) { if ( ! wp_verify_nonce( sanitize_key( wp_unslash( $_GET['_wpnonce'] ) ), 'carticy_admin_message' ) ) { // Invalid nonce - skip message display for security. $message = ''; } else { $message = sanitize_text_field( wp_unslash( $_GET['message'] ) ); } if ( 'ip_refresh_success' === $message ) { $ip_ranges = get_option( 'carticy_ai_checkout_openai_ip_ranges_backup', array() ); $count = count( $ip_ranges ); add_settings_error( 'carticy_ai_checkout_settings', 'ip_refresh_success', '' . esc_html__( 'IP ranges refreshed successfully!', 'carticy-ai-checkout-for-woocommerce' ) . '' . sprintf( /* translators: %d: number of IP ranges loaded */ esc_html__( '%d IP ranges loaded from OpenAI.', 'carticy-ai-checkout-for-woocommerce' ), $count ), 'success' ); } elseif ( 'ip_refresh_failed' === $message ) { add_settings_error( 'carticy_ai_checkout_settings', 'ip_refresh_failed', '' . esc_html__( 'Failed to refresh IP ranges.', 'carticy-ai-checkout-for-woocommerce' ) . '' . esc_html__( 'Unable to fetch fresh ranges from OpenAI. Using cached ranges.', 'carticy-ai-checkout-for-woocommerce' ), 'error' ); } } // Get raw prerequisites data from checker. $prerequisites_raw = $this->prerequisites->check_all(); // Transform prerequisites data to match template format. $prerequisites = array(); $labels = array( 'wordpress_version' => __( 'WordPress Version', 'carticy-ai-checkout-for-woocommerce' ), 'php_version' => __( 'PHP Version', 'carticy-ai-checkout-for-woocommerce' ), 'woocommerce' => __( 'WooCommerce', 'carticy-ai-checkout-for-woocommerce' ), 'stripe_gateway' => __( 'WooCommerce Stripe Gateway', 'carticy-ai-checkout-for-woocommerce' ), 'stripe_keys' => __( 'Stripe API Keys', 'carticy-ai-checkout-for-woocommerce' ), 'ssl' => __( 'SSL Certificate', 'carticy-ai-checkout-for-woocommerce' ), 'permalinks' => __( 'Permalink Structure', 'carticy-ai-checkout-for-woocommerce' ), 'stripe_api' => __( 'Stripe API Connection', 'carticy-ai-checkout-for-woocommerce' ), ); foreach ( $prerequisites_raw as $key => $check ) { $prerequisites[ $key ] = array( 'label' => $labels[ $key ] ?? ucwords( str_replace( '_', ' ', $key ) ), 'status' => $check['passed'], 'message' => $check['message'], 'action_url' => $check['action'] ?? '', 'action_label' => __( 'Fix This', 'carticy-ai-checkout-for-woocommerce' ), ); } $api_key = get_option( 'carticy_ai_checkout_api_key', '' ); $webhook_secret = get_option( 'carticy_ai_checkout_webhook_secret', '' ); $product_feed_url = rest_url( 'carticy-ai-checkout/v1/products' ); // Render with unified layout. $this->render_with_layout( __( 'AI Checkout Settings', 'carticy-ai-checkout-for-woocommerce' ), function ( $data ) { // Extract variables for template. extract( $data ); // phpcs:ignore WordPress.PHP.DontExtract.extract_extract // Include the settings page template. require plugin_dir_path( dirname( __DIR__ ) ) . 'templates/admin/settings-page.php'; }, compact( 'prerequisites', 'api_key', 'webhook_secret', 'product_feed_url' ) ); } /** * Render product feed manager page * * @return void */ public function render_product_feed_page(): void { if ( ! current_user_can( 'manage_options' ) ) { return; } // Get ProductFeedManager from container. $product_feed_manager = \Carticy\AiCheckout\Init::get_instance()->get_service( 'product_feed_manager' ); // Use reflection to handle bulk actions and product toggle. $reflection = new \ReflectionClass( $product_feed_manager ); $handle_bulk_actions_method = $reflection->getMethod( 'handle_bulk_actions' ); $handle_bulk_actions_method->setAccessible( true ); $handle_bulk_actions_method->invoke( $product_feed_manager ); $handle_product_toggle_method = $reflection->getMethod( 'handle_product_toggle' ); $handle_product_toggle_method->setAccessible( true ); $handle_product_toggle_method->invoke( $product_feed_manager ); // Get ProductQualityChecker from ProductFeedManager. $quality_checker_property = $reflection->getProperty( 'quality_checker' ); $quality_checker_property->setAccessible( true ); $quality_checker = $quality_checker_property->getValue( $product_feed_manager ); // Create list table instance. $list_table = new \Carticy\AiCheckout\Admin\ProductsListTable( $quality_checker ); $list_table->prepare_items(); // Get statistics. $get_stats_method = $reflection->getMethod( 'get_feed_statistics' ); $get_stats_method->setAccessible( true ); $stats = $get_stats_method->invoke( $product_feed_manager ); // Render with unified layout. $this->render_with_layout( __( 'Product Feed Manager', 'carticy-ai-checkout-for-woocommerce' ), function ( $template_data ) { // Extract variables for template. extract( $template_data ); // phpcs:ignore WordPress.PHP.DontExtract.extract_extract // Include the product feed manager template. require plugin_dir_path( dirname( __DIR__ ) ) . 'templates/admin/product-feed-manager.php'; }, compact( 'stats', 'list_table' ) ); } /** * Render application wizard page * * @return void */ public function render_application_wizard_page(): void { if ( ! current_user_can( 'manage_options' ) ) { return; } // Get ApplicationWizard from container. $application_wizard = \Carticy\AiCheckout\Init::get_instance()->get_service( 'application_wizard' ); // Use reflection to access private/protected methods. $reflection = new \ReflectionClass( $application_wizard ); // Get wizard service. $wizard_service_property = $reflection->getProperty( 'wizard_service' ); $wizard_service_property->setAccessible( true ); $wizard_service = $wizard_service_property->getValue( $application_wizard ); // Get wizard data. $current_step = $wizard_service->get_current_step(); $wizard_data = $wizard_service->get_wizard_data(); $total_steps = $wizard_service->get_total_steps(); $completion = $wizard_service->get_completion_percentage(); // Create a wrapper object with public methods for template. $wizard_instance = new class( $application_wizard, $reflection, $wizard_service ) { /** * Application wizard instance. * * @var object */ private $wizard; /** * Reflection class instance. * * @var object */ private $reflection; /** * Wizard service instance. * * @var object */ private $wizard_service; /** * Constructor. * * @param object $wizard Application wizard instance. * @param object $reflection Reflection class instance. * @param object $wizard_service Wizard service instance. */ public function __construct( $wizard, $reflection, $wizard_service ) { $this->wizard = $wizard; $this->reflection = $reflection; $this->wizard_service = $wizard_service; } /** * Check if a step is completed. * * @param int $step Step number. * @return bool True if step is completed. */ public function is_step_completed( int $step ): bool { return $this->wizard_service->is_step_completed( $step ); } /** * Get step title via reflection. * * @param int $step Step number. * @return string Step title. */ public function get_step_title_public( int $step ): string { $method = $this->reflection->getMethod( 'get_step_title' ); $method->setAccessible( true ); return $method->invoke( $this->wizard, $step ); } /** * Render step content via reflection. * * @param int $step Step number. */ public function render_step_public( int $step ): void { $method = $this->reflection->getMethod( 'render_step' ); $method->setAccessible( true ); $method->invoke( $this->wizard, $step ); } }; // Render with unified layout. $this->render_with_layout( __( 'OpenAI Application Wizard', 'carticy-ai-checkout-for-woocommerce' ), function ( $template_data ) { // Extract variables for template. extract( $template_data ); // phpcs:ignore WordPress.PHP.DontExtract.extract_extract // Include the application wizard template. require plugin_dir_path( dirname( __DIR__ ) ) . 'templates/admin/application-wizard.php'; }, compact( 'current_step', 'total_steps', 'completion', 'wizard_data', 'wizard_instance' ) ); } /** * Render analytics page * * @return void */ public function render_analytics_page(): void { if ( ! current_user_can( 'manage_options' ) ) { return; } // Get AnalyticsDashboard from container. $analytics_dashboard = \Carticy\AiCheckout\Init::get_instance()->get_service( 'analytics_dashboard' ); // Get analytics data using reflection to access private methods. $reflection = new \ReflectionClass( $analytics_dashboard ); // Get days filter. $days_property = $reflection->getProperty( 'days' ); $days_property->setAccessible( true ); $days = $days_property->getValue( $analytics_dashboard ); // Handle filter request with nonce verification. if ( isset( $_GET['days'] ) && isset( $_GET['_wpnonce'] ) ) { if ( wp_verify_nonce( sanitize_key( wp_unslash( $_GET['_wpnonce'] ) ), 'carticy_analytics_filter' ) ) { $days_filter = absint( wp_unslash( $_GET['days'] ) ); if ( in_array( $days_filter, array( 7, 30, 90 ), true ) ) { $days = $days_filter; } } } // Get analytics service. $analytics_property = $reflection->getProperty( 'analytics' ); $analytics_property->setAccessible( true ); $analytics = $analytics_property->getValue( $analytics_dashboard ); // Get analytics data. $chatgpt_orders = $analytics->get_chatgpt_order_count( $days ); $chatgpt_revenue = $analytics->get_chatgpt_revenue( $days ); $regular_orders = $analytics->get_regular_order_count( $days ); $regular_revenue = $analytics->get_regular_revenue( $days ); $conversion_rate = $analytics->get_conversion_rate( $days ); $chatgpt_aov = $analytics->get_average_order_value( 'chatgpt', $days ); $regular_aov = $analytics->get_average_order_value( 'regular', $days ); $recent_orders = $analytics->get_recent_chatgpt_orders( 10 ); $status_stats = $analytics->get_order_stats_by_status( $days ); // Prepare template data. $data = array( 'days' => $days, 'chatgpt_orders' => $chatgpt_orders, 'chatgpt_revenue' => $chatgpt_revenue, 'regular_orders' => $regular_orders, 'regular_revenue' => $regular_revenue, 'conversion_rate' => $conversion_rate, 'chatgpt_aov' => $chatgpt_aov, 'regular_aov' => $regular_aov, 'recent_orders' => $recent_orders, 'status_stats' => $status_stats, 'total_orders' => $chatgpt_orders + $regular_orders, 'total_revenue' => $chatgpt_revenue + $regular_revenue, ); // Render with unified layout. $this->render_with_layout( __( 'Analytics Dashboard', 'carticy-ai-checkout-for-woocommerce' ), function ( $template_data ) { // Extract variables for template. extract( $template_data ); // phpcs:ignore WordPress.PHP.DontExtract.extract_extract // Include the analytics dashboard template. require plugin_dir_path( dirname( __DIR__ ) ) . 'templates/admin/analytics-dashboard.php'; }, $data ); } /** * Render logs and monitoring page * * @return void */ public function render_logs_page(): void { if ( ! current_user_can( 'manage_options' ) ) { return; } // Get LogsViewer from container. $logs_viewer = \Carticy\AiCheckout\Init::get_instance()->get_service( 'logs_viewer' ); // Get active tab with nonce verification. $active_tab = 'api'; // Default tab. if ( isset( $_GET['tab'] ) && isset( $_GET['_wpnonce'] ) ) { if ( wp_verify_nonce( sanitize_key( wp_unslash( $_GET['_wpnonce'] ) ), 'carticy_logs_tab' ) ) { $active_tab = sanitize_text_field( wp_unslash( $_GET['tab'] ) ); } } // Get tabs configuration. $tabs = $logs_viewer->get_tabs(); // Get tab-specific data. $data = $this->get_logs_tab_data( $logs_viewer, $active_tab ); // Render with unified layout. $this->render_with_layout( __( 'Logs & Monitoring', 'carticy-ai-checkout-for-woocommerce' ), function ( $template_data ) { // Extract variables for template. extract( $template_data ); // phpcs:ignore WordPress.PHP.DontExtract.extract_extract // Include the logs viewer template. require plugin_dir_path( dirname( __DIR__ ) ) . 'templates/admin/logs-viewer.php'; }, compact( 'active_tab', 'tabs', 'data' ) ); } /** * Get logs tab data * * @param \Carticy\AiCheckout\Admin\LogsViewer $logs_viewer LogsViewer instance. * @param string $tab Active tab. * @return array Tab data. */ private function get_logs_tab_data( $logs_viewer, string $tab ): array { // Use reflection to access private method (temporary solution). $reflection = new \ReflectionClass( $logs_viewer ); $method = $reflection->getMethod( 'get_tab_data' ); $method->setAccessible( true ); return $method->invoke( $logs_viewer, $tab ); } /** * Handle manual IP refresh request * * @return void */ public function handle_ip_refresh(): void { // Verify nonce. if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ), 'carticy_refresh_ips' ) ) { 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' ) ); } // Get IP allowlist service from container. $ip_allowlist = \Carticy\AiCheckout\Init::get_instance()->get_service( 'ip_allowlist' ); $refresh_result = $ip_allowlist->refresh_ip_ranges(); // Redirect back with message and preserve Security tab state. // Include nonce for message verification on redirect destination. $message_nonce = wp_create_nonce( 'carticy_admin_message' ); if ( is_wp_error( $refresh_result ) ) { wp_safe_redirect( add_query_arg( array( 'page' => 'carticy-ai-checkout-for-woocommerce', 'message' => 'ip_refresh_failed', 'tab' => 'security', '_wpnonce' => $message_nonce, ), admin_url( 'admin.php' ) ) ); } else { wp_safe_redirect( add_query_arg( array( 'page' => 'carticy-ai-checkout-for-woocommerce', 'message' => 'ip_refresh_success', 'tab' => 'security', '_wpnonce' => $message_nonce, ), admin_url( 'admin.php' ) ) ); } exit; } /** * AJAX handler for regenerating API key * * @return void */ public function ajax_regenerate_api_key(): void { // Verify nonce. check_ajax_referer( 'carticy_settings_nonce', 'nonce' ); // Verify permissions. if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => __( 'Unauthorized', 'carticy-ai-checkout-for-woocommerce' ) ), 403 ); } // Get API key service from container. $api_key_service = \Carticy\AiCheckout\Init::get_instance()->get_service( 'api_key' ); try { // Regenerate the API key. $new_api_key = $api_key_service->regenerate_api_key(); wp_send_json_success( array( 'api_key' => $new_api_key, 'message' => __( 'API key regenerated successfully.', 'carticy-ai-checkout-for-woocommerce' ), ) ); } catch ( \Exception $e ) { wp_send_json_error( array( 'message' => $e->getMessage() ), 500 ); } } /** * AJAX handler for regenerating webhook secret * * @return void */ public function ajax_regenerate_webhook_secret(): void { // Verify nonce. check_ajax_referer( 'carticy_settings_nonce', 'nonce' ); // Verify permissions. if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => __( 'Unauthorized', 'carticy-ai-checkout-for-woocommerce' ) ), 403 ); } // Get API key service from container. $api_key_service = \Carticy\AiCheckout\Init::get_instance()->get_service( 'api_key' ); try { // Regenerate the webhook secret. $new_webhook_secret = $api_key_service->regenerate_webhook_secret(); wp_send_json_success( array( 'webhook_secret' => $new_webhook_secret, 'message' => __( 'Webhook secret regenerated successfully.', 'carticy-ai-checkout-for-woocommerce' ), ) ); } catch ( \Exception $e ) { wp_send_json_error( array( 'message' => $e->getMessage() ), 500 ); } } /** * AJAX handler for testing webhook * * @return void */ public function ajax_test_webhook(): void { // Verify nonce. check_ajax_referer( 'carticy_settings_nonce', 'nonce' ); // Verify permissions. if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => __( 'Unauthorized', 'carticy-ai-checkout-for-woocommerce' ) ), 403 ); } // Get webhook service from container. $webhook_service = \Carticy\AiCheckout\Init::get_instance()->get_service( 'webhook' ); // Create test webhook payload. $test_data = array( 'session_id' => 'test_session_' . time(), 'permalink_url' => home_url( '/test-order' ), 'status' => 'test', 'refunds' => array(), ); try { // Send test webhook. $result = $webhook_service->send( 'order_created', $test_data ); if ( $result ) { wp_send_json_success( array( 'message' => __( 'Test webhook sent successfully! Check your webhook endpoint logs.', 'carticy-ai-checkout-for-woocommerce' ) ) ); } else { wp_send_json_error( array( 'message' => __( 'Failed to send test webhook. Verify your webhook URL and secret are configured.', 'carticy-ai-checkout-for-woocommerce' ) ), 400 ); } } catch ( \Exception $e ) { wp_send_json_error( array( 'message' => $e->getMessage() ), 500 ); } } /** * Add ChatGPT order column to orders list * * @param array $columns Existing columns. * @return array Modified columns. */ public function add_chatgpt_order_column( array $columns ): array { // Insert after 'order_status' column. $new_columns = array(); foreach ( $columns as $key => $value ) { $new_columns[ $key ] = $value; if ( 'order_status' === $key ) { $new_columns['chatgpt_order'] = __( 'Source', 'carticy-ai-checkout-for-woocommerce' ); } } return $new_columns; } /** * Render ChatGPT order column content (HPOS) * * @param string $column Column name. * @param mixed $order Order object or ID. * @return void */ public function render_chatgpt_order_column( string $column, mixed $order ): void { if ( 'chatgpt_order' !== $column ) { return; } // Get order object if not already. if ( is_numeric( $order ) ) { $order = wc_get_order( $order ); } if ( ! $order ) { return; } // Check if this is a ChatGPT order. $is_chatgpt_order = 'yes' === $order->get_meta( '_chatgpt_checkout' ); if ( $is_chatgpt_order ) { echo ''; echo ''; echo '' . esc_html__( 'AI', 'carticy-ai-checkout-for-woocommerce' ) . ''; echo ''; } else { echo '—'; } } /** * Render ChatGPT order column content (Legacy CPT) * * @param string $column Column name. * @param int $order_id Order ID. * @return void */ public function render_chatgpt_order_column_legacy( string $column, int $order_id ): void { $this->render_chatgpt_order_column( $column, $order_id ); } }