PluginProbe
Yatra – Travel Booking & Tour Operator Software / trunk
Yatra – Travel Booking & Tour Operator Software vtrunk
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
← All changes | app/Controllers/CustomerController.php +206 -3 3.0.6trunk View file →
@@ -61,8 +61,48 @@
61 61 'permission_callback' => [$this, 'checkCustomerPermission'],
62 62 ],
63 63 ]);
64 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 + // Request an account email change (WordPress pending-change pattern:
75 + // a confirmation link is emailed to the new address; nothing changes yet).
76 + // The confirmation link itself is a normal front-end URL handled by
77 + // AccountPageHandler — NOT a REST route — because a browser GET to REST
78 + // carries no nonce and would be treated as anonymous.
79 + register_rest_route($namespace, '/' . $base . '/me/email', [
80 + [
81 + 'methods' => \WP_REST_Server::CREATABLE,
82 + 'callback' => [$this, 'updateMyEmail'],
83 + 'permission_callback' => [$this, 'checkCustomerPermission'],
84 + ],
85 + ]);
86 +
87 + // Re-send the confirmation email for an already-pending email change.
88 + register_rest_route($namespace, '/' . $base . '/me/email/resend', [
89 + [
90 + 'methods' => \WP_REST_Server::CREATABLE,
91 + 'callback' => [$this, 'resendMyEmailConfirmation'],
92 + 'permission_callback' => [$this, 'checkCustomerPermission'],
93 + ],
94 + ]);
95 +
96 + // Cancel (dismiss) a pending email change — deletes the stored token.
97 + register_rest_route($namespace, '/' . $base . '/me/email', [
98 + [
99 + 'methods' => \WP_REST_Server::DELETABLE,
100 + 'callback' => [$this, 'cancelMyEmailChange'],
101 + 'permission_callback' => [$this, 'checkCustomerPermission'],
102 + ],
103 + ]);
104 +
65 105 // Current customer's bookings
66 106 register_rest_route($namespace, '/' . $base . '/my-bookings', [
67 107 [
68 108 'methods' => \WP_REST_Server::READABLE,
@@ -251,15 +291,47 @@
251 291 try {
252 292 $userId = get_current_user_id();
253 293 $data = $request->get_json_params();
254 294
295 + // Email is the account login and is intentionally NOT editable from
296 + // the account profile. Strip it server-side so it can never be
297 + // changed via this endpoint, regardless of what the client sends.
298 + if (is_array($data)) {
299 + unset($data['email'], $data['user_email']);
300 + }
301 +
255 302 Logger::apiRequest('/customers/me', 'PUT', $data);
256 303
257 304 $customer = $this->customerService->getCustomerByUserId($userId);
258 305
259 306 if (!$customer) {
260 - Logger::warning("Customer profile not found for user", ['user_id' => $userId]);
261 - return $this->not_found(__('Customer profile not found', 'yatra'));
307 + // No customer record yet (e.g. registered but never booked).
308 + // Create one linked to this user so the profile persists instead
309 + // of failing. Email stays the account login (never client-set).
310 + $wpUser = get_user_by('id', $userId);
311 + if (!$wpUser) {
312 + return $this->not_found(__('Customer profile not found', 'yatra'));
313 + }
314 +
315 + $createData = CustomerValidator::sanitize($data);
316 + $createData['user_id'] = $userId;
317 + $createData['email'] = $wpUser->user_email;
318 + if (empty($createData['first_name'])) {
319 + $createData['first_name'] = $wpUser->first_name !== ''
320 + ? $wpUser->first_name
321 + : ($wpUser->display_name !== '' ? $wpUser->display_name : $wpUser->user_login);
322 + }
323 + if (empty($createData['last_name'])) {
324 + $createData['last_name'] = (string) $wpUser->last_name;
325 + }
326 +
327 + $createResult = $this->customerService->createCustomer($createData);
328 + if (empty($createResult['success'])) {
329 + Logger::warning('Could not create customer profile on update', ['user_id' => $userId, 'result' => $createResult]);
330 + return $this->error_response($createResult['message'] ?? __('Failed to save profile.', 'yatra'), 400);
331 + }
332 +
333 + return $this->success_response($this->customerService->getAccountProfileForUser($userId));
262 334 }
263 335
264 336 $customerId = (int) $customer['id'];
265 337
@@ -274,9 +346,15 @@
274 346 return $this->error_response($result['message'] ?? 'Failed to update customer profile', 400);
275 347 }
276 348
277 349 Logger::info("Customer profile updated successfully", ['customer_id' => $customerId, 'user_id' => $userId]);
278 - return $this->success_response($result['data']);
350 +
351 + // updateCustomer() returns success/message only — return the fresh
352 + // profile so the response carries the updated values (and avoids an
353 + // "undefined key data" warning).
354 + return $this->success_response(
355 + $this->customerService->getAccountProfileForUser($userId)
356 + );
279 357
280 358 } catch (\Exception $e) {
281 359 Logger::error("Failed to update customer profile", ['user_id' => $userId ?? 0, 'data' => $data ?? [], 'error' => $e->getMessage()]);
282 360 return $this->handle_exception($e);
@@ -283,8 +361,104 @@
283 361 }
284 362 }
285 363
286 364 /**
365 + * PUT /customers/me/password - Change the current customer's account password.
366 + *
367 + * Requires the correct current password, then sets the new one and refreshes
368 + * the auth cookie so the customer stays logged in (wp_set_password otherwise
369 + * invalidates the current session).
370 + */
371 + public function updateMyPassword(WP_REST_Request $request)
372 + {
373 + $userId = get_current_user_id();
374 + if ($userId <= 0) {
375 + return $this->error_response(__('Authentication required.', 'yatra'), 401);
376 + }
377 +
378 + $data = $request->get_json_params();
379 + $current = isset($data['current_password']) ? (string) $data['current_password'] : '';
380 + $newPassword = isset($data['new_password']) ? (string) $data['new_password'] : '';
381 +
382 + if ($current === '' || $newPassword === '') {
383 + return $this->error_response(__('Current and new password are required.', 'yatra'), 400);
384 + }
385 +
386 + $user = get_user_by('id', $userId);
387 + if (!$user || !wp_check_password($current, $user->user_pass, $userId)) {
388 + return $this->error_response(__('Your current password is incorrect.', 'yatra'), 400);
389 + }
390 +
391 + wp_set_password($newPassword, $userId);
392 +
393 + // wp_set_password() invalidates the session token / logs the user out.
394 + // Re-establish the current session so the account page stays authenticated.
395 + wp_set_current_user($userId);
396 + wp_set_auth_cookie($userId, true);
397 +
398 + Logger::info('Customer changed account password', ['user_id' => $userId]);
399 +
400 + return $this->success_response(['updated' => true]);
401 + }
402 +
403 + /**
404 + * POST /customers/me/email - Request a change to the account login email.
405 + *
406 + * Follows WordPress core's pending-change pattern: the email is NOT changed
407 + * here. A confirmation link is emailed to the NEW address; the change only
408 + * applies once the customer clicks it (confirmed by AccountPageHandler).
409 + */
410 + public function updateMyEmail(WP_REST_Request $request)
411 + {
412 + $userId = get_current_user_id();
413 + if ($userId <= 0) {
414 + return $this->error_response(__('Authentication required.', 'yatra'), 401);
415 + }
416 +
417 + $data = $request->get_json_params();
418 + $newEmail = '';
419 + if (is_array($data)) {
420 + $newEmail = (string) ($data['email'] ?? $data['new_email'] ?? '');
421 + }
422 +
423 + $result = $this->customerService->requestEmailChange($userId, $newEmail);
424 +
425 + return new WP_REST_Response($result, empty($result['success']) ? 400 : 200);
426 + }
427 +
428 + /**
429 + * POST /customers/me/email/resend - Re-send the confirmation link for a
430 + * pending email change (reuses the existing token; nothing else changes).
431 + */
432 + public function resendMyEmailConfirmation(WP_REST_Request $request)
433 + {
434 + $userId = get_current_user_id();
435 + if ($userId <= 0) {
436 + return $this->error_response(__('Authentication required.', 'yatra'), 401);
437 + }
438 +
439 + $result = $this->customerService->resendEmailChangeConfirmation($userId);
440 +
441 + return new WP_REST_Response($result, empty($result['success']) ? 400 : 200);
442 + }
443 +
444 + /**
445 + * DELETE /customers/me/email - Cancel (dismiss) a pending email change,
446 + * discarding the stored token so the emailed link stops working.
447 + */
448 + public function cancelMyEmailChange(WP_REST_Request $request)
449 + {
450 + $userId = get_current_user_id();
451 + if ($userId <= 0) {
452 + return $this->error_response(__('Authentication required.', 'yatra'), 401);
453 + }
454 +
455 + $result = $this->customerService->cancelEmailChange($userId);
456 +
457 + return new WP_REST_Response($result, empty($result['success']) ? 400 : 200);
458 + }
459 +
460 + /**
287 461 * GET /customers/my-bookings - Get current customer's bookings
288 462 */
289 463 public function getMyBookings(WP_REST_Request $request): WP_REST_Response
290 464 {
@@ -450,10 +624,25 @@
450 624 public function createCustomer(WP_REST_Request $request): WP_REST_Response
451 625 {
452 626 $data = $request->get_json_params();
453 627
628 + // Creating a WordPress login account is a higher-privilege action than
629 + // adding a CRM record, so it needs the WP user-creation capability. A
630 + // staffer who can manage customers but not create users simply gets a
631 + // CRM-only record — the request still succeeds.
632 + if (!empty($data['create_account']) && !current_user_can('create_users')) {
633 + unset($data['create_account']);
634 + }
635 +
454 636 $result = $this->customerService->createCustomer($data);
455 637
638 + // A confirmation request (email already has a login) is not an error — the
639 + // client shows a prompt and re-submits with confirm_link_existing. Return
640 + // 200 so it isn't treated as a failed request.
641 + if (!empty($result['needs_link_confirmation'])) {
642 + return new WP_REST_Response($result, 200);
643 + }
644 +
456 645 if (!$result['success']) {
457 646 return new WP_REST_Response($result, 400);
458 647 }
459 648
@@ -467,9 +656,23 @@
467 656 {
468 657 $id = (int) $request->get_param('id');
469 658 $data = $request->get_json_params();
470 659
660 + // Adding a login account from the edit form is the same higher-privilege
661 + // action as on create, so it needs the WP user-creation capability. A
662 + // staffer who can edit customers but not create users just saves the edit
663 + // without an account being made.
664 + if (is_array($data) && !empty($data['create_account']) && !current_user_can('create_users')) {
665 + unset($data['create_account']);
666 + }
667 +
471 668 $result = $this->customerService->updateCustomer($id, $data);
669 +
670 + // Confirmation request (email already has a login) — not an error; 200 so
671 + // the client can prompt and re-submit with confirm_link_existing.
672 + if (!empty($result['needs_link_confirmation'])) {
673 + return new WP_REST_Response($result, 200);
674 + }
472 675
473 676 if (!$result['success']) {
474 677 return new WP_REST_Response($result, 400);
475 678 }