PluginProbe
Yatra – Travel Booking & Tour Operator Software / trunk
Yatra – Travel Booking & Tour Operator Software vtrunk
3.0.15 3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 2.0.1 2.0.10 All 83 releases
yatra / app / Core / Handlers / BookingPageHandler.php

BookingPageHandler.php in Yatra – Travel Booking & Tour Operator Software trunk, at app/Core/Handlers/BookingPageHandler.php

272 lines 13.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare(strict_types=1);
4
5 namespace Yatra\Core\Handlers;
6
7 use Yatra\Services\SettingsService;
8
9 /**
10 * Booking Page Handler
11 *
12 * Handles booking page requests
13 */
14 class BookingPageHandler extends BasePageHandler
15 {
16 /**
17 * Handle booking page request
18 *
19 * @param array $route_data Route data from RouteMatcher
20 * @return bool True if handled successfully
21 */
22 public function handle(array $route_data): bool
23 {
24 // Check if custom booking page is enabled
25 if (SettingsService::useCustomBookingPage()) {
26 return false;
27 }
28
29 $page = $route_data['page'] ?? 'main';
30 $base = $route_data['base'];
31
32 if (!empty($route_data['trip']) && is_string($route_data['trip'])) {
33 $_GET['trip'] = sanitize_title($route_data['trip']);
34 }
35
36 // Configure $wp_query + virtual WP_Post so FSE block themes don't fall back
37 // to 404.html — see BasePageHandler::setupPageEnvironment().
38 $this->setupPageEnvironment('singular', [
39 'title' => __('Booking', 'yatra'),
40 'post_type' => 'page',
41 'post_name' => $base,
42 ]);
43
44 // Set up query vars for backward compatibility
45 $this->setQueryVars([
46 'yatra_page' => $base,
47 ]);
48
49 // Hydrate global $booking from session so templates don't crash
50 $booking = null;
51 if (function_exists('yatra_get_booking_session')) {
52 $session = yatra_get_booking_session();
53
54 // If no session exists, initialize with defaults for display purposes
55 // This allows logged-out users to see pricing without "Contact for pricing" error
56 if (empty($session) || empty($session['trip_id'])) {
57 // Try to get trip_id from URL parameters (for direct booking page access)
58 $trip_id = isset($_GET['trip_id']) ? (int) $_GET['trip_id'] : null;
59
60 if ($trip_id) {
61 // Initialize minimal session for pricing display
62 $session = [
63 'trip_id' => $trip_id,
64 'travelers' => 1, // Default to 1 traveler
65 'travel_date' => '',
66 'timestamp' => time(),
67 ];
68 } elseif (function_exists('yatra_has_remaining_session') && yatra_has_remaining_session()) {
69 // "Pay remaining balance" from My Account clears booking session and only sets
70 // yatra remaining session — hydrate a virtual booking session so checkout can render.
71 $rem = yatra_get_remaining_session();
72 if (!empty($rem['trip_id'])) {
73 if (function_exists('yatra_start_session')) {
74 yatra_start_session();
75 }
76 $session = [
77 'trip_id' => (int) $rem['trip_id'],
78 'travelers' => max(1, (int) ($rem['travelers'] ?? 1)),
79 'travel_date' => is_string($rem['travel_date'] ?? null) ? $rem['travel_date'] : '',
80 'timestamp' => time(),
81 'remaining_amount' => isset($rem['remaining_amount']) ? (float) $rem['remaining_amount'] : null,
82 'amount_paid' => isset($rem['amount_paid']) ? (float) $rem['amount_paid'] : null,
83 'total_amount' => isset($rem['total_amount']) ? (float) $rem['total_amount'] : null,
84 'booking_reference' => $rem['booking_reference'] ?? '',
85 'booking_id' => (int) ($rem['booking_id'] ?? 0),
86 'is_remaining_payment' => true,
87 ];
88 }
89 }
90 }
91
92 if (!empty($session) && !empty($session['trip_id'])) {
93 // Load trip for display context
94 try {
95 $tripRepo = new \Yatra\Repositories\TripRepository();
96 $trip = $tripRepo->findPublished((int) $session['trip_id']);
97 } catch (\Throwable $e) {
98 $trip = null;
99 }
100
101 // Ensure trip has a price property for templates (centralized pricing)
102 if ($trip && !isset($trip->price)) {
103 $trip->price = \Yatra\Services\TripPricingService::resolveRegularCurrentPrice($trip);
104 }
105
106 // Ensure trip has a usable featured_image URL (not attachment ID)
107 if ($trip) {
108 // If featured_image is not set, try to get it from WordPress
109 if (empty($trip->featured_image) && !empty($trip->id)) {
110 $attachment_id = get_post_thumbnail_id($trip->id);
111 if ($attachment_id) {
112 $trip->featured_image = $attachment_id;
113 }
114 }
115
116 // Convert attachment ID to URL if needed
117 if (!empty($trip->featured_image) && is_numeric($trip->featured_image)) {
118 $imgUrl = wp_get_attachment_url((int) $trip->featured_image);
119 if ($imgUrl) {
120 $trip->featured_image = $imgUrl;
121 } else {
122 // If attachment URL fails, use placeholder
123 $trip->featured_image = plugins_url('assets/images/trip-placeholder.svg', YATRA_PLUGIN_FILE);
124 }
125 } elseif (empty($trip->featured_image)) {
126 // No featured image at all, use placeholder
127 $trip->featured_image = plugins_url('assets/images/trip-placeholder.svg', YATRA_PLUGIN_FILE);
128 }
129 }
130
131 // Load trip attributes for booking page display
132 if ($trip && !empty($trip->id)) {
133 try {
134 $singleTripController = new \Yatra\Controllers\SingleTripController();
135 // Use reflection to access private method
136 $reflection = new \ReflectionClass($singleTripController);
137 $method = $reflection->getMethod('getTripAttributes');
138 $method->setAccessible(true);
139 $trip->attributes = $method->invoke($singleTripController, (int) $trip->id);
140 } catch (\Throwable $e) {
141 $trip->attributes = [];
142 }
143 }
144
145 // Checkout must always reflect current Settings + registry — never filter by session.
146 // Session snapshots enabled_gateways at "add to booking" time; admin/site changes and older
147 // sessions would otherwise hide newly enabled gateways (e.g. only Pay Later forever).
148 $gatewayRegistry = \Yatra\PaymentGateways\PaymentGatewayRegistry::getInstance();
149 $enabled_gateways = [];
150 foreach ($gatewayRegistry->getForCheckout() as $gateway) {
151 if (!empty($gateway['id'])) {
152 $enabled_gateways[$gateway['id']] = $gateway;
153 }
154 }
155
156 if (function_exists('yatra_start_session')) {
157 yatra_start_session();
158 if (isset($_SESSION['yatra_booking']) && is_array($_SESSION['yatra_booking'])) {
159 $_SESSION['yatra_booking']['enabled_gateways'] = $enabled_gateways;
160 $token = $_SESSION['yatra_booking_token'] ?? '';
161 if (is_string($token) && $token !== '') {
162 $transient_payload = array_merge($_SESSION['yatra_booking'], [
163 'enabled_gateways' => $enabled_gateways,
164 ]);
165 set_transient($token, $transient_payload, 1800);
166 }
167 }
168 }
169 // Calculate pricing using CalculationService (single source of truth)
170 $calculationService = new \Yatra\Services\CalculationService();
171 $pricing_calculation = [];
172
173 // Get coupon code from session if available
174 $coupon_code = isset($session['coupon']['code']) ? $session['coupon']['code'] : '';
175
176 try {
177 $pricing_calculation = $calculationService->calculateFromSession($session, $coupon_code);
178 } catch (\Throwable $e) {
179 // If calculation fails, set empty pricing
180 $pricing_calculation = [
181 'base_amount' => 0,
182 'gross_total' => 0,
183 'final_total' => 0,
184 'amount_due' => 0,
185 'tax_calculation' => [
186 'tax_breakdown' => [],
187 'total_tax_amount' => 0,
188 'tax_inclusive' => false,
189 ],
190 ];
191 }
192
193 // Get itinerary costs (premium feature)
194 $trip_id = (int) $session['trip_id'];
195 $total_travelers = (int) ($session['travelers'] ?? 1);
196 $traveler_counts = $session['traveler_counts'] ?? [];
197 $travel_date = $session['travel_date'] ?? '';
198
199 $itinerary_costs = apply_filters('yatra_booking_itinerary_costs', [], $trip_id, $total_travelers, $traveler_counts, $travel_date);
200 $itinerary_costs_total = 0.0;
201
202 foreach ($itinerary_costs as $cost) {
203 $basePrice = (float) ($cost['price'] ?? 0);
204 $pricePer = $cost['price_per'] ?? 'person';
205
206 switch ($pricePer) {
207 case 'person':
208 $calculatedPrice = $basePrice * $total_travelers;
209 break;
210 case 'group':
211 $calculatedPrice = $basePrice;
212 break;
213 case 'day':
214 $duration_days = (int) ($trip->duration_days ?? 1);
215 $calculatedPrice = $basePrice * $duration_days;
216 break;
217 default:
218 $calculatedPrice = $basePrice;
219 break;
220 }
221
222 $itinerary_costs_total += $calculatedPrice;
223 }
224
225 // Add itinerary costs to pricing calculation
226 $pricing_calculation['itinerary_costs'] = $itinerary_costs;
227 $pricing_calculation['itinerary_costs_total'] = $itinerary_costs_total;
228
229 // Note: CalculationService already includes itinerary costs in final_total and amount_due
230 // No need to add them again here
231
232 // Create Checkout model instance
233 $checkout = new \Yatra\Models\Checkout($trip, $session, $pricing_calculation);
234
235 // Build booking view model expected by templates
236 $booking = (object) [
237 'trip' => $trip,
238 'travel_date' => $session['travel_date'] ?? '',
239 'travelers' => $session['travelers'] ?? 0,
240 'pricing_type' => $session['pricing_type'] ?? 'regular',
241 'price_types' => $session['price_types'] ?? [],
242 'traveler_counts' => $session['traveler_counts'] ?? [],
243 'partial_payment' => $session['partial_payment'] ?? null,
244 'partial_payment_percentage' => $session['partial_payment_percentage'] ?? null,
245 'deposit_required' => $session['deposit_required'] ?? null,
246 'deposit_percentage' => $session['deposit_percentage'] ?? null,
247 'enabled_gateways' => $enabled_gateways,
248 'group_discount' => $session['group_discount'] ?? null,
249 // Pre-calculated pricing data
250 'pricing_calculation' => $pricing_calculation,
251 'tax_calculation' => $pricing_calculation['tax_calculation'] ?? [],
252 // amounts for templates
253 'total_amount' => $session['total_amount'] ?? null,
254 'amount_paid' => $session['amount_paid'] ?? null,
255 'remaining_amount' => $session['remaining_amount'] ?? null,
256 'booking_reference' => $session['booking_reference'] ?? null,
257 'is_remaining_payment' => !empty($session['is_remaining_payment']),
258 'existing_booking_id' => (int) ($session['booking_id'] ?? 0),
259 // Checkout model for clean template access
260 'checkout' => $checkout,
261 ];
262 }
263 }
264
265 if ($booking !== null) {
266 $this->setGlobal('booking', $booking);
267 }
268
269 return $this->selectTemplate('booking', null, 'booking');
270 }
271 }
272