1e4e21e8d3581ee6ffb66ff8d258597d8f702f2f
220
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1e4e21e8d3 |
Merge pull request 'Show a recurring lesson's policy acceptances and intake answers on every week of it' (#168) from fix/167-series-policy-acceptances into main
CI / Tests (PHP 8.2) (push) Successful in 44s
CI / Tests (PHP 8.1) (push) Successful in 54s
CI / No Debug Code (push) Successful in 1s
CI / PHPStan (push) Successful in 2m54s
CI / Coding Standards (push) Successful in 2m58s
CI / Tests (PHP 8.3) (push) Successful in 2m43s
Release / Build and Publish Release (push) Successful in 2m58s
Release / Open next-version bump PR (push) Successful in 4s
CI / Build Plugin Zip (push) Successful in 2m46s
Reviewed-on: #168v1.5.0 |
||
|
|
df3462a8b3
|
Show a series' policy acceptances on every occurrence
CI / Tests (PHP 8.2) (pull_request) Successful in 44s
CI / Tests (PHP 8.1) (pull_request) Successful in 54s
CI / No Debug Code (pull_request) Successful in 2s
CI / PHPStan (pull_request) Successful in 2m54s
CI / Coding Standards (pull_request) Successful in 2m59s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m43s
CI / Build Plugin Zip (pull_request) Skipped
A weekly booking reserves a series of lessons, but the student answers the intake and ticks the policy boxes once — so BookingEndpoint records both against the anchor lesson alone. The admin detail view looked them up by whichever lesson id was being viewed, so every occurrence after the first showed no answers and no acceptances at all. LessonDetail now takes the Lesson rather than a bare id and resolves the registration to `series_id ?? id`, so each occurrence reads the anchor's records. This is the same seam PaymentService already uses to find a series lesson's payment on the anchor. Nothing was ever missing from the database, so existing bookings read correctly with no migration and no schema change. Closes #167 Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
748478f2f1 |
Merge pull request 'Let the account holder edit their own details on the profile page' (#166) from feature/165-editable-own-profile into main
CI / Tests (PHP 8.1) (push) Successful in 46s
CI / Tests (PHP 8.2) (push) Successful in 55s
CI / No Debug Code (push) Successful in 3s
CI / PHPStan (push) Successful in 2m57s
CI / Coding Standards (push) Successful in 3m3s
CI / Tests (PHP 8.3) (push) Successful in 2m48s
CI / Build Plugin Zip (push) Successful in 2m50s
Reviewed-on: #166 |
||
|
|
f97b8a4576
|
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
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]> |
||
|
|
325a86f247 |
Merge pull request 'Ask some registration questions of students only, and require them of students without requiring them of the account holder' (#164) from feature/student-scoped-registration-questions into main
CI / Tests (PHP 8.2) (push) Successful in 57s
CI / Build Plugin Zip (push) Successful in 2m49s
CI / Tests (PHP 8.1) (push) Successful in 58s
CI / No Debug Code (push) Successful in 3s
CI / Coding Standards (push) Successful in 2m50s
CI / PHPStan (push) Successful in 2m59s
CI / Tests (PHP 8.3) (push) Successful in 2m42s
Reviewed-on: #164 |
||
|
|
434fe801ba
|
Ask some registration questions of students only
CI / Tests (PHP 8.2) (pull_request) Successful in 58s
CI / Tests (PHP 8.1) (pull_request) Successful in 58s
CI / No Debug Code (pull_request) Successful in 3s
CI / Coding Standards (pull_request) Successful in 2m53s
CI / PHPStan (pull_request) Successful in 3m0s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m42s
CI / Build Plugin Zip (pull_request) Skipped
Every account-signup question was asked of everybody who registered, on the same terms: "school and grade" had to be put to an adult signing themselves up, and a question a studio needed answered for each student could only be made required by demanding it of everyone. A question now carries an audience — everyone, or only the students someone registers on behalf of — and its own required flag for each side, so optional for you and required for every student you enrol is expressible. Both settings are account-scope only: an offering asks its questions once, about the student being booked, so there is no second audience to differ from, and an offering question mirrors its single "required" into both columns. Every caller reads askedOfSelf()/isRequiredForSelf()/isRequiredForChild() rather than the raw flags, so a students-only question can neither block the account holder nor have an answer filed against them by a crafted post. The family screen, which only ever adds a student, is held to the students' rule. is_required_child arrives from dbDelta defaulting to 0, which would quietly stop every existing required question being required of the students a guardian registers — the case it most likely existed for. A one-time backfill copies is_required across, guarded by its own option so a question later made optional for students stays that way. Closes #163 Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
84378e856b |
Merge pull request 'Bump version to 1.4.2' (#162) from release/bump-1.4.2 into main
CI / Tests (PHP 8.2) (push) Successful in 51s
CI / No Debug Code (push) Successful in 2s
CI / PHPStan (push) Successful in 2m53s
CI / Coding Standards (push) Successful in 2m58s
CI / Build Plugin Zip (push) Successful in 2m45s
CI / Tests (PHP 8.1) (push) Successful in 45s
CI / Tests (PHP 8.3) (push) Successful in 2m42s
Reviewed-on: #162 |
||
|
|
a2cece750b | Bump version to 1.4.2 and open changelog section | ||
|
|
5d98aedfa5 |
Merge pull request 'Editable policy name, one-page signup with adult birth year, group classes in upcoming lessons, and booking cleanup on user delete' (#161) from feature/demo-followups-2026-07-30 into main
CI / PHPStan (push) Successful in 2m53s
CI / Coding Standards (push) Successful in 3m2s
Release / Build and Publish Release (push) Successful in 2m59s
Release / Open next-version bump PR (push) Successful in 4s
CI / Tests (PHP 8.3) (push) Successful in 2m42s
CI / Build Plugin Zip (push) Successful in 2m46s
CI / Tests (PHP 8.1) (push) Successful in 44s
CI / Tests (PHP 8.2) (push) Successful in 51s
CI / No Debug Code (push) Successful in 2s
Reviewed-on: #161v1.4.1 |
||
|
|
8fd7bf983d
|
Name people by their name, not their email address
CI / Tests (PHP 8.2) (pull_request) Successful in 58s
CI / Tests (PHP 8.1) (pull_request) Successful in 59s
CI / Coding Standards (pull_request) Successful in 2m51s
CI / PHPStan (pull_request) Successful in 3m1s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m42s
CI / No Debug Code (pull_request) Successful in 3s
CI / Build Plugin Zip (pull_request) Skipped
Anywhere the plugin named a person it could show their email instead — "Managed by [email protected]" in the students table, the same under Booked by, instructor names on the class pages. WordPress defaults a new account's `nickname` to its `user_login`, and signup uses the email address as the login. So every self-registered account carried its own address as its nickname, and UserName::format() fell straight through to it. The name they typed was in `display_name` all along. Accounts created by a guardian were never affected — GuardianService::createChild() sets `nickname` outright, which is exactly why children read correctly and their parents did not. UserName::format() now walks nickname then display name, skipping either when it is really the login or the email, so existing accounts read correctly with nothing to migrate. An identifier still never reaches the screen: an account with nothing but its address on file falls back to the id, as before. Signup also sets `nickname` at insert, so new accounts are right at the source rather than relying on the fallback. Tests: composer test (866), composer lint, composer cs all pass. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
122f7a0f53
|
Show "Booked by" in the Account section of a student's detail page
CI / No Debug Code (pull_request) Successful in 2s
CI / PHPStan (pull_request) Successful in 2m48s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m42s
CI / Tests (PHP 8.2) (pull_request) Successful in 57s
CI / Tests (PHP 8.1) (pull_request) Successful in 59s
CI / Coding Standards (pull_request) Successful in 3m1s
CI / Build Plugin Zip (pull_request) Skipped
The parent/guardian was only named further down under Profile, where it reads as background rather than as an account fact, and only when there was one — so a page with no such line was ambiguous between "books for themselves" and "the lookup found nothing". It now sits in the Account table beside display name and email, as the guardian's name linked to their own detail page, and always renders: a student who books for themselves says so outright. No email address — theirs is one click away on their own page, and repeating it here only makes the row harder to scan. The Profile section keeps only the note explaining the placeholder email, which is a different point. Tests: composer test (863), composer lint, composer cs all pass. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
c9a1205fc0
|
Never drop an enrolled class from upcoming lessons; delete a guardian's children with them; pin the panel's line spacing
CI / Tests (PHP 8.2) (pull_request) Successful in 58s
CI / PHPStan (pull_request) Successful in 2m53s
CI / Coding Standards (pull_request) Successful in 2m58s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m47s
CI / Build Plugin Zip (pull_request) Skipped
CI / Tests (PHP 8.1) (pull_request) Successful in 1m1s
CI / No Debug Code (pull_request) Successful in 3s
Three fixes from testing the branch. A group class was only listed when its schedule resolved to exact datetimes, which needs a class time *and* a duration — both optional on the offering form, and the schedule note exists precisely so a studio can write "Tuesdays 4:00pm" instead. A class configured that way vanished from the list, which is the one thing this feature must never do. So Offering::sessionStarts() splits "when does it meet" from "how long does it run" (sessionWindows() is that plus the duration, unchanged), and SessionSchedule degrades instead of disappearing: dated rows with an open end when there is no duration, and a single row carrying Offering::scheduleLabel() when there is no time to derive dates from. Only a class whose last day has passed drops out. Deleting a guardian now deletes the children linked to them, releasing each one's lessons and enrolments first. A child account is login-less and exists only so the guardian has somebody to book for; without the guardian nobody can reach it, book for it, or be billed for it, so it was left stranded on the roster still holding slots. A `handled` set makes the re-entrant delete_user each child deletion fires a no-op, and stops a circular link recursing. The upcoming panel never stated its own line-height, so a theme setting line-height: 0 above it — the usual icon-font reset — was inherited straight through. Below 1 that produces both reported symptoms at once: stacked lines overlap, and the status pill's background is shorter than the text in it. Pinned at the same id-level specificity as the rest. Tests: composer test (863), composer lint, composer cs all pass. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
cb347ffca0
|
Demo follow-ups: editable policy name, one-page signup, group classes in upcoming lessons, deletion cleanup
CI / Tests (PHP 8.1) (pull_request) Successful in 1m0s
CI / Tests (PHP 8.2) (pull_request) Successful in 1m0s
CI / No Debug Code (pull_request) Successful in 3s
CI / Coding Standards (pull_request) Successful in 3m8s
CI / Build Plugin Zip (pull_request) Skipped
CI / PHPStan (pull_request) Successful in 2m49s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m44s
Five items from the latest demo pass: - A policy's title can be edited from the Policies screen. Only the title moves; the slug is what the gates resolve policies by, so a rename can never detach a policy from acceptances already recorded against it. - Signup is one page again. The studio's registration questions move from a second step behind "Next" onto the main form, in an "About you" panel above the students being added, and that panel also asks an adult student for their birth year (the same us_birth_year meta a child's uses). register.js disables and hides the whole panel for a pure guardian, since the questions describe a student. - The password is re-scored on submit, not only as it is typed. zxcvbn's dictionary arrives after page load, so a password typed straight away was never scored at all and the first the student heard of it was the server rejecting the whole form. - Group-class sessions appear alongside lessons wherever upcoming lessons are listed: the [us_scheduler] panel (students and instructors) and the admin student detail page. GroupClass\SessionSchedule derives them from Offering::sessionWindows(), the same derivation the billing scan uses. They carry kind = 'group_class' and no Cancel action - a session is one date in a term, not a booked slot. - Deleting a user releases what the account was holding: each upcoming lesson is cancelled, its slot freed for rebooking, its pending payment voided, and active class enrolments cancelled. Past lessons and paid history are left alone. Tests: composer test (851), composer lint, composer cs all pass. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
258468093b |
Merge pull request 'Bump version to 1.4.1' (#160) from release/bump-1.4.1 into main
CI / PHPStan (push) Successful in 2m47s
CI / Coding Standards (push) Successful in 2m57s
CI / Tests (PHP 8.1) (push) Successful in 48s
CI / Tests (PHP 8.2) (push) Successful in 45s
CI / No Debug Code (push) Successful in 2s
CI / Tests (PHP 8.3) (push) Successful in 2m40s
CI / Build Plugin Zip (push) Successful in 2m48s
Reviewed-on: #160 |
||
|
|
3a4b25a711 | Bump version to 1.4.1 and open changelog section | ||
|
|
969d864106 |
Merge pull request 'Ask who the signup is for, and ask each student the studio's questions' (#159) from feature/145-registering-for-choice into main
CI / Tests (PHP 8.2) (push) Successful in 45s
CI / No Debug Code (push) Successful in 2s
CI / Coding Standards (push) Successful in 2m54s
CI / PHPStan (push) Successful in 2m55s
CI / Tests (PHP 8.3) (push) Successful in 2m44s
CI / Tests (PHP 8.1) (push) Successful in 53s
Release / Build and Publish Release (push) Successful in 3m1s
Release / Open next-version bump PR (push) Successful in 4s
CI / Build Plugin Zip (push) Successful in 2m53s
Reviewed-on: #159v1.4.0 |
||
|
|
69179b75c9
|
Ask the account holder the studio's questions when they are a student too
CI / Tests (PHP 8.2) (pull_request) Successful in 43s
CI / Tests (PHP 8.1) (pull_request) Successful in 52s
CI / No Debug Code (pull_request) Successful in 2s
CI / PHPStan (pull_request) Successful in 2m50s
CI / Coding Standards (pull_request) Successful in 3m19s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m47s
CI / Build Plugin Zip (pull_request) Skipped
Under "both" the questions were collected per student only, so someone registering themselves alongside their children was never asked their own instrument, level or anything else — despite being able to book lessons. The account-scope questions describe a student, and under "both" the account holder is one. Their answers are recorded against their own user id, not shared with a child's, and recorded after the children so a rollback that deletes the account cannot leave answers pointing at a user that no longer exists. A pure guardian is unchanged: they are not a student, so anything posted for them is still ignored. Validation became two passes rather than one so the message can say whose answers are missing — with one pass, "both" had to blame "each student" for the account holder's own blank field. In the form, the two questions turn out to be independent: whether student blocks are in play, and whether the account holder answers for themselves. "Both" is the case that needs its own answer to each, so sync() now tracks them separately, and step two comes back into play under "both". Verified in a headless browser: 21 checks across all three choices, now including that "both" enables the account holder's own question panel and offers Next rather than the early submit. Closes #146 Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
4e5382e259
|
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]> |
||
|
|
28046e0fd1 |
Merge pull request 'Return to a bookable calendar after a booking is confirmed' (#158) from fix/143-return-to-bookable into main
CI / Tests (PHP 8.1) (push) Successful in 44s
CI / No Debug Code (push) Successful in 1s
CI / Build Plugin Zip (push) Successful in 2m49s
CI / Tests (PHP 8.2) (push) Successful in 51s
CI / PHPStan (push) Successful in 2m55s
CI / Coding Standards (push) Successful in 2m59s
CI / Tests (PHP 8.3) (push) Successful in 2m41s
Reviewed-on: #158 |
||
|
|
699e479805
|
Return to a bookable calendar after a booking is confirmed
CI / Tests (PHP 8.3) (pull_request) Successful in 2m44s
CI / Build Plugin Zip (pull_request) Skipped
CI / Coding Standards (pull_request) Successful in 3m0s
CI / Tests (PHP 8.1) (pull_request) Successful in 42s
CI / Tests (PHP 8.2) (pull_request) Successful in 44s
CI / No Debug Code (pull_request) Successful in 2s
CI / PHPStan (pull_request) Successful in 2m46s
showConfirmation() hid the slot list and put the confirmation in its place,
which is a dead end: a student wanting a second lesson had nothing to click
and no way back short of reloading the page. Enrolling in a group class did
the same thing.
The confirmation is now a dismissible notice above the calendar. The
calendar is reloaded first — so the slot just taken is already gone and the
upcoming-lessons panel is current — and the notice is shown over it, which
is why loadSlots() had to start returning its promise. "It worked" and "book
another" are the same screen.
The notice clears when dismissed, when another slot's form is opened, and on
any reload of the calendar. group-classes.js gets the identical treatment.
It is built from DOM nodes rather than innerHTML because the message can
carry a studio's e-transfer address, and it is toggled with the `hidden`
attribute rather than an inline display — an inline style would outrank the
stylesheet's display:flex and stack the notice's parts. `hidden` needs the
!important guard for the same reason the upcoming-lessons panel does: the
div{display:block} theme reset outranks the UA sheet.
The slotList/list `display = 'block'` lines went with it. Nothing hides
those any more, so restoring them each load only implied otherwise.
Verified in a headless browser against a stubbed REST API: booking twice in
a row without a reload, the booked slot leaving the calendar, the upcoming
panel updating, dismissal, the notice clearing when the next form opens, and
the notice staying hidden under div{display:block}.
Closes #143
Co-Authored-By: Claude Opus 5 <[email protected]>
|
||
|
|
1b42d20541 |
Merge pull request 'Add an account block showing who is signed in' (#156) from feature/142-account-block into main
CI / Build Plugin Zip (push) Successful in 2m48s
CI / Tests (PHP 8.2) (push) Successful in 43s
CI / Tests (PHP 8.1) (push) Successful in 52s
CI / PHPStan (push) Successful in 2m53s
CI / Coding Standards (push) Successful in 2m57s
CI / Tests (PHP 8.3) (push) Successful in 2m44s
CI / No Debug Code (push) Successful in 2s
Reviewed-on: #156 |
||
|
|
ab5212282d
|
Show only the name and email, not who the account books for
CI / Tests (PHP 8.2) (pull_request) Successful in 41s
CI / Tests (PHP 8.1) (pull_request) Successful in 42s
CI / No Debug Code (pull_request) Successful in 2s
CI / PHPStan (pull_request) Successful in 2m48s
CI / Coding Standards (pull_request) Successful in 3m1s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m44s
CI / Build Plugin Zip (pull_request) Skipped
The block reports who is signed in and nothing more. Dropping the "Booking for …" line takes GuardianService with it — it was the only reason the page had a dependency at all, so AccountPage now constructs with no arguments. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
6e3affb1cb
|
Add an account block showing who is signed in
[us_account], or the Account block: the signed-in visitor's name, their email, a Sign out link, and — only when the account books for someone besides itself — the students it books for. A parent's first question on seeing "signed in as Grace" is whether this is the account their children's lessons are on. Two decisions worth naming. Signed out with no login page chosen, the block renders nothing. Its whole subject is the person signed in, which a stranger is not, and a bare "you are not signed in" in a site header is noise with no way to act on it. With a login page chosen it offers a Sign in link instead. The editor preview is populated regardless, so the block is never an invisible box to the person placing it. Signing out returns to the chosen login page, or to the current page when there is none. A block meant for a header should not also navigate someone somewhere when they use it; the login page wins when configured, because the page they were on may well be members-only. The name comes from UserName::format(), so the block never exposes a username the way display_name can. Also brings docs/features/editor-blocks.md back in step: it still described "four shortcodes" and had never listed the family block. Closes #142 Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
7875cb1bf7 |
Merge pull request 'Fix main: two signup fixtures use a now-rejected password' (#157) from fix/green-main into main
CI / Coding Standards (push) Successful in 2m57s
CI / Tests (PHP 8.1) (push) Successful in 57s
CI / Tests (PHP 8.2) (push) Successful in 45s
CI / No Debug Code (push) Successful in 2s
CI / PHPStan (push) Successful in 2m51s
CI / Tests (PHP 8.3) (push) Successful in 2m46s
CI / Build Plugin Zip (push) Successful in 2m50s
Reviewed-on: #157 |
||
|
|
04cba9702c
|
Fix main: two signup fixtures use a now-rejected password
CI / Tests (PHP 8.2) (pull_request) Successful in 52s
CI / No Debug Code (pull_request) Successful in 2s
CI / PHPStan (pull_request) Successful in 2m46s
CI / Tests (PHP 8.1) (pull_request) Successful in 54s
CI / Coding Standards (pull_request) Successful in 3m6s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m49s
CI / Build Plugin Zip (pull_request) Skipped
#154 and #155 each passed on their own branch and broke on landing together. #154 added two guardian-signup tests using 'password123' as their fixture; #155 then added PasswordPolicy, which rejects exactly that. Neither branch ever saw the other's change, because #155 was cut from main before #154 merged. Both tests now use the same policy-clearing fixture as the rest of the file. The deliberate 'password123' in the rejected-passwords provider stays — that one is the point. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
d554e35d80 |
Merge pull request 'Validate signup email and password strength' (#155) from feature/150-signup-credential-validation into main
CI / Tests (PHP 8.2) (push) Failing after 43s
CI / No Debug Code (push) Successful in 2s
CI / PHPStan (push) Successful in 2m55s
CI / Coding Standards (push) Successful in 2m56s
CI / Tests (PHP 8.3) (push) Failing after 2m44s
CI / Build Plugin Zip (push) Skipped
CI / Tests (PHP 8.1) (push) Failing after 50s
Reviewed-on: #155 |
||
|
|
b5b9a7ac54
|
Validate signup email and password strength
CI / No Debug Code (pull_request) Successful in 3s
CI / Tests (PHP 8.2) (pull_request) Successful in 42s
CI / Tests (PHP 8.1) (pull_request) Successful in 53s
CI / PHPStan (pull_request) Successful in 2m53s
CI / Coding Standards (pull_request) Successful in 2m57s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m44s
CI / Build Plugin Zip (pull_request) Skipped
The password was only ever checked for length. It is now checked on both sides, with each side doing the job it can actually do. The browser scores it with zxcvbn, through WordPress's own password-strength-meter script rather than a second opinion of our own, and refuses to submit below "medium". That is the nuanced test — it knows Tr0ub4dor&3 is weaker than it looks — but it is advice a client can decline to take. Auth\PasswordPolicy runs on the server and is the rule that holds. It does not try to reproduce a strength score in PHP; it rejects the categorically bad, which is what a server can check without shipping a dictionary: too short, a well-known leaked password, fewer than four distinct characters, or the user's own name or email inside it. No composition rules — NIST advises against them, and they mostly produce predictable substitutions. Both thresholds come from the same two constants, handed to JavaScript by wp_localize_script, so the sides cannot drift into disagreeing about what was accepted. The verdict is attached to the field with setCustomValidity() rather than by disabling a button. The form has up to three submits plus a "Next" that already gates on checkValidity(), and an invalid field stops all of them without any of them needing to know why. Email validation moved ahead of the password check, since the password is now checked against the email. A blank form therefore reports the email first, which also matches the order the fields appear in. Verified the browser half against a controllable scorer: each score band blocks or allows as intended, the identity list reaches the meter, and the gate stays open while zxcvbn's dictionary is still loading — the server covers that window. Closes #150 Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
f0149042cc |
Merge pull request 'Require a name and birth year for every student' (#154) from feature/148-required-name-and-birth-year into main
CI / Tests (PHP 8.2) (push) Successful in 43s
CI / Tests (PHP 8.1) (push) Successful in 51s
CI / No Debug Code (push) Successful in 2s
CI / PHPStan (push) Successful in 2m53s
CI / Coding Standards (push) Successful in 2m59s
CI / Tests (PHP 8.3) (push) Successful in 2m45s
CI / Build Plugin Zip (push) Successful in 2m45s
Reviewed-on: #154 |
||
|
|
1d2f95d388
|
Require a name and birth year for every student
CI / Tests (PHP 8.1) (pull_request) Successful in 42s
CI / Coding Standards (pull_request) Successful in 2m56s
CI / PHPStan (pull_request) Successful in 2m56s
CI / Tests (PHP 8.2) (pull_request) Successful in 50s
CI / No Debug Code (pull_request) Successful in 2s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m45s
CI / Build Plugin Zip (pull_request) Skipped
Both fields are marked in their labels the same way a required registration question is, and enforced on the server whichever form they arrive from: GuardianService::createChild() and updateChild() now refuse a blank name or an unusable birth year, and the signup form checks the same rule up front, before it creates a single user, so a bad block never leaves a half-registered family behind. normaliseBirthYear() became public and static so both paths share one definition of what a usable year is. The signup form cannot lean on the browser here. Its child blocks are hidden until the parent/guardian box is ticked, and a `required` field inside a hidden container makes the whole form unsubmittable with no control the user can reach to fix — the same trap the guardian's own question panel already sidesteps by disabling rather than hiding. So register.js puts `required` on and takes it off along with the block itself, and the server is what makes the rule hold with JavaScript off. The profile screen has no such problem: its forms are always visible, so the attribute is static there. One behaviour change beyond the requirement: a child block with anything typed into it is now reported back instead of dropped. Previously any block without a name was silently discarded, which would now mean losing a birth year the guardian had filled in. A wholly untouched spare block — the one the form always renders for "add another" — is still ignored. Verified the required-toggling in a headless browser: unticked submits, ticked blocks an empty block, a cloned block inherits the requirement, and re-unticking leaves nothing behind to block a non-guardian signup. Closes #148 Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
2878beb221 |
Merge pull request 'Collect a birth year instead of a full date of birth' (#153) from feature/147-birth-year into main
CI / Tests (PHP 8.2) (push) Successful in 50s
CI / Tests (PHP 8.1) (push) Successful in 52s
CI / No Debug Code (push) Successful in 2s
CI / PHPStan (push) Successful in 2m47s
CI / Build Plugin Zip (push) Successful in 2m35s
CI / Coding Standards (push) Successful in 2m58s
CI / Tests (PHP 8.3) (push) Successful in 2m44s
Reviewed-on: #153 |
||
|
|
7e2bba79fe
|
Collect a birth year instead of a full date of birth
CI / Tests (PHP 8.1) (pull_request) Successful in 43s
CI / No Debug Code (pull_request) Successful in 2s
CI / Tests (PHP 8.2) (pull_request) Successful in 50s
CI / PHPStan (pull_request) Successful in 2m55s
CI / Coding Standards (pull_request) Successful in 3m0s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m40s
CI / Build Plugin Zip (pull_request) Skipped
Signup and the profile page now ask for a four-digit year between 1900 and the current year. Anything else — a short year, a full date, a year in the future — is discarded rather than stored, so a typo cannot leave a nonsense age on the record. The year lives in a new us_birth_year user meta rather than reusing us_date_of_birth, which would have left one key holding two formats. The old key is not migrated in bulk. Instead GuardianService handles it in two halves: birthYear() falls back to the year of the old date when the new key is absent, so a student added before this change still shows one, and setBirthYear() deletes the old date on every save. That deletion is what makes the fallback safe rather than merely tidy. Without it, clearing the birth year on a student who predates the change would leave the old date behind for the fallback to read straight back, and the year could never be cleared at all. Stored in user meta, so no Schema.php change and no USC_VERSION bump. Closes #147 Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
3a83decc82 |
Merge pull request 'Say "student" and "profile" in the UI, not "child" and "family"' (#152) from refactor/144-student-profile-copy into main
CI / No Debug Code (push) Successful in 2s
CI / Tests (PHP 8.1) (push) Successful in 52s
CI / Tests (PHP 8.2) (push) Successful in 51s
CI / PHPStan (push) Successful in 2m47s
CI / Coding Standards (push) Successful in 2m54s
CI / Tests (PHP 8.3) (push) Successful in 2m39s
CI / Build Plugin Zip (push) Successful in 2m50s
Reviewed-on: #152 |
||
|
|
76caf178f0
|
Say "student" and "profile" in the UI, not "child" and "family"
CI / Tests (PHP 8.1) (pull_request) Successful in 41s
CI / Tests (PHP 8.2) (pull_request) Successful in 40s
CI / No Debug Code (pull_request) Successful in 2s
CI / Coding Standards (pull_request) Successful in 2m55s
CI / PHPStan (pull_request) Successful in 3m1s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m43s
CI / Build Plugin Zip (pull_request) Skipped
Sweep the translatable strings across the frontend templates, the admin screens, the editor previews and the block inserter entry. Nothing else moves: the database columns, request parameters, form field names, CSS classes, the us_family shortcode and the us-scheduler/family block name are contracts with existing installs and with post content people have already saved, so renaming them would break sites for no user-visible gain. Two strings are reworded rather than swapped, because the direct substitution reads wrong: - The students list said "Child of Jane" and now says "Managed by Jane". "Student of Jane" would read as a teacher's pupil, which is exactly the wrong idea in a music studio. - A managed account is now "a managed student account" rather than "a student account", which would not distinguish it from the account holder. The guardian feature doc gains a short section on the split, so the next person to work on it does not read the mismatch as drift and "fix" it. Closes #144 Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
8013d05d68 |
Merge pull request 'Stop upcoming lesson rows rendering on top of each other' (#151) from fix/149-upcoming-lesson-overlap into main
CI / Coding Standards (push) Successful in 2m56s
CI / Tests (PHP 8.1) (push) Successful in 51s
CI / No Debug Code (push) Successful in 2s
CI / Tests (PHP 8.2) (push) Successful in 43s
CI / PHPStan (push) Successful in 2m50s
CI / Tests (PHP 8.3) (push) Successful in 2m40s
CI / Build Plugin Zip (push) Successful in 2m51s
Reviewed-on: #151 |
||
|
|
6b29c0e78e
|
Stop upcoming lesson rows rendering on top of each other
CI / No Debug Code (pull_request) Successful in 2s
CI / PHPStan (pull_request) Successful in 2m51s
CI / Coding Standards (pull_request) Successful in 3m6s
CI / Tests (PHP 8.1) (pull_request) Successful in 53s
CI / Tests (PHP 8.2) (pull_request) Successful in 51s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m48s
CI / Build Plugin Zip (pull_request) Skipped
The panel's row and its two columns are divs with explicit flex rules, but
the text itself still sits in inline elements. A theme is free to take those
out of normal flow, and when it does the date and time land on the lesson
title and the status pill lands on the Cancel button. Pin position, float
and margin on the leaf elements at the same id-level specificity the rest of
the panel already uses, so a theme rule cannot lift them out of the column.
The rows behind "Show all" had the same shape of problem from the other
direction: `[hidden]` is only a UA-stylesheet rule, so the `div {
display: block }` reset that many themes still ship outranks it and the
collapsed rows render anyway. An author `!important` is the only way to win
that particular cascade.
Verified with a headless-browser harness rendering the exact markup
booking.js emits against twelve theme CSS patterns at two widths: before,
five patterns overlapped text or revealed the hidden rows; after, all pass.
Closes #149
Co-Authored-By: Claude Opus 5 <[email protected]>
|
||
|
|
7ea6616ba0 |
Merge pull request 'Trim CLAUDE.md to what the codebase can't tell you' (#141) from docs/trim-claude-md into main
CI / Tests (PHP 8.1) (push) Successful in 50s
CI / Coding Standards (push) Successful in 2m53s
CI / No Debug Code (push) Successful in 2s
CI / Tests (PHP 8.2) (push) Successful in 42s
CI / PHPStan (push) Successful in 2m46s
CI / Tests (PHP 8.3) (push) Successful in 2m49s
CI / Build Plugin Zip (push) Successful in 2m45s
Reviewed-on: #141 |
||
|
|
7fdf97b073
|
Trim CLAUDE.md to what the codebase can't tell you
CI / Tests (PHP 8.2) (pull_request) Successful in 53s
CI / No Debug Code (pull_request) Successful in 3s
CI / Coding Standards (pull_request) Successful in 2m54s
CI / PHPStan (pull_request) Successful in 3m0s
CI / Tests (PHP 8.1) (pull_request) Successful in 53s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m47s
CI / Build Plugin Zip (pull_request) Skipped
Most of this file described the repo as it was around v1.0: three domain packages, two database tables, twenty-one classes. There are now eleven packages, fifteen tables, and well over a hundred classes, so those sections were not just redundant with `ls` and Schema.php — they were teaching the wrong shape of the codebase. Same for the CI section, which had drifted past the build job. Cut the command list (composer.json has the scripts), the bootstrap description, the directory tree, the table list, the Key Classes table, and the CI job summary. Kept every rule the code can't explain on its own: package-by-domain, no $wpdb outside repositories, capability checks rather than role names, and the Schema.php version-bump gotcha. Moved the Brain\Monkey and Mockery gotchas to tests/CLAUDE.md, which loads only when working under tests/ instead of in every session. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
d3843186c0 |
Merge pull request 'Bump version to 1.3.1' (#140) from release/bump-1.3.1 into main
CI / Tests (PHP 8.2) (push) Successful in 48s
CI / No Debug Code (push) Successful in 2s
CI / PHPStan (push) Successful in 2m55s
CI / Coding Standards (push) Successful in 2m56s
CI / Tests (PHP 8.3) (push) Successful in 2m40s
CI / Build Plugin Zip (push) Successful in 2m45s
CI / Tests (PHP 8.1) (push) Successful in 43s
Reviewed-on: #140 |
||
|
|
e44972abe9 | Bump version to 1.3.1 and open changelog section | ||
|
|
3c41d1119d |
Merge pull request 'Let parents register once and book for their children' (#139) from feature/parent-guardian-accounts into main
CI / No Debug Code (push) Successful in 2s
CI / PHPStan (push) Successful in 2m56s
CI / Tests (PHP 8.3) (push) Successful in 2m45s
CI / Tests (PHP 8.2) (push) Successful in 43s
CI / Tests (PHP 8.1) (push) Successful in 54s
CI / Coding Standards (push) Successful in 3m1s
Release / Build and Publish Release (push) Successful in 3m1s
Release / Open next-version bump PR (push) Successful in 3s
CI / Build Plugin Zip (push) Successful in 2m49s
Reviewed-on: #139v1.3.0 |
||
|
|
8122c158cf
|
Keep the guardian service within the PHP the plugin supports
CI / Tests (PHP 8.2) (pull_request) Successful in 51s
CI / No Debug Code (pull_request) Successful in 2s
CI / Coding Standards (pull_request) Successful in 2m54s
CI / PHPStan (pull_request) Successful in 2m55s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m43s
CI / Build Plugin Zip (pull_request) Skipped
CI / Tests (PHP 8.1) (pull_request) Successful in 42s
`true` as a return type is PHP 8.2, but the plugin advertises 8.1, so the family screen's two service calls fataled on the 8.1 test job while every other job passed. They now return `?\WP_Error` — null on success — which matches RegistrationGate::validate() and works on 8.1. PHPStan was analysing against whatever PHP happened to be running (8.3 in CI, newer locally), so `composer lint` was green on syntax the plugin promises not to use. It is now pinned to the supported 8.1-8.3 range, which reproduces this failure at lint time instead of three jobs later. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
b772e1811e
|
Let parents register once and book for their children
A parent registers once and manages lessons for one or more children, who need no login of their own. A child is a real wp_users row with the student role but no usable login — so student_id keeps meaning "a WordPress user" on every table, and booking, credits, policies and enrolments work unchanged. A us_guardians link table maps guardian to child. The signup form gains a parent/guardian tick that reveals a block per child, with the account-signup questions asked per child rather than per guardian — they describe the student, not the account holder. Signup policies are recorded once per child with the guardian as the acceptor, which is the record that actually means something. A family that half-creates is rolled back entirely rather than leaving a guardian who cannot re-register. The booking and enrolment forms gain a "Who is this for?" picker listing children first, so the default selection is never the parent — booking for the wrong child is correctable, quietly billing a parent for their kid's lesson is not. POST /bookings and POST /enrollments take an optional student_id honoured only for that child's guardian; anything else is a 403. That check is the authorisation boundary of the feature. Payments and credits gain a payer: the charge names the child it was for and the guardian who owes it, so per-child reporting is unchanged while notices, receipts and the payment step reach the parent. Credit is held by the payer, so one child's cancellation can settle a sibling's charge, and the daily billing scan sends a guardian one notice covering every child. Closes #132 Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
c25260a367 |
Merge pull request 'Bump version to 1.2.5' (#138) from release/bump-1.2.5 into main
CI / Tests (PHP 8.1) (push) Successful in 47s
CI / No Debug Code (push) Successful in 2s
CI / Coding Standards (push) Successful in 2m51s
CI / PHPStan (push) Successful in 3m0s
CI / Tests (PHP 8.2) (push) Successful in 47s
CI / Tests (PHP 8.3) (push) Successful in 2m42s
CI / Build Plugin Zip (push) Successful in 2m49s
Reviewed-on: #138 |
||
|
|
96aaeff79c | Bump version to 1.2.5 and open changelog section | ||
|
|
bbc85d88f1 |
Merge pull request 'Stop the availability form failing in silence' (#137) from fix/availability-silent-failures into main
CI / Build Plugin Zip (push) Successful in 2m50s
CI / Tests (PHP 8.2) (push) Successful in 52s
CI / No Debug Code (push) Successful in 2s
CI / PHPStan (push) Successful in 2m50s
CI / Coding Standards (push) Successful in 2m53s
Release / Open next-version bump PR (push) Successful in 3s
CI / Tests (PHP 8.1) (push) Successful in 48s
CI / Tests (PHP 8.3) (push) Successful in 2m42s
Release / Build and Publish Release (push) Successful in 2m55s
Reviewed-on: #137v1.2.4 |
||
|
|
171b655bb8
|
Stop the availability form failing in silence
CI / Tests (PHP 8.1) (pull_request) Successful in 56s
CI / Tests (PHP 8.2) (pull_request) Successful in 46s
CI / No Debug Code (pull_request) Successful in 2s
CI / Coding Standards (pull_request) Successful in 3m3s
CI / PHPStan (pull_request) Successful in 2m51s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m50s
CI / Build Plugin Zip (pull_request) Skipped
Adding availability for 5:30-6:00 PM with the lesson length left on its 60-minute default saved nothing and said nothing. A window is stored as consecutive lesson-length slots, so one that fits no lesson splits into none: splitByDuration() returned [], createFromWindow() inserted nothing, and addSlot() discarded the result and re-rendered the page unchanged. The REST endpoint already rejected that window with a 400. The admin form checked the same rules separately, and its copy was both laxer and mute — an unreadable date, an end before the start, and a two-day window were bare `return`s, and it never checked offering ownership at all, so a crafted POST could tie a slot to another instructor's offering and inherit their price and payment routing. Both callers now go through WindowValidator, which returns the window or a WP_Error explaining the refusal. The endpoint returns that error as is; the page renders its message as a notice. handleFormAction returns a [notice, error] pair so deletes report themselves too, and a successful add says how many slots it created. Two failures could also go unnoticed underneath: wpdb::insert's result was ignored, and insert_id still holds the previous statement's id after a failed write, so a failure looked like a success — and could become the recurrence group of a weekly series, orphaning every later occurrence. weeks was unbounded server-side despite the form's max=52. availability-admin.js narrows the lesson-length choices to those that fit the window and blocks submission when none do, which is what makes the original mistake hard to repeat. It is a convenience: the server validates regardless. Closes #130 |
||
|
|
d9dd576630 |
Merge pull request 'Link a signed-in visitor to the configured continue page' (#134) from feature/registration-logged-in-continue-link into main
CI / Tests (PHP 8.1) (push) Successful in 50s
CI / Tests (PHP 8.2) (push) Successful in 52s
CI / No Debug Code (push) Successful in 2s
CI / Coding Standards (push) Successful in 2m51s
CI / PHPStan (push) Successful in 3m3s
CI / Tests (PHP 8.3) (push) Successful in 2m41s
CI / Build Plugin Zip (push) Successful in 2m47s
Reviewed-on: #134 |
||
|
|
61b00c2ed3
|
Add the changelog entry for the continue link
CI / Tests (PHP 8.1) (pull_request) Successful in 50s
CI / Coding Standards (pull_request) Successful in 3m0s
CI / No Debug Code (pull_request) Successful in 2s
CI / Tests (PHP 8.2) (pull_request) Successful in 52s
CI / PHPStan (pull_request) Successful in 3m0s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m53s
CI / Build Plugin Zip (pull_request) Skipped
|
||
|
|
7eb2afc6a3
|
Name the destination page in the continue link
"Continue to your account" says nothing about where the link goes. Use the chosen page's own title instead — "Continue to Book a Lesson" — so the visitor knows before clicking. An untitled page keeps the generic wording rather than rendering "Continue to ". |
||
|
|
8a985f04d6
|
Link a signed-in visitor to the configured continue page
The registration page's already-logged-in branch returned a bare sentence with nowhere to go, leaving the visitor to find their own way to their account. The invited-student branch a few lines above already built exactly the link that was missing. Extract that into continueLink() and use it for both logged-in outcomes. There is deliberately still no wp_login_url() fallback: sending someone already signed in to the login screen is the same dead end with extra steps, so with no page configured there is no link. Both messages now carry the us-register-form wrapper and enqueue the plugin stylesheet, which the invite branch emitted markup for but never loaded. Closes #131 |