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:
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Unsupervised\Schedular\Auth;
|
||||
|
||||
use Unsupervised\Schedular\Val;
|
||||
|
||||
/**
|
||||
* Admin page (Students → Pending Students) for reviewing self-signup accounts:
|
||||
* approve a confirmed applicant into a full student, or reject (delete) them.
|
||||
* Only relevant while open registration is enabled.
|
||||
*/
|
||||
class RegistrationApprovalController {
|
||||
|
||||
public const PAGE_SLUG = 'us-pending-students';
|
||||
public const NONCE_ACTION = 'usc_registration_approval';
|
||||
|
||||
public function __construct( private RegistrationMailer $mailer ) {}
|
||||
|
||||
public function renderPage(): void {
|
||||
if ( ! current_user_can( RoleManager::CAP_MANAGE_STUDENTS ) ) {
|
||||
wp_die( esc_html__( 'You do not have permission to manage student registrations.', 'unsupervised-schedular' ) );
|
||||
}
|
||||
|
||||
if ( isset( $_POST['usc_action'] ) && check_admin_referer( self::NONCE_ACTION ) ) {
|
||||
$this->handleAction();
|
||||
}
|
||||
|
||||
$awaitingApproval = [];
|
||||
$awaitingConfirmation = [];
|
||||
foreach ( $this->pendingUsers() as $user ) {
|
||||
if ( RegistrationStatus::emailConfirmed( (int) $user->ID ) ) {
|
||||
$awaitingApproval[] = $user;
|
||||
} else {
|
||||
$awaitingConfirmation[] = $user;
|
||||
}
|
||||
}
|
||||
|
||||
include USC_PLUGIN_DIR . 'templates/admin/registrations.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Approve or reject the posted user. Approval clears the pending flags and
|
||||
* emails the student; rejection emails them, then hard-deletes the account so
|
||||
* the email is freed to re-apply.
|
||||
*/
|
||||
private function handleAction(): void {
|
||||
// Nonce is verified by the caller (renderPage) before this method runs.
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Missing
|
||||
$action = sanitize_key( Val::string( wp_unslash( $_POST['usc_action'] ?? '' ) ) );
|
||||
$userId = absint( Val::int( $_POST['user_id'] ?? 0 ) );
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Missing
|
||||
|
||||
if ( $userId <= 0 || ! RegistrationStatus::isAwaitingApproval( $userId ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( 'approve' === $action ) {
|
||||
RegistrationStatus::approve( $userId );
|
||||
$user = get_user_by( 'id', $userId );
|
||||
if ( $user instanceof \WP_User ) {
|
||||
$this->mailer->sendApproved( $user );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ( 'reject' === $action ) {
|
||||
$user = get_user_by( 'id', $userId );
|
||||
$email = $user instanceof \WP_User ? (string) $user->user_email : '';
|
||||
if ( '' !== $email ) {
|
||||
$this->mailer->sendRejected( $email );
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'wp_delete_user' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/user.php';
|
||||
}
|
||||
wp_delete_user( $userId );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Every account still awaiting approval (confirmed or not).
|
||||
*
|
||||
* @return list<\WP_User>
|
||||
*/
|
||||
private function pendingUsers(): array {
|
||||
return array_values(
|
||||
array_filter(
|
||||
get_users(
|
||||
[
|
||||
'meta_key' => RegistrationStatus::META_AWAITING_APPROVAL, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
|
||||
'meta_value' => '1', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
|
||||
'number' => 500,
|
||||
'orderby' => 'user_registered',
|
||||
'order' => 'ASC',
|
||||
]
|
||||
),
|
||||
static fn( mixed $user ): bool => $user instanceof \WP_User
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user