Ask who the signup is for as a three-way choice
CI / Tests (PHP 8.2) (pull_request) Successful in 50s
CI / PHPStan (pull_request) Successful in 2m53s
CI / Coding Standards (pull_request) Successful in 2m57s
CI / Tests (PHP 8.1) (pull_request) Successful in 45s
CI / No Debug Code (pull_request) Successful in 2s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m40s
CI / Build Plugin Zip (pull_request) Skipped

Replaces the single "I'm registering as a parent or guardian" tick with
"Just myself" / "On behalf of one or more students" / "Both".

Radios, not checkboxes as the feedback put it: the three answers are
mutually exclusive, and "both" only means anything as a third choice
alongside the other two.

The tick could only ever say whether there were children to add. It could
not say whether the account holder was a student, so bookableStudents()
always offered them their own name and any guardian could book themselves a
lesson nobody meant to sell. "On behalf of" now records us_guardian_only and
leaves them out of the picker.

That flag is stored as the negative on purpose. Every account predating this
choice is a bookable student, and absence has to keep meaning exactly that,
or the picker would quietly stop offering people themselves on upgrade.
setGuardianOnly() clears the key rather than writing 0, so "not set" stays
the single spelling of "yes, a student". A guardian-only account with nobody
linked to it is still offered itself — an empty picker is no way to book at
all, and they can put the account right from the profile page.

An unrecognised or absent value reads as "just myself": the choice that
collects the least and grants the least. A missing radio must never be taken
as "register these children".

Bumps to 1.4.0.

The account holder's own questions stay out of play whenever students are
being added, "both" included — asking them there is #146.

Verified the form in a headless browser across all three choices: which
blocks show, which fields carry `required`, whether the account holder's
question panel is disabled, which submit is offered, and that switching back
to "just myself" leaves no hidden required field blocking submit.

Closes #145

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-07-29 23:09:47 -03:00
co-authored by Claude Opus 5
parent 28046e0fd1
commit 4e5382e259
9 changed files with 319 additions and 43 deletions
+43 -1
View File
@@ -34,6 +34,17 @@ class GuardianService {
*/
public const META_DOB = 'us_date_of_birth';
/**
* Set on an account that registered **only** to book for other people, so it
* is not offered as a student in its own right.
*
* Stored as the negative on purpose. Every account that existed before this
* choice was offered is a bookable student, and absence of the flag has to
* keep meaning exactly that — otherwise the picker would quietly stop
* offering people themselves on upgrade.
*/
public const META_GUARDIAN_ONLY = 'us_guardian_only';
/**
* The earliest birth year the form will accept. Old enough for any student a
* studio will ever enrol, and late enough to reject a typo like `19` or `190`
@@ -228,7 +239,9 @@ class GuardianService {
* their kid is the error worth designing out.
*
* The guardian is still offered, last, so a parent taking lessons alongside
* their children can book for themselves from the same account.
* their children can book for themselves from the same account — unless they
* said at signup that they are not a student, in which case offering them is
* an invitation to book a lesson nobody meant to buy.
*
* @return list<array{id: int, name: string, is_self: bool}>
*/
@@ -243,6 +256,13 @@ class GuardianService {
];
}
// A guardian-only account with nobody linked to it would otherwise get an
// empty list and no way to book at all. Offering them themselves is the
// lesser wrong: they can still correct the account from the profile page.
if ( self::isGuardianOnly( $userId ) && [] !== $out ) {
return $out;
}
$self = get_userdata( $userId );
$out[] = [
@@ -254,6 +274,28 @@ class GuardianService {
return $out;
}
/**
* Whether this account books only for other people. False for every account
* that predates the choice — see {@see META_GUARDIAN_ONLY}.
*/
public static function isGuardianOnly( int $userId ): bool {
return '1' === Val::string( get_user_meta( $userId, self::META_GUARDIAN_ONLY, true ) );
}
/**
* Record whether this account is a student in its own right. Clears the flag
* rather than storing a `0`, so "not set" stays the single meaning of "yes,
* they are a student".
*/
public function setGuardianOnly( int $userId, bool $guardianOnly ): void {
if ( $guardianOnly ) {
update_user_meta( $userId, self::META_GUARDIAN_ONLY, '1' );
return;
}
delete_user_meta( $userId, self::META_GUARDIAN_ONLY );
}
/**
* A guardian's children, in link order, with the details the family and admin
* screens display.