diff --git a/docs/features/account-registration.md b/docs/features/account-registration.md index 16297ce..7c49b99 100644 --- a/docs/features/account-registration.md +++ b/docs/features/account-registration.md @@ -41,12 +41,20 @@ recorded in `us_policy_acceptances` with `registration_type = account` and ## Admin Interface **Invites** in wp-admin (`manage_students`, studio admin only): +- Select the **registration page** (the page hosting `[us_student_register]`), stored in the `us_registration_page_id` option; invitation links point there (falling back to the home page if unset) - Invite an email (creates a pending invite + link) - List pending invites; revoke an invite ## Frontend Shortcode - `[us_student_register]` — the registration page. Shows the form for a valid pending invite; otherwise shows an "by invitation only" message (in `invite` mode). +## Token Redirect +A `template_redirect` handler (`RegistrationPage::maybeRedirectToRegistrationPage()`) +sends any front-end request carrying a `us_invite` token to the configured +registration page (preserving the token), unless it is already on that page. This +covers invitation links generated/shared before a registration page was selected. +No-op when no registration page is set. + ## Capabilities - `manage_students` — manage invites (studio admin; administrators inherit it via the `user_has_cap` filter). Added to `RoleManager::STUDIO_ADMIN_CAPS`. diff --git a/src/Auth/RegistrationController.php b/src/Auth/RegistrationController.php index f70f5e6..2bc1308 100644 --- a/src/Auth/RegistrationController.php +++ b/src/Auth/RegistrationController.php @@ -5,6 +5,11 @@ namespace Unsupervised\Schedular\Auth; class RegistrationController { + /** + * Option storing the page ID that hosts the [us_student_register] shortcode. + */ + public const OPTION_PAGE = 'us_registration_page_id'; + public function __construct( private InviteRepository $invites ) {} public function renderPage(): void { @@ -16,7 +21,9 @@ class RegistrationController { $this->handleFormAction(); } - $pendingInvites = $this->invites->findPending(); + $pendingInvites = $this->invites->findPending(); + $registrationPageId = (int) get_option( self::OPTION_PAGE, 0 ); + $registrationPageUrl = $registrationPageId > 0 ? (string) get_permalink( $registrationPageId ) : ''; include USC_PLUGIN_DIR . 'templates/admin/invites.php'; } @@ -26,6 +33,10 @@ class RegistrationController { // phpcs:disable WordPress.Security.NonceVerification.Missing $action = sanitize_key( wp_unslash( $_POST['usc_action'] ?? '' ) ); + if ( 'set_page' === $action ) { + update_option( self::OPTION_PAGE, absint( $_POST['registration_page_id'] ?? 0 ) ); + } + if ( 'invite' === $action ) { $email = sanitize_email( wp_unslash( $_POST['email'] ?? '' ) ); diff --git a/src/Auth/RegistrationPage.php b/src/Auth/RegistrationPage.php index 0603231..bcafff2 100644 --- a/src/Auth/RegistrationPage.php +++ b/src/Auth/RegistrationPage.php @@ -52,6 +52,31 @@ class RegistrationPage { return (string) ob_get_clean(); } + /** + * Redirect to the configured registration page when an invite token lands + * elsewhere (e.g. a link generated before the page was selected). Hooked on + * `template_redirect`. + */ + public function maybeRedirectToRegistrationPage(): void { + if ( is_admin() ) { + return; + } + + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only token used only to build the redirect target. + $token = sanitize_text_field( wp_unslash( $_GET['us_invite'] ?? '' ) ); + if ( '' === $token ) { + return; + } + + $pageId = (int) get_option( RegistrationController::OPTION_PAGE, 0 ); + if ( $pageId <= 0 || is_page( $pageId ) ) { + return; + } + + wp_safe_redirect( add_query_arg( 'us_invite', rawurlencode( $token ), (string) get_permalink( $pageId ) ) ); + exit; + } + /** * Process the submitted registration. Returns true on success or an error * message string on failure. diff --git a/src/ShortcodeRegistrar.php b/src/ShortcodeRegistrar.php index 24a1762..0fb3501 100644 --- a/src/ShortcodeRegistrar.php +++ b/src/ShortcodeRegistrar.php @@ -32,6 +32,7 @@ class ShortcodeRegistrar { add_shortcode( 'us_booking', [ $this->bookingPage, 'render' ] ); add_shortcode( 'us_student_login', [ $this->loginPage, 'render' ] ); add_shortcode( 'us_student_register', [ $this->registrationPage, 'render' ] ); + add_action( 'template_redirect', [ $this->registrationPage, 'maybeRedirectToRegistrationPage' ] ); add_action( 'wp_enqueue_scripts', [ $this, 'enqueueAssets' ] ); } diff --git a/templates/admin/invites.php b/templates/admin/invites.php index b5ce256..0d7c19f 100644 --- a/templates/admin/invites.php +++ b/templates/admin/invites.php @@ -5,12 +5,46 @@ if (! defined('ABSPATH')) { exit; } -/** @var list<\Unsupervised\Schedular\Auth\Invite> $pendingInvites */ +/** + * @var list<\Unsupervised\Schedular\Auth\Invite> $pendingInvites + * @var int $registrationPageId + * @var string $registrationPageUrl + */ ?>