Ask some registration questions of students only, and require them of students without requiring them of the account holder #164

Merged
thatguygriff merged 1 commits from feature/student-scoped-registration-questions into main 2026-07-30 16:56:33 +00:00
Owner

Closes #163.

What a studio can now do

Each account signup question, on Offerings → Questions → Account signup, carries two new settings:

  • Asked ofEveryone registering (default) or Students only — not the account holder. A students-only question is left off the account holder's own About you panel and appears only in the student blocks.
  • Required — now two checkboxes: the account holder must answer for themselves, and each student they register must answer. Tick either, both, or neither, so "optional for you, required for every student you enrol" is expressible.

Both settings are hidden for offering-scope questions, where a booking asks its questions once about the student being booked and there is no second audience to differ from.

Implementation

  • Schema: us_questions.audience and us_questions.is_required_child; plugin bumped 1.4.2 → 1.5.0 so dbDelta actually runs. The top changelog section was empty and unreleased, so it was renamed rather than a third one opened.
  • Question grew askedOfSelf() / isRequiredForSelf() / isRequiredForChild(), and every caller reads those instead of the raw flags. A row missing the columns — or carrying an unknown audience — reads as "asked of everyone", the old behaviour.
  • RegistrationPage validates each side against its own flag, and drops students-only questions from the account holder's set before validating or storing, so a hand-crafted POST cannot file one against them. FamilyPage uses the students' rule, since that screen only ever adds a student.
  • REST is unchanged in contract: account-scope questions were already 404'd there (no offering to own them), so an audience can neither be read nor overwritten through it. An offering question written over REST mirrors its single is_required into is_required_child, as the admin form and the backfill both do.

Upgrade safety

is_required_child arrives DEFAULT 0, which would quietly make every existing required question optional for students — the case it most likely existed for. QuestionRepository::backfillChildRequired() copies is_required across once, wired into Plugin::boot() behind the us_questions_child_required_backfilled option (the same pattern as the existing ensureOfferingNullable repair), after the version gate has let dbDelta add the column. Existing questions come out unchanged: asked of everyone, required of everyone or of nobody.

Tests

composer test — 887 tests, 2524 assertions, all passing (new coverage in QuestionTest, QuestionRepositoryTest, a new QuestionFieldTest, RegistrationPageTest, FamilyPageTest).
composer lint — no errors.
composer cs — clean.

Not included

A student's admin detail page still lists every account question with "—" for unanswered ones, so a students-only question shows as unanswered on an adult self-registrant's page. Filtering it correctly needs a guardian lookup StudentHistory does not currently have; happy to do it as a follow-up.

🤖 Generated with Claude Code

Closes #163. ## What a studio can now do Each **account signup** question, on **Offerings → Questions → Account signup**, carries two new settings: - **Asked of** — *Everyone registering* (default) or *Students only — not the account holder*. A students-only question is left off the account holder's own **About you** panel and appears only in the student blocks. - **Required** — now two checkboxes: *the account holder must answer for themselves*, and *each student they register must answer*. Tick either, both, or neither, so "optional for you, required for every student you enrol" is expressible. Both settings are hidden for offering-scope questions, where a booking asks its questions once about the student being booked and there is no second audience to differ from. ## Implementation - Schema: `us_questions.audience` and `us_questions.is_required_child`; plugin bumped **1.4.2 → 1.5.0** so `dbDelta` actually runs. The top changelog section was empty and unreleased, so it was renamed rather than a third one opened. - `Question` grew `askedOfSelf()` / `isRequiredForSelf()` / `isRequiredForChild()`, and every caller reads those instead of the raw flags. A row missing the columns — or carrying an unknown audience — reads as "asked of everyone", the old behaviour. - `RegistrationPage` validates each side against its own flag, and drops students-only questions from the account holder's set before validating *or* storing, so a hand-crafted POST cannot file one against them. `FamilyPage` uses the students' rule, since that screen only ever adds a student. - REST is unchanged in contract: account-scope questions were already 404'd there (no offering to own them), so an `audience` can neither be read nor overwritten through it. An offering question written over REST mirrors its single `is_required` into `is_required_child`, as the admin form and the backfill both do. ## Upgrade safety `is_required_child` arrives `DEFAULT 0`, which would quietly make every existing required question optional for students — the case it most likely existed for. `QuestionRepository::backfillChildRequired()` copies `is_required` across once, wired into `Plugin::boot()` behind the `us_questions_child_required_backfilled` option (the same pattern as the existing `ensureOfferingNullable` repair), after the version gate has let `dbDelta` add the column. Existing questions come out unchanged: asked of everyone, required of everyone or of nobody. ## Tests `composer test` — 887 tests, 2524 assertions, all passing (new coverage in `QuestionTest`, `QuestionRepositoryTest`, a new `QuestionFieldTest`, `RegistrationPageTest`, `FamilyPageTest`). `composer lint` — no errors. `composer cs` — clean. ## Not included A student's admin detail page still lists every account question with "—" for unanswered ones, so a students-only question shows as unanswered on an adult self-registrant's page. Filtering it correctly needs a guardian lookup `StudentHistory` does not currently have; happy to do it as a follow-up. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
thatguygriff added 1 commit 2026-07-30 16:52:17 +00:00
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
434fe801ba
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]>
thatguygriff merged commit 325a86f247 into main 2026-07-30 16:56:33 +00:00
thatguygriff deleted branch feature/student-scoped-registration-questions 2026-07-30 16:56:33 +00:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Unsupervised/unsupervised-scheduler#164