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]>
The student-administration spec deferred three detail-view sections until
Payments landed. Adds them now: policy-acceptance history (title, version,
context, date), intake answers (label, answer, context), and — gated on
manage_billing — payment history with HST breakdown and receipt numbers.
New Auth\StudentHistory builds the display rows from per-student queries
added to AcceptanceRepository, AnswerRepository, and PaymentRepository;
the Payment model now carries created_at so unpaid rows still have a date.
Closes#69
Co-Authored-By: Claude Fable 5 <[email protected]>
- Bump phpstan/phpstan ^2.0 and szepeviktor/phpstan-wordpress ^2.0
- Move the analysis level into phpstan.neon (single source) and raise it to 10
- Add Val, a runtime coercion helper that narrows untyped WordPress boundary
values (wpdb rows, REST params, superglobals, options) with explicit checks
instead of blind casts, plus unit tests
- Type value-object fromRow() params as stdClass (what wpdb returns) and map
columns through Val so unexpected shapes degrade safely
- Use %i identifier placeholders for table names in all wpdb::prepare() calls
so every query string is a literal and identifiers are escaped by WordPress;
raises the minimum WordPress version to 6.2 where %i was introduced
- Guard wpdb::prepare() null result before wpdb::query() in updateTax()
- Fix nullable get_permalink()/strtotime() handling, list types at REST and
capability call sites, dead null-coalescing on checked superglobals, and
narrow get_users() results before mapping
- Register Val method names with the ValidatedSanitizedInput sniff so it
validates the real sanitizer around each superglobal read
- Update repository unit tests for the %i placeholder arguments
Co-Authored-By: Claude Fable 5 <[email protected]>
Implements #6: studio admins draft, version, and publish policies; the
public registration gate reads the current published version of each, and
acceptance is recorded against the exact version so a new version must be
re-accepted at the next booking.
- src/Policy/: Policy, PolicyVersion, PolicyAcceptance value objects;
PolicyRepository, PolicyVersionRepository, AcceptanceRepository;
PolicyService (orchestrates create/add-draft/publish across the policies
and versions tables); PolicyEndpoint (REST); PolicyController +
templates/admin/policies.php (Policies admin menu, manage_policies)
- us_policies, us_policy_versions, us_policy_acceptances tables in Schema
- REST: public GET /policies (current published versions); manage_policies
for create, add version, edit draft, and publish
- Wiring in Plugin, RestRegistrar, AdminMenu
AcceptanceRepository is built now and consumed by the booking/enrolment
gate in #3/#4.
Also bump PHPStan to --memory-limit=1G in the composer lint script; the
default 128M now crashes the analysis as the codebase has grown.
Tests: tests/Unit/Policy/ (value objects, repositories, service).
composer test (90 total), cs, and PHPStan level 6 all pass.
Refs #6
Co-Authored-By: Claude Opus 4.8 <[email protected]>