PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.9
Yatra – Travel Booking & Tour Operator Software v3.0.9
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 / CustomerController.php

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

632 lines 21.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\Controllers;
6
7 use WP_REST_Request;
8 use WP_REST_Response;
9 use WP_Error;
10 use Yatra\Services\CustomerService;
11 use Yatra\Validators\CustomerValidator;
12 use Yatra\Exceptions\ValidationException;
13 use Yatra\Utils\Logger;
14
15 /**
16 * Customer REST API Controller
17 *
18 * Handles HTTP requests only - delegates business logic to CustomerService.
19 *
20 * NO DATABASE QUERIES OR BUSINESS LOGIC IN THIS FILE.
21 *
22 * @package Yatra\Controllers
23 */
24 class CustomerController extends BaseController
25 {
26 /**
27 * Customer service instance
28 */
29 private CustomerService $customerService;
30
31 /**
32 * Constructor
33 */
34 public function __construct()
35 {
36 $this->customerService = new CustomerService();
37 }
38
39 /**
40 * Register routes
41 */
42 public function register_routes(): void
43 {
44 $namespace = 'yatra/v1';
45 $base = 'customers';
46
47 // =====================
48 // CUSTOMER (FRONTEND) ROUTES
49 // =====================
50
51 // Get current customer profile
52 register_rest_route($namespace, '/' . $base . '/me', [
53 [
54 'methods' => \WP_REST_Server::READABLE,
55 'callback' => [$this, 'getMe'],
56 'permission_callback' => [$this, 'checkCustomerPermission'],
57 ],
58 [
59 'methods' => \WP_REST_Server::EDITABLE,
60 'callback' => [$this, 'updateMe'],
61 'permission_callback' => [$this, 'checkCustomerPermission'],
62 ],
63 ]);
64
65 // Change current customer's account password
66 register_rest_route($namespace, '/' . $base . '/me/password', [
67 [
68 'methods' => \WP_REST_Server::EDITABLE,
69 'callback' => [$this, 'updateMyPassword'],
70 'permission_callback' => [$this, 'checkCustomerPermission'],
71 ],
72 ]);
73
74 // Current customer's bookings
75 register_rest_route($namespace, '/' . $base . '/my-bookings', [
76 [
77 'methods' => \WP_REST_Server::READABLE,
78 'callback' => [$this, 'getMyBookings'],
79 'permission_callback' => [$this, 'checkCustomerPermission'],
80 ],
81 ]);
82
83 register_rest_route($namespace, '/' . $base . '/my-bookings/(?P<id>\d+)', [
84 [
85 'methods' => \WP_REST_Server::READABLE,
86 'callback' => [$this, 'getMyBooking'],
87 'permission_callback' => [$this, 'checkCustomerPermission'],
88 'args' => [
89 'id' => [
90 'required' => true,
91 'type' => 'integer',
92 'sanitize_callback' => 'absint',
93 ],
94 ],
95 ],
96 ]);
97
98 // Current customer's payments
99 register_rest_route($namespace, '/' . $base . '/my-payments', [
100 [
101 'methods' => \WP_REST_Server::READABLE,
102 'callback' => [$this, 'getMyPayments'],
103 'permission_callback' => [$this, 'checkCustomerPermission'],
104 ],
105 ]);
106
107 // Current customer's documents
108 register_rest_route($namespace, '/' . $base . '/my-documents', [
109 [
110 'methods' => \WP_REST_Server::READABLE,
111 'callback' => [$this, 'getMyDocuments'],
112 'permission_callback' => [$this, 'checkCustomerPermission'],
113 ],
114 ]);
115
116 // Current customer's support tickets
117 register_rest_route($namespace, '/' . $base . '/my-support-tickets', [
118 [
119 'methods' => \WP_REST_Server::READABLE,
120 'callback' => [$this, 'getMySupportTickets'],
121 'permission_callback' => [$this, 'checkCustomerPermission'],
122 ],
123 ]);
124
125 // =====================
126 // ADMIN ROUTES
127 // =====================
128
129 // List + create customers. List gates on view, create gates
130 // on edit (creating a customer is a write).
131 register_rest_route($namespace, '/' . $base, [
132 [
133 'methods' => \WP_REST_Server::READABLE,
134 'callback' => [$this, 'getCustomers'],
135 'permission_callback' => [$this, 'checkCanView'],
136 ],
137 [
138 'methods' => \WP_REST_Server::CREATABLE,
139 'callback' => [$this, 'createCustomer'],
140 'permission_callback' => [$this, 'checkCanEdit'],
141 ],
142 ]);
143
144 // Single customer — read / update / delete with distinct caps.
145 register_rest_route($namespace, '/' . $base . '/(?P<id>\d+)', [
146 [
147 'methods' => \WP_REST_Server::READABLE,
148 'callback' => [$this, 'getCustomer'],
149 'permission_callback' => [$this, 'checkCanView'],
150 ],
151 [
152 'methods' => \WP_REST_Server::EDITABLE,
153 'callback' => [$this, 'updateCustomer'],
154 'permission_callback' => [$this, 'checkCanEdit'],
155 ],
156 [
157 'methods' => \WP_REST_Server::DELETABLE,
158 'callback' => [$this, 'deleteCustomer'],
159 'permission_callback' => [$this, 'checkCanEdit'],
160 ],
161 ]);
162
163 // Customer bookings list — view cap.
164 register_rest_route($namespace, '/' . $base . '/(?P<id>\d+)/bookings', [
165 [
166 'methods' => \WP_REST_Server::READABLE,
167 'callback' => [$this, 'getCustomerBookings'],
168 'permission_callback' => [$this, 'checkCanView'],
169 ],
170 ]);
171
172 // Merge customers — destructive write. Edit cap.
173 register_rest_route($namespace, '/' . $base . '/merge', [
174 [
175 'methods' => \WP_REST_Server::CREATABLE,
176 'callback' => [$this, 'mergeCustomers'],
177 'permission_callback' => [$this, 'checkCanEdit'],
178 ],
179 ]);
180
181 // Customer statistics — view cap.
182 register_rest_route($namespace, '/' . $base . '/stats', [
183 [
184 'methods' => \WP_REST_Server::READABLE,
185 'callback' => [$this, 'getCustomerStats'],
186 'permission_callback' => [$this, 'checkCanView'],
187 ],
188 ]);
189 }
190
191 /**
192 * Check if user is logged in (for /me, /my-bookings etc. — these
193 * are customer-facing endpoints that work for any logged-in WP
194 * user, not just team members).
195 */
196 public function checkCustomerPermission(): bool
197 {
198 return is_user_logged_in();
199 }
200
201 /**
202 * Granular admin-side permission checks. WP administrators pass
203 * every cap via the Team module's admin-fallback filter, so an
204 * explicit `manage_options` check isn't needed here.
205 */
206 public function checkCanView(): bool
207 {
208 return current_user_can('yatra_view_customers');
209 }
210
211 public function checkCanEdit(): bool
212 {
213 return current_user_can('yatra_edit_customers');
214 }
215
216 /**
217 * @deprecated Kept for any external code referencing the old
218 * method name. The previous implementation OR-ed
219 * `yatra_manage_customers` which was never registered anywhere
220 * — that arm has been removed because it was always false in
221 * practice. Routes to view-only — admin users still pass via
222 * the admin-fallback layer.
223 */
224 public function checkAdminPermission(): bool
225 {
226 return $this->checkCanView();
227 }
228
229 // =========================================================================
230 // FRONTEND ENDPOINTS (Current User)
231 // =========================================================================
232
233 /**
234 * GET /customers/me - Get current customer profile
235 */
236 public function getMe(WP_REST_Request $request): WP_REST_Response
237 {
238 $userId = get_current_user_id();
239
240 $profile = $this->customerService->getAccountProfileForUser($userId);
241
242 if ($profile === null) {
243 return new WP_REST_Response([
244 'success' => false,
245 'message' => __('Customer profile not found.', 'yatra'),
246 ], 404);
247 }
248
249 return new WP_REST_Response([
250 'success' => true,
251 'data' => $profile,
252 ]);
253 }
254
255 /**
256 * PUT /customers/me - Update current customer profile
257 */
258 public function updateMe(WP_REST_Request $request)
259 {
260 try {
261 $userId = get_current_user_id();
262 $data = $request->get_json_params();
263
264 // Email is the account login and is intentionally NOT editable from
265 // the account profile. Strip it server-side so it can never be
266 // changed via this endpoint, regardless of what the client sends.
267 if (is_array($data)) {
268 unset($data['email'], $data['user_email']);
269 }
270
271 Logger::apiRequest('/customers/me', 'PUT', $data);
272
273 $customer = $this->customerService->getCustomerByUserId($userId);
274
275 if (!$customer) {
276 // No customer record yet (e.g. registered but never booked).
277 // Create one linked to this user so the profile persists instead
278 // of failing. Email stays the account login (never client-set).
279 $wpUser = get_user_by('id', $userId);
280 if (!$wpUser) {
281 return $this->not_found(__('Customer profile not found', 'yatra'));
282 }
283
284 $createData = CustomerValidator::sanitize($data);
285 $createData['user_id'] = $userId;
286 $createData['email'] = $wpUser->user_email;
287 if (empty($createData['first_name'])) {
288 $createData['first_name'] = $wpUser->first_name !== ''
289 ? $wpUser->first_name
290 : ($wpUser->display_name !== '' ? $wpUser->display_name : $wpUser->user_login);
291 }
292 if (empty($createData['last_name'])) {
293 $createData['last_name'] = (string) $wpUser->last_name;
294 }
295
296 $createResult = $this->customerService->createCustomer($createData);
297 if (empty($createResult['success'])) {
298 Logger::warning('Could not create customer profile on update', ['user_id' => $userId, 'result' => $createResult]);
299 return $this->error_response($createResult['message'] ?? __('Failed to save profile.', 'yatra'), 400);
300 }
301
302 return $this->success_response($this->customerService->getAccountProfileForUser($userId));
303 }
304
305 $customerId = (int) $customer['id'];
306
307 // Validate and sanitize input data
308 CustomerValidator::validateUpdate($data, $customerId);
309 $data = CustomerValidator::sanitize($data);
310
311 $result = $this->customerService->updateCustomer($customerId, $data);
312
313 if (!$result['success']) {
314 Logger::warning("Customer profile update failed", ['customer_id' => $customerId, 'user_id' => $userId, 'result' => $result]);
315 return $this->error_response($result['message'] ?? 'Failed to update customer profile', 400);
316 }
317
318 Logger::info("Customer profile updated successfully", ['customer_id' => $customerId, 'user_id' => $userId]);
319
320 // updateCustomer() returns success/message only — return the fresh
321 // profile so the response carries the updated values (and avoids an
322 // "undefined key data" warning).
323 return $this->success_response(
324 $this->customerService->getAccountProfileForUser($userId)
325 );
326
327 } catch (\Exception $e) {
328 Logger::error("Failed to update customer profile", ['user_id' => $userId ?? 0, 'data' => $data ?? [], 'error' => $e->getMessage()]);
329 return $this->handle_exception($e);
330 }
331 }
332
333 /**
334 * PUT /customers/me/password - Change the current customer's account password.
335 *
336 * Requires the correct current password, then sets the new one and refreshes
337 * the auth cookie so the customer stays logged in (wp_set_password otherwise
338 * invalidates the current session).
339 */
340 public function updateMyPassword(WP_REST_Request $request)
341 {
342 $userId = get_current_user_id();
343 if ($userId <= 0) {
344 return $this->error_response(__('Authentication required.', 'yatra'), 401);
345 }
346
347 $data = $request->get_json_params();
348 $current = isset($data['current_password']) ? (string) $data['current_password'] : '';
349 $newPassword = isset($data['new_password']) ? (string) $data['new_password'] : '';
350
351 if ($current === '' || $newPassword === '') {
352 return $this->error_response(__('Current and new password are required.', 'yatra'), 400);
353 }
354
355 $user = get_user_by('id', $userId);
356 if (!$user || !wp_check_password($current, $user->user_pass, $userId)) {
357 return $this->error_response(__('Your current password is incorrect.', 'yatra'), 400);
358 }
359
360 wp_set_password($newPassword, $userId);
361
362 // wp_set_password() invalidates the session token / logs the user out.
363 // Re-establish the current session so the account page stays authenticated.
364 wp_set_current_user($userId);
365 wp_set_auth_cookie($userId, true);
366
367 Logger::info('Customer changed account password', ['user_id' => $userId]);
368
369 return $this->success_response(['updated' => true]);
370 }
371
372 /**
373 * GET /customers/my-bookings - Get current customer's bookings
374 */
375 public function getMyBookings(WP_REST_Request $request): WP_REST_Response
376 {
377 $userId = get_current_user_id();
378
379 // Get bookings by user ID (checks both customer_id and user_id)
380 $bookings = $this->customerService->getBookingsByUserId($userId);
381
382 return new WP_REST_Response([
383 'success' => true,
384 'data' => $bookings,
385 ]);
386 }
387
388 public function getMyBooking(WP_REST_Request $request): WP_REST_Response
389 {
390 $userId = get_current_user_id();
391 $bookingId = (int) $request->get_param('id');
392
393 $booking = $this->customerService->getBookingDetailsForUser($userId, $bookingId);
394
395 if (!$booking) {
396 return new WP_REST_Response([
397 'success' => false,
398 'message' => __('Booking not found.', 'yatra'),
399 ], 404);
400 }
401
402 return new WP_REST_Response([
403 'success' => true,
404 'data' => $booking,
405 ]);
406 }
407
408 /**
409 * GET /customers/my-payments - Get current customer's payments
410 */
411 public function getMyPayments(WP_REST_Request $request): WP_REST_Response
412 {
413 $userId = get_current_user_id();
414
415 $customer = $this->customerService->getCustomerByUserId($userId);
416
417 if ($customer) {
418 $payments = $this->customerService->getCustomerPayments((int) $customer['id']);
419 } else {
420 $payments = $this->customerService->getPaymentsByUserId($userId);
421 }
422
423 return new WP_REST_Response([
424 'success' => true,
425 'data' => $payments,
426 ]);
427 }
428
429 /**
430 * GET /customers/my-documents - Get current customer's documents
431 */
432 public function getMyDocuments(WP_REST_Request $request): WP_REST_Response
433 {
434 $userId = get_current_user_id();
435
436 $customer = $this->customerService->getCustomerByUserId($userId);
437
438 if (!$customer) {
439 $bookings = $this->customerService->getBookingsByUserId($userId, 1000);
440 $documents = $this->customerService->getDocumentsForBookings($bookings, 0);
441
442 return new WP_REST_Response([
443 'success' => true,
444 'data' => is_array($documents) ? $documents : [],
445 ]);
446 }
447
448 $documents = $this->customerService->getCustomerDocuments((int) $customer['id']);
449
450 return new WP_REST_Response([
451 'success' => true,
452 'data' => $documents,
453 ]);
454 }
455
456 /**
457 * GET /customers/my-support-tickets - Get current customer's support tickets
458 */
459 public function getMySupportTickets(WP_REST_Request $request): WP_REST_Response
460 {
461 $userId = get_current_user_id();
462
463 $customer = $this->customerService->getCustomerByUserId($userId);
464
465 if (!$customer) {
466 return new WP_REST_Response([
467 'success' => true,
468 'data' => [],
469 ]);
470 }
471
472 $tickets = $this->customerService->getCustomerSupportTickets((int) $customer['id']);
473
474 return new WP_REST_Response([
475 'success' => true,
476 'data' => $tickets,
477 ]);
478 }
479
480 // =========================================================================
481 // ADMIN ENDPOINTS
482 // =========================================================================
483
484 /**
485 * GET /customers - List all customers
486 */
487 public function getCustomers(WP_REST_Request $request): WP_REST_Response
488 {
489 $filters = [
490 'page' => (int) ($request->get_param('page') ?: 1),
491 'per_page' => (int) ($request->get_param('per_page') ?: 20),
492 'status' => $request->get_param('status') ?: '',
493 'search' => $request->get_param('search') ?: '',
494 'orderby' => $request->get_param('orderby') ?: 'created_at',
495 'order' => $request->get_param('order') ?: 'desc',
496 ];
497
498 $result = $this->customerService->getCustomers($filters);
499
500 return new WP_REST_Response([
501 'success' => true,
502 'data' => $result['data'],
503 'total' => $result['total'],
504 'page' => $result['page'],
505 'per_page' => $result['per_page'],
506 'pages' => $result['pages'] ?? $result['total_pages'] ?? 1,
507 ]);
508 }
509
510 /**
511 * GET /customers/{id} - Get single customer
512 */
513 public function getCustomer(WP_REST_Request $request): WP_REST_Response
514 {
515 $id = (int) $request->get_param('id');
516
517 $customer = $this->customerService->getCustomer($id);
518
519 if (!$customer) {
520 return new WP_REST_Response([
521 'success' => false,
522 'message' => __('Customer not found.', 'yatra'),
523 ], 404);
524 }
525
526 // Return both legacy flat fields and wrapped data for UI compatibility
527 return new WP_REST_Response(array_merge([
528 'success' => true,
529 'data' => $customer,
530 ], is_array($customer) ? $customer : []));
531 }
532
533 /**
534 * POST /customers - Create customer
535 */
536 public function createCustomer(WP_REST_Request $request): WP_REST_Response
537 {
538 $data = $request->get_json_params();
539
540 $result = $this->customerService->createCustomer($data);
541
542 if (!$result['success']) {
543 return new WP_REST_Response($result, 400);
544 }
545
546 return new WP_REST_Response($result, 201);
547 }
548
549 /**
550 * PUT /customers/{id} - Update customer
551 */
552 public function updateCustomer(WP_REST_Request $request): WP_REST_Response
553 {
554 $id = (int) $request->get_param('id');
555 $data = $request->get_json_params();
556
557 $result = $this->customerService->updateCustomer($id, $data);
558
559 if (!$result['success']) {
560 return new WP_REST_Response($result, 400);
561 }
562
563 return new WP_REST_Response($result);
564 }
565
566 /**
567 * DELETE /customers/{id} - Delete customer
568 */
569 public function deleteCustomer(WP_REST_Request $request): WP_REST_Response
570 {
571 $id = (int) $request->get_param('id');
572
573 $result = $this->customerService->deleteCustomer($id);
574
575 if (!$result['success']) {
576 return new WP_REST_Response($result, 400);
577 }
578
579 return new WP_REST_Response($result);
580 }
581
582 /**
583 * GET /customers/{id}/bookings - Get customer's bookings (admin)
584 */
585 public function getCustomerBookings(WP_REST_Request $request): WP_REST_Response
586 {
587 $id = (int) $request->get_param('id');
588 $limit = (int) ($request->get_param('limit') ?: 10);
589
590 $bookings = $this->customerService->getCustomerBookings($id, $limit);
591
592 return new WP_REST_Response([
593 'success' => true,
594 'data' => $bookings,
595 ]);
596 }
597
598 /**
599 * POST /customers/merge - Merge two customers
600 */
601 public function mergeCustomers(WP_REST_Request $request): WP_REST_Response
602 {
603 $data = $request->get_json_params();
604 $sourceId = (int) ($data['source_id'] ?? 0);
605 $targetId = (int) ($data['target_id'] ?? 0);
606
607 if (!$sourceId || !$targetId) {
608 return new WP_REST_Response([
609 'success' => false,
610 'message' => __('Both source and target customer IDs are required.', 'yatra'),
611 ], 400);
612 }
613
614 $result = $this->customerService->mergeCustomers($sourceId, $targetId);
615
616 if (!$result['success']) {
617 return new WP_REST_Response($result, 400);
618 }
619
620 return new WP_REST_Response($result);
621 }
622
623 /**
624 * GET /customers/stats - Get customer statistics
625 */
626 public function getCustomerStats(WP_REST_Request $request): WP_REST_Response
627 {
628 $stats = $this->customerService->getStats();
629 return new WP_REST_Response($stats ?? []);
630 }
631 }
632