PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.4
Yatra – Travel Booking & Tour Operator Software v3.0.4
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 2.0.11 All 82 releases
yatra / app / Controllers / ItineraryController.php

ItineraryController.php in Yatra – Travel Booking & Tour Operator Software 3.0.4, at app/Controllers/ItineraryController.php

399 lines 14.0 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\Controllers;
6
7 use WP_REST_Request;
8 use WP_REST_Response;
9 use WP_Error;
10 use Yatra\Services\ItineraryService;
11 use Yatra\Repositories\ItineraryRepository;
12
13 /**
14 * Itinerary REST API Controller
15 * API endpoints for itinerary entries management
16 */
17 class ItineraryController extends BaseController
18 {
19 private ItineraryService $service;
20
21 public function __construct()
22 {
23 $this->service = new ItineraryService(new ItineraryRepository());
24 }
25
26 /**
27 * Register routes
28 */
29 public function register_routes(): void
30 {
31 $namespace = 'yatra/v1';
32 $base = 'itinerary';
33
34 // Collection routes
35 register_rest_route($namespace, '/' . $base, [
36 [
37 'methods' => \WP_REST_Server::CREATABLE,
38 'callback' => [$this, 'create_item'],
39 'permission_callback' => [$this, 'check_permission'],
40 ],
41 [
42 'methods' => \WP_REST_Server::DELETABLE,
43 'callback' => [$this, 'bulk_delete_items'],
44 'permission_callback' => [$this, 'check_permission'],
45 ],
46 [
47 'methods' => \WP_REST_Server::READABLE,
48 'callback' => [$this, 'get_items_by_trip'],
49 'permission_callback' => [$this, 'check_permission'],
50 ],
51 ]);
52
53 // Single item routes
54 register_rest_route($namespace, '/' . $base . '/(?P<id>[\d]+)', [
55 [
56 'methods' => \WP_REST_Server::READABLE,
57 'callback' => [$this, 'get_item'],
58 'permission_callback' => [$this, 'check_permission'],
59 ],
60 [
61 'methods' => \WP_REST_Server::EDITABLE,
62 'callback' => [$this, 'update_item'],
63 'permission_callback' => [$this, 'check_permission'],
64 ],
65 [
66 'methods' => \WP_REST_Server::DELETABLE,
67 'callback' => [$this, 'delete_item'],
68 'permission_callback' => [$this, 'check_permission'],
69 ],
70 ]);
71
72 // Get day entry ID by day_id
73 register_rest_route($namespace, '/' . $base . '/day-entry-by-day-id/(?P<day_id>[\d]+)', [
74 [
75 'methods' => \WP_REST_Server::READABLE,
76 'callback' => [$this, 'get_day_entry_id_by_day_id'],
77 'permission_callback' => [$this, 'check_permission'],
78 ],
79 ]);
80
81 // Bulk-update / -create activities for a single day in one request.
82 // Replaces the React day-edit "save" path that previously fired one PUT
83 // per activity (5 activities = 5 sequential round-trips). The endpoint
84 // accepts an `activities` array carrying full payloads + optional `id`
85 // per row; rows with `id` are PUTs, rows without are POSTs.
86 register_rest_route($namespace, '/' . $base . '/day/(?P<day_id>[\d]+)/activities/bulk', [
87 [
88 'methods' => \WP_REST_Server::EDITABLE,
89 'callback' => [$this, 'bulk_save_day_activities'],
90 'permission_callback' => [$this, 'check_permission'],
91 ],
92 ]);
93 }
94
95 /**
96 * Get items by trip ID
97 */
98 public function get_items_by_trip(WP_REST_Request $request)
99 {
100 try {
101 $trip_id = (int) $request->get_param('trip_id');
102
103 if (!$trip_id) {
104 return $this->error_response('Trip ID is required', 400);
105 }
106
107 $items = $this->service->getByTripId($trip_id);
108
109 // Debug logging
110 $prepared_items = array_map(function ($item) use ($request) {
111 $prepared = $this->prepare_item_for_response($item, $request);
112 return $prepared;
113 }, $items);
114
115 $response = $this->success_response($prepared_items);
116 return $response;
117 } catch (\Exception $e) {
118 return $this->error_response($e->getMessage(), 500);
119 }
120 }
121
122 /**
123 * Get item
124 */
125 public function get_item(WP_REST_Request $request)
126 {
127 try {
128 $id = (int) $request->get_param('id');
129 $mode = $request->get_param('mode') ?: 'activity'; // Default to activity mode
130
131 // Use mode to determine which method to call
132 if ($mode === 'day') {
133 // Get day entry (from days table)
134 $item = $this->service->find($id);
135 } else {
136 // Get activity entry (from entries table)
137 $item = $this->service->findActivity($id);
138 }
139
140 if (!$item) {
141 return $this->error_response('Itinerary entry not found', 404);
142 }
143
144 $prepared = $this->prepare_item_for_response($item, $request);
145 return $this->success_response($prepared);
146 } catch (\Exception $e) {
147 return $this->error_response($e->getMessage(), 500);
148 }
149 }
150
151 /**
152 * Create item
153 */
154 public function create_item(WP_REST_Request $request)
155 {
156 try {
157 $data = $request->get_json_params();
158 $id = $this->service->create($data);
159
160 return $this->success_response([
161 'id' => $id,
162 'message' => __('Itinerary entry created successfully', 'yatra'),
163 ], 201);
164 } catch (\InvalidArgumentException $e) {
165 return $this->error_response($e->getMessage(), 400);
166 } catch (\Exception $e) {
167 return $this->error_response($e->getMessage(), 500);
168 }
169 }
170
171 /**
172 * Update item
173 */
174 public function update_item(WP_REST_Request $request)
175 {
176 try {
177 $id = (int) $request->get_param('id');
178 $mode = $request->get_param('mode') ?: 'activity'; // Default to activity mode
179 $data = $request->get_json_params();
180 $result = $this->service->update($id, $data, $mode);
181
182 if (!$result) {
183 return $this->error_response('Failed to update itinerary entry', 500);
184 }
185
186 return $this->success_response([
187 'message' => __('Itinerary entry updated successfully', 'yatra'),
188 ]);
189 } catch (\InvalidArgumentException $e) {
190 return $this->error_response($e->getMessage(), 400);
191 } catch (\Exception $e) {
192 return $this->error_response($e->getMessage(), 500);
193 }
194 }
195
196 /**
197 * Delete item
198 */
199 public function delete_item(WP_REST_Request $request)
200 {
201 try {
202 $id = (int) $request->get_param('id');
203 $mode = $request->get_param('mode') ?: 'activity'; // Default to activity mode
204 $result = $this->service->delete($id, $mode);
205
206 if (!$result) {
207 return $this->error_response('Failed to delete itinerary entry', 500);
208 }
209
210 return $this->success_response([
211 'message' => __('Itinerary entry deleted successfully', 'yatra'),
212 ]);
213 } catch (\Exception $e) {
214 return $this->error_response($e->getMessage(), 500);
215 }
216 }
217
218 /**
219 * Bulk-save the activities of a single day in one request.
220 *
221 * Body shape:
222 * {
223 * "trip_id": 14,
224 * "activities": [
225 * { "id": 39, "title": "...", "order": 0, ...full activity fields... },
226 * { "title": "...", "order": 1, ... } // no id => create
227 * ]
228 * }
229 *
230 * Each row goes through the same validation / sanitisation as a single
231 * update / create — the difference is one HTTP round-trip and one cache
232 * invalidation instead of N. Returns per-row results so the React layer
233 * can surface partial failures without losing the rows that succeeded.
234 */
235 public function bulk_save_day_activities(WP_REST_Request $request)
236 {
237 try {
238 $dayId = (int) $request->get_param('day_id');
239 $body = $request->get_json_params() ?: [];
240 $tripId = isset($body['trip_id']) ? (int) $body['trip_id'] : 0;
241 $activities = $body['activities'] ?? [];
242
243 if ($dayId <= 0) {
244 return $this->error_response('Invalid day_id', 400);
245 }
246 if (!is_array($activities)) {
247 return $this->error_response('activities must be an array', 400);
248 }
249
250 $result = $this->service->bulkSaveDayActivities($dayId, $tripId, $activities);
251
252 return $this->success_response($result);
253 } catch (\InvalidArgumentException $e) {
254 return $this->error_response($e->getMessage(), 400);
255 } catch (\Exception $e) {
256 return $this->error_response($e->getMessage(), 500);
257 }
258 }
259
260 /**
261 * Bulk delete items
262 */
263 public function bulk_delete_items(WP_REST_Request $request)
264 {
265 try {
266 $data = $request->get_json_params();
267 $ids = $data['ids'] ?? [];
268
269 if (empty($ids) || !is_array($ids)) {
270 return $this->error_response('No IDs provided for bulk delete', 400);
271 }
272
273 $result = $this->service->bulkDelete($ids);
274
275 return $this->success_response([
276 'deleted' => $result['deleted'],
277 'failed' => $result['failed'],
278 'message' => sprintf(
279 __('%d item(s) deleted successfully. %d item(s) failed to delete.', 'yatra'),
280 $result['deleted'],
281 $result['failed']
282 ),
283 ]);
284 } catch (\Exception $e) {
285 return $this->error_response($e->getMessage(), 500);
286 }
287 }
288
289 /**
290 * Get day entry ID by day_id
291 */
292 public function get_day_entry_id_by_day_id(WP_REST_Request $request)
293 {
294 try {
295 $dayId = (int) $request->get_param('day_id');
296 $repository = new ItineraryRepository();
297 $dayEntryId = $repository->getDayEntryIdByDayId($dayId);
298
299 return $this->success_response([
300 'day_entry_id' => $dayEntryId,
301 ]);
302 } catch (\Exception $e) {
303 return $this->error_response($e->getMessage(), 500);
304 }
305 }
306
307 /**
308 * Prepare item for response
309 */
310 protected function prepare_item_for_response($item, WP_REST_Request $request): array
311 {
312 // Parse included_items and excluded_items from JSON if they're strings
313 $includedItems = $item->included_items ?? [];
314 if (is_string($includedItems)) {
315 $decoded = json_decode($includedItems, true);
316 $includedItems = is_array($decoded) ? $decoded : [];
317 }
318
319 $excludedItems = $item->excluded_items ?? [];
320 if (is_string($excludedItems)) {
321 $decoded = json_decode($excludedItems, true);
322 $excludedItems = is_array($decoded) ? $decoded : [];
323 }
324
325 return [
326 'id' => (int) $item->id,
327 'trip_id' => (int) $item->trip_id,
328 'day_id' => (int) $item->day_id,
329 'day' => isset($item->day_number) ? (int) $item->day_number : null,
330 'day_title' => $item->day_title ?? null,
331 'day_description' => $item->day_description ?? null,
332 'title' => $item->title ?? '',
333 'description' => $item->description ?? '',
334 'time' => $item->time ?? null,
335 'start_time' => $item->start_time ?? null,
336 'end_time' => $item->end_time ?? null,
337 'time_type' => $item->time_type ?? 'exact',
338 'location' => $item->location ?? null,
339 'location_latitude' => $item->location_latitude ?? null,
340 'location_longitude' => $item->location_longitude ?? null,
341 'duration' => $item->duration ?? null,
342 'cost' => isset($item->cost) ? (float) $item->cost : null,
343 'cost_per_person' => isset($item->cost_per_person) ? (bool) $item->cost_per_person : false,
344 'notes' => $item->notes ?? null,
345 'item_type_id' => ($item->item_type_id !== null && $item->item_type_id !== '') ? (int) $item->item_type_id : null,
346 'item_id' => ($item->item_id !== null && $item->item_id !== '') ? (int) $item->item_id : null,
347 'item_type_name' => $item->item_type_name ?? null,
348 'item_name' => $item->item_name ?? null,
349 'item_type_icon' => $item->item_type_icon ?? null,
350 'included_items' => $includedItems,
351 'excluded_items' => $excludedItems,
352 'status' => $item->status ?? 'draft',
353 'images' => $item->images ?? [],
354 'gallery' => $this->decodeGallery($item->gallery ?? null),
355 'video_url' => $item->video_url ?? null,
356 'order' => (int) ($item->order ?? 0),
357 'created_at' => $item->created_at ?? null,
358 'updated_at' => $item->updated_at ?? null,
359 ];
360 }
361
362 /**
363 * Decode gallery JSON from database and convert attachment IDs to URLs
364 */
365 private function decodeGallery(?string $galleryJson): array
366 {
367 if (empty($galleryJson)) {
368 return [];
369 }
370
371 $gallery = json_decode($galleryJson, true);
372 if (!is_array($gallery)) {
373 return [];
374 }
375
376 // Convert attachment IDs to URLs for frontend compatibility
377 foreach ($gallery as &$item) {
378 if (isset($item['attachment_id']) && $item['attachment_id'] > 0) {
379 // Get attachment URL from WordPress
380 $attachment_url = wp_get_attachment_url($item['attachment_id']);
381 if ($attachment_url) {
382 $item['url'] = $attachment_url;
383 }
384
385 // Get thumbnail URL for images
386 if (isset($item['type']) && $item['type'] === 'image') {
387 $thumbnail_url = wp_get_attachment_image_src($item['attachment_id'], 'medium');
388 if ($thumbnail_url) {
389 $item['thumbnail_url'] = $thumbnail_url[0];
390 }
391 }
392 }
393 }
394
395 return $gallery;
396 }
397 }
398
399