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();
?>
>
'',
'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.');
}
}