PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.16
Yatra – Travel Booking & Tour Operator Software v3.0.16
3.0.16 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 All 84 releases
← All changes | app/Controllers/BookingsController.php +121 -242 3.0.2.8 → 3.0.16 View file →
@@ -66,20 +66,20 @@
66 66 // =====================
67 67 // BOOKINGS ROUTES
68 68 // =====================
69 69
70 - // List bookings
70 + // List bookings — view cap.
71 71 register_rest_route($this->namespace, '/bookings', [
72 72 'methods' => 'GET',
73 73 'callback' => [$this, 'getBookings'],
74 - 'permission_callback' => [$this, 'checkAdminPermission'],
74 + 'permission_callback' => [$this, 'checkCanView'],
75 75 ]);
76 76
77 - // Get single booking
77 + // Get single booking — view cap.
78 78 register_rest_route($this->namespace, '/bookings/(?P<id>\d+)', [
79 79 'methods' => 'GET',
80 80 'callback' => [$this, 'getBooking'],
81 - 'permission_callback' => [$this, 'checkAdminPermission'],
81 + 'permission_callback' => [$this, 'checkCanView'],
82 82 'args' => [
83 83 'id' => [
84 84 'required' => true,
85 85 'type' => 'integer',
@@ -87,66 +87,75 @@
87 87 ],
88 88 ],
89 89 ]);
90 90
91 - // Create booking
91 + // Create booking — create cap.
92 92 register_rest_route($this->namespace, '/bookings', [
93 93 'methods' => 'POST',
94 94 'callback' => [$this, 'createBooking'],
95 - 'permission_callback' => [$this, 'checkAdminPermission'],
95 + 'permission_callback' => [$this, 'checkCanCreate'],
96 96 ]);
97 97
98 - // Update booking
98 + // Update booking — edit cap.
99 99 register_rest_route($this->namespace, '/bookings/(?P<id>\d+)', [
100 100 'methods' => 'PUT',
101 101 'callback' => [$this, 'updateBooking'],
102 - 'permission_callback' => [$this, 'checkAdminPermission'],
102 + 'permission_callback' => [$this, 'checkCanEdit'],
103 103 ]);
104 104
105 - // Delete booking
105 + // Delete booking — critical-sensitivity delete cap. Only
106 + // Owner role gets this by default.
106 107 register_rest_route($this->namespace, '/bookings/(?P<id>\d+)', [
107 108 'methods' => 'DELETE',
108 109 'callback' => [$this, 'deleteBooking'],
109 - 'permission_callback' => [$this, 'checkAdminPermission'],
110 + 'permission_callback' => [$this, 'checkCanDelete'],
110 111 ]);
111 112
112 - // Update booking status
113 + // Update booking status — dedicated change-status cap so
114 + // Front Desk (who has this cap but NOT edit) can flip
115 + // confirmed → checked-in without being able to mutate other
116 + // fields.
113 117 register_rest_route($this->namespace, '/bookings/(?P<id>\d+)/status', [
114 118 'methods' => 'PUT',
115 119 'callback' => [$this, 'updateBookingStatus'],
116 - 'permission_callback' => [$this, 'checkAdminPermission'],
120 + 'permission_callback' => [$this, 'checkCanChangeStatus'],
117 121 ]);
118 122
119 - // Get booking statistics
123 + // Get booking statistics — view cap (aggregates only).
120 124 register_rest_route($this->namespace, '/bookings/stats', [
121 125 'methods' => 'GET',
122 126 'callback' => [$this, 'getBookingStats'],
123 - 'permission_callback' => [$this, 'checkAdminPermission'],
127 + 'permission_callback' => [$this, 'checkCanView'],
124 128 ]);
125 129
126 - // Send booking email
130 + // Send booking email — edit cap. Sending a transactional
131 + // re-confirmation is a write-side operation against the
132 + // customer's record.
127 133 register_rest_route($this->namespace, '/bookings/(?P<id>\d+)/send-email', [
128 134 'methods' => 'POST',
129 135 'callback' => [$this, 'sendBookingEmail'],
130 - 'permission_callback' => [$this, 'checkAdminPermission'],
136 + 'permission_callback' => [$this, 'checkCanEdit'],
131 137 ]);
132 138
133 139 // =====================
134 140 // PAYMENTS ROUTES
135 141 // =====================
136 -
137 - // Get booking payments
142 +
143 + // Get booking payments — view cap.
138 144 register_rest_route($this->namespace, '/bookings/(?P<id>\d+)/payments', [
139 145 'methods' => 'GET',
140 146 'callback' => [$this, 'getBookingPayments'],
141 - 'permission_callback' => [$this, 'checkAdminPermission'],
147 + 'permission_callback' => [$this, 'checkCanView'],
142 148 ]);
143 149
144 - // Add payment to booking
150 + // Add payment to booking — edit cap (modifies the booking's
151 + // payment state). Refunds + payment deletion live on the
152 + // dedicated PaymentController with their own high-sensitivity
153 + // caps.
145 154 register_rest_route($this->namespace, '/bookings/(?P<id>\d+)/payments', [
146 155 'methods' => 'POST',
147 156 'callback' => [$this, 'addPayment'],
148 - 'permission_callback' => [$this, 'checkAdminPermission'],
157 + 'permission_callback' => [$this, 'checkCanEdit'],
149 158 ]);
150 159
151 160 // NOTE: Payment CRUD operations moved to PaymentController
152 161 // This keeps BookingsController focused on booking operations only
@@ -153,56 +162,23 @@
153 162
154 163 // =====================
155 164 // TRAVELERS ROUTES
156 165 // =====================
157 -
166 +
167 + // Travelers list — view cap.
158 168 register_rest_route($this->namespace, '/travelers', [
159 169 'methods' => 'GET',
160 170 'callback' => [$this, 'getTravelers'],
161 - 'permission_callback' => [$this, 'checkAdminPermission'],
171 + 'permission_callback' => [$this, 'checkCanView'],
162 172 ]);
163 173
164 - // Traveler bulk actions
174 + // Traveler bulk actions — edit cap.
165 175 register_rest_route($this->namespace, '/travelers/bulk', [
166 176 'methods' => 'PUT',
167 177 'callback' => [$this, 'bulkTravelers'],
168 - 'permission_callback' => [$this, 'checkAdminPermission'],
178 + 'permission_callback' => [$this, 'checkCanEdit'],
169 179 ]);
170 180
171 - // =====================
172 - // SCHEDULED PAYMENTS
173 - // =====================
174 -
175 - register_rest_route($this->namespace, '/scheduled-payments', [
176 - 'methods' => 'GET',
177 - 'callback' => [$this, 'getScheduledPayments'],
178 - 'permission_callback' => [$this, 'checkAdminPermission'],
179 - ]);
180 -
181 - register_rest_route($this->namespace, '/scheduled-payments/(?P<id>\d+)', [
182 - 'methods' => 'GET',
183 - 'callback' => [$this, 'getScheduledPayment'],
184 - 'permission_callback' => [$this, 'checkAdminPermission'],
185 - ]);
186 -
187 - register_rest_route($this->namespace, '/scheduled-payments/(?P<id>\d+)', [
188 - 'methods' => 'PUT',
189 - 'callback' => [$this, 'updateScheduledPayment'],
190 - 'permission_callback' => [$this, 'checkAdminPermission'],
191 - ]);
192 -
193 - register_rest_route($this->namespace, '/scheduled-payments/(?P<id>\d+)/cancel', [
194 - 'methods' => 'POST',
195 - 'callback' => [$this, 'cancelScheduledPayment'],
196 - 'permission_callback' => [$this, 'checkAdminPermission'],
197 - ]);
198 -
199 - register_rest_route($this->namespace, '/bookings/(?P<id>\d+)/scheduled-payments', [
200 - 'methods' => 'GET',
201 - 'callback' => [$this, 'getBookingScheduledPayments'],
202 - 'permission_callback' => [$this, 'checkAdminPermission'],
203 - ]);
204 -
205 181 // Download travel voucher for a booking
206 182 register_rest_route($this->namespace, '/bookings/(?P<id>\d+)/voucher', [
207 183 'methods' => 'GET',
208 184 'callback' => [$this, 'downloadVoucher'],
@@ -217,17 +193,53 @@
217 193 ]);
218 194 }
219 195
220 196 /**
221 - * Check admin permission
197 + * Granular permission checks — one per operation. WP administrators
198 + * pass every cap via the Team module's admin-fallback filter
199 + * (priority 7 / 8), so an explicit `manage_options` check isn't
200 + * needed at this layer — the cap covers it.
222 201 */
202 + public function checkCanView(): bool
203 + {
204 + return current_user_can('yatra_view_bookings');
205 + }
206 +
207 + public function checkCanCreate(): bool
208 + {
209 + return current_user_can('yatra_create_bookings');
210 + }
211 +
212 + public function checkCanEdit(): bool
213 + {
214 + return current_user_can('yatra_edit_bookings');
215 + }
216 +
217 + public function checkCanDelete(): bool
218 + {
219 + // Critical-sensitivity cap. By default only the Owner role
220 + // holds this — Manager, Sales Agent, Front Desk, etc. cannot
221 + // delete bookings even when they can edit them.
222 + return current_user_can('yatra_delete_bookings');
223 + }
224 +
225 + public function checkCanChangeStatus(): bool
226 + {
227 + // Separate from edit — Front Desk has this without the
228 + // broader edit cap so they can confirm/check-in bookings
229 + // without being able to mutate other fields.
230 + return current_user_can('yatra_change_booking_status');
231 + }
232 +
233 + /**
234 + * @deprecated Kept for any external code (snippet, integration)
235 + * that referenced the old method name. Routes to the view-only
236 + * cap — safer than the old `view OR manage_options` shorthand,
237 + * and admin users still pass via the admin-fallback layer.
238 + */
223 239 public function checkAdminPermission(): bool
224 240 {
225 - // Allow custom booking capability or fallback to manage_options
226 - if (current_user_can('yatra_view_bookings')) {
227 - return true;
228 - }
229 - return current_user_can('manage_options');
241 + return $this->checkCanView();
230 242 }
231 243
232 244 // =========================================================================
233 245 // BOOKING ENDPOINTS
@@ -247,8 +259,11 @@
247 259 'trip_id' => (int) $request->get_param('trip_id'),
248 260 'search' => $request->get_param('search') ?: '',
249 261 'date_from' => $request->get_param('date_from') ?: '',
250 262 'date_to' => $request->get_param('date_to') ?: '',
263 + // Column sorting from the table headers — whitelisted in the repository.
264 + 'orderby' => $request->get_param('orderby') ?: '',
265 + 'order' => $request->get_param('order') ?: '',
251 266 ];
252 267
253 268 // Delegate to service
254 269 $result = $this->bookingService->getBookings($filters);
@@ -347,9 +362,9 @@
347 362 return $this->error_response($result['message'] ?? 'Failed to update booking', 400);
348 363 }
349 364
350 365 Logger::info("Booking updated successfully", ['booking_id' => $id]);
351 - return $this->success_response($result['data']);
366 + return $this->success_response($result['data'] ?? null);
352 367
353 368 } catch (\Exception $e) {
354 369 Logger::error("Failed to update booking", ['booking_id' => $id ?? 0, 'data' => $data ?? [], 'error' => $e->getMessage()]);
355 370 return $this->handle_exception($e);
@@ -618,108 +633,9 @@
618 633
619 634 return new WP_REST_Response($result, $result['success'] ? 200 : 400);
620 635 }
621 636
622 - // =========================================================================
623 - // SCHEDULED PAYMENTS ENDPOINTS
624 - // =========================================================================
625 -
626 637 /**
627 - * GET /scheduled-payments - List scheduled payments
628 - */
629 - public function getScheduledPayments(WP_REST_Request $request): WP_REST_Response
630 - {
631 - $filters = [
632 - 'page' => (int) ($request->get_param('page') ?: 1),
633 - 'per_page' => (int) ($request->get_param('per_page') ?: 20),
634 - 'status' => $request->get_param('status') ?: '',
635 - ];
636 -
637 - $result = $this->paymentService->getScheduledPayments($filters);
638 -
639 - return new WP_REST_Response([
640 - 'success' => true,
641 - 'data' => $result['data'],
642 - 'meta' => [
643 - 'total' => $result['total'],
644 - 'page' => $result['page'],
645 - 'per_page' => $result['per_page'],
646 - 'total_pages' => $result['total_pages'],
647 - ],
648 - ]);
649 - }
650 -
651 - /**
652 - * GET /scheduled-payments/{id} - Get single scheduled payment
653 - */
654 - public function getScheduledPayment(WP_REST_Request $request): WP_REST_Response
655 - {
656 - $id = (int) $request->get_param('id');
657 -
658 - $payment = $this->paymentService->getScheduledPayment($id);
659 -
660 - if (!$payment) {
661 - return new WP_REST_Response([
662 - 'success' => false,
663 - 'message' => __('Scheduled payment not found.', 'yatra'),
664 - ], 404);
665 - }
666 -
667 - return new WP_REST_Response([
668 - 'success' => true,
669 - 'data' => $payment,
670 - ]);
671 - }
672 -
673 - /**
674 - * PUT /scheduled-payments/{id} - Update scheduled payment
675 - */
676 - public function updateScheduledPayment(WP_REST_Request $request): WP_REST_Response
677 - {
678 - $id = (int) $request->get_param('id');
679 - $data = $request->get_json_params();
680 -
681 - $result = $this->paymentService->updateScheduledPayment($id, $data);
682 -
683 - if (!$result['success']) {
684 - return new WP_REST_Response($result, 400);
685 - }
686 -
687 - return new WP_REST_Response($result);
688 - }
689 -
690 - /**
691 - * POST /scheduled-payments/{id}/cancel - Cancel scheduled payment
692 - */
693 - public function cancelScheduledPayment(WP_REST_Request $request): WP_REST_Response
694 - {
695 - $id = (int) $request->get_param('id');
696 -
697 - $result = $this->paymentService->cancelScheduledPayment($id);
698 -
699 - if (!$result['success']) {
700 - return new WP_REST_Response($result, 400);
701 - }
702 -
703 - return new WP_REST_Response($result);
704 - }
705 -
706 - /**
707 - * GET /bookings/{id}/scheduled-payments - Get booking's scheduled payments
708 - */
709 - public function getBookingScheduledPayments(WP_REST_Request $request): WP_REST_Response
710 - {
711 - $bookingId = (int) $request->get_param('id');
712 -
713 - $payments = $this->paymentService->getBookingScheduledPayments($bookingId);
714 -
715 - return new WP_REST_Response([
716 - 'success' => true,
717 - 'data' => $payments,
718 - ]);
719 - }
720 -
721 - /**
722 638 * GET /bookings/{id}/voucher - Download travel voucher for a booking
723 639 */
724 640 public function downloadVoucher(WP_REST_Request $request)
725 641 {
@@ -865,11 +781,21 @@
865 781 $bookingDate = !empty($createdAt) ? date_i18n(get_option('date_format'), strtotime((string) $createdAt)) : '';
866 782 $travelDateRaw = $booking['travel_date'] ?? '';
867 783 $travelDate = !empty($travelDateRaw) ? date_i18n(get_option('date_format'), strtotime((string) $travelDateRaw)) : '';
868 784
785 + // Return date. Prefer the booking's STORED end_date — the actual booked
786 + // return (accounts for a flexible window or a trip duration changed after
787 + // booking). Fall back to the trip duration only when no end is stored:
788 + // duration_days is INCLUSIVE, so the return is travel_date + (days - 1)
789 + // (matches BookingRepository::calculateEndDate; a bare "+ duration_days"
790 + // was one day too far and implied an extra night — see ItineraryPdfBuilder).
869 791 $returnDate = '';
870 - if (!empty($travelDateRaw) && $trip && !empty($trip->duration)) {
871 - $returnTimestamp = strtotime((string) $travelDateRaw . ' +' . (int) $trip->duration . ' days');
792 + $storedEnd = (string) ($booking['end_date'] ?? '');
793 + if ($storedEnd !== '' && ($travelDateRaw === '' || $storedEnd >= $travelDateRaw)) {
794 + $returnDate = date_i18n(get_option('date_format'), strtotime($storedEnd));
795 + } elseif (!empty($travelDateRaw) && $trip && !empty($trip->duration_days)) {
796 + $returnOffset = max(0, (int) $trip->duration_days - 1);
797 + $returnTimestamp = strtotime((string) $travelDateRaw . ' +' . $returnOffset . ' days');
872 798 $returnDate = date_i18n(get_option('date_format'), $returnTimestamp);
873 799 }
874 800
875 801 $statusRaw = (string) ($booking['booking_status'] ?? $booking['status'] ?? '');
@@ -882,12 +808,14 @@
882 808
883 809 $templateData = [
884 810 'company_name' => $companyName,
885 811 'company_address' => $companyAddress,
812 + 'company_address_lines' => \Yatra\Helpers\FormatHelper::companyAddressLines(),
886 813 'company_email' => $companyEmail,
887 814 'company_phone' => $companyPhone,
888 815 'customer_name' => $customerName,
889 816 'customer_email' => (string) ($booking['contact_email'] ?? $booking['customer_email'] ?? ''),
817 + 'customer_address_lines' => FormatHelper::customerAddressLines($booking),
890 818 'booking_ref' => $bookingRef,
891 819 'booking_date' => $bookingDate,
892 820 'booking_status' => ucfirst($statusRaw ?: 'pending'),
893 821 'status_class' => in_array(strtolower($statusRaw), ['confirmed', 'completed', 'success'], true) ? 'confirmed' :
@@ -892,9 +820,19 @@
892 820 'booking_status' => ucfirst($statusRaw ?: 'pending'),
893 821 'status_class' => in_array(strtolower($statusRaw), ['confirmed', 'completed', 'success'], true) ? 'confirmed' :
894 822 (in_array(strtolower($statusRaw), ['cancelled'], true) ? 'cancelled' : 'pending'),
895 823 'trip_title' => $trip ? ($trip->title ?? $booking['trip_title'] ?? __('Trip Booking', 'yatra')) : ($booking['trip_title'] ?? __('Trip Booking', 'yatra')),
896 - 'trip_duration' => $trip && $trip->duration ? sprintf(__('%d days', 'yatra'), (int) $trip->duration) : '',
824 + // Trip duration comes from duration_days/duration_nights (there is no
825 + // `duration` column — accessing it caused a blank value + PHP notice).
826 + 'trip_duration' => $trip
827 + ? yatra_format_duration(
828 + (int) ($trip->duration_days ?? 0),
829 + isset($trip->duration_nights) ? (int) $trip->duration_nights : null,
830 + // Hour-based day tours: "8 hours" instead of "1 day". Absent
831 + // or NULL on every day-based trip, which keeps its wording.
832 + (int) ($trip->duration_hours ?? 0)
833 + )
834 + : '',
897 835 'trip_difficulty' => $trip ? ($trip->difficulty_name ?? '') : '',
898 836 'departure_location' => $trip ? ($trip->departure_location ?? '') : '',
899 837 'destination' => $trip ? ($trip->destination ?? '') : '',
900 838 'travel_date' => $travelDate,
@@ -899,11 +837,11 @@
899 837 'destination' => $trip ? ($trip->destination ?? '') : '',
900 838 'travel_date' => $travelDate,
901 839 'return_date' => $returnDate,
902 840 'currency_symbol' => $currencySymbol,
903 - 'total_amount' => number_format((float) ($booking['total_amount'] ?? 0), 2),
904 - 'amount_paid' => number_format((float) ($booking['amount_paid'] ?? 0), 2),
905 - 'amount_due' => number_format((float) ($booking['amount_due'] ?? 0), 2),
841 + 'total_amount' => yatra_format_price((float) ($booking['total_amount'] ?? 0), $currency, false),
842 + 'amount_paid' => yatra_format_price((float) ($booking['amount_paid'] ?? 0), $currency, false),
843 + 'amount_due' => yatra_format_price((float) ($booking['amount_due'] ?? 0), $currency, false),
906 844 'traveler_count' => (int) ($booking['travelers_count'] ?? $booking['travelers'] ?? 1),
907 845 ];
908 846
909 847 $pdfService = new PdfService();
@@ -939,43 +877,10 @@
939 877 * @param array<string,mixed> $booking From BookingService::getBooking()
940 878 */
941 879 private function renderItineraryFromBookingData(array $booking, bool $isPreview)
942 880 {
943 - $tripRepository = new TripRepository();
944 - $trip = null;
945 - $tripId = (int) ($booking['trip_id'] ?? 0);
946 - if ($tripId > 0) {
947 - $trip = $tripRepository->find($tripId);
948 - }
949 -
950 - $companyName = SettingsService::get('company_name', get_bloginfo('name'));
951 - $companyAddress = SettingsService::get('company_address', '');
952 - $companyEmail = SettingsService::get('company_email', get_option('admin_email'));
953 - $companyPhone = SettingsService::get('company_phone', '');
954 - $currency = SettingsService::getCurrency();
955 - $currencySymbol = FormatHelper::getCurrencySymbol($currency);
956 -
957 - $createdAt = $booking['created_at'] ?? $booking['booking_date'] ?? '';
958 - $bookingDate = !empty($createdAt) ? date_i18n(get_option('date_format'), strtotime((string) $createdAt)) : '';
959 - $travelDateRaw = $booking['travel_date'] ?? '';
960 - $travelDate = !empty($travelDateRaw) ? date_i18n(get_option('date_format'), strtotime((string) $travelDateRaw)) : '';
961 -
962 - $returnDate = '';
963 - if (!empty($travelDateRaw) && $trip && !empty($trip->duration)) {
964 - $returnTimestamp = strtotime((string) $travelDateRaw . ' +' . (int) $trip->duration . ' days');
965 - $returnDate = date_i18n(get_option('date_format'), $returnTimestamp);
966 - }
967 -
968 - $statusRaw = (string) ($booking['booking_status'] ?? $booking['status'] ?? '');
969 - $bookingId = (int) ($booking['id'] ?? 0);
970 - $bookingRef = 'YTR-' . strtoupper(str_pad((string) $bookingId, 8, '0', STR_PAD_LEFT));
971 -
972 - $customerName = trim(
973 - (string) ($booking['contact_first_name'] ?? '') . ' ' . (string) ($booking['contact_last_name'] ?? '')
974 - ) ?: (string) ($booking['customer_name'] ?? __('Customer', 'yatra'));
975 -
976 - $pdfService = new PdfService();
977 - if (!$pdfService->isAvailable()) {
881 + $builder = new \Yatra\Services\ItineraryPdfBuilder();
882 + if (!$builder->pdfService()->isAvailable()) {
978 883 return new WP_Error(
979 884 'pdf_engine_missing',
980 885 __('Itinerary PDF generator is not installed. Please run composer install to install dompdf/dompdf.', 'yatra'),
981 886 ['status' => 500]
@@ -981,46 +886,20 @@
981 886 ['status' => 500]
982 887 );
983 888 }
984 889
890 + $bookingId = (int) ($booking['id'] ?? 0);
891 + $bookingRef = $bookingId > 0
892 + ? 'YTR-' . strtoupper(str_pad((string) $bookingId, 8, '0', STR_PAD_LEFT))
893 + : 'PENDING';
985 894 $filename = 'Travel-Itinerary-' . $bookingRef . '.pdf';
986 895
987 - $templateData = [
988 - 'company_name' => $companyName,
989 - 'company_address' => $companyAddress,
990 - 'company_email' => $companyEmail,
991 - 'company_phone' => $companyPhone,
992 - 'customer_name' => $customerName,
993 - 'customer_email' => (string) ($booking['contact_email'] ?? $booking['customer_email'] ?? ''),
994 - 'booking_ref' => $bookingRef,
995 - 'booking_date' => $bookingDate,
996 - 'booking_status' => ucfirst($statusRaw ?: 'pending'),
997 - 'status_class' => in_array(strtolower($statusRaw), ['confirmed', 'completed', 'success'], true) ? 'confirmed' :
998 - (in_array(strtolower($statusRaw), ['cancelled'], true) ? 'cancelled' : 'pending'),
999 - 'trip_title' => $trip ? ($trip->title ?? $booking['trip_title'] ?? __('Trip Booking', 'yatra')) : ($booking['trip_title'] ?? __('Trip Booking', 'yatra')),
1000 - 'trip_description' => $trip ? ($trip->description ?? $trip->content ?? '') : '',
1001 - 'trip_duration' => $trip && $trip->duration ? sprintf(__('%d days', 'yatra'), (int) $trip->duration) : '',
1002 - 'trip_difficulty' => $trip ? ($trip->difficulty_name ?? '') : '',
1003 - 'trip_highlights' => $trip ? ($trip->highlights ?? $trip->trip_highlights ?? '') : '',
1004 - 'trip_includes' => $trip ? ($trip->includes ?? $trip->trip_includes ?? '') : '',
1005 - 'trip_excludes' => $trip ? ($trip->excludes ?? $trip->trip_excludes ?? '') : '',
1006 - 'departure_location' => $trip ? ($trip->departure_location ?? '') : '',
1007 - 'destination' => $trip ? ($trip->destination ?? '') : '',
1008 - 'travel_date' => $travelDate,
1009 - 'return_date' => $returnDate,
1010 - 'currency_symbol' => $currencySymbol,
1011 - 'total_amount' => number_format((float) ($booking['total_amount'] ?? 0), 2),
1012 - 'amount_paid' => number_format((float) ($booking['amount_paid'] ?? 0), 2),
1013 - 'amount_due' => number_format((float) ($booking['amount_due'] ?? 0), 2),
1014 - 'traveler_count' => (int) ($booking['travelers_count'] ?? $booking['travelers'] ?? 1),
1015 - ];
896 + // The builder accepts the booking array shape directly — just
897 + // forward `id` as `booking_id` so the reference resolves the
898 + // same as the legacy code, and let it normalise everything else.
899 + $source = $booking + ['booking_id' => $bookingId];
900 + $pdfBinary = $builder->build($source);
1016 901
1017 - $pdfBinary = $pdfService->renderTemplateToPdfSafely('pdf/itinerary.php', $templateData, [
1018 - 'paper' => 'A4',
1019 - 'orientation' => 'portrait',
1020 - 'default_font' => 'DejaVu Sans',
1021 - ]);
1022 -
1023 902 if ($isPreview) {
1024 903 return new WP_REST_Response([
1025 904 'success' => true,
1026 905 'pdf_data' => base64_encode($pdfBinary),
@@ -1027,8 +906,8 @@
1027 906 'filename' => $filename,
1028 907 ]);
1029 908 }
1030 909
1031 - $pdfService->outputPdfDownload($pdfBinary, $filename);
910 + $builder->pdfService()->outputPdfDownload($pdfBinary, $filename);
1032 911 exit;
1033 912 }
1034 913 }