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
← All changes | app/Controllers/AuthController.php +174 -30 3.0.2.8trunk View file →
@@ -293,8 +293,21 @@
293 293 'message' => __('New customer registration is disabled.', 'yatra'),
294 294 ], 403);
295 295 }
296 296
297 + // reCAPTCHA v3 (no-op unless the registration form is protected in settings).
298 + $recaptcha = \Yatra\Services\RecaptchaService::verifyForm(
299 + 'registration',
300 + (string) ($request->get_param('recaptcha_token') ?? ''),
301 + $_SERVER['REMOTE_ADDR'] ?? null
302 + );
303 + if (empty($recaptcha['success'])) {
304 + return new \WP_REST_Response([
305 + 'success' => false,
306 + 'message' => $recaptcha['message'] ?? __('reCAPTCHA verification failed.', 'yatra'),
307 + ], 400);
308 + }
309 +
297 310 $first_name = sanitize_text_field($request->get_param('first_name') ?? '');
298 311 $last_name = sanitize_text_field($request->get_param('last_name') ?? '');
299 312 $email = sanitize_email($request->get_param('email') ?? '');
300 313 $phone = sanitize_text_field($request->get_param('phone') ?? '');
@@ -483,14 +496,29 @@
483 496 * Handle email verification
484 497 */
485 498 public static function handleEmailVerification(): void
486 499 {
487 - $secure_token = get_query_var('yatra_verify_email');
488 -
489 - if (empty($secure_token)) {
500 + $secure_token = (string) get_query_var('yatra_verify_email');
501 + if ($secure_token === '' && isset($_GET['yatra_verify_email'])) {
502 + $raw = wp_unslash($_GET['yatra_verify_email']);
503 + $secure_token = is_string($raw) ? preg_replace('/[^a-zA-Z0-9_-]/', '', $raw) ?? '' : '';
504 + }
505 +
506 + if ($secure_token === '') {
507 + self::showVerificationError(__('Invalid verification link.', 'yatra'));
508 +
490 509 return;
491 510 }
492 511
512 + $previewToken = defined('YATRA_EMAIL_VERIFICATION_PREVIEW_TOKEN')
513 + ? (string) YATRA_EMAIL_VERIFICATION_PREVIEW_TOKEN
514 + : 'preview-verify-token';
515 + if ($secure_token === $previewToken) {
516 + self::showEmailVerificationPreviewNotice();
517 +
518 + return;
519 + }
520 +
493 521 // Decode token
494 522 $secure_token = str_replace(['-', '_'], ['+', '/'], $secure_token);
495 523 $decoded = base64_decode($secure_token);
496 524
@@ -514,67 +542,183 @@
514 542 }
515 543
516 544 $stored_token = get_user_meta($user_id, 'yatra_verification_token', true);
517 545 $token_expiry = get_user_meta($user_id, 'yatra_verification_token_expiry', true);
518 -
546 +
547 + // Idempotent path: the token+expiry are deleted as soon as a successful
548 + // verification completes (see below), so a second click on the same
549 + // link previously fell into the "Invalid or expired" branch and made
550 + // already-verified customers believe their account was broken. Detect
551 + // the prior-success state explicitly and reuse the success page so the
552 + // outcome is clear regardless of how many times the link is clicked.
553 + $already_verified = get_user_meta($user_id, 'yatra_email_verified', true) === '1';
554 + if ($already_verified) {
555 + self::showVerificationSuccess(true);
556 + return;
557 + }
558 +
519 559 if (empty($stored_token) || $stored_token !== $token) {
520 560 self::showVerificationError(__('Invalid or expired verification link.', 'yatra'));
521 561 return;
522 562 }
523 -
563 +
524 564 if ($token_expiry && time() > (int) $token_expiry) {
525 565 self::showVerificationError(__('This verification link has expired. Please register again.', 'yatra'));
526 566 return;
527 567 }
528 -
568 +
529 569 // Mark as verified
530 570 update_user_meta($user_id, 'yatra_email_verified', '1');
531 571 delete_user_meta($user_id, 'yatra_verification_token');
532 572 delete_user_meta($user_id, 'yatra_verification_token_expiry');
533 -
534 - $redirect_url = add_query_arg(['email_verified' => '1'], yatra_get_checkout_url());
535 - wp_safe_redirect($redirect_url);
536 - exit;
573 +
574 + self::showVerificationSuccess(false);
575 + return;
537 576 }
538 577
539 578 /**
540 579 * Send verification email
541 580 */
542 - private static function sendVerificationEmail(int $user_id, string $email, string $first_name, string $secure_token, bool $isResend = false): void
581 + private static function sendVerificationEmail(int $_user_id, string $email, string $first_name, string $secure_token, bool $isResend = false): void
543 582 {
544 - $verification_url = home_url('/yatra-verify-email/' . $secure_token . '/');
545 - $site_name = get_bloginfo('name');
546 - $subject = sprintf(__('[%s] Please verify your email address', 'yatra'), $site_name);
547 -
548 - $intro = $isResend
549 - ? __("You requested a new verification link for your account at %s.", 'yatra')
550 - : __("Thank you for registering at %s.", 'yatra');
551 -
552 - $message = sprintf(
553 - __("Hello %s,\n\n" . $intro . "\n\nPlease click the link below to verify your email address:\n\n%s\n\nThis link will expire in 24 hours.\n\nIf you did not " . ($isResend ? "request this" : "create this account") . ", please ignore this email.\n\nBest regards,\n%s", 'yatra'),
554 - $first_name,
555 - $site_name,
556 - $verification_url,
557 - $site_name
583 + $verificationUrl = function_exists('yatra_get_email_verification_url')
584 + ? yatra_get_email_verification_url($secure_token)
585 + : home_url('/yatra-verify-email/' . rawurlencode($secure_token) . '/');
586 +
587 + $siteName = get_bloginfo('name');
588 + $introParagraph = $isResend
589 + ? sprintf(
590 + /* translators: %s: site name */
591 + __('You requested a new verification link for your account at %s. Click the button below to verify your email address.', 'yatra'),
592 + $siteName
593 + )
594 + : sprintf(
595 + /* translators: %s: site name */
596 + __('Thank you for registering at %s. Please verify your email address to activate your account.', 'yatra'),
597 + $siteName
598 + );
599 +
600 + $footerNote = $isResend
601 + ? __('If you did not request this email, you can ignore it.', 'yatra')
602 + : __('If you did not create this account, you can ignore this email.', 'yatra');
603 +
604 + $expiryNoticeHtml = esc_html(
605 + sprintf(
606 + /* translators: %d: hours until link expiry */
607 + __('This verification link expires in %d hours for your security.', 'yatra'),
608 + 24
609 + )
558 610 );
559 611
560 - $headers = ['Content-Type: text/plain; charset=UTF-8'];
561 - wp_mail($email, $subject, $message, $headers);
612 + \Yatra\Services\TransactionalEmailTemplateService::sendIfEnabled(
613 + \Yatra\Services\TransactionalEmailTemplateService::TYPE_CUSTOMER_EMAIL_VERIFICATION,
614 + $email,
615 + [
616 + 'customer_first_name' => $first_name,
617 + 'customer_name' => $first_name,
618 + 'customer_email' => $email,
619 + 'verification_link' => $verificationUrl,
620 + 'intro_paragraph' => $introParagraph,
621 + 'footer_note' => $footerNote,
622 + 'expiry_notice_html' => $expiryNoticeHtml,
623 + ]
624 + );
562 625 }
563 626
564 627 /**
565 - * Show verification error page
628 + * Show verification error page (hard-fail dead-end with a route back home).
629 + * Every string is translatable — operators run Yatra in many locales and
630 + * the pre-3.0.5 hardcoded "Verification Failed" heading was untranslatable.
566 631 */
567 632 private static function showVerificationError(string $message): void
568 633 {
634 + $heading = esc_html__('Verification Failed', 'yatra');
635 + $cta = esc_html__('Go to Homepage', 'yatra');
636 +
569 637 wp_die(
570 638 '<div style="text-align: center; padding: 50px; font-family: -apple-system, BlinkMacSystemFont, \'Segoe UI\', Roboto, sans-serif;">
571 - <h1 style="color: #dc2626; margin-bottom: 20px;">Verification Failed</h1>
639 + <h1 style="color: #dc2626; margin-bottom: 20px;">' . $heading . '</h1>
572 640 <p style="color: #4b5563; font-size: 16px; margin-bottom: 30px;">' . esc_html($message) . '</p>
573 - <a href="' . esc_url(home_url()) . '" style="display: inline-block; background: #3b82f6; color: #fff; padding: 12px 24px; border-radius: 8px; text-decoration: none; font-weight: 600;">Go to Homepage</a>
641 + <a href="' . esc_url(home_url()) . '" style="display: inline-block; background: #3b82f6; color: #fff; padding: 12px 24px; border-radius: 8px; text-decoration: none; font-weight: 600;">' . $cta . '</a>
574 642 </div>',
575 643 __('Verification Failed', 'yatra'),
576 644 ['response' => 400]
645 + );
646 + }
647 +
648 + /**
649 + * Render an unambiguous "email verified" confirmation page.
650 + *
651 + * The pre-3.0.5 flow silently 302-redirected to the checkout URL with a
652 + * `?email_verified=1` flag, relying on `booking-auth.php` to surface a
653 + * one-liner notice. That notice only renders when the auth form itself
654 + * renders (no booking session / logged-in user → notice never shown), so
655 + * customers regularly saw "nothing happened" after clicking the link.
656 + *
657 + * Now we render a dedicated success page with explicit confirmation, the
658 + * verified email-state, and a primary CTA back into the booking flow.
659 + * Idempotent: a second click on the same link reaches `$already=true`
660 + * and shows "Your email is already verified" instead of the misleading
661 + * "Invalid or expired link" error.
662 + *
663 + * @param bool $already True when the user has already been verified by an
664 + * earlier click on the same link (idempotent path).
665 + */
666 + private static function showVerificationSuccess(bool $already): void
667 + {
668 + $checkoutUrl = function_exists('yatra_get_checkout_url')
669 + ? yatra_get_checkout_url()
670 + : home_url('/');
671 + $continueUrl = add_query_arg(['email_verified' => '1'], $checkoutUrl);
672 +
673 + $heading = $already
674 + ? esc_html__('Email Already Verified', 'yatra')
675 + : esc_html__('Email Verified', 'yatra');
676 + $message = $already
677 + ? esc_html__('Your email address is already verified — no further action is needed. You can continue with your booking.', 'yatra')
678 + : esc_html__('Your email address has been verified successfully. You can now log in and continue with your booking.', 'yatra');
679 + $cta = esc_html__('Continue to Checkout', 'yatra');
680 + $homeCta = esc_html__('Go to Homepage', 'yatra');
681 + $title = $already
682 + ? __('Email Already Verified', 'yatra')
683 + : __('Email Verified', 'yatra');
684 +
685 + // Inline-only styling so the page renders correctly regardless of
686 + // theme stylesheet load order (wp_die() can fire before themes
687 + // enqueue their styles).
688 + $body = '<div style="text-align: center; padding: 50px 20px; max-width: 520px; margin: 0 auto; font-family: -apple-system, BlinkMacSystemFont, \'Segoe UI\', Roboto, sans-serif;">'
689 + . '<div style="display: inline-flex; align-items: center; justify-content: center; width: 72px; height: 72px; border-radius: 50%; background: #d1fae5; margin: 0 auto 24px;">'
690 + . '<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="#059669" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">'
691 + . '<polyline points="20 6 9 17 4 12"></polyline>'
692 + . '</svg>'
693 + . '</div>'
694 + . '<h1 style="color: #065f46; margin: 0 0 12px; font-size: 26px;">' . $heading . '</h1>'
695 + . '<p style="color: #4b5563; font-size: 16px; line-height: 1.6; margin: 0 0 28px;">' . $message . '</p>'
696 + . '<a href="' . esc_url($continueUrl) . '" style="display: inline-block; background: #059669; color: #fff; padding: 12px 28px; border-radius: 8px; text-decoration: none; font-weight: 600; margin-right: 8px;">' . $cta . '</a>'
697 + . '<a href="' . esc_url(home_url()) . '" style="display: inline-block; color: #4b5563; padding: 12px 16px; text-decoration: none; font-weight: 500;">' . $homeCta . '</a>'
698 + . '</div>';
699 +
700 + wp_die($body, $title, ['response' => 200]);
701 + }
702 +
703 + /**
704 + * Inform users that the link is only for email template previews.
705 + */
706 + private static function showEmailVerificationPreviewNotice(): void
707 + {
708 + $body = '<div style="text-align: center; padding: 50px; max-width: 520px; margin: 0 auto; font-family: -apple-system, BlinkMacSystemFont, \'Segoe UI\', Roboto, sans-serif;">
709 + <h1 style="color: #1e40af; margin-bottom: 16px;">' . esc_html__('Sample verification link', 'yatra') . '</h1>
710 + <p style="color: #4b5563; font-size: 16px; line-height: 1.6; margin-bottom: 24px;">' . esc_html__(
711 + 'This URL is used only in email previews and test messages. It does not verify an account. Use the link from your real verification email to activate your account.',
712 + 'yatra'
713 + ) . '</p>
714 + <a href="' . esc_url(home_url()) . '" style="display: inline-block; background: #3b82f6; color: #fff; padding: 12px 24px; border-radius: 8px; text-decoration: none; font-weight: 600;">' . esc_html__('Go to homepage', 'yatra') . '</a>
715 + </div>';
716 +
717 + wp_die(
718 + $body,
719 + __('Email verification (preview)', 'yatra'),
720 + ['response' => 200]
577 721 );
578 722 }
579 723 }
580 724