Add open student registration with email confirmation and approval
CI / Tests (PHP 8.1) (pull_request) Successful in 1m18s
CI / Tests (PHP 8.2) (pull_request) Successful in 1m18s
CI / No Debug Code (pull_request) Successful in 2s
CI / PHPStan (pull_request) Successful in 3m20s
CI / Coding Standards (pull_request) Successful in 3m25s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m33s
CI / Build Plugin Zip (pull_request) Skipped
CI / Tests (PHP 8.1) (pull_request) Successful in 1m18s
CI / Tests (PHP 8.2) (pull_request) Successful in 1m18s
CI / No Debug Code (pull_request) Successful in 2s
CI / PHPStan (pull_request) Successful in 3m20s
CI / Coding Standards (pull_request) Successful in 3m25s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m33s
CI / Build Plugin Zip (pull_request) Skipped
Students could previously join by invite only. Add an optional self-approval mode, toggled from Studio Settings → Registration: anyone may sign up on the existing [us_student_register] page, confirm their email via a tokenised link, and then be approved by a studio admin before the account is usable. - Enabling the toggle mirrors WordPress's own membership settings (users_can_register + default_role = us_student) and snapshots their previous values so disabling restores them. - WordPress's native registration form is blocked while open registration is on (login_init redirect + registration_errors fail-safe + register_url) so it cannot bypass signup policy acceptance. - Pending accounts: unconfirmed email cannot log in; confirmed but unapproved can log in but the booking capability is withheld and the booking page shows an "awaiting approval" screen. - Approve/reject from Students → Pending Students; reject hard-deletes the account so the email is freed to re-apply. - Invite registration is unchanged; both modes coexist. Account lifecycle lives in user meta (RegistrationStatus); no new tables. Closes #63 Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
@@ -16,6 +16,18 @@ class StudioSettings {
|
||||
public const OPT_ETRANSFER_EMAIL = 'us_etransfer_email';
|
||||
public const OPT_HST_RATE = 'us_hst_rate';
|
||||
|
||||
public const OPT_REGISTRATION_MODE = 'us_registration_mode';
|
||||
public const MODE_INVITE = 'invite';
|
||||
public const MODE_SELF_APPROVAL = 'self_approval';
|
||||
|
||||
/**
|
||||
* Snapshots of the two core WordPress options this feature takes over while
|
||||
* open registration is enabled, so disabling restores them exactly rather
|
||||
* than clobbering a site that set them for its own reasons.
|
||||
*/
|
||||
public const OPT_PREV_USERS_CAN_REGISTER = 'us_registration_prev_can_register';
|
||||
public const OPT_PREV_DEFAULT_ROLE = 'us_registration_prev_default_role';
|
||||
|
||||
public function publishableKey(): string {
|
||||
return Val::string( get_option( self::OPT_PUBLISHABLE, '' ) );
|
||||
}
|
||||
@@ -65,6 +77,24 @@ class StudioSettings {
|
||||
return '' !== $this->publishableKey() && '' !== $this->secretKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Which student registration mode is active: `invite` (default) — only a
|
||||
* valid invite token grants the registration form — or `self_approval` —
|
||||
* anyone may sign up, confirm their email, and await studio approval.
|
||||
*/
|
||||
public function registrationMode(): string {
|
||||
return self::MODE_SELF_APPROVAL === get_option( self::OPT_REGISTRATION_MODE, self::MODE_INVITE )
|
||||
? self::MODE_SELF_APPROVAL
|
||||
: self::MODE_INVITE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether anyone may self-register (the `self_approval` mode).
|
||||
*/
|
||||
public function openRegistrationEnabled(): bool {
|
||||
return self::MODE_SELF_APPROVAL === $this->registrationMode();
|
||||
}
|
||||
|
||||
public function renderPage(): void {
|
||||
if ( ! current_user_can( RoleManager::CAP_MANAGE_BILLING ) ) {
|
||||
wp_die( esc_html__( 'You do not have permission to manage billing settings.', 'unsupervised-schedular' ) );
|
||||
@@ -86,6 +116,7 @@ class StudioSettings {
|
||||
$etransferEmail = $this->etransferEmail();
|
||||
$hstRate = $this->hstRate();
|
||||
$stripeConfigured = $this->isStripeConfigured();
|
||||
$openRegistration = $this->openRegistrationEnabled();
|
||||
|
||||
include USC_PLUGIN_DIR . 'templates/admin/settings.php';
|
||||
}
|
||||
@@ -111,6 +142,43 @@ class StudioSettings {
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Val::float() coerces to float; slashes cannot survive numeric coercion.
|
||||
$hstRate = isset( $_POST['hst_rate'] ) ? Val::float( $_POST['hst_rate'] ) : 0.0;
|
||||
update_option( self::OPT_HST_RATE, max( 0.0, $hstRate ) );
|
||||
|
||||
$this->applyRegistrationMode( isset( $_POST['open_registration'] ) );
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Missing
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable open (self-approval) registration, mirroring the change
|
||||
* into the two core WordPress options it depends on.
|
||||
*
|
||||
* Enabling snapshots the current `users_can_register` and `default_role`,
|
||||
* then turns registration on and makes Student the default new-user role.
|
||||
* Disabling restores that snapshot, so this toggle never permanently
|
||||
* overwrites a site's own membership settings. Only transitions act, so
|
||||
* saving unrelated settings leaves the core options untouched.
|
||||
*/
|
||||
private function applyRegistrationMode( bool $enable ): void {
|
||||
$currentlyOpen = $this->openRegistrationEnabled();
|
||||
|
||||
if ( $enable && ! $currentlyOpen ) {
|
||||
update_option( self::OPT_PREV_USERS_CAN_REGISTER, get_option( 'users_can_register' ) ? '1' : '0' );
|
||||
update_option( self::OPT_PREV_DEFAULT_ROLE, Val::string( get_option( 'default_role', 'subscriber' ) ) );
|
||||
|
||||
update_option( 'users_can_register', '1' );
|
||||
update_option( 'default_role', RoleManager::STUDENT );
|
||||
update_option( self::OPT_REGISTRATION_MODE, self::MODE_SELF_APPROVAL );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $enable && $currentlyOpen ) {
|
||||
$prevCanRegister = '1' === Val::string( get_option( self::OPT_PREV_USERS_CAN_REGISTER, '0' ) );
|
||||
$prevRole = Val::string( get_option( self::OPT_PREV_DEFAULT_ROLE, 'subscriber' ) );
|
||||
|
||||
update_option( 'users_can_register', $prevCanRegister ? '1' : '0' );
|
||||
update_option( 'default_role', '' !== $prevRole ? $prevRole : 'subscriber' );
|
||||
delete_option( self::OPT_PREV_USERS_CAN_REGISTER );
|
||||
delete_option( self::OPT_PREV_DEFAULT_ROLE );
|
||||
update_option( self::OPT_REGISTRATION_MODE, self::MODE_INVITE );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user