Let the account holder edit their own profile details
CI / Tests (PHP 8.1) (pull_request) Successful in 45s
CI / No Debug Code (pull_request) Successful in 2s
CI / Tests (PHP 8.2) (pull_request) Successful in 55s
CI / PHPStan (pull_request) Successful in 2m57s
CI / Coding Standards (pull_request) Successful in 3m3s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m42s
CI / Build Plugin Zip (pull_request) Skipped
CI / Tests (PHP 8.1) (pull_request) Successful in 45s
CI / No Debug Code (pull_request) Successful in 2s
CI / Tests (PHP 8.2) (pull_request) Successful in 55s
CI / PHPStan (pull_request) Successful in 2m57s
CI / Coding Standards (pull_request) Successful in 3m3s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m42s
CI / Build Plugin Zip (pull_request) Skipped
The Profile block is headed "Your profile", but the one person on it you could not change was yourself: your name, your birth year, and whether you take lessons yourself were fixed at whatever signup recorded, and correcting any of them meant asking a studio admin. A "Your details" section now opens the page, saved through the same nonce-checked template_redirect post/redirect/get path the child rows use: - Your name, written to display_name and nickname together, for the reason updateChild() does — UserName reads the nickname first, and leaving it behind would put the account's email address back on every screen that names a person. - "I take lessons myself", the positive of us_guardian_only. This makes good on the claim already in bookableStudents() and the feature doc that a guardian-only account can put itself right from the profile page. - Your birth year, held to the same normaliseBirthYear() rule as every other student. The email is shown but not editable: it is the account's user_login as well as its address, so changing it stays a studio-side job. The birth-year field deliberately carries no `required` attribute. It is asked of a student only, and this page loads no JavaScript, so a browser-enforced `required` would leave a guardian who books solely for other people unable to submit the form at all; handleSelf() enforces it against the checkbox instead. Unticking the box does not clear a stored birth year — it says who books, not "forget what is on file". Closes #165 Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -10,8 +10,8 @@ use Unsupervised\Schedular\Registration\QuestionRepository;
|
||||
use Unsupervised\Schedular\Val;
|
||||
|
||||
/**
|
||||
* The guardian's "my family" screen (`[us_family]`): list, add, edit and remove
|
||||
* the children they book for.
|
||||
* The guardian's "my family" screen (`[us_family]`): their own details, plus
|
||||
* list, add, edit and remove the children they book for.
|
||||
*
|
||||
* Submissions are processed on `template_redirect` — before any output — and
|
||||
* post/redirect/get back to the page, so a refresh cannot resubmit and add the
|
||||
@@ -23,6 +23,7 @@ class FamilyPage {
|
||||
private const RESULT_ADDED = 'added';
|
||||
private const RESULT_UPDATED = 'updated';
|
||||
private const RESULT_REMOVED = 'removed';
|
||||
private const RESULT_SELF = 'self';
|
||||
|
||||
/**
|
||||
* Error from the most recent submission processed on `template_redirect`,
|
||||
@@ -58,6 +59,7 @@ class FamilyPage {
|
||||
|
||||
$userId = get_current_user_id();
|
||||
|
||||
$self = $this->guardians->accountHolder( $userId );
|
||||
$children = $this->guardians->children( $userId );
|
||||
$questions = $this->questions->findByScope( Question::SCOPE_ACCOUNT, activeOnly: true );
|
||||
$error = $this->submitError;
|
||||
@@ -99,6 +101,7 @@ class FamilyPage {
|
||||
'add' => $this->handleAdd( $userId ),
|
||||
'edit' => $this->handleEdit( $userId ),
|
||||
'remove' => $this->handleRemove( $userId ),
|
||||
'self' => $this->handleSelf( $userId ),
|
||||
default => new \WP_Error( 'unknown_action', __( 'Unrecognised request.', 'unsupervised-schedular' ) ),
|
||||
};
|
||||
|
||||
@@ -149,6 +152,21 @@ class FamilyPage {
|
||||
return $error ?? self::RESULT_UPDATED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the account holder's own details. The birth year is only asked of a
|
||||
* student, so it is the checkbox — not the browser — that decides whether one
|
||||
* is required; the field carries no `required` attribute, or a guardian who
|
||||
* books only for other people could never submit the form at all.
|
||||
*/
|
||||
private function handleSelf( int $userId ): string|\WP_Error {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce checked by the caller.
|
||||
$isStudent = isset( $_POST['is_student'] );
|
||||
|
||||
$error = $this->guardians->updateSelf( $userId, $this->postString( 'own_name' ), $this->postString( 'own_birth_year' ), $isStudent );
|
||||
|
||||
return $error ?? self::RESULT_SELF;
|
||||
}
|
||||
|
||||
private function handleRemove( int $guardianId ): string|\WP_Error {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce checked by the caller.
|
||||
$childId = absint( Val::int( $_POST['child_id'] ?? 0 ) );
|
||||
@@ -244,6 +262,7 @@ class FamilyPage {
|
||||
self::RESULT_ADDED => __( 'Student added.', 'unsupervised-schedular' ),
|
||||
self::RESULT_UPDATED => __( 'Details updated.', 'unsupervised-schedular' ),
|
||||
self::RESULT_REMOVED => __( 'Student removed.', 'unsupervised-schedular' ),
|
||||
self::RESULT_SELF => __( 'Your details have been updated.', 'unsupervised-schedular' ),
|
||||
default => '',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -163,6 +163,52 @@ class GuardianService {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the account holder's own details from the profile screen: their
|
||||
* name, whether they are a student in their own right, and — when they are —
|
||||
* their birth year.
|
||||
*
|
||||
* `$isStudent` is the positive of what {@see META_GUARDIAN_ONLY} stores, so
|
||||
* the form can ask the question the way a person would answer it and this is
|
||||
* the single place the sense is flipped.
|
||||
*
|
||||
* Returns null on success, mirroring {@see updateChild()}.
|
||||
*/
|
||||
public function updateSelf( int $userId, string $name, string $birthYear, bool $isStudent ): ?\WP_Error {
|
||||
$name = trim( $name );
|
||||
if ( '' === $name ) {
|
||||
return new \WP_Error( 'missing_name', __( 'Please give your name.', 'unsupervised-schedular' ) );
|
||||
}
|
||||
|
||||
if ( $isStudent && 0 === self::normaliseBirthYear( $birthYear ) ) {
|
||||
return new \WP_Error( 'missing_birth_year', self::ownBirthYearError() );
|
||||
}
|
||||
|
||||
$result = wp_update_user(
|
||||
[
|
||||
'ID' => $userId,
|
||||
'display_name' => $name,
|
||||
'nickname' => $name,
|
||||
]
|
||||
);
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$this->setGuardianOnly( $userId, ! $isStudent );
|
||||
|
||||
// Only written when they are a student. Saying "I only book for other
|
||||
// people" is a statement about who books, not an instruction to forget a
|
||||
// year already on file — and someone who ticks the box back on the next
|
||||
// visit should find their own details as they left them.
|
||||
if ( $isStudent ) {
|
||||
$this->setBirthYear( $userId, $birthYear );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink a child and delete their account. Refused once the child has any
|
||||
* lesson or enrolment history: their id is referenced by lessons, payments and
|
||||
@@ -319,6 +365,26 @@ class GuardianService {
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* The account holder's own details, as the profile screen's form needs them.
|
||||
* The counterpart to {@see children()} for the person reading the page.
|
||||
*
|
||||
* `is_student` is the positive of {@see META_GUARDIAN_ONLY} — see
|
||||
* {@see updateSelf()}, which reads it back the same way round.
|
||||
*
|
||||
* @return array{name: string, email: string, birth_year: string, is_student: bool}
|
||||
*/
|
||||
public function accountHolder( int $userId ): array {
|
||||
$user = get_userdata( $userId );
|
||||
|
||||
return [
|
||||
'name' => UserName::format( $user instanceof \WP_User ? $user : null, $userId ),
|
||||
'email' => $user instanceof \WP_User ? $user->user_email : '',
|
||||
'birth_year' => $this->birthYear( $userId ),
|
||||
'is_student' => ! self::isGuardianOnly( $userId ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* The guardian behind a child, or null when the student books for themselves.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user