steps)) { return $this->steps; } $this->steps = array( 'welcome' => array( 'name' => __('Welcome', 'yatra'), 'view' => array($this, 'setup_welcome'), 'handler' => array($this, 'setup_welcome_save'), ), 'business' => array( 'name' => __('Business', 'yatra'), 'view' => array($this, 'setup_business'), 'handler' => array($this, 'setup_business_save'), ), 'bookings' => array( 'name' => __('Bookings', 'yatra'), 'view' => array($this, 'setup_bookings'), 'handler' => array($this, 'setup_bookings_save'), ), 'communications' => array( 'name' => __('Email & payments', 'yatra'), 'view' => array($this, 'setup_communications'), 'handler' => array($this, 'setup_communications_save'), ), 'currency' => array( 'name' => __('Currency', 'yatra'), 'view' => array($this, 'setup_currency'), 'handler' => array($this, 'setup_currency_save'), ), 'theme' => array( 'name' => __('Theme', 'yatra'), 'view' => array($this, 'setup_theme'), 'handler' => array($this, 'setup_theme_save'), ), 'complete' => array( 'name' => __('Done', 'yatra'), 'view' => array($this, 'setup_complete'), 'handler' => array($this, 'setup_complete_save'), ), ); $this->steps = apply_filters('yatra_setup_wizard_steps', $this->steps, $this); return $this->steps; } /** * Register admin menus */ public function admin_menus() { // Must stay registered in $submenu: WordPress runs user_can_access_admin_page() in menu.php // before admin_init; remove_submenu_page() breaks parent resolution and causes 403 on this URL. // The item is hidden from the sidebar via CSS in AdminServiceProvider. add_submenu_page( 'yatra', __('Yatra Setup Wizard', 'yatra'), __('Setup wizard', 'yatra'), 'manage_options', 'yatra-setup', array($this, 'setup_wizard') ); } /** * Show the setup wizard */ public function setup_wizard() { if (empty($_GET['page']) || 'yatra-setup' !== $_GET['page']) { return; } // Handle skip setup request if (isset($_GET['skip_setup']) && $_GET['skip_setup'] === '1') { $this->skip_setup_wizard(); } $steps = $this->get_steps(); $this->step = isset($_GET['step']) ? sanitize_key($_GET['step']) : current(array_keys($steps)); if (class_exists(StatsUsage::class)) { $usage = StatsUsage::instance(); if ($this->step === 'welcome') { $usage->mark_onboarding_started(); } $usage->set_onboarding_step($this->step); } // Legacy URL: step=general → business if ($this->step === 'general' && isset($steps['business'])) { wp_safe_redirect(esc_url_raw($this->get_step_url('business'))); exit; } // Process form submission BEFORE any output if (!empty($_POST['save_step'])) { $save_step = sanitize_key($_POST['save_step']); if ($save_step === $this->step && isset($steps[$this->step]['handler'])) { call_user_func($steps[$this->step]['handler'], $this); // Handler should redirect and exit, so code below won't execute } } // Enqueue styles and scripts before outputting HTML $this->enqueue_wizard_assets(); $this->setup_wizard_steps(); $this->setup_wizard_content(); exit; } /** * Enqueue wizard assets */ private function enqueue_wizard_assets() { // Enqueue setup wizard styles wp_enqueue_style( 'yatra-setup-wizard', YATRA_PLUGIN_URL . 'assets/admin/css/setup-wizard.css', [], YATRA_VERSION ); // Enqueue setup wizard scripts wp_enqueue_script( 'yatra-setup-wizard', YATRA_PLUGIN_URL . 'assets/admin/js/setup-wizard.js', ['jquery'], YATRA_VERSION, true ); // Localize script wp_localize_script('yatra-setup-wizard', 'yatraSetupWizard', [ 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('yatra_setup_wizard_nonce'), 'skipConfirmMessage' => __('Skip this step? Nothing on this screen will be saved—you can finish later in Yatra Settings.', 'yatra'), 'strings' => [ 'confirm_leave' => __('Are you sure you want to leave the setup wizard?', 'yatra'), 'saving' => __('Saving...', 'yatra'), 'saved' => __('Saved!', 'yatra'), 'error' => __('Error occurred', 'yatra'), ], ]); } /** * Get the URL for the next step */ public function get_next_step_link() { $steps = $this->get_steps(); $keys = array_keys($steps); $current_key = array_search($this->step, $keys); if (false === $current_key) { return ''; } $next_key = $current_key + 1; if (isset($keys[$next_key])) { return $this->get_step_url($keys[$next_key]); } return ''; } /** * Get URL for a specific step */ public function get_step_url($step) { return add_query_arg( array( 'page' => 'yatra-setup', 'step' => $step ), admin_url('admin.php') ); } /** * Setup wizard steps */ public function setup_wizard_steps() { $output_steps = $this->get_steps(); ?> > <?php esc_html_e('Yatra › Setup Wizard', 'yatra'); ?>
    '', 'business' => '', 'bookings' => '', 'communications' => '', 'currency' => '', 'theme' => '', 'complete' => '' ); $ordered_keys = array_keys($output_steps); foreach ($output_steps as $step_key => $step) { $cur = array_search($this->step, $ordered_keys, true); $pos = array_search($step_key, $ordered_keys, true); $is_completed = $cur !== false && $pos !== false && $cur > $pos; $icon = isset($step_icons[$step_key]) ? $step_icons[$step_key] : ''; if ($step_key === $this->step) { ?>
get_steps(); echo '
'; if (isset($steps[$this->step]['view'])) { call_user_func($steps[$this->step]['view'], $this); } echo '
'; echo '
'; // .yatra-setup-wrapper ?> enable(true); } else { StatsUsage::instance()->disable(); } } wp_safe_redirect($this->get_redirect_target((string) $this->get_next_step_link())); exit; } /** * Human-readable progress for task steps (excludes decorative copy on welcome). */ public function get_wizard_progress_label(): string { $steps = $this->get_steps(); $keys = array_keys($steps); $idx = array_search($this->step, $keys, true); if ($idx === false) { return ''; } return sprintf( /* translators: 1: current step number, 2: total steps */ __('Step %1$d of %2$d', 'yatra'), (int) $idx + 1, count($keys) ); } /** * Business profile (company contact only). */ public function setup_business() { include YATRA_ABSPATH . 'templates/setup-wizard/business.php'; } /** * Save business profile. */ public function setup_business_save() { check_admin_referer('yatra-setup'); $settings = [ 'company_name' => isset($_POST['company_name']) ? sanitize_text_field(wp_unslash($_POST['company_name'])) : '', 'company_email' => isset($_POST['company_email']) ? sanitize_email(wp_unslash($_POST['company_email'])) : '', 'company_phone' => isset($_POST['company_phone']) ? sanitize_text_field(wp_unslash($_POST['company_phone'])) : '', ]; foreach ($settings as $key => $value) { update_option('yatra_' . $key, $value); } $extra = apply_filters('yatra_setup_wizard_business_save', [], $_POST); if (is_array($extra)) { foreach ($extra as $optionKey => $value) { if (! is_string($optionKey) || $optionKey === '') { continue; } if (strpos($optionKey, 'yatra_') === 0) { update_option($optionKey, $value); } else { update_option('yatra_' . $optionKey, $value); } } } SettingsService::reload(); wp_safe_redirect($this->get_redirect_target((string) $this->get_next_step_link())); exit; } /** * Booking behaviour (guest checkout, confirmations). */ public function setup_bookings() { include YATRA_ABSPATH . 'templates/setup-wizard/bookings.php'; } /** * Save booking behaviour. */ public function setup_bookings_save() { check_admin_referer('yatra-setup'); update_option('yatra_enable_guest_booking', isset($_POST['enable_guest_booking']) && $_POST['enable_guest_booking'] === 'true'); update_option('yatra_booking_confirmation', isset($_POST['booking_confirmation']) && $_POST['booking_confirmation'] === 'true'); $extra = apply_filters('yatra_setup_wizard_bookings_save', [], $_POST); if (is_array($extra)) { foreach ($extra as $optionKey => $value) { if (! is_string($optionKey) || $optionKey === '') { continue; } if (strpos($optionKey, 'yatra_') === 0) { update_option($optionKey, $value); } else { update_option('yatra_' . $optionKey, $value); } } } SettingsService::reload(); wp_safe_redirect($this->get_redirect_target((string) $this->get_next_step_link())); exit; } /** * Email identity, notifications, payment safety. */ public function setup_communications() { include YATRA_ABSPATH . 'templates/setup-wizard/communications.php'; } /** * Save communications & payment safety settings. */ public function setup_communications_save() { check_admin_referer('yatra-setup'); $companyName = (string) SettingsService::get('company_name', ''); $companyEmail = (string) SettingsService::get('company_email', ''); $fromName = isset($_POST['email_from_name']) ? sanitize_text_field(wp_unslash($_POST['email_from_name'])) : ''; $fromEmail = isset($_POST['email_from_address']) ? sanitize_email(wp_unslash($_POST['email_from_address'])) : ''; if ($fromName === '') { $fromName = $companyName; } if ($fromEmail === '') { $fromEmail = $companyEmail; } update_option('yatra_from_name', $fromName); update_option('yatra_from_email', $fromEmail); update_option('yatra_email_from_name', $fromName); update_option('yatra_email_from_address', $fromEmail); update_option('yatra_payment_test_mode', isset($_POST['payment_test_mode']) && $_POST['payment_test_mode'] === 'true'); $extra = apply_filters('yatra_setup_wizard_communications_save', [], $_POST); if (is_array($extra)) { foreach ($extra as $optionKey => $value) { if (! is_string($optionKey) || $optionKey === '') { continue; } if (strpos($optionKey, 'yatra_') === 0) { update_option($optionKey, $value); } else { update_option('yatra_' . $optionKey, $value); } } } // Back-compat: extensions that hooked the old single "general" save. $legacyExtra = apply_filters('yatra_setup_wizard_general_save', [], $_POST); if (is_array($legacyExtra)) { foreach ($legacyExtra as $optionKey => $value) { if (! is_string($optionKey) || $optionKey === '') { continue; } if (strpos($optionKey, 'yatra_') === 0) { update_option($optionKey, $value); } else { update_option('yatra_' . $optionKey, $value); } } } SettingsService::reload(); wp_safe_redirect($this->get_redirect_target((string) $this->get_next_step_link())); exit; } /** * Currency settings step */ public function setup_currency() { include YATRA_ABSPATH . 'templates/setup-wizard/currency.php'; } /** * Save currency settings */ public function setup_currency_save() { check_admin_referer('yatra-setup'); $currency_code = isset($_POST['currency']) ? sanitize_text_field($_POST['currency']) : 'USD'; // Validate currency exists if (!CurrencyHelper::exists($currency_code)) { $currency_code = 'USD'; // Fallback to USD } // Get recommended decimal places for the selected currency $currency_data = CurrencyHelper::get($currency_code); $recommended_decimals = $currency_data ? $currency_data['decimal_digits'] : 2; // Use user-specified decimals or fall back to currency recommendation $user_decimals = isset($_POST['decimal_places']) ? absint($_POST['decimal_places']) : $recommended_decimals; $settings = array( 'yatra_currency' => $currency_code, 'yatra_currency_position' => isset($_POST['currency_position']) ? sanitize_text_field($_POST['currency_position']) : 'before', 'yatra_thousand_separator' => isset($_POST['thousand_separator']) ? sanitize_text_field($_POST['thousand_separator']) : ',', 'yatra_decimal_separator' => isset($_POST['decimal_separator']) ? sanitize_text_field($_POST['decimal_separator']) : '.', 'yatra_decimal_places' => $user_decimals, ); foreach ($settings as $key => $value) { update_option($key, $value); } SettingsService::reload(); wp_safe_redirect($this->get_redirect_target((string) $this->get_next_step_link())); exit; } /** * Theme step */ public function setup_theme() { include YATRA_ABSPATH . 'templates/setup-wizard/theme.php'; } /** * Save theme settings */ public function setup_theme_save() { check_admin_referer('yatra-setup'); // Handle theme installation if requested if (isset($_POST['install_resa_theme']) && $_POST['install_resa_theme'] === 'yes') { set_transient('yatra_install_resa_theme', 1, 300); } update_option('yatra_setup_wizard_theme_step_done', '1'); SettingsService::reload(); wp_safe_redirect($this->get_redirect_target((string) $this->get_next_step_link())); exit; } /** * Complete step */ public function setup_complete() { include YATRA_ABSPATH . 'templates/setup-wizard/complete.php'; } /** * Handle complete step actions */ public function setup_complete_save() { check_admin_referer('yatra-setup'); update_option(self::WIZARD_COMPLETED_OPTION, '1'); SettingsService::reload(); do_action('yatra_setup_wizard_completed'); wp_safe_redirect(admin_url('admin.php?page=yatra')); exit; } /** * Check if wizard is completed */ public static function is_wizard_completed() { return get_option(self::WIZARD_COMPLETED_OPTION, '0') === '1'; } /** * Reset wizard */ public static function reset_wizard() { delete_option(self::WIZARD_COMPLETED_OPTION); delete_option(self::WIZARD_REDIRECT_OPTION); } /** * Check if wizard should run (for debugging/testing) */ public static function should_run_wizard() { return get_option(self::WIZARD_COMPLETED_OPTION, '0') !== '1' && apply_filters('yatra_enable_setup_wizard', true) && current_user_can('manage_options'); } /** * Handle skipping the setup wizard */ public function skip_setup_wizard() { update_option('yatra_allow_usage_tracking', false); if (class_exists(StatsUsage::class)) { $u = StatsUsage::instance(); $u->patch_onboarding_meta([ 'dropoff_step' => 'welcome', 'skipped_at' => time(), ]); if ($u->is_enabled()) { $u->record_event('setup_abandoned'); } $u->disable(); } // Mark wizard as completed update_option(self::WIZARD_COMPLETED_OPTION, '1'); // Redirect to admin dashboard wp_safe_redirect(admin_url()); exit; } /** * Setup wizard redirect on activation */ public static function setup_wizard_redirect() { // Only redirect if not completed and redirect flag is set if (get_transient(self::WIZARD_REDIRECT_OPTION)) { delete_transient(self::WIZARD_REDIRECT_OPTION); if (!self::is_wizard_completed()) { wp_safe_redirect(admin_url('admin.php?page=yatra-setup')); exit; } } } /** * AJAX handler for theme installation */ public function ajax_install_theme() { check_ajax_referer('yatra_theme_actions', 'nonce'); if (!current_user_can('install_themes')) { wp_send_json_error('You do not have permission to install themes.'); } $theme_slug = isset($_POST['theme_slug']) ? sanitize_text_field($_POST['theme_slug']) : ''; if (empty($theme_slug)) { wp_send_json_error('Theme slug is required.'); } // Include WordPress theme installation functions if (!class_exists('Theme_Upgrader')) { require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; } if (!class_exists('Theme_Installer_Skin')) { require_once ABSPATH . 'wp-admin/includes/theme.php'; } // Check if theme is already installed if ($resa_theme = wp_get_theme($theme_slug)) { if ($resa_theme->exists()) { // Theme is already installed, just activate it wp_send_json_success('Theme already installed, proceeding to activation.'); } } // Install the theme with aggressive output buffering $original_level = ob_get_level(); ob_start(); $upgrader = new \Theme_Upgrader(new \Theme_Installer_Skin()); $result = $upgrader->install("https://downloads.wordpress.org/theme/resa.zip"); // Clean all output buffers while (ob_get_level() > $original_level) { ob_end_clean(); } // Also clean any remaining output if (ob_get_length() > 0) { ob_clean(); } if (is_wp_error($result)) { wp_send_json_error($result->get_error_message()); } if (!$result) { wp_send_json_error('Theme installation failed.'); } wp_send_json_success('Theme installed successfully.'); } /** * AJAX handler for theme activation */ public function ajax_activate_theme() { check_ajax_referer('yatra_theme_actions', 'nonce'); if (!current_user_can('switch_themes')) { wp_send_json_error('You do not have permission to activate themes.'); } $theme_slug = isset($_POST['theme_slug']) ? sanitize_text_field($_POST['theme_slug']) : ''; if (empty($theme_slug)) { wp_send_json_error('Theme slug is required.'); } // Check if theme exists $theme = wp_get_theme($theme_slug); if (!$theme->exists()) { wp_send_json_error('Theme is not installed.'); } // Activate the theme $result = switch_theme($theme_slug); if (is_wp_error($result)) { wp_send_json_error($result->get_error_message()); } wp_send_json_success('Theme activated successfully.'); } }