Implements #22: a read-only Students area for studio admins.
- StudentController (manage_students): a list of us_student users with
upcoming-lesson and active-enrolment counts, each linking to a detail page
showing account info, upcoming/past lessons (offering, instructor, status),
and group-class enrolments.
- StudentSchedule::partition() — pure, unit-tested upcoming/past split.
- Repo counts: BookingRepository::countUpcomingForStudent and
EnrollmentRepository::countActiveForStudent (single-query, tested).
- Templates: templates/admin/students.php, student-detail.php.
- Students admin menu wired in AdminMenu (no Plugin change — the repos were
already available there).
- Docs: README status flipped to implemented; feature spec updated.
Payment history slots into the detail when Payments (#7) lands.
Tests: StudentScheduleTest + the two repo count tests. composer test (127),
cs, and PHPStan level 6 all pass.
Refs #22
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- README.md: plugin overview, package-by-domain architecture, feature/status
table linking each docs/features spec (implemented vs planned), shortcodes,
REST namespace, roles, install via composer build, dev commands, and CI.
- docs/features/student-administration.md: spec for the planned studio-admin
Students list + per-student detail (upcoming/past lessons, group enrolments);
read-only; manage_students; no new tables. Tracked as #22.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Implements #4: students enrol in a group_class offering via the same
registration gate as private lessons (intake questions + booking-scoped
policy acceptance). Enrolment is capacity-enforced and prevents duplicates.
- Schema: us_group_enrollments table.
- Enrollment value object + EnrollmentRepository (countActiveForOffering,
hasActiveEnrollment, per-student/instructor/all-active queries, status).
- EnrollmentEndpoint: GET /enrollments (scoped) and POST /enrollments
(validates group_class, capacity, no-duplicate; reuses RegistrationGate;
records answers/acceptances type enrollment).
- GroupClassController + admin page (view_all_lessons): all active enrolments.
- Front-end: [us_group_classes] shortcode (GroupClassPage) + group-classes.js
enrol flow (list classes -> questions + policies -> POST /enrollments).
- Wiring in Plugin, RestRegistrar, AdminMenu, ShortcodeRegistrar.
Payment is the deferred seam (#7): enrolment lands active, payment_id null.
JS left untested for parity with the repo's no-build vanilla-JS posture.
Tests: tests/Unit/GroupClass/ (Enrollment, EnrollmentRepository).
composer test (121), cs, and PHPStan level 6 all pass.
Refs #4
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Implements #3: students register for a private lesson by picking a slot,
answering the offering's intake questions, and accepting booking-scoped
policies. Payment is a clean seam for #7 (lessons land pending; payment_id
null; instructor confirms via PATCH /bookings/{id}/status).
- Schema: us_lessons += offering_id, recurrence, series_id, payment_id.
- Lesson: new fields + recurrence constants.
- BookingRepository::insertSeries() builds a weekly series sharing a
series_id; AvailabilityRepository::findUnbookedInGroup() reserves a group.
- RegistrationGate (src/Registration/): validate + record intake answers and
booking-scoped policy acceptances. Reused by group enrolment (#4).
- BookingEndpoint::book(): offering_id, recurrence, answers,
accepted_policy_version_ids; single or weekly; records answers/acceptances
(type lesson).
- GET /policies?scope=booking filter.
- Front-end booking.js: slot -> questions + policies -> submit.
- Wiring: RegistrationGate built in Plugin, passed via RestRegistrar.
- Test-only WP_Error stub in tests/bootstrap.php for gate testing.
Refs #3
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- RegistrationPage::maybeRedirectToRegistrationPage() (hooked on
template_redirect): any front-end request carrying a us_invite token is
redirected to the configured registration page (token preserved), unless
already there. Covers links shared before a page was selected; no-op when
no page is set.
- Invites button text: "Send Invite" -> "Generate Invitation Link".
- Doc updated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Invitation links previously pointed at the site home page, which usually
does not host the [us_student_register] shortcode. Let the studio admin
choose the registration page (stored in the us_registration_page_id
option); invitation links now point there, falling back to the home page
when unset (with a warning notice).
- RegistrationController: OPTION_PAGE constant; set_page action; pass the
page id/url to the template.
- templates/admin/invites.php: wp_dropdown_pages selector + save; build the
invite link from the selected page.
- Doc updated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Implements #16: invite-only student self-registration through a front-end
page, accepting signup-scoped policies at account creation.
Policy domain:
- us_policies.acceptance_scope (signup/booking/both); Policy::appliesTo();
PolicyRepository::findForScope(); scope threaded through PolicyService,
the REST create, the admin controller, and the Policies form.
- PolicyAcceptance::REG_ACCOUNT (registration_id = the new user's ID).
Auth:
- Invite value object + InviteRepository; us_invites table.
- RegistrationController + Invites admin page (manage_students): invite an
email, share the registration link, revoke.
- RegistrationPage ([us_student_register] shortcode): validates the invite
token, collects name/password, renders signup-scoped published policies
with required acceptance, creates the us_student user, records account-type
acceptances, marks the invite accepted, and logs the user in.
- RoleManager: manage_students cap added to STUDIO_ADMIN_CAPS.
Invite-only is implemented; the us_registration_mode self_approval path is a
documented future seam.
Docs: docs/features/account-registration.md; policies.md updated.
Tests: tests/Unit/Auth/ (Invite, InviteRepository) plus Policy scope
updates. composer test (104), cs, and PHPStan level 6 all pass.
Refs #16
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Availability (#2):
- us_availability gains offering_id, duration_minutes (default 60), and
recurrence_group; AvailabilitySlot carries the new fields.
- AvailabilityRepository::createWeeklySeries() generates N weekly rows
sharing a recurrence_group; findAvailable() filters by offering and
duration. Date math uses DateTimeImmutable::modify() (the no-debug CI
regex `dd\(` matches `->add(`).
- REST GET filters by offering_id/duration_minutes; POST accepts
duration_minutes, offering_id, recurrence (single|weekly) + weeks.
- Admin form adds duration, an offering picker, and one-off/weekly options
(OfferingRepository wired into AvailabilityController).
- booking.js renders an agenda calendar (slots grouped by day, with
duration). The richer booking UX lands with the booking-flow work.
Offering price in dollars:
- Switch us_offerings.price_cents (INT) to price DECIMAL(10,2); Offering
uses float $price. Admin form and REST take dollars.
- Fix a pre-existing misalignment in the Offering insert/update $wpdb
format arrays (billing_mode/capacity/is_active were mapped to the wrong
specifiers, which would corrupt values) via a single COLUMN_FORMATS list.
Also bump PHPStan to --memory-limit=1G in the lint script; 128M now
crashes analysis as the codebase has grown.
Refs #2
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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 <noreply@anthropic.com>
Gitea/Actions re-zips artifacts on download, so uploading the built plugin
zip produced a double-wrapped archive (a zip containing a zip). WordPress
then reported "No valid plugins were found" because the upload had no
plugin folder/header at its top level.
Unpack the built zip and upload the resulting plugin folder instead, so the
downloaded artifact's top level is unsupervised-schedular/ and installs
directly via Plugins -> Add New -> Upload.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reflect the in-progress, pre-release state. Bump to 1.0.0 before tagging a
release. Updates both the plugin header and USC_VERSION; the build/CI zip
artifact name tracks this automatically.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
WordPress administrators (manage_options) now implicitly hold every
studio-admin capability via a user_has_cap filter, so the site owner runs
the studio without being assigned the separate us_studio_admin role. The
grant persists nothing and is removed on deactivation. The us_studio_admin
role still exists for non-administrator staff and does NOT confer any core
WordPress admin powers.
Also re-gate the studio-wide "Scheduler" dashboard off manage_options onto
a new view_all_lessons capability (added to the studio-admin cap set), so a
us_studio_admin user can see it too — previously it was administrator-only.
- RoleManager: STUDIO_ADMIN_CAPS constant, CAP_VIEW_ALL_LESSONS,
grantStudioCapsToAdministrators() user_has_cap filter
- AdminMenu + LessonController: Scheduler gated on view_all_lessons
- Docs: user-roles.md cap matrix + administrator note; lesson-booking.md
- Tests: administrators receive studio caps; non-admins do not
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- bin/build-zip.sh + `composer build`: stage runtime files only, generate a
production (no-dev) optimized autoloader, and emit
dist/<slug>-<version>.zip with a single top-level plugin folder, ready to
upload via wp-admin. Tests, tooling configs, docs, and dev dependencies
are excluded; version is read from the plugin header.
- CI `build` job: on push to main (post-merge), after lint/static-analysis/
test/no-debug pass, runs the build and uploads the zip via
actions/upload-artifact.
- Ignore build/ and dist/.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Implements #5: studio admin / instructors author intake questions scoped
per offering; answers are stored against a lesson or group enrolment via a
polymorphic registration reference.
- src/Registration/: Question + Answer value objects, QuestionRepository
and AnswerRepository, QuestionEndpoint (REST), QuestionController +
templates/admin/questions.php (Offerings -> Questions submenu)
- us_questions and us_question_answers tables in Schema.php
- REST: public GET /offerings/{id}/questions; POST/PATCH/DELETE /questions
gated by manage_questions + offering ownership (owner or studio admin)
- Field types text/textarea/select/checkbox; select options stored as JSON
- Wiring in Plugin, RestRegistrar, AdminMenu
AnswerRepository is built now and consumed by the booking/enrolment flow
in #3/#4.
Tests: tests/Unit/Registration/ (19 tests). composer test (63 total), cs,
and PHPStan level 6 all pass.
Refs #5
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Implements the offerings catalog (#1): private-lesson types and group
classes carrying pricing, billing mode (one_time/full_term), duration,
capacity, and term details. Adds the src/Offering/ domain (value object,
repository, REST endpoint, admin controller + template), the us_offerings
table, and an Offerings admin page.
Also lands the capability slice of #9: registers the us_studio_admin role
and the new capability strings (manage_instructors, manage_offerings,
manage_questions, manage_policies, manage_billing, view_all_payments,
view_own_payments, export_payments) so offering management gates correctly.
Tests: tests/Unit/Offering/ (value object + repository) and a studio-admin
case in RoleManagerTest. composer test, cs, and PHPStan level 6 all pass.
Refs #1#9
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Update availability, lesson-booking, and user-roles docs and add specs
for offerings, group classes, registration questions, versioned policies,
Stripe payments (with e-transfer/comp overrides and receipts), and
monthly per-instructor payment reporting. Tracked in issues #1-#9.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
All classes are now organised by domain (Availability, Booking, Auth).
Each domain package contains its value object, repository, admin controller,
REST endpoint, and any shortcode pages under a matching sub-namespace.
Cross-cutting wiring (Plugin, AdminMenu, RestRegistrar, ShortcodeRegistrar,
Schema) lives at src/ root. Tests mirror the domain structure.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add phpcs.xml.dist: excludes PSR-4 file naming, camelCase naming,
short array syntax, and redundant per-method/property docblocks
- Fix wp_unslash() on all $_POST reads (LoginPage, AvailabilityController)
- Add phpcs:ignore for password field (must not be sanitized)
- Fix Yoda conditions throughout (AvailabilityRepository, AvailabilityEndpoint,
BookingEndpoint, AvailabilityController)
- Fix inline comments to end with full stops (AdminMenu)
- Replace short ternary ?: with explicit full ternary (BookingEndpoint)
- Rename $namespace param to $route_namespace (reserved keyword warning)
- Add short descriptions to doc blocks that had tag-only blocks
- Add nonce suppression comment in handleFormAction (nonce verified by caller)
- Update composer.json and CI to use phpcs.xml.dist
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove unused AvailabilityRepository/BookingRepository from BookingPage
and ShortcodeRegistrar (template is a JS shell; no PHP data needed yet)
- Add @param array<string, string> to shortcode render() signatures
- Add @return array<string, mixed> to model toArray() methods
- Fix get_permalink() ?? '' — returns string|false not nullable, use cast
- Remove unused ignoreErrors pattern from phpstan.neon
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Custom DB tables for availability slots and lesson bookings
- Instructor (wp-admin) and student (front-end) roles with custom capabilities
- REST API under us-scheduler/v1 for availability CRUD and booking
- [us_booking] and [us_student_login] shortcodes for student front end
- PHPUnit + Brain\Monkey unit test suite (29 tests)
- Gitea Actions CI: lint, PHPStan, tests on PHP 8.1/8.2/8.3, no-debug check
- Feature docs under docs/features/
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>