id) ? (int) $trip_data->id : 0; $max_travelers = (int) ($trip_data->max_travelers ?? 20); $item_id = isset($card['id']) ? (string) $card['id'] : (string) $index; // Get pricing from card (already calculated with dynamic pricing in Controller) $sale_price = (float) ($card['sale_price'] ?? 0); $original_price = (float) ($card['original_price'] ?? 0); // For traveler-based pricing, use first category price for initial total $initial_total_price = $sale_price; if (!empty($card['pricing_type']) && $card['pricing_type'] === 'traveler_based' && !empty($card['traveler_pricing'])) { $first_traveler = is_array($card['traveler_pricing']) ? $card['traveler_pricing'][0] : null; if ($first_traveler) { $first_traveler = is_array($first_traveler) ? (object) $first_traveler : $first_traveler; $initial_total_price = (float) ($first_traveler->effective_price ?? $first_traveler->discounted_price ?? $first_traveler->original_price ?? $sale_price); } } $seats_available = (int) ($card['seats_available'] ?? 0); $is_limited = !empty($card['is_limited']); $card_status = $card['status'] ?? 'available'; $is_sold_out = isset($card['is_sold_out']) && $card['is_sold_out']; if ($is_sold_out || $card_status === 'sold_out') { $card_status = 'sold_out'; } // Match Y-m-d even if DB returns datetime (e.g. 2026-08-13 00:00:00) $yatra_card_date_norm = static function ($v): string { $v = trim((string) $v); return preg_match('/^(\d{4}-\d{2}-\d{2})/', $v, $m) ? $m[1] : $v; }; $sel_norm = $yatra_card_date_norm($selected_date_filter ?? ''); $card_date_norm = $yatra_card_date_norm($card['data_date'] ?? ''); $is_selected_card = ($sel_norm !== '' && $card_date_norm !== '' && $sel_norm === $card_date_norm); $should_be_open = $is_selected_card || $index === 0; // Apply Yatra date format to availability header dates when the controller returns ISO. $yatra_format_card_date_for_display = static function ($value): string { $raw = trim((string) $value); if ($raw === '') { return ''; } // Only transform ISO-like values; leave already-human strings unchanged. if (!preg_match('/^\d{4}-\d{2}-\d{2}/', $raw)) { return $raw; } return \Yatra\Helpers\FormatHelper::formatDate($raw); }; $from_date_display = $yatra_format_card_date_for_display($card['from_date'] ?? ''); $to_date_display = $yatra_format_card_date_for_display($card['to_date'] ?? ''); $date_display_display = $yatra_format_card_date_for_display($card['date_display'] ?? ''); ?>
' . esc_html($card['discount_text']) . '
'; } echo '' . yatra_format_price($display_sale_price) . ''; ?>
' . esc_html__('Sold Out', 'yatra') . '
'; } elseif ($card_status === 'limited') { echo '
' . esc_html__('Limited', 'yatra') . '
'; } elseif ($card_status === 'blocked') { echo '
' . esc_html__('Blocked', 'yatra') . '
'; } elseif ($card_status === 'closed') { echo '
' . esc_html__('Closed', 'yatra') . '
'; } elseif ($card_status === 'cancelled') { echo '
' . esc_html__('Cancelled', 'yatra') . '
'; } else { echo '
' . esc_html__('Available', 'yatra') . '
'; } ?>
duration_days ?? 1); echo esc_html($duration_days . ' ' . _n('Day', 'Days', $duration_days, 'yatra')); ?>
getCancellationPolicy(); } elseif (isset($trip_data->cancellation_policy)) { $cancellation_policy = $trip_data->cancellation_policy; } $cancellation_label = ''; $cancellation_value = ''; if (!empty($cancellation_policy)) { // Extract cancellation info from policy $policy_lower = strtolower($cancellation_policy); if (strpos($policy_lower, 'free') !== false || strpos($policy_lower, 'no charge') !== false) { $cancellation_label = __('Free Cancellation', 'yatra'); if (preg_match('/(\d+)\s*(hour|day|week)s?\s*before/i', $cancellation_policy, $matches)) { $time_value = $matches[1]; $time_unit = $matches[2]; $cancellation_value = sprintf( __('Up to %d %s%s before', 'yatra'), $time_value, $time_unit, $time_value > 1 ? 's' : '' ); } else { $cancellation_value = __('No charge', 'yatra'); } } else { // Non-free cancellation $cancellation_label = __('Cancellation Policy', 'yatra'); $cancellation_value = __('See details', 'yatra'); } } else { // Default fallback when no policy is set $cancellation_label = __('Cancellation Policy', 'yatra'); $cancellation_value = __('May apply', 'yatra'); } ?>
$pt) { $pt_label = $pt->category_label ?? $pt->label ?? __('Traveler', 'yatra'); $pt_pricing_mode = $pt->pricing_mode ?? 'per_person'; $pt_is_per_group = ($pt_pricing_mode === 'per_group'); $pt_category_id = $pt->category_id ?? $pt_index; $pt_default = $pt_index === 0 ? 1 : 0; $pt_min_qty = 0; $pt_max_qty = (int) min($seats_available, $max_travelers); // Determine price with fallback chain $pt_price = 0; if (isset($pt->effective_price) && $pt->effective_price > 0) { $pt_price = (float) $pt->effective_price; } elseif (isset($pt->discounted_price) && $pt->discounted_price > 0) { $pt_price = (float) $pt->discounted_price; } elseif (isset($pt->original_price) && $pt->original_price > 0) { $pt_price = (float) $pt->original_price; } // Apply dynamic pricing to traveler category prices if ($dp_enabled && $pt_price > 0) { $pt_price = apply_filters('yatra_availability_price', $pt_price, $trip_id, [ 'departure_date' => $card['date'] ?? null, 'spots_remaining' => $card['spots_remaining'] ?? null, 'availability_id' => $item_id, 'price_type_id' => $pt->id ?? ($pt->price_type_id ?? null), ]); } // Age info $pt_age_text = ''; $pt_age_min = isset($pt->age_min) ? (int) $pt->age_min : null; $pt_age_max = isset($pt->age_max) ? (int) $pt->age_max : null; if ($pt_age_min !== null || $pt_age_max !== null) { if ($pt_age_min !== null && $pt_age_max !== null) { $pt_age_text = sprintf(__('(Age %d-%d)', 'yatra'), $pt_age_min, $pt_age_max); } elseif ($pt_age_min !== null) { $pt_age_text = sprintf(__('(Age %d+)', 'yatra'), $pt_age_min); } else { $pt_age_text = sprintf(__('(Up to age %d)', 'yatra'), $pt_age_max); } } // Build pricing label for per_group mode $pricing_label = ''; if ($pt_is_per_group) { if (!empty($pt->min_pax) && !empty($pt->max_pax)) { $pricing_label = sprintf(__('per group (%d-%d pax)', 'yatra'), $pt->min_pax, $pt->max_pax); } elseif (!empty($pt->max_pax)) { $pricing_label = sprintf(__('per group (up to %d pax)', 'yatra'), $pt->max_pax); } elseif (!empty($pt->min_pax)) { $pricing_label = sprintf(__('per group (%d+ pax)', 'yatra'), $pt->min_pax); } else { $pricing_label = __('per group', 'yatra'); } } // Build price HTML $pt_price_html = ''; if ($pt_price > 0) { $pt_price_html = '
'; $pt_price_html .= '' . yatra_format_price($pt_price) . ''; if ($pt_is_per_group) { $pt_price_html .= '' . esc_html($pricing_label) . ''; } $pt_price_html .= '
'; } $input_id = 'traveler_' . $pt_category_id; $traveler_rows[] = [ 'label' => $pt_label, 'subtitle' => $pt_age_text, 'price_html' => $pt_price_html, 'row_attrs' => [ 'data-category-id' => $pt_category_id, 'data-price' => $pt_price, 'data-pricing-mode' => $pt_pricing_mode, ], 'minus_disabled' => $pt_default <= $pt_min_qty, 'plus_disabled' => $pt_default >= $pt_max_qty, 'minus_attrs' => [ 'data-target' => $input_id, 'data-item' => $item_id, 'data-category' => $pt_category_id, 'aria-label' => sprintf(__('Decrease %s', 'yatra'), $pt_label), ], 'plus_attrs' => [ 'data-target' => $input_id, 'data-item' => $item_id, 'data-category' => $pt_category_id, 'aria-label' => sprintf(__('Increase %s', 'yatra'), $pt_label), ], 'input_attrs' => [ 'id' => $input_id, 'name' => 'travelers[' . $pt_category_id . ']', 'value' => $pt_default, 'min' => $pt_min_qty, 'max' => $pt_max_qty, 'data-item' => $item_id, 'data-category' => $pt_category_id, 'data-category-label' => $pt_label, 'data-price' => $pt_price, 'data-pricing-mode' => $pt_pricing_mode, ], ]; if ($pt_default > 0) { $display_parts[] = $pt_label . ' x ' . $pt_default; } } $traveler_display_text = !empty($display_parts) ? implode(', ', $display_parts) : __('Select travelers', 'yatra'); // Setup variables for traveler-selector.php using availability-specific data $root_id = ''; $root_class = 'yatra-booking-field-select yatra-participants-select yatra-availability-participants'; $container_attrs = [ 'data-item' => $item_id, ]; $display_id = 'participants-display'; $display_class = 'yatra-participants-display yatra-availability-participants-display'; $display_attrs = [ 'data-item' => $item_id, ]; $dropdown_id = ''; $dropdown_class = 'yatra-booking-quantity-selector yatra-availability-quantity-selector'; $dropdown_attrs = [ 'data-item' => $item_id, ]; $display_text = $traveler_display_text; $icon_html = yatra_svg_icon('users', 'yatra-icon-sm'); $rows = $traveler_rows; include YATRA_PLUGIN_PATH . 'templates/partials/traveler-selector.php'; ?>
$pt->category_id ?? null, 'category_label' => $pt->category_label ?? $pt->label ?? '', 'original_price' => $pt->original_price ?? 0, 'sale_price' => $pt->sale_price ?? null, 'discounted_price' => $pt->discounted_price ?? null, 'effective_price' => $pt->effective_price ?? $pt->sale_price ?? $pt->discounted_price ?? $pt->original_price ?? 0, 'age_min' => $pt->age_min ?? null, 'age_max' => $pt->age_max ?? null, ]; }, $normalized_price_types); $price_types_json = wp_json_encode($price_types_for_js); } // Get departure time - prefer card data, fallback to from_date if it looks like time $card_departure_time = ''; if (!empty($card['departure_time'])) { $card_departure_time = $card['departure_time']; } elseif (!empty($card['from_date']) && preg_match('/^\d{1,2}:\d{2}\s*(AM|PM)?$/i', $card['from_date'])) { $card_departure_time = $card['from_date']; } // Is this a day trip? $is_card_day_trip = !empty($card['is_day_trip']) || !empty($card['date_display']); ?>